Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/application/PhabricatorApplicationDifferential.php b/src/applications/differential/application/PhabricatorApplicationDifferential.php
index 771567a18c..366e28db4f 100644
--- a/src/applications/differential/application/PhabricatorApplicationDifferential.php
+++ b/src/applications/differential/application/PhabricatorApplicationDifferential.php
@@ -1,121 +1,122 @@
<?php
final class PhabricatorApplicationDifferential extends PhabricatorApplication {
public function getBaseURI() {
return '/differential/';
}
public function getShortDescription() {
return pht('Review Code');
}
public function getIconName() {
return 'differential';
}
public function getHelpURI() {
return PhabricatorEnv::getDoclink('article/Differential_User_Guide.html');
}
public function getFactObjectsForAnalysis() {
return array(
new DifferentialRevision(),
);
}
public function getTitleGlyph() {
return "\xE2\x9A\x99";
}
public function getEventListeners() {
return array(
new DifferentialPeopleMenuEventListener(),
new DifferentialHovercardEventListener(),
);
}
public function getRoutes() {
return array(
'/D(?P<id>[1-9]\d*)' => 'DifferentialRevisionViewController',
'/differential/' => array(
'' => 'DifferentialRevisionListController',
'filter/(?P<filter>\w+)/(?:(?P<username>[\w._-]+)/)?' =>
'DifferentialRevisionListController',
'diff/' => array(
'(?P<id>[1-9]\d*)/' => 'DifferentialDiffViewController',
'create/' => 'DifferentialDiffCreateController',
),
'changeset/' => 'DifferentialChangesetViewController',
'revision/edit/(?:(?P<id>[1-9]\d*)/)?'
=> 'DifferentialRevisionEditController',
'comment/' => array(
'preview/(?P<id>[1-9]\d*)/' => 'DifferentialCommentPreviewController',
'save/' => 'DifferentialCommentSaveController',
'inline/' => array(
'preview/(?P<id>[1-9]\d*)/'
=> 'DifferentialInlineCommentPreviewController',
'edit/(?P<id>[1-9]\d*)/'
=> 'DifferentialInlineCommentEditController',
),
),
'subscribe/(?P<action>add|rem)/(?P<id>[1-9]\d*)/'
=> 'DifferentialSubscribeController',
),
);
}
public function getApplicationGroup() {
return self::GROUP_CORE;
}
public function getApplicationOrder() {
return 0.100;
}
public function getRemarkupRules() {
return array(
new DifferentialRemarkupRule(),
);
}
public function loadStatus(PhabricatorUser $user) {
$revisions = id(new DifferentialRevisionQuery())
+ ->setViewer($user)
->withResponsibleUsers(array($user->getPHID()))
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->needRelationships(true)
->execute();
list($blocking, $active, $waiting) =
DifferentialRevisionQuery::splitResponsible(
$revisions,
array($user->getPHID()));
$status = array();
$blocking = count($blocking);
$type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Review(s) Blocking Others', $blocking))
->setCount($blocking);
$active = count($active);
$type = PhabricatorApplicationStatusView::TYPE_WARNING;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Review(s) Need Attention', $active))
->setCount($active);
$waiting = count($waiting);
$type = PhabricatorApplicationStatusView::TYPE_INFO;
$status[] = id(new PhabricatorApplicationStatusView())
->setType($type)
->setText(pht('%d Review(s) Waiting on Others', $waiting))
->setCount($waiting);
return $status;
}
}
diff --git a/src/applications/differential/conduit/ConduitAPI_differential_query_Method.php b/src/applications/differential/conduit/ConduitAPI_differential_query_Method.php
index 5bef573d75..4a3c819120 100644
--- a/src/applications/differential/conduit/ConduitAPI_differential_query_Method.php
+++ b/src/applications/differential/conduit/ConduitAPI_differential_query_Method.php
@@ -1,256 +1,258 @@
<?php
/**
* @group conduit
*/
final class ConduitAPI_differential_query_Method
extends ConduitAPIMethod {
public function getMethodDescription() {
return "Query Differential revisions which match certain criteria.";
}
public function defineParamTypes() {
$hash_types = ArcanistDifferentialRevisionHash::getTypes();
$hash_types = implode(', ', $hash_types);
$status_types = array(
DifferentialRevisionQuery::STATUS_ANY,
DifferentialRevisionQuery::STATUS_OPEN,
DifferentialRevisionQuery::STATUS_ACCEPTED,
DifferentialRevisionQuery::STATUS_CLOSED,
);
$status_types = implode(', ', $status_types);
$order_types = array(
DifferentialRevisionQuery::ORDER_MODIFIED,
DifferentialRevisionQuery::ORDER_CREATED,
);
$order_types = implode(', ', $order_types);
return array(
'authors' => 'optional list<phid>',
'ccs' => 'optional list<phid>',
'reviewers' => 'optional list<phid>',
'paths' => 'optional list<pair<callsign, path>>',
'commitHashes' => 'optional list<pair<enum<'.
$hash_types.'>, string>>',
'status' => 'optional enum<'.$status_types.'>',
'order' => 'optional enum<'.$order_types.'>',
'limit' => 'optional uint',
'offset' => 'optional uint',
'ids' => 'optional list<uint>',
'phids' => 'optional list<phid>',
'subscribers' => 'optional list<phid>',
'responsibleUsers' => 'optional list<phid>',
'branches' => 'optional list<string>',
'arcanistProjects' => 'optional list<string>',
);
}
public function defineReturnType() {
return 'list<dict>';
}
public function defineErrorTypes() {
return array(
'ERR-INVALID-PARAMETER' => 'Missing or malformed parameter.',
);
}
protected function execute(ConduitAPIRequest $request) {
$authors = $request->getValue('authors');
$ccs = $request->getValue('ccs');
$reviewers = $request->getValue('reviewers');
$status = $request->getValue('status');
$order = $request->getValue('order');
$path_pairs = $request->getValue('paths');
$commit_hashes = $request->getValue('commitHashes');
$limit = $request->getValue('limit');
$offset = $request->getValue('offset');
$ids = $request->getValue('ids');
$phids = $request->getValue('phids');
$subscribers = $request->getValue('subscribers');
$responsible_users = $request->getValue('responsibleUsers');
$branches = $request->getValue('branches');
$arc_projects = $request->getValue('arcanistProjects');
- $query = new DifferentialRevisionQuery();
+ $query = id(new DifferentialRevisionQuery())
+ ->setViewer($request->getUser());
+
if ($authors) {
$query->withAuthors($authors);
}
if ($ccs) {
$query->withCCs($ccs);
}
if ($reviewers) {
$query->withReviewers($reviewers);
}
if ($path_pairs) {
$paths = array();
foreach ($path_pairs as $pair) {
list($callsign, $path) = $pair;
$paths[] = $path;
}
$path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
if (count($path_map) != count($paths)) {
$unknown_paths = array();
foreach ($paths as $p) {
if (!idx($path_map, $p)) {
$unknown_paths[] = $p;
}
}
throw id(new ConduitException('ERR-INVALID-PARAMETER'))
->setErrorDescription(
'Unknown paths: '.implode(', ', $unknown_paths));
}
$repos = array();
foreach ($path_pairs as $pair) {
list($callsign, $path) = $pair;
if (!idx($repos, $callsign)) {
$repos[$callsign] = id(new PhabricatorRepository())->loadOneWhere(
'callsign = %s',
$callsign);
if (!$repos[$callsign]) {
throw id(new ConduitException('ERR-INVALID-PARAMETER'))
->setErrorDescription(
'Unknown repo callsign: '.$callsign);
}
}
$repo = $repos[$callsign];
$query->withPath($repo->getID(), idx($path_map, $path));
}
}
if ($commit_hashes) {
$hash_types = ArcanistDifferentialRevisionHash::getTypes();
foreach ($commit_hashes as $info) {
list($type, $hash) = $info;
if (empty($type) ||
!in_array($type, $hash_types) ||
empty($hash)) {
throw new ConduitException('ERR-INVALID-PARAMETER');
}
}
$query->withCommitHashes($commit_hashes);
}
if ($status) {
$query->withStatus($status);
}
if ($order) {
$query->setOrder($order);
}
if ($limit) {
$query->setLimit($limit);
}
if ($offset) {
$query->setOffset($offset);
}
if ($ids) {
$query->withIDs($ids);
}
if ($phids) {
$query->withPHIDs($phids);
}
if ($responsible_users) {
$query->withResponsibleUsers($responsible_users);
}
if ($subscribers) {
$query->withSubscribers($subscribers);
}
if ($branches) {
$query->withBranches($branches);
}
if ($arc_projects) {
// This is sort of special-cased, but don't make arc do an extra round
// trip.
$projects = id(new PhabricatorRepositoryArcanistProject())
->loadAllWhere(
'name in (%Ls)',
$arc_projects);
if (!$projects) {
return array();
}
$query->withArcanistProjectPHIDs(mpull($projects, 'getPHID'));
}
$query->needRelationships(true);
$query->needCommitPHIDs(true);
$query->needDiffIDs(true);
$query->needActiveDiffs(true);
$query->needHashes(true);
$revisions = $query->execute();
$results = array();
foreach ($revisions as $revision) {
$diff = $revision->getActiveDiff();
if (!$diff) {
continue;
}
$id = $revision->getID();
$auxiliary_fields = $this->loadAuxiliaryFields(
$revision, $request->getUser());
$result = array(
'id' => $id,
'phid' => $revision->getPHID(),
'title' => $revision->getTitle(),
'uri' => PhabricatorEnv::getProductionURI('/D'.$id),
'dateCreated' => $revision->getDateCreated(),
'dateModified' => $revision->getDateModified(),
'authorPHID' => $revision->getAuthorPHID(),
'status' => $revision->getStatus(),
'statusName' =>
ArcanistDifferentialRevisionStatus::getNameForRevisionStatus(
$revision->getStatus()),
'branch' => $diff->getBranch(),
'summary' => $revision->getSummary(),
'testPlan' => $revision->getTestPlan(),
'lineCount' => $revision->getLineCount(),
'diffs' => $revision->getDiffIDs(),
'commits' => $revision->getCommitPHIDs(),
'reviewers' => array_values($revision->getReviewers()),
'ccs' => array_values($revision->getCCPHIDs()),
'hashes' => $revision->getHashes(),
'auxiliary' => $auxiliary_fields,
);
// TODO: This is a hacky way to put permissions on this field until we
// have first-class support, see T838.
if ($revision->getAuthorPHID() == $request->getUser()->getPHID()) {
$result['sourcePath'] = $diff->getSourcePath();
}
$results[] = $result;
}
return $results;
}
private function loadAuxiliaryFields(
DifferentialRevision $revision,
PhabricatorUser $user) {
$aux_fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
foreach ($aux_fields as $key => $aux_field) {
$aux_field->setUser($user);
if (!$aux_field->shouldAppearOnConduitView()) {
unset($aux_fields[$key]);
}
}
$aux_fields = DifferentialAuxiliaryField::loadFromStorage(
$revision,
$aux_fields);
return mpull($aux_fields, 'getValueForConduit', 'getKeyForConduit');
}
}
diff --git a/src/applications/differential/controller/DifferentialRevisionListController.php b/src/applications/differential/controller/DifferentialRevisionListController.php
index 6434614af9..bb8ef64cf8 100644
--- a/src/applications/differential/controller/DifferentialRevisionListController.php
+++ b/src/applications/differential/controller/DifferentialRevisionListController.php
@@ -1,452 +1,452 @@
<?php
final class DifferentialRevisionListController extends DifferentialController {
private $filter;
private $username;
public function shouldRequireLogin() {
return !$this->allowsAnonymousAccess();
}
public function willProcessRequest(array $data) {
$this->filter = idx($data, 'filter');
$this->username = idx($data, 'username');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$params = array_filter(
array(
'status' => $request->getStr('status'),
'order' => $request->getStr('order'),
));
$params['participants'] = $request->getArr('participants');
$filters = $this->getFilters();
$this->filter = $this->selectFilter($filters,
$this->filter, !$user->isLoggedIn());
// Redirect from search to canonical URL.
$phid_arr = $request->getArr('view_users');
if ($phid_arr) {
$view_users = id(new PhabricatorUser())
->loadAllWhere('phid IN (%Ls)', $phid_arr);
if (count($view_users) == 1) {
// This is a single user, so generate a pretty URI.
$uri = new PhutilURI(
'/differential/filter/'.$this->filter.'/'.
phutil_escape_uri(reset($view_users)->getUserName()).'/');
$uri->setQueryParams($params);
return id(new AphrontRedirectResponse())->setURI($uri);
}
}
$uri = new PhutilURI('/differential/filter/'.$this->filter.'/');
$uri->setQueryParams($params);
$username = '';
if ($this->username) {
$view_user = id(new PhabricatorUser())
->loadOneWhere('userName = %s', $this->username);
if (!$view_user) {
return new Aphront404Response();
}
$username = phutil_escape_uri($this->username).'/';
$uri->setPath('/differential/filter/'.$this->filter.'/'.$username);
$params['view_users'] = array($view_user->getPHID());
} else {
$phids = $request->getArr('view_users');
if ($phids) {
$params['view_users'] = $phids;
$uri->setQueryParams($params);
}
}
// Fill in the defaults we'll actually use for calculations if any
// parameters are missing.
$params += array(
'view_users' => array($user->getPHID()),
'status' => 'all',
'order' => 'modified',
);
$side_nav = $this->buildSideNav($this->filter, false, $username);
$side_nav->selectFilter($this->filter.'/'.$username, null);
$panels = array();
$handles = array();
$controls = $this->getFilterControls($this->filter);
if ($this->getFilterRequiresUser($this->filter) && !$params['view_users']) {
// In the anonymous case, we still want to let you see some user's
// list, but we don't have a default PHID to provide (normally, we use
// the viewing user's). Show a warning instead.
$warning = new AphrontErrorView();
$warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$warning->setTitle(pht('User Required'));
$warning->appendChild(
pht('This filter requires that a user be specified above.'));
$panels[] = $warning;
} else {
$query = $this->buildQuery($this->filter, $params);
$pager = null;
if ($this->getFilterAllowsPaging($this->filter)) {
$pager = new AphrontPagerView();
$pager->setOffset($request->getInt('page'));
$pager->setPageSize(1000);
$pager->setURI($uri, 'page');
$query->setOffset($pager->getOffset());
$query->setLimit($pager->getPageSize() + 1);
}
foreach ($controls as $control) {
$this->applyControlToQuery($control, $query, $params);
}
$revisions = $query->execute();
if ($pager) {
$revisions = $pager->sliceResults($revisions);
}
$views = $this->buildViews(
$this->filter,
$params['view_users'],
$revisions);
$view_objects = array();
foreach ($views as $view) {
if (empty($view['special'])) {
$view_objects[] = $view['view'];
}
}
$phids = mpull($view_objects, 'getRequiredHandlePHIDs');
$phids[] = $params['view_users'];
$phids = array_mergev($phids);
$handles = $this->loadViewerHandles($phids);
foreach ($views as $view) {
$view['view']->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader($view['title']);
$panel->appendChild($view['view']);
if ($pager) {
$panel->appendChild($pager);
}
$panel->setNoBackground();
$panels[] = $panel;
}
}
$filter_form = id(new AphrontFormView())
->setMethod('GET')
->setNoShading(true)
->setAction('/differential/filter/'.$this->filter.'/')
->setUser($user);
foreach ($controls as $control) {
$control_view = $this->renderControl($control, $handles, $uri, $params);
$filter_form->appendChild($control_view);
}
$filter_form
->addHiddenInput('status', $params['status'])
->addHiddenInput('order', $params['order'])
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Filter Revisions')));
$filter_view = new AphrontListFilterView();
$filter_view->appendChild($filter_form);
$side_nav->appendChild($filter_view);
foreach ($panels as $panel) {
$side_nav->appendChild($panel);
}
$crumbs = $this->buildApplicationCrumbs();
$name = $side_nav
->getMenu()
->getItem($side_nav->getSelectedFilter())
->getName();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($name)
->setHref($request->getRequestURI()));
$side_nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$side_nav,
array(
'title' => pht('Differential Home'),
'device' => true,
'dust' => true,
));
}
private function getFilterRequiresUser($filter) {
static $requires = array(
'active' => true,
'revisions' => true,
'reviews' => true,
'subscribed' => true,
'drafts' => true,
'all' => false,
);
if (!isset($requires[$filter])) {
throw new Exception("Unknown filter '{$filter}'!");
}
return $requires[$filter];
}
private function getFilterAllowsPaging($filter) {
static $allows = array(
'active' => false,
'revisions' => true,
'reviews' => true,
'subscribed' => true,
'drafts' => true,
'all' => true,
);
if (!isset($allows[$filter])) {
throw new Exception("Unknown filter '{$filter}'!");
}
return $allows[$filter];
}
private function getFilterControls($filter) {
static $controls = array(
'active' => array('phid'),
'revisions' => array('phid', 'participants', 'status', 'order'),
'reviews' => array('phid', 'participants', 'status', 'order'),
'subscribed' => array('subscriber', 'status', 'order'),
'drafts' => array('phid', 'status', 'order'),
'all' => array('status', 'order'),
);
if (!isset($controls[$filter])) {
throw new Exception("Unknown filter '{$filter}'!");
}
return $controls[$filter];
}
private function buildQuery($filter, array $params) {
$user_phids = $params['view_users'];
- $query = new DifferentialRevisionQuery();
-
- $query->needRelationships(true);
+ $query = id(new DifferentialRevisionQuery())
+ ->setViewer($this->getRequest()->getUser())
+ ->needRelationships(true);
switch ($filter) {
case 'active':
$query->withResponsibleUsers($user_phids);
$query->withStatus(DifferentialRevisionQuery::STATUS_OPEN);
$query->setLimit(null);
break;
case 'revisions':
$query->withAuthors($user_phids);
$query->withReviewers($params['participants']);
break;
case 'reviews':
$query->withReviewers($user_phids);
$query->withAuthors($params['participants']);
break;
case 'subscribed':
$query->withSubscribers($user_phids);
break;
case 'drafts':
$query->withDraftRepliesByAuthors($user_phids);
break;
case 'all':
break;
default:
throw new Exception("Unknown filter '{$filter}'!");
}
return $query;
}
private function renderControl(
$control,
array $handles,
PhutilURI $uri,
array $params) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
switch ($control) {
case 'subscriber':
case 'phid':
$value = mpull(
array_select_keys($handles, $params['view_users']),
'getFullName');
if ($control == 'subscriber') {
$source = '/typeahead/common/allmailable/';
$label = pht('View Subscribers');
} else {
$source = '/typeahead/common/accounts/';
switch ($this->filter) {
case 'revisions':
$label = pht('Authors');
break;
case 'reviews':
$label = pht('Reviewers');
break;
default:
$label = pht('View Users');
break;
}
}
return id(new AphrontFormTokenizerControl())
->setDatasource($source)
->setLabel($label)
->setName('view_users')
->setValue($value);
case 'participants':
switch ($this->filter) {
case 'revisions':
$label = pht('Reviewers');
break;
case 'reviews':
$label = pht('Authors');
break;
}
$value = mpull(
array_select_keys($handles, $params['participants']),
'getFullName');
return id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/accounts/')
->setLabel($label)
->setName('participants')
->setValue($value);
case 'status':
return id(new AphrontFormToggleButtonsControl())
->setLabel(pht('Status'))
->setValue($params['status'])
->setBaseURI($uri, 'status')
->setButtons(
array(
'all' => pht('All'),
'open' => pht('Open'),
'closed' => pht('Closed'),
'abandoned' => pht('Abandoned'),
));
case 'order':
return id(new AphrontFormToggleButtonsControl())
->setLabel(pht('Order'))
->setValue($params['order'])
->setBaseURI($uri, 'order')
->setButtons(
array(
'modified' => pht('Updated'),
'created' => pht('Created'),
));
default:
throw new Exception("Unknown control '{$control}'!");
}
}
private function applyControlToQuery($control, $query, array $params) {
switch ($control) {
case 'phid':
case 'subscriber':
case 'participants':
// Already applied by query construction.
break;
case 'status':
if ($params['status'] == 'open') {
$query->withStatus(DifferentialRevisionQuery::STATUS_OPEN);
} else if ($params['status'] == 'closed') {
$query->withStatus(DifferentialRevisionQuery::STATUS_CLOSED);
} else if ($params['status'] == 'abandoned') {
$query->withStatus(DifferentialRevisionQuery::STATUS_ABANDONED);
}
break;
case 'order':
if ($params['order'] == 'created') {
$query->setOrder(DifferentialRevisionQuery::ORDER_CREATED);
}
break;
default:
throw new Exception("Unknown control '{$control}'!");
}
}
private function buildViews($filter, array $user_phids, array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$user = $this->getRequest()->getUser();
$template = id(new DifferentialRevisionListView())
->setUser($user)
->setFields(DifferentialRevisionListView::getDefaultFields($user));
$views = array();
switch ($filter) {
case 'active':
list($blocking, $active, $waiting) =
DifferentialRevisionQuery::splitResponsible(
$revisions,
$user_phids);
$view = id(clone $template)
->setHighlightAge(true)
->setRevisions($blocking)
->loadAssets();
$views[] = array(
'title' => pht('Blocking Others'),
'view' => $view,
);
$view = id(clone $template)
->setHighlightAge(true)
->setRevisions($active)
->loadAssets();
$views[] = array(
'title' => pht('Action Required'),
'view' => $view,
);
$view = id(clone $template)
->setRevisions($waiting)
->loadAssets();
$views[] = array(
'title' => pht('Waiting On Others'),
'view' => $view,
);
break;
case 'revisions':
case 'reviews':
case 'subscribed':
case 'drafts':
case 'all':
$titles = array(
'revisions' => pht('Revisions by Author'),
'reviews' => pht('Revisions by Reviewer'),
'subscribed' => pht('Revisions by Subscriber'),
'all' => pht('Revisions'),
);
$view = id(clone $template)
->setRevisions($revisions)
->loadAssets();
$views[] = array(
'title' => idx($titles, $filter),
'view' => $view,
);
break;
default:
throw new Exception("Unknown filter '{$filter}'!");
}
return $views;
}
}
diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php
index 9190783440..6d8ebaf7db 100644
--- a/src/applications/differential/controller/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -1,970 +1,971 @@
<?php
final class DifferentialRevisionViewController extends DifferentialController {
private $revisionID;
public function shouldRequireLogin() {
return !$this->allowsAnonymousAccess();
}
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 DifferentialRevision())->load($this->revisionID);
if (!$revision) {
return new Aphront404Response();
}
$revision->loadRelationships();
$diffs = $revision->loadDiffs();
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(
$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();
$comments = array_merge(
$this->getImplicitComments($revision, reset($diffs)),
$comments);
$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) {
$metadata = $comment->getMetadata();
$added_reviewers = idx(
$metadata,
DifferentialComment::METADATA_ADDED_REVIEWERS);
if ($added_reviewers) {
foreach ($added_reviewers as $phid) {
$object_phids[] = $phid;
}
}
$added_ccs = idx(
$metadata,
DifferentialComment::METADATA_ADDED_CCS);
if ($added_ccs) {
foreach ($added_ccs 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 = new DifferentialRevisionDetailView();
$revision_detail->setRevision($revision);
$revision_detail->setDiff(end($diffs));
$revision_detail->setAuxiliaryFields($aux_fields);
$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 = new DifferentialRevisionCommentListView();
$comment_view->setComments($comments);
$comment_view->setHandles($handles);
$comment_view->setInlineComments($inlines);
$comment_view->setChangesets($all_changesets);
$comment_view->setUser($user);
$comment_view->setTargetDiff($target);
$comment_view->setVersusDiffID($diff_vs);
if ($arc_project) {
Javelin::initBehavior(
'repository-crossreference',
array(
'section' => $comment_view->getID(),
'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'));
}
$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->render(),
$diff_history->render(),
$warning,
$local_view->render(),
$toc_view->render(),
$other_view,
$changeset_view->render(),
));
if ($comment_form) {
$page_pane->appendChild($comment_form->render());
}
$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->addCrumb(
id(new PhabricatorCrumbView())
->setName($object_id)
->setHref('/'.$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 getImplicitComments(
DifferentialRevision $revision,
DifferentialDiff $diff) {
$author_phid = nonempty(
$diff->getAuthorPHID(),
$revision->getAuthorPHID());
$template = new DifferentialComment();
$template->setAuthorPHID($author_phid);
$template->setRevisionID($revision->getID());
$template->setDateCreated($revision->getDateCreated());
$comments = array();
if (strlen($revision->getSummary())) {
$summary_comment = clone $template;
$summary_comment->setContent($revision->getSummary());
$summary_comment->setAction(DifferentialAction::ACTION_SUMMARIZE);
$comments[] = $summary_comment;
}
if (strlen($revision->getTestPlan())) {
$testplan_comment = clone $template;
$testplan_comment->setContent($revision->getTestPlan());
$testplan_comment->setAction(DifferentialAction::ACTION_TESTPLAN);
$comments[] = $testplan_comment;
}
return $comments;
}
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());
$viewer_is_anonymous = !$this->getRequest()->getUser()->isLoggedIn();
$status = $revision->getStatus();
$revision_id = $revision->getID();
$revision_phid = $revision->getPHID();
$links = array();
if ($viewer_is_owner) {
$links[] = array(
'icon' => 'edit',
'href' => "/differential/revision/edit/{$revision_id}/",
'name' => pht('Edit Revision'),
);
}
if (!$viewer_is_anonymous) {
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' => true,
);
} else {
$links[] = array(
'icon' => 'enable',
'name' => pht('Automatically Subscribed'),
'disabled' => true,
);
}
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$links[] = array(
'icon' => 'link',
'name' => pht('Edit Dependencies'),
'href' => "/search/attach/{$revision_phid}/DREV/dependencies/",
'sigil' => 'workflow',
);
$maniphest = 'PhabricatorApplicationManiphest';
if (PhabricatorApplication::isClassInstalled($maniphest)) {
$links[] = array(
'icon' => 'attach',
'name' => pht('Edit Maniphest Tasks'),
'href' => "/search/attach/{$revision_phid}/TASK/",
'sigil' => 'workflow',
);
}
}
$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());
$viewer_did_accept = ($viewer_phid === $revision->loadReviewedBy());
$status = $revision->getStatus();
$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_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] =
$viewer_is_reviewer && !$viewer_did_accept;
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)
->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();
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
return hsprintf(
'%s<div class="differential-panel">%s</div>',
id(new PhabricatorHeaderView())
->setHeader(pht('Open Revisions Affecting These Files'))
->render(),
$view->render());
}
/**
* Straight copy of the loadFileByPhid method in
* @{class:DifferentialReviewRequestMail}.
*
* This is because of the code similarity between the buildPatch method in
* @{class:DifferentialReviewRequestMail} and @{method:buildRawDiffResponse}
* in this class. Both of these methods end up using call_user_func and this
* piece of code is the lucky function.
*
* @return mixed (@{class:PhabricatorFile} if found, null if not)
*/
public function loadFileByPHID($phid) {
$file = id(new PhabricatorFile())->loadOneWhere(
'phid = %s',
$phid);
if (!$file) {
return null;
}
return $file->loadFileData();
}
/**
* Note this code is somewhat similar to the buildPatch method in
* @{class:DifferentialReviewRequestMail}.
*
* @return @{class:AphrontRedirectResponse}
*/
private function buildRawDiffResponse(
array $changesets,
array $vs_changesets,
array $vs_map,
PhabricatorRepository $repository = null) {
assert_instances_of($changesets, 'DifferentialChangeset');
assert_instances_of($vs_changesets, 'DifferentialChangeset');
$engine = new PhabricatorDifferenceEngine();
$generated_changesets = array();
foreach ($changesets as $changeset) {
$changeset->attachHunks($changeset->loadHunks());
$right = $changeset->makeNewFile();
$choice = $changeset;
$vs = idx($vs_map, $changeset->getID());
if ($vs == -1) {
$left = $right;
$right = $changeset->makeOldFile();
} else if ($vs) {
$choice = $vs_changeset = $vs_changesets[$vs];
$vs_changeset->attachHunks($vs_changeset->loadHunks());
$left = $vs_changeset->makeNewFile();
} else {
$left = $changeset->makeOldFile();
}
$synthetic = $engine->generateChangesetFromFileContent(
$left,
$right);
if (!$synthetic->getAffectedLineCount()) {
$filetype = $choice->getFileType();
if ($filetype == DifferentialChangeType::FILE_TEXT ||
$filetype == DifferentialChangeType::FILE_SYMLINK) {
continue;
}
}
$choice->attachHunks($synthetic->getHunks());
$generated_changesets[] = $choice;
}
$diff = new DifferentialDiff();
$diff->attachChangesets($generated_changesets);
$diff_dict = $diff->getDiffDict();
$changes = array();
foreach ($diff_dict['changes'] as $changedict) {
$changes[] = ArcanistDiffChange::newFromDictionary($changedict);
}
$bundle = ArcanistBundle::newFromChanges($changes);
$bundle->setLoadFileDataCallback(array($this, 'loadFileByPHID'));
$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,
));
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
}
diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php
index a46ac0ffc6..a7fa970bee 100644
--- a/src/applications/differential/view/DifferentialRevisionListView.php
+++ b/src/applications/differential/view/DifferentialRevisionListView.php
@@ -1,207 +1,208 @@
<?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;
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 = null;
$stale = null;
if ($this->highlightAge) {
$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);
}
}
Javelin::initBehavior('phabricator-tooltips', array());
require_celerity_resource('aphront-tooltip-css');
$flagged = mpull($this->flags, null, 'getObjectPHID');
foreach ($this->fields as $field) {
$field->setUser($this->user);
$field->setHandles($this->handles);
}
$cell_classes = array();
$rows = array();
foreach ($this->revisions as $revision) {
$phid = $revision->getPHID();
$flag = '';
if (isset($flagged[$phid])) {
$class = PhabricatorFlagColor::getCSSClass($flagged[$phid]->getColor());
$note = $flagged[$phid]->getNote();
$flag = javelin_tag(
'div',
$note ? array(
'class' => 'phabricator-flag-icon '.$class,
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $note,
'align' => 'N',
'size' => 240,
),
) : array(
'class' => 'phabricator-flag-icon '.$class,
),
'');
} else if (array_key_exists($revision->getID(), $this->drafts)) {
$src = '/rsrc/image/icon/fatcow/page_white_edit.png';
$flag = hsprintf(
'<a href="%s">%s</a>',
'/D'.$revision->getID().'#comment-preview',
phutil_tag(
'img',
array(
'src' => celerity_get_resource_uri($src),
'width' => 16,
'height' => 16,
'alt' => 'Draft',
'title' => pht('Draft Comment'),
)));
}
$row = array($flag);
$modified = $revision->getDateModified();
foreach ($this->fields as $field) {
if (($fresh || $stale) &&
$field instanceof DifferentialDateModifiedFieldSpecification) {
if ($stale && $modified < $stale) {
$class = 'revision-age-old';
} else if ($fresh && $modified < $fresh) {
$class = 'revision-age-stale';
} else {
$class = 'revision-age-fresh';
}
$cell_classes[count($rows)][count($row)] = $class;
}
$row[] = $field->renderValueForRevisionList($revision);
}
$rows[] = $row;
}
$headers = array('');
$classes = array('');
foreach ($this->fields as $field) {
$headers[] = $field->renderHeaderForRevisionList();
$classes[] = $field->getColumnClassForRevisionList();
}
$table = new AphrontTableView($rows);
$table->setHeaders($headers);
$table->setColumnClasses($classes);
$table->setCellClasses($cell_classes);
$table->setNoDataString(pht('No revisions found.'));
require_celerity_resource('differential-revision-history-css');
return $table->render();
}
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/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php
index 8f079ba348..9e932fc490 100644
--- a/src/applications/diffusion/controller/DiffusionController.php
+++ b/src/applications/diffusion/controller/DiffusionController.php
@@ -1,344 +1,345 @@
<?php
abstract class DiffusionController extends PhabricatorController {
protected $diffusionRequest;
public function willProcessRequest(array $data) {
if (isset($data['callsign'])) {
$drequest = DiffusionRequest::newFromAphrontRequestDictionary(
$data,
$this->getRequest());
$this->diffusionRequest = $drequest;
}
}
public function setDiffusionRequest(DiffusionRequest $request) {
$this->diffusionRequest = $request;
return $this;
}
protected function getDiffusionRequest() {
if (!$this->diffusionRequest) {
throw new Exception("No Diffusion request object!");
}
return $this->diffusionRequest;
}
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->setApplicationName(pht('Diffusion'));
$page->setBaseURI('/diffusion/');
$page->setTitle(idx($data, 'title'));
$page->setGlyph("\xE2\x89\x88");
$page->setSearchDefaultScope(PhabricatorSearchScope::SCOPE_COMMITS);
$page->appendChild($view);
$response = new AphrontWebpageResponse();
return $response->setContent($page->render());
}
final protected function buildSideNav($selected, $has_change_view) {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI(''));
$navs = array(
'history' => pht('History View'),
'browse' => pht('Browse View'),
'change' => pht('Change View'),
);
if (!$has_change_view) {
unset($navs['change']);
}
$drequest = $this->getDiffusionRequest();
$branch = $drequest->loadBranch();
if ($branch && $branch->getLintCommit()) {
$navs['lint'] = pht('Lint View');
}
$selected_href = null;
foreach ($navs as $action => $name) {
$href = $drequest->generateURI(
array(
'action' => $action,
));
if ($action == $selected) {
$selected_href = $href;
}
$nav->addFilter($href, $name, $href);
}
$nav->selectFilter($selected_href, null);
// TODO: URI encoding might need to be sorted out for this link.
$nav->addFilter(
'',
pht("Search Owners \xE2\x86\x97"),
'/owners/view/search/'.
'?repository='.phutil_escape_uri($drequest->getCallsign()).
'&path='.phutil_escape_uri('/'.$drequest->getPath()));
return $nav;
}
public function buildCrumbs(array $spec = array()) {
$crumbs = $this->buildApplicationCrumbs();
$crumb_list = $this->buildCrumbList($spec);
foreach ($crumb_list as $crumb) {
$crumbs->addCrumb($crumb);
}
return $crumbs;
}
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)
->execute();
if (!$revisions) {
return null;
}
$view = id(new DifferentialRevisionListView())
->setRevisions($revisions)
->setFields(DifferentialRevisionListView::getDefaultFields($user))
- ->setUser($this->getRequest()->getUser())
+ ->setUser($user)
->loadAssets();
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setId('pending-differential-revisions');
$panel->setHeader('Pending Differential Revisions');
$panel->appendChild($view);
return $panel;
}
private function buildCrumbList(array $spec = array()) {
$spec = $spec + array(
'commit' => null,
'tags' => null,
'branches' => null,
'view' => null,
);
$crumb_list = array();
// On the home page, we don't have a DiffusionRequest.
if ($this->diffusionRequest) {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
} else {
$drequest = null;
$repository = null;
}
if (!$repository) {
return $crumb_list;
}
$callsign = $repository->getCallsign();
$repository_name = 'r'.$callsign;
if (!$spec['commit'] && !$spec['tags'] && !$spec['branches']) {
$branch_name = $drequest->getBranch();
if ($branch_name) {
$repository_name .= ' ('.$branch_name.')';
}
}
$crumb = id(new PhabricatorCrumbView())
->setName($repository_name);
if (!$spec['view'] && !$spec['commit'] &&
!$spec['tags'] && !$spec['branches']) {
$crumb_list[] = $crumb;
return $crumb_list;
}
$crumb->setHref("/diffusion/{$callsign}/");
$crumb_list[] = $crumb;
$raw_commit = $drequest->getRawCommit();
if ($spec['tags']) {
$crumb = new PhabricatorCrumbView();
if ($spec['commit']) {
$crumb->setName(
pht("Tags for %s", 'r'.$callsign.$raw_commit));
$crumb->setHref($drequest->generateURI(
array(
'action' => 'commit',
'commit' => $raw_commit,
)));
} else {
$crumb->setName(pht('Tags'));
}
$crumb_list[] = $crumb;
return $crumb_list;
}
if ($spec['branches']) {
$crumb = id(new PhabricatorCrumbView())
->setName(pht('Branches'));
$crumb_list[] = $crumb;
return $crumb_list;
}
if ($spec['commit']) {
$crumb = id(new PhabricatorCrumbView())
->setName("r{$callsign}{$raw_commit}")
->setHref("r{$callsign}{$raw_commit}");
$crumb_list[] = $crumb;
return $crumb_list;
}
$crumb = new PhabricatorCrumbView();
$view = $spec['view'];
$path = null;
if (isset($spec['path'])) {
$path = $drequest->getPath();
}
if ($raw_commit) {
$commit_link = DiffusionView::linkCommit(
$repository,
$raw_commit);
} else {
$commit_link = '';
}
switch ($view) {
case 'history':
$view_name = pht('History');
break;
case 'browse':
$view_name = pht('Browse');
break;
case 'lint':
$view_name = pht('Lint');
break;
case 'change':
$view_name = pht('Change');
$crumb_list[] = $crumb->setName(
hsprintf('%s (%s)', $path, $commit_link));
return $crumb_list;
}
$uri_params = array(
'action' => $view,
);
$crumb = id(new PhabricatorCrumbView())
->setName($view_name);
if (!strlen($path)) {
$crumb_list[] = $crumb;
} else {
$crumb->setHref($drequest->generateURI(
array(
'path' => '',
) + $uri_params));
$crumb_list[] = $crumb;
$path_parts = explode('/', $path);
do {
$last = array_pop($path_parts);
} while ($last == '');
$path_sections = array();
$thus_far = '';
foreach ($path_parts as $path_part) {
$thus_far .= $path_part.'/';
$path_sections[] = '/';
$path_sections[] = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'path' => $thus_far,
) + $uri_params),
),
$path_part);
}
$path_sections[] = '/'.$last;
$crumb_list[] = id(new PhabricatorCrumbView())
->setName($path_sections);
}
$last_crumb = array_pop($crumb_list);
if ($raw_commit) {
$jump_link = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'commit' => '',
) + $uri_params),
),
pht('Jump to HEAD'));
$name = $last_crumb->getName();
$name = hsprintf('%s @ %s (%s)', $name, $commit_link, $jump_link);
$last_crumb->setName($name);
} else if ($spec['view'] != 'lint') {
$name = $last_crumb->getName();
$name = hsprintf('%s @ HEAD', $name);
$last_crumb->setName($name);
}
$crumb_list[] = $last_crumb;
return $crumb_list;
}
protected function callConduitWithDiffusionRequest(
$method,
array $params = array()) {
$user = $this->getRequest()->getUser();
$drequest = $this->getDiffusionRequest();
return DiffusionQuery::callConduitWithDiffusionRequest(
$user,
$drequest,
$method,
$params);
}
protected function getRepositoryControllerURI(
PhabricatorRepository $repository,
$path) {
return $this->getApplicationURI($repository->getCallsign().'/'.$path);
}
}
diff --git a/src/applications/directory/controller/PhabricatorDirectoryMainController.php b/src/applications/directory/controller/PhabricatorDirectoryMainController.php
index a19188763f..3c136c87cb 100644
--- a/src/applications/directory/controller/PhabricatorDirectoryMainController.php
+++ b/src/applications/directory/controller/PhabricatorDirectoryMainController.php
@@ -1,476 +1,477 @@
<?php
final class PhabricatorDirectoryMainController
extends PhabricatorDirectoryController {
private $filter;
private $minipanels = array();
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;
}
if (PhabricatorEnv::getEnvConfig('welcome.html') !== null) {
$welcome_panel = $this->buildWelcomePanel();
} else {
$welcome_panel = null;
}
$jump_panel = $this->buildJumpPanel();
$revision_panel = $this->buildRevisionPanel();
$audit_panel = $this->buildAuditPanel();
$commit_panel = $this->buildCommitPanel();
$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,
'dust' => true,
));
}
private function buildJumpResponse() {
$request = $this->getRequest();
$jump = $request->getStr('jump');
$response = PhabricatorJumpNavHandler::jumpPostResponse($jump);
if ($response) {
return $response;
} else if ($request->isFormPost()) {
$query = new PhabricatorSearchQuery();
$query->setQuery($jump);
$query->save();
return id(new AphrontRedirectResponse())
->setURI('/search/'.$query->getQueryKey().'/');
} else {
return id(new AphrontRedirectResponse())->setURI('/');
}
}
private function buildUnbreakNowPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->withPriority(ManiphestTaskPriority::PRIORITY_UNBREAK_NOW);
$task_query->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
'No "Unbreak Now!" Tasks',
'Nothing appears to be critically broken right now.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Unbreak Now!');
$panel->setCaption('Open tasks with "Unbreak Now!" priority.');
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/maniphest/view/all/',
'class' => 'grey button',
),
"View All Unbreak Now \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
$panel->setNoBackground();
return $panel;
}
private function buildNeedsTriagePanel(array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
if ($projects) {
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$task_query->withAnyProjects(mpull($projects, 'getPHID'));
$task_query->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.'));
}
$panel = new AphrontPanelView();
$panel->setHeader('Needs Triage');
$panel->setCaption(hsprintf(
'Open tasks with "Needs Triage" priority in '.
'<a href="/project/">projects you are a member of</a>.'));
$panel->addButton(
phutil_tag(
'a',
array(
// TODO: This should filter to just your projects' need-triage
// tasks?
'href' => '/maniphest/view/projecttriage/',
'class' => 'grey button',
),
"View All Triage \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
$panel->setNoBackground();
return $panel;
}
private function buildRevisionPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
- $revision_query = new DifferentialRevisionQuery();
- $revision_query->withStatus(DifferentialRevisionQuery::STATUS_OPEN);
- $revision_query->withResponsibleUsers(array($user_phid));
- $revision_query->needRelationships(true);
+ $revision_query = id(new DifferentialRevisionQuery())
+ ->setViewer($user)
+ ->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
+ ->withResponsibleUsers(array($user_phid))
+ ->needRelationships(true);
// NOTE: We need to unlimit this query to hit the responsible user
// fast-path.
$revision_query->setLimit(null);
$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.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Revisions Waiting on You');
$panel->setCaption('Revisions waiting for you for review or commit.');
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/differential/',
'class' => 'button grey',
),
"View Active Revisions \xC2\xBB"));
$revision_view = id(new DifferentialRevisionListView())
->setHighlightAge(true)
->setRevisions(array_merge($blocking, $active))
->setFields(DifferentialRevisionListView::getDefaultFields($user))
->setUser($user)
->loadAssets();
$phids = array_merge(
array($user_phid),
$revision_view->getRequiredHandlePHIDs());
$handles = $this->loadViewerHandles($phids);
$revision_view->setHandles($handles);
$panel->appendChild($revision_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 = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY);
$task_query->withOwners(array($user_phid));
$task_query->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
'No Assigned Tasks',
'You have no assigned tasks.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Assigned Tasks');
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/maniphest/',
'class' => 'button grey',
),
"View Active Tasks \xC2\xBB"));
$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 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);
$panel = new AphrontPanelView();
$panel->setHeader('Audits');
$panel->setCaption('Commits awaiting your audit.');
$panel->appendChild($view);
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/audit/',
'class' => 'button grey',
),
"View Active Audits \xC2\xBB"));
$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);
$panel = new AphrontPanelView();
$panel->setHeader('Problem Commits');
$panel->setCaption('Commits which auditors have raised concerns about.');
$panel->appendChild($view);
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/audit/',
'class' => 'button grey',
),
"View Problem Commits \xC2\xBB"));
$panel->setNoBackground();
return $panel;
}
}
diff --git a/src/applications/phid/handle/PhabricatorObjectHandleData.php b/src/applications/phid/handle/PhabricatorObjectHandleData.php
index 4e9720ae25..37293ef54f 100644
--- a/src/applications/phid/handle/PhabricatorObjectHandleData.php
+++ b/src/applications/phid/handle/PhabricatorObjectHandleData.php
@@ -1,758 +1,757 @@
<?php
final class PhabricatorObjectHandleData {
private $phids;
private $viewer;
public function __construct(array $phids) {
$this->phids = array_unique($phids);
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public static function loadOneHandle($phid, PhabricatorUser $viewer) {
$query = new PhabricatorObjectHandleData(array($phid));
$query->setViewer($viewer);
$handles = $query->loadHandles();
return $handles[$phid];
}
public function loadObjects() {
$types = phid_group_by_type($this->phids);
$objects = array();
foreach ($types as $type => $phids) {
$objects += $this->loadObjectsOfType($type, $phids);
}
return $objects;
}
private function loadObjectsOfType($type, array $phids) {
if (!$this->viewer) {
throw new Exception(
"You must provide a viewer to load handles or objects.");
}
switch ($type) {
case PhabricatorPHIDConstants::PHID_TYPE_USER:
// TODO: Update query + Batch User Images
$user_dao = new PhabricatorUser();
$users = $user_dao->loadAllWhere(
'phid in (%Ls)',
$phids);
return mpull($users, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_CMIT:
$commits = id(new DiffusionCommitQuery())
->setViewer($this->viewer)
->withPHIDs($phids)
->execute();
return mpull($commits, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_TASK:
// TODO: Update this to ManiphestTaskQuery, //especially// after we have
// policy-awareness
$task_dao = new ManiphestTask();
$tasks = $task_dao->loadAllWhere(
'phid IN (%Ls)',
$phids);
return mpull($tasks, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_CONF:
$config_dao = new PhabricatorConfigEntry();
$entries = $config_dao->loadAllWhere(
'phid IN (%Ls)',
$phids);
return mpull($entries, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_FILE:
// TODO: Update this to PhabricatorFileQuery
$object = new PhabricatorFile();
$files = $object->loadAllWhere('phid IN (%Ls)', $phids);
return mpull($files, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_PROJ:
$projects = id(new PhabricatorProjectQuery())
->setViewer($this->viewer)
->withPHIDs($phids)
->execute();
return mpull($projects, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_REPO:
// TODO: Update this to PhabricatorRepositoryQuery
$object = new PhabricatorRepository();
$repositories = $object->loadAllWhere('phid in (%Ls)', $phids);
return mpull($repositories, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_OPKG:
$object = new PhabricatorOwnersPackage();
$packages = $object->loadAllWhere('phid in (%Ls)', $phids);
return mpull($packages, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_APRJ:
$project_dao = new PhabricatorRepositoryArcanistProject();
$projects = $project_dao->loadAllWhere(
'phid IN (%Ls)',
$phids);
return mpull($projects, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_MLST:
$object = new PhabricatorMetaMTAMailingList();
$lists = $object->loadAllWhere('phid IN (%Ls)', $phids);
return mpull($lists, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
- // TODO: Update this to DifferentialRevisionQuery
- $revision_dao = new DifferentialRevision();
- $revisions = $revision_dao->loadAllWhere(
- 'phid IN (%Ls)',
- $phids);
+ $revisions = id(new DifferentialRevisionQuery())
+ ->setViewer($this->viewer)
+ ->withPHIDs($phids)
+ ->execute();
return mpull($revisions, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_WIKI:
// TODO: Update this to PhrictionDocumentQuery, already pre-package
// content DAO
$document_dao = new PhrictionDocument();
$documents = $document_dao->loadAllWhere(
'phid IN (%Ls)',
$phids);
return mpull($documents, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_QUES:
$questions = id(new PonderQuestionQuery())
->setViewer($this->viewer)
->withPHIDs($phids)
->execute();
return mpull($questions, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_MOCK:
$mocks = id(new PholioMockQuery())
->setViewer($this->viewer)
->withPHIDs($phids)
->execute();
return mpull($mocks, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_POLL:
$polls = id(new PhabricatorSlowvotePoll())
->loadAllWhere('phid IN (%Ls)', $phids);
return mpull($polls, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_XACT:
$subtypes = array();
foreach ($phids as $phid) {
$subtypes[phid_get_subtype($phid)][] = $phid;
}
$xactions = array();
foreach ($subtypes as $subtype => $subtype_phids) {
// TODO: Do this magically.
switch ($subtype) {
case PhabricatorPHIDConstants::PHID_TYPE_MOCK:
$results = id(new PholioTransactionQuery())
->setViewer($this->viewer)
->withPHIDs($subtype_phids)
->execute();
$xactions += mpull($results, null, 'getPHID');
break;
case PhabricatorPHIDConstants::PHID_TYPE_MCRO:
$results = id(new PhabricatorMacroTransactionQuery())
->setViewer($this->viewer)
->withPHIDs($subtype_phids)
->execute();
$xactions += mpull($results, null, 'getPHID');
break;
}
}
return mpull($xactions, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_MCRO:
$macros = id(new PhabricatorMacroQuery())
->setViewer($this->viewer)
->withPHIDs($phids)
->execute();
return mpull($macros, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_PSTE:
$pastes = id(new PhabricatorPasteQuery())
->withPHIDs($phids)
->setViewer($this->viewer)
->execute();
return mpull($pastes, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_BLOG:
$blogs = id(new PhameBlogQuery())
->withPHIDs($phids)
->setViewer($this->viewer)
->execute();
return mpull($blogs, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_POST:
$posts = id(new PhamePostQuery())
->withPHIDs($phids)
->setViewer($this->viewer)
->execute();
return mpull($posts, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_PVAR:
$vars = id(new PhluxVariableQuery())
->withPHIDs($phids)
->setViewer($this->viewer)
->execute();
return mpull($vars, null, 'getPHID');
case PhabricatorPHIDConstants::PHID_TYPE_XUSR:
$xusr_dao = new PhabricatorExternalAccount();
$xusrs = $xusr_dao->loadAllWhere(
'phid in (%Ls)',
$phids);
return mpull($xusrs, null, 'getPHID');
}
return array();
}
public function loadHandles() {
$types = phid_group_by_type($this->phids);
$handles = array();
$external_loaders = PhabricatorEnv::getEnvConfig('phid.external-loaders');
foreach ($types as $type => $phids) {
$objects = $this->loadObjectsOfType($type, $phids);
switch ($type) {
case PhabricatorPHIDConstants::PHID_TYPE_MAGIC:
// Black magic!
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
switch ($phid) {
case ManiphestTaskOwner::OWNER_UP_FOR_GRABS:
$handle->setName('Up For Grabs');
$handle->setFullName('upforgrabs (Up For Grabs)');
$handle->setComplete(true);
break;
case ManiphestTaskOwner::PROJECT_NO_PROJECT:
$handle->setName('No Project');
$handle->setFullName('noproject (No Project)');
$handle->setComplete(true);
break;
default:
$handle->setName('Foul Magicks');
break;
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_USER:
$image_phids = mpull($objects, 'getProfileImagePHID');
$image_phids = array_unique(array_filter($image_phids));
$images = array();
if ($image_phids) {
$images = id(new PhabricatorFile())->loadAllWhere(
'phid IN (%Ls)',
$image_phids);
$images = mpull($images, 'getBestURI', 'getPHID');
}
$statuses = id(new PhabricatorUserStatus())->loadCurrentStatuses(
$phids);
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown User');
} else {
$user = $objects[$phid];
$handle->setName($user->getUsername());
$handle->setURI('/p/'.$user->getUsername().'/');
$handle->setFullName(
$user->getUsername().' ('.$user->getRealName().')');
$handle->setComplete(true);
if (isset($statuses[$phid])) {
$handle->setStatus($statuses[$phid]->getTextStatus());
$handle->setTitle(
$statuses[$phid]->getTerseSummary($this->viewer));
}
$handle->setDisabled($user->getIsDisabled());
$img_uri = idx($images, $user->getProfileImagePHID());
if ($img_uri) {
$handle->setImageURI($img_uri);
} else {
$handle->setImageURI(
PhabricatorUser::getDefaultProfileImageURI());
}
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_MLST:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Mailing List');
} else {
$list = $objects[$phid];
$handle->setName($list->getName());
$handle->setURI($list->getURI());
$handle->setFullName($list->getName());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Revision');
} else {
$rev = $objects[$phid];
$handle->setName('D'.$rev->getID());
$handle->setURI('/D'.$rev->getID());
$handle->setFullName('D'.$rev->getID().': '.$rev->getTitle());
$handle->setComplete(true);
$status = $rev->getStatus();
if (($status == ArcanistDifferentialRevisionStatus::CLOSED) ||
($status == ArcanistDifferentialRevisionStatus::ABANDONED)) {
$closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
$handle->setStatus($closed);
}
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_CMIT:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Commit');
} else {
$repository = $objects[$phid]->getRepository();
$commit = $objects[$phid];
$callsign = $repository->getCallsign();
$commit_identifier = $commit->getCommitIdentifier();
$name = $repository->formatCommitName($commit_identifier);
$handle->setName($name);
$summary = $commit->getSummary();
if (strlen($summary)) {
$handle->setFullName($name.': '.$summary);
} else {
$handle->setFullName($name);
}
$handle->setURI('/r'.$callsign.$commit_identifier);
$handle->setTimestamp($commit->getEpoch());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_TASK:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Task');
} else {
$task = $objects[$phid];
$handle->setName('T'.$task->getID());
$handle->setURI('/T'.$task->getID());
$handle->setFullName('T'.$task->getID().': '.$task->getTitle());
$handle->setComplete(true);
if ($task->getStatus() != ManiphestTaskStatus::STATUS_OPEN) {
$closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
$handle->setStatus($closed);
}
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_CONF:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Config Entry');
} else {
$entry = $objects[$phid];
$handle->setName($entry->getKey());
$handle->setURI('/config/edit/'.$entry->getKey());
$handle->setFullName($entry->getKey());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_FILE:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown File');
} else {
$file = $objects[$phid];
$handle->setName('F'.$file->getID());
$handle->setFullName('F'.$file->getID().' '.$file->getName());
$handle->setURI($file->getBestURI());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_PROJ:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Project');
} else {
$project = $objects[$phid];
$handle->setName($project->getName());
$handle->setURI('/project/view/'.$project->getID().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_REPO:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Repository');
} else {
$repository = $objects[$phid];
$handle->setName('r'.$repository->getCallsign());
$handle->setFullName("r" . $repository->getCallsign() .
" (" . $repository->getName() . ")");
$handle->setURI('/diffusion/'.$repository->getCallsign().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_OPKG:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Package');
} else {
$package = $objects[$phid];
$handle->setName($package->getName());
$handle->setURI('/owners/package/'.$package->getID().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_APRJ:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Arcanist Project');
} else {
$project = $objects[$phid];
$handle->setName($project->getName());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_WIKI:
// TODO: Update this
$document_dao = new PhrictionDocument();
$content_dao = new PhrictionContent();
$conn = $document_dao->establishConnection('r');
$documents = queryfx_all(
$conn,
'SELECT * FROM %T document JOIN %T content
ON document.contentID = content.id
WHERE document.phid IN (%Ls)',
$document_dao->getTableName(),
$content_dao->getTableName(),
$phids);
$documents = ipull($documents, null, 'phid');
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($documents[$phid])) {
$handle->setName('Unknown Document');
} else {
$info = $documents[$phid];
$handle->setName($info['title']);
$handle->setURI(PhrictionDocument::getSlugURI($info['slug']));
$handle->setFullName($info['title']);
$handle->setComplete(true);
if ($info['status'] != PhrictionDocumentStatus::STATUS_EXISTS) {
$closed = PhabricatorObjectHandleStatus::STATUS_CLOSED;
$handle->setStatus($closed);
}
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_QUES:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Ponder Question');
} else {
$question = $objects[$phid];
$handle->setName('Q' . $question->getID());
$handle->setFullName(
phutil_utf8_shorten($question->getTitle(), 60));
$handle->setURI(new PhutilURI('/Q' . $question->getID()));
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_PSTE:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Paste');
} else {
$paste = $objects[$phid];
$handle->setName('P'.$paste->getID());
$handle->setFullName($paste->getFullName());
$handle->setURI('/P'.$paste->getID());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_BLOG:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Blog');
} else {
$blog = $objects[$phid];
$handle->setName($blog->getName());
$handle->setFullName($blog->getName());
$handle->setURI('/phame/blog/view/'.$blog->getID().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_POST:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Post');
} else {
$post = $objects[$phid];
$handle->setName($post->getTitle());
$handle->setFullName($post->getTitle());
$handle->setURI('/phame/post/view/'.$post->getID().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_MOCK:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Mock');
} else {
$mock = $objects[$phid];
$handle->setName('M'.$mock->getID());
$handle->setFullName('M'.$mock->getID().': '.$mock->getName());
$handle->setURI('/M'.$mock->getID());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_POLL:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Slowvote');
} else {
$poll = $objects[$phid];
$handle->setName('V'.$poll->getID());
$handle->setFullName(
'V'.$poll->getID().': '.$poll->getQuestion());
$handle->setURI('/V'.$poll->getID());
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_MCRO:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Macro');
} else {
$macro = $objects[$phid];
$handle->setName($macro->getName());
$handle->setFullName('Image Macro "'.$macro->getName().'"');
$handle->setURI('/macro/view/'.$macro->getID().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_PVAR:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Variable');
} else {
$var = $objects[$phid];
$key = $var->getVariableKey();
$handle->setName($key);
$handle->setFullName('Phlux Variable "'.$key.'"');
$handle->setURI('/phlux/view/'.$key.'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_XUSR:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Display Name');
} else {
$xusr = $objects[$phid];
$display_name = $xusr->getDisplayName();
$handle->setName($display_name);
$handle->setFullName($display_name.' (External User)');
}
$handles[$phid] = $handle;
}
break;
case PhabricatorPHIDConstants::PHID_TYPE_CDWN:
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setType($type);
if (empty($objects[$phid])) {
$handle->setName('Unknown Countdown');
} else {
$countdown = $objects[$phid];
$handle->setName($countdown->getTitle());
$handle->setFullName($countdown->getTitle());
$handle->setURI('/countdown/'.$countdown->getID().'/');
$handle->setComplete(true);
}
$handles[$phid] = $handle;
}
break;
default:
$loader = null;
if (isset($external_loaders[$type])) {
$loader = $external_loaders[$type];
} else if (isset($external_loaders['*'])) {
$loader = $external_loaders['*'];
}
if ($loader) {
$object = newv($loader, array());
assert_instances_of(array($type => $object), 'ObjectHandleLoader');
$handles += $object->loadHandles($phids);
break;
}
foreach ($phids as $phid) {
$handle = new PhabricatorObjectHandle();
$handle->setType($type);
$handle->setPHID($phid);
$handle->setName('Unknown Object');
$handle->setFullName('An Unknown Object');
$handles[$phid] = $handle;
}
break;
}
}
return $handles;
}
}
diff --git a/src/applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php b/src/applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php
index cf5a2b91f4..fa191c797c 100644
--- a/src/applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php
@@ -1,77 +1,78 @@
<?php
final class ReleephDiffChurnFieldSpecification
extends ReleephFieldSpecification {
const REJECTIONS_WEIGHT = 30;
const COMMENTS_WEIGHT = 7;
const UPDATES_WEIGHT = 10;
const MAX_POINTS = 100;
public function getName() {
return 'Churn';
}
public function renderValueForHeaderView() {
$diff_rev = $this->getReleephRequest()->loadDifferentialRevision();
if (!$diff_rev) {
return null;
}
$diff_rev = $this->getReleephRequest()->loadDifferentialRevision();
$comments = id(new DifferentialRevisionQuery())
+ ->setViewer($this->getUser())
->withRevisionIDs(array($diff_rev->getID()))
->excute();
$counts = array();
foreach ($comments as $comment) {
$action = $comment->getAction();
if (!isset($counts[$action])) {
$counts[$action] = 0;
}
$counts[$action] += 1;
}
// 'none' action just means a plain comment
$comments = idx($counts, 'none', 0);
$rejections = idx($counts, 'reject', 0);
$updates = idx($counts, 'update', 0);
$points =
self::REJECTIONS_WEIGHT * $rejections +
self::COMMENTS_WEIGHT * $comments +
self::UPDATES_WEIGHT * $updates;
if ($points === 0) {
$points = 0.15 * self::MAX_POINTS;
$blurb = 'Silent diff';
} else {
$parts = array();
if ($rejections) {
$parts[] = pht('%d rejection(s)', $rejections);
}
if ($comments) {
$parts[] = pht('%d comment(s)', $comments);
}
if ($updates) {
$parts[] = pht('%d update(s)', $updates);
}
if (count($parts) === 0) {
$blurb = '';
} else if (count($parts) === 1) {
$blurb = head($parts);
} else {
$last = array_pop($parts);
$blurb = implode(', ', $parts).' and '.$last;
}
}
return id(new AphrontProgressBarView())
->setValue($points)
->setMax(self::MAX_POINTS)
->setCaption($blurb)
->render();
}
}
diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
index b8faffbaf6..a910695d1c 100644
--- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
+++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php
@@ -1,531 +1,531 @@
<?php
abstract class PhabricatorRepositoryCommitMessageParserWorker
extends PhabricatorRepositoryCommitParserWorker {
abstract protected function getCommitHashes(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit);
final protected function updateCommitData($author, $message,
$committer = null) {
$commit = $this->commit;
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$commit->getID());
if (!$data) {
$data = new PhabricatorRepositoryCommitData();
}
$data->setCommitID($commit->getID());
$data->setAuthorName($author);
$data->setCommitDetail(
'authorPHID',
$this->resolveUserPHID($author));
$data->setCommitMessage($message);
if ($committer) {
$data->setCommitDetail('committer', $committer);
$data->setCommitDetail(
'committerPHID',
$this->resolveUserPHID($committer));
}
$repository = $this->repository;
$author_phid = $this->lookupUser(
$commit,
$data->getAuthorName(),
$data->getCommitDetail('authorPHID'));
$data->setCommitDetail('authorPHID', $author_phid);
$user = new PhabricatorUser();
if ($author_phid) {
$user = $user->loadOneWhere(
'phid = %s',
$author_phid);
}
$call = new ConduitCall(
'differential.parsecommitmessage',
array(
'corpus' => $message,
'partial' => true,
));
$call->setUser($user);
$result = $call->execute();
$field_values = $result['fields'];
if (!empty($field_values['reviewedByPHIDs'])) {
$data->setCommitDetail(
'reviewerPHID',
reset($field_values['reviewedByPHIDs']));
}
$data->setCommitDetail(
'differential.revisionID',
idx($field_values, 'revisionID'));
$committer_phid = $this->lookupUser(
$commit,
$data->getCommitDetail('committer'),
$data->getCommitDetail('committerPHID'));
$data->setCommitDetail('committerPHID', $committer_phid);
if ($author_phid != $commit->getAuthorPHID()) {
$commit->setAuthorPHID($author_phid);
}
$commit->setSummary($data->getSummary());
$commit->save();
$conn_w = id(new DifferentialRevision())->establishConnection('w');
// NOTE: The `differential_commit` table has a unique ID on `commitPHID`,
// preventing more than one revision from being associated with a commit.
// Generally this is good and desirable, but with the advent of hash
// tracking we may end up in a situation where we match several different
// revisions. We just kind of ignore this and pick one, we might want to
// revisit this and do something differently. (If we match several revisions
// someone probably did something very silly, though.)
$revision = null;
$should_autoclose = $repository->shouldAutocloseCommit($commit, $data);
$revision_id = $data->getCommitDetail('differential.revisionID');
if (!$revision_id) {
$hashes = $this->getCommitHashes(
$this->repository,
$this->commit);
if ($hashes) {
-
- $query = new DifferentialRevisionQuery();
- $query->withCommitHashes($hashes);
- $revisions = $query->execute();
+ $revisions = id(new DifferentialRevisionQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withCommitHashes($hashes)
+ ->execute();
if (!empty($revisions)) {
$revision = $this->identifyBestRevision($revisions);
$revision_id = $revision->getID();
}
}
}
if ($revision_id) {
$lock = PhabricatorGlobalLock::newLock(get_class($this).':'.$revision_id);
$lock->lock(5 * 60);
$revision = id(new DifferentialRevision())->load($revision_id);
if ($revision) {
$commit_drev = PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV;
id(new PhabricatorEdgeEditor())
->setActor($user)
->addEdge($commit->getPHID(), $commit_drev, $revision->getPHID())
->save();
$revision->loadRelationships();
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (revisionID, commitPHID) VALUES (%d, %s)',
DifferentialRevision::TABLE_COMMIT,
$revision->getID(),
$commit->getPHID());
$status_closed = ArcanistDifferentialRevisionStatus::CLOSED;
$should_close = ($revision->getStatus() != $status_closed) &&
$should_autoclose;
if ($should_close) {
$actor_phid = nonempty(
$committer_phid,
$author_phid,
$revision->getAuthorPHID());
$actor = id(new PhabricatorUser())
->loadOneWhere('phid = %s', $actor_phid);
$diff = $this->attachToRevision($revision, $actor_phid);
$revision->setDateCommitted($commit->getEpoch());
$editor = new DifferentialCommentEditor(
$revision,
DifferentialAction::ACTION_CLOSE);
$editor->setActor($actor);
$editor->setIsDaemonWorkflow(true);
$vs_diff = $this->loadChangedByCommit($diff);
if ($vs_diff) {
$data->setCommitDetail('vsDiff', $vs_diff->getID());
$changed_by_commit = PhabricatorEnv::getProductionURI(
'/D'.$revision->getID().
'?vs='.$vs_diff->getID().
'&id='.$diff->getID().
'#toc');
$editor->setChangedByCommit($changed_by_commit);
}
$commit_name = $repository->formatCommitName(
$commit->getCommitIdentifier());
$committer_name = $this->loadUserName(
$committer_phid,
$data->getCommitDetail('committer'),
$actor);
$author_name = $this->loadUserName(
$author_phid,
$data->getAuthorName(),
$actor);
$info = array();
$info[] = "authored by {$author_name}";
if ($committer_name && ($committer_name != $author_name)) {
$info[] = "committed by {$committer_name}";
}
$info = implode(', ', $info);
$editor
->setMessage("Closed by commit {$commit_name} ({$info}).")
->save();
}
}
$lock->unlock();
}
if ($should_autoclose) {
$fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
foreach ($fields as $key => $field) {
if (!$field->shouldAppearOnCommitMessage()) {
continue;
}
$field->setUser($user);
$value = idx($field_values, $field->getCommitMessageKey());
$field->setValueFromParsedCommitMessage($value);
if ($revision) {
$field->setRevision($revision);
}
$field->didParseCommit($repository, $commit, $data);
}
}
$data->save();
}
private function loadUserName($user_phid, $default, PhabricatorUser $actor) {
if (!$user_phid) {
return $default;
}
$handle = PhabricatorObjectHandleData::loadOneHandle(
$user_phid,
$actor);
return '@'.$handle->getName();
}
private function attachToRevision(
DifferentialRevision $revision,
$actor_phid) {
$drequest = DiffusionRequest::newFromDictionary(array(
'user' => PhabricatorUser::getOmnipotentUser(),
'initFromConduit' => false,
'repository' => $this->repository,
'commit' => $this->commit->getCommitIdentifier(),
));
$raw_diff = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest)
->loadRawDiff();
// TODO: Support adds, deletes and moves under SVN.
$changes = id(new ArcanistDiffParser())->parseDiff($raw_diff);
$diff = DifferentialDiff::newFromRawChanges($changes)
->setRevisionID($revision->getID())
->setAuthorPHID($actor_phid)
->setCreationMethod('commit')
->setSourceControlSystem($this->repository->getVersionControlSystem())
->setLintStatus(DifferentialLintStatus::LINT_SKIP)
->setUnitStatus(DifferentialUnitStatus::UNIT_SKIP)
->setDateCreated($this->commit->getEpoch())
->setDescription(
'Commit r'.
$this->repository->getCallsign().
$this->commit->getCommitIdentifier());
// TODO: This is not correct in SVN where one repository can have multiple
// Arcanist projects.
$arcanist_project = id(new PhabricatorRepositoryArcanistProject())
->loadOneWhere('repositoryID = %d LIMIT 1', $this->repository->getID());
if ($arcanist_project) {
$diff->setArcanistProjectPHID($arcanist_project->getPHID());
}
$parents = DiffusionCommitParentsQuery::newFromDiffusionRequest($drequest)
->loadParents();
if ($parents) {
$diff->setSourceControlBaseRevision(head_key($parents));
}
// TODO: Attach binary files.
$revision->setLineCount($diff->getLineCount());
return $diff->save();
}
private function loadChangedByCommit(DifferentialDiff $diff) {
$repository = $this->repository;
$vs_changesets = array();
$vs_diff = id(new DifferentialDiff())->loadOneWhere(
'revisionID = %d AND creationMethod != %s ORDER BY id DESC LIMIT 1',
$diff->getRevisionID(),
'commit');
foreach ($vs_diff->loadChangesets() as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $vs_diff);
$path = ltrim($path, '/');
$vs_changesets[$path] = $changeset;
}
$changesets = array();
foreach ($diff->getChangesets() as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $diff);
$path = ltrim($path, '/');
$changesets[$path] = $changeset;
}
if (array_fill_keys(array_keys($changesets), true) !=
array_fill_keys(array_keys($vs_changesets), true)) {
return $vs_diff;
}
$hunks = id(new DifferentialHunk())->loadAllWhere(
'changesetID IN (%Ld)',
mpull($vs_changesets, 'getID'));
$hunks = mgroup($hunks, 'getChangesetID');
foreach ($vs_changesets as $changeset) {
$changeset->attachHunks(idx($hunks, $changeset->getID(), array()));
}
$file_phids = array();
foreach ($vs_changesets as $changeset) {
$metadata = $changeset->getMetadata();
$file_phid = idx($metadata, 'new:binary-phid');
if ($file_phid) {
$file_phids[$file_phid] = $file_phid;
}
}
$files = array();
if ($file_phids) {
$files = id(new PhabricatorFile())->loadAllWhere(
'phid IN (%Ls)',
$file_phids);
$files = mpull($files, null, 'getPHID');
}
foreach ($changesets as $path => $changeset) {
$vs_changeset = $vs_changesets[$path];
$file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid');
if ($file_phid) {
if (!isset($files[$file_phid])) {
return $vs_diff;
}
$drequest = DiffusionRequest::newFromDictionary(array(
'user' => PhabricatorUser::getOmnipotentUser(),
'initFromConduit' => false,
'repository' => $this->repository,
'commit' => $this->commit->getCommitIdentifier(),
'path' => $path,
));
$corpus = DiffusionFileContentQuery::newFromDiffusionRequest($drequest)
->setViewer(PhabricatorUser::getOmnipotentUser())
->loadFileContent()
->getCorpus();
if ($files[$file_phid]->loadFileData() != $corpus) {
return $vs_diff;
}
} else {
$context = implode("\n", $changeset->makeChangesWithContext());
$vs_context = implode("\n", $vs_changeset->makeChangesWithContext());
// We couldn't just compare $context and $vs_context because following
// diffs will be considered different:
//
// -(empty line)
// -echo 'test';
// (empty line)
//
// (empty line)
// -echo "test";
// -(empty line)
$hunk = id(new DifferentialHunk())->setChanges($context);
$vs_hunk = id(new DifferentialHunk())->setChanges($vs_context);
if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() ||
$hunk->makeNewFile() != $vs_hunk->makeNewFile()) {
return $vs_diff;
}
}
}
return null;
}
/**
* When querying for revisions by hash, more than one revision may be found.
* This function identifies the "best" revision from such a set. Typically,
* there is only one revision found. Otherwise, we try to pick an accepted
* revision first, followed by an open revision, and otherwise we go with a
* closed or abandoned revision as a last resort.
*/
private function identifyBestRevision(array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
// get the simplest, common case out of the way
if (count($revisions) == 1) {
return reset($revisions);
}
$first_choice = array();
$second_choice = array();
$third_choice = array();
foreach ($revisions as $revision) {
switch ($revision->getStatus()) {
// "Accepted" revisions -- ostensibly what we're looking for!
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$first_choice[] = $revision;
break;
// "Open" revisions
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$second_choice[] = $revision;
break;
// default is a wtf? here
default:
case ArcanistDifferentialRevisionStatus::ABANDONED:
case ArcanistDifferentialRevisionStatus::CLOSED:
$third_choice[] = $revision;
break;
}
}
// go down the ladder like a bro at last call
if (!empty($first_choice)) {
return $this->identifyMostRecentRevision($first_choice);
}
if (!empty($second_choice)) {
return $this->identifyMostRecentRevision($second_choice);
}
if (!empty($third_choice)) {
return $this->identifyMostRecentRevision($third_choice);
}
}
/**
* Given a set of revisions, returns the revision with the latest
* updated time. This is ostensibly the most recent revision.
*/
private function identifyMostRecentRevision(array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$revisions = msort($revisions, 'getDateModified');
return end($revisions);
}
/**
* Emit an event so installs can do custom lookup of commit authors who may
* not be naturally resolvable.
*/
private function lookupUser(
PhabricatorRepositoryCommit $commit,
$query,
$guess) {
$type = PhabricatorEventType::TYPE_DIFFUSION_LOOKUPUSER;
$data = array(
'commit' => $commit,
'query' => $query,
'result' => $guess,
);
$event = new PhabricatorEvent($type, $data);
PhutilEventEngine::dispatchEvent($event);
return $event->getValue('result');
}
private function resolveUserPHID($user_name) {
if (!strlen($user_name)) {
return null;
}
$phid = $this->findUserByUserName($user_name);
if ($phid) {
return $phid;
}
$phid = $this->findUserByEmailAddress($user_name);
if ($phid) {
return $phid;
}
$phid = $this->findUserByRealName($user_name);
if ($phid) {
return $phid;
}
// No hits yet, try to parse it as an email address.
$email = new PhutilEmailAddress($user_name);
$phid = $this->findUserByEmailAddress($email->getAddress());
if ($phid) {
return $phid;
}
$display_name = $email->getDisplayName();
if ($display_name) {
$phid = $this->findUserByUserName($display_name);
if ($phid) {
return $phid;
}
$phid = $this->findUserByRealName($display_name);
if ($phid) {
return $phid;
}
}
return null;
}
private function findUserByUserName($user_name) {
$by_username = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$user_name);
if ($by_username) {
return $by_username->getPHID();
}
return null;
}
private function findUserByRealName($real_name) {
// Note, real names are not guaranteed unique, which is why we do it this
// way.
$by_realname = id(new PhabricatorUser())->loadAllWhere(
'realName = %s',
$real_name);
if (count($by_realname) == 1) {
return reset($by_realname)->getPHID();
}
return null;
}
private function findUserByEmailAddress($email_address) {
$by_email = PhabricatorUser::loadOneWithEmailAddress($email_address);
if ($by_email) {
return $by_email->getPHID();
}
return null;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Jul 28, 7:30 AM (1 w, 19 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
186518
Default Alt Text
(135 KB)

Event Timeline