Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php b/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php
index 38b85fbbc8..7e591cb1fe 100644
--- a/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php
+++ b/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php
@@ -1,441 +1,449 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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();
$viewer_is_anonymous = !$user->isLoggedIn();
$params = array_filter(
array(
'status' => $request->getStr('status'),
'order' => $request->getStr('order'),
));
$default_filter = ($viewer_is_anonymous ? 'all' : 'active');
$filters = $this->getFilters();
$this->filter = $this->selectFilter(
$filters,
$this->filter,
$default_filter);
// Redirect from search to canonical URL.
$phid_arr = $request->getArr('view_user');
if ($phid_arr) {
$view_user = id(new PhabricatorUser())
->loadOneWhere('phid = %s', head($phid_arr));
if (!$view_user) {
return new Aphront404Response();
}
$uri = id(new PhutilURI('/differential/filter/'.$this->filter.'/'.
phutil_escape_uri($view_user->getUserName()).'/'))
->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['phid'] = $view_user->getPHID();
}
// Fill in the defaults we'll actually use for calculations if any
// parameters are missing.
$params += array(
'phid' => $user->getPHID(),
'status' => 'all',
'order' => 'modified',
);
$side_nav = new AphrontSideNavView();
foreach ($filters as $filter) {
list($filter_name, $display_name) = $filter;
if ($filter_name) {
$href = clone $uri;
$href->setPath('/differential/filter/'.$filter_name.'/'.$username);
if ($filter_name == $this->filter) {
$class = 'aphront-side-nav-selected';
} else {
$class = null;
}
$item = phutil_render_tag(
'a',
array(
'href' => (string)$href,
'class' => $class,
),
phutil_escape_html($display_name));
} else {
$item = phutil_render_tag(
'span',
array(),
phutil_escape_html($display_name));
}
$side_nav->addNavItem($item);
}
$panels = array();
$handles = array();
$controls = $this->getFilterControls($this->filter);
if ($this->getFilterRequiresUser($this->filter) && !$params['phid']) {
// 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('User Required');
$warning->appendChild(
'This filter requires that a user be specified above.');
$panels[] = $warning;
} else {
$query = $this->buildQuery($this->filter, $params['phid']);
$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['phid'], $revisions);
$view_objects = ipull($views, 'view');
$phids = array_mergev(mpull($view_objects, 'getRequiredHandlePHIDs'));
$phids[] = $params['phid'];
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
foreach ($views as $view) {
$view['view']->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader($view['title']);
$panel->appendChild($view['view']);
if ($pager) {
$panel->appendChild($pager);
}
$panels[] = $panel;
}
}
$filter_form = id(new AphrontFormView())
->setMethod('GET')
->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('Filter Revisions'));
$filter_view = new AphrontListFilterView();
$filter_view->appendChild($filter_form);
if (!$viewer_is_anonymous) {
$create_uri = new PhutilURI('/differential/diff/create/');
$filter_view->addButton(
phutil_render_tag(
'a',
array(
'href' => (string)$create_uri,
'class' => 'green button',
),
'Create Revision'));
}
$side_nav->appendChild($filter_view);
foreach ($panels as $panel) {
$side_nav->appendChild($panel);
}
return $this->buildStandardPageResponse(
$side_nav,
array(
'title' => 'Differential Home',
'tab' => 'revisions',
));
}
private function getFilters() {
return array(
array(null, 'User Revisions'),
array('active', 'Active'),
array('revisions', 'Revisions'),
array('reviews', 'Reviews'),
array('subscribed', 'Subscribed'),
+ array('drafts', 'Draft Reviews'),
array(null, 'All Revisions'),
array('all', 'All'),
);
}
private function selectFilter(
array $filters,
$requested_filter,
$default_filter) {
// If the user requested a filter, make sure it actually exists.
if ($requested_filter) {
foreach ($filters as $filter) {
if ($filter[0] === $requested_filter) {
return $requested_filter;
}
}
}
// If not, return the default filter.
return $default_filter;
}
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', 'status', 'order'),
'reviews' => array('phid', 'status', 'order'),
'subscribed' => array('phid', '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, $user_phid) {
$query = new DifferentialRevisionQuery();
$query->needRelationships(true);
switch ($filter) {
case 'active':
$query->withResponsibleUsers(array($user_phid));
$query->withStatus(DifferentialRevisionQuery::STATUS_OPEN);
$query->setLimit(null);
break;
case 'revisions':
$query->withAuthors(array($user_phid));
break;
case 'reviews':
$query->withReviewers(array($user_phid));
break;
case 'subscribed':
$query->withSubscribers(array($user_phid));
break;
+ case 'drafts':
+ $query->withDraftRepliesByAuthors(array($user_phid));
+ break;
case 'all':
break;
default:
throw new Exception("Unknown filter '{$filter}'!");
}
return $query;
}
private function renderControl(
$control,
array $handles,
PhutilURI $uri,
array $params) {
switch ($control) {
case 'phid':
$view_phid = $params['phid'];
$value = array();
if ($view_phid) {
$value = array(
$view_phid => $handles[$view_phid]->getFullName(),
);
}
return id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setLabel('View User')
->setName('view_user')
->setValue($value)
->setLimit(1);
case 'status':
return id(new AphrontFormToggleButtonsControl())
->setLabel('Status')
->setValue($params['status'])
->setBaseURI($uri, 'status')
->setButtons(
array(
'all' => 'All',
'open' => 'Open',
'committed' => 'Committed',
'abandoned' => 'Abandoned',
));
case 'order':
return id(new AphrontFormToggleButtonsControl())
->setLabel('Order')
->setValue($params['order'])
->setBaseURI($uri, 'order')
->setButtons(
array(
'modified' => 'Updated',
'created' => 'Created',
));
default:
throw new Exception("Unknown control '{$control}'!");
}
}
private function applyControlToQuery($control, $query, array $params) {
switch ($control) {
case 'phid':
// Already applied by query construction.
break;
case 'status':
if ($params['status'] == 'open') {
$query->withStatus(DifferentialRevisionQuery::STATUS_OPEN);
} elseif ($params['status'] == 'committed') {
$query->withStatus(DifferentialRevisionQuery::STATUS_COMMITTED);
} elseif ($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, $user_phid, array $revisions) {
$user = $this->getRequest()->getUser();
$template = id(new DifferentialRevisionListView())
->setUser($user)
->setFields(DifferentialRevisionListView::getDefaultFields());
$views = array();
switch ($filter) {
case 'active':
list($active, $waiting) = DifferentialRevisionQuery::splitResponsible(
$revisions,
$user_phid);
$view = id(clone $template)
->setRevisions($active)
->setNoDataString("You have no active revisions requiring action.");
$views[] = array(
'title' => 'Action Required',
'view' => $view,
);
$view = id(clone $template)
->setRevisions($waiting)
->setNoDataString("You have no active revisions waiting on others.");
$views[] = array(
'title' => 'Waiting On Others',
'view' => $view,
);
break;
case 'revisions':
case 'reviews':
case 'subscribed':
+ case 'drafts':
case 'all':
$titles = array(
'revisions' => 'Revisions by Author',
'reviews' => 'Revisions by Reviewer',
'subscribed' => 'Revisions by Subscriber',
'all' => 'Revisions',
);
$view = id(clone $template)
->setRevisions($revisions);
$views[] = array(
'title' => idx($titles, $filter),
'view' => $view,
);
break;
default:
throw new Exception("Unknown filter '{$filter}'!");
}
return $views;
}
}
diff --git a/src/applications/differential/query/revision/DifferentialRevisionQuery.php b/src/applications/differential/query/revision/DifferentialRevisionQuery.php
index 1641481c6f..e38f1d9c30 100644
--- a/src/applications/differential/query/revision/DifferentialRevisionQuery.php
+++ b/src/applications/differential/query/revision/DifferentialRevisionQuery.php
@@ -1,823 +1,850 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Flexible query API for Differential revisions. Example:
*
* // Load open revisions
* $revisions = id(new DifferentialRevisionQuery())
* ->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
* ->execute();
*
* @task config Query Configuration
* @task exec Query Execution
* @task internal Internals
*/
final class DifferentialRevisionQuery {
// TODO: Replace DifferentialRevisionListData with this class.
private $pathIDs = array();
private $status = 'status-any';
const STATUS_ANY = 'status-any';
const STATUS_OPEN = 'status-open';
const STATUS_ACCEPTED = 'status-accepted';
const STATUS_COMMITTED = 'status-committed';
const STATUS_ABANDONED = 'status-abandoned';
private $authors = array();
+ private $draftAuthors = array();
private $ccs = array();
private $reviewers = array();
private $revIDs = array();
private $commitHashes = array();
private $phids = array();
private $subscribers = array();
private $responsibles = array();
private $branches = array();
private $order = 'order-modified';
const ORDER_MODIFIED = 'order-modified';
const ORDER_CREATED = 'order-created';
/**
* This is essentially a denormalized copy of the revision modified time that
* should perform better for path queries with a LIMIT. Critically, when you
* browse "/", every revision in that repository for all time will match so
* the query benefits from being able to stop before fully materializing the
* result set.
*/
const ORDER_PATH_MODIFIED = 'order-path-modified';
private $limit = 1000;
private $offset = 0;
private $needRelationships = false;
private $needActiveDiffs = false;
private $needDiffIDs = false;
private $needCommitPHIDs = false;
/* -( Query Configuration )------------------------------------------------ */
/**
* Filter results to revisions which affect a Diffusion path ID in a given
* repository. You can call this multiple times to select revisions for
* several paths.
*
* @param int Diffusion repository ID.
* @param int Diffusion path ID.
* @return this
* @task config
*/
public function withPath($repository_id, $path_id) {
$this->pathIDs[] = array(
'repositoryID' => $repository_id,
'pathID' => $path_id,
);
return $this;
}
/**
* Filter results to revisions authored by one of the given PHIDs. Calling
* this function will clear anything set by previous calls to
* @{method:withAuthors}.
*
* @param array List of PHIDs of authors
* @return this
* @task config
*/
public function withAuthors(array $author_phids) {
$this->authors = $author_phids;
return $this;
}
+ /**
+ * Filter results to revisions with comments authored bythe given PHIDs
+ *
+ * @param array List of PHIDs of authors
+ * @return this
+ * @task config
+ */
+ public function withDraftRepliesByAuthors(array $author_phids) {
+ $this->draftAuthors = $author_phids;
+ return $this;
+ }
+
/**
* Filter results to revisions which CC one of the listed people. Calling this
* function will clear anything set by previous calls to @{method:withCCs}.
*
* @param array List of PHIDs of subscribers
* @return this
* @task config
*/
public function withCCs(array $cc_phids) {
$this->ccs = $cc_phids;
return $this;
}
/**
* Filter results to revisions that have one of the provided PHIDs as
* reviewers. Calling this function will clear anything set by previous calls
* to @{method:withReviewers}.
*
* @param array List of PHIDs of reviewers
* @return this
* @task config
*/
public function withReviewers(array $reviewer_phids) {
$this->reviewers = $reviewer_phids;
return $this;
}
/**
* Filter results to revisions that have one of the provided commit hashes.
* Calling this function will clear anything set by previous calls to
* @{method:withCommitHashes}.
*
* @param array List of pairs <Class
* ArcanistDifferentialRevisionHash::HASH_$type constant,
* hash>
* @return this
* @task config
*/
public function withCommitHashes(array $commit_hashes) {
$this->commitHashes = $commit_hashes;
return $this;
}
/**
* Filter results to revisions with a given status. Provide a class constant,
* such as ##DifferentialRevisionQuery::STATUS_OPEN##.
*
* @param const Class STATUS constant, like STATUS_OPEN.
* @return this
* @task config
*/
public function withStatus($status_constant) {
$this->status = $status_constant;
return $this;
}
/**
* Filter results to revisions on given branches.
*
* @param list List of branch names.
* @return this
* @task config
*/
public function withBranches(array $branches) {
$this->branches = $branches;
return $this;
}
/**
* Filter results to only return revisions whose ids are in the given set.
*
* @param array List of revision ids
* @return this
* @task config
*/
public function withIDs(array $ids) {
$this->revIDs = $ids;
return $this;
}
/**
* Filter results to only return revisions whose PHIDs are in the given set.
*
* @param array List of revision PHIDs
* @return this
* @task config
*/
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
/**
* Given a set of users, filter results to return only revisions they are
* responsible for (i.e., they are either authors or reviewers).
*
* @param array List of user PHIDs.
* @return this
* @task config
*/
public function withResponsibleUsers(array $responsible_phids) {
$this->responsibles = $responsible_phids;
return $this;
}
/**
* Filter results to only return revisions with a given set of subscribers
* (i.e., they are authors, reviewers or CC'd).
*
* @param array List of user PHIDs.
* @return this
* @task config
*/
public function withSubscribers(array $subscriber_phids) {
$this->subscribers = $subscriber_phids;
return $this;
}
/**
* Set result ordering. Provide a class constant, such as
* ##DifferentialRevisionQuery::ORDER_CREATED##.
*
* @task config
*/
public function setOrder($order_constant) {
$this->order = $order_constant;
return $this;
}
/**
* Set result limit. If unspecified, defaults to 1000.
*
* @param int Result limit.
* @return this
* @task config
*/
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
/**
* Set result offset. If unspecified, defaults to 0.
*
* @param int Result offset.
* @return this
* @task config
*/
public function setOffset($offset) {
$this->offset = $offset;
return $this;
}
/**
* Set whether or not the query will load and attach relationships.
*
* @param bool True to load and attach relationships.
* @return this
* @task config
*/
public function needRelationships($need_relationships) {
$this->needRelationships = $need_relationships;
return $this;
}
/**
* Set whether or not the query should load the active diff for each
* revision.
*
* @param bool True to load and attach diffs.
* @return this
* @task config
*/
public function needActiveDiffs($need_active_diffs) {
$this->needActiveDiffs = $need_active_diffs;
return $this;
}
/**
* Set whether or not the query should load the associated commit PHIDs for
* each revision.
*
* @param bool True to load and attach diffs.
* @return this
* @task config
*/
public function needCommitPHIDs($need_commit_phids) {
$this->needCommitPHIDs = $need_commit_phids;
return $this;
}
/**
* Set whether or not the query should load associated diff IDs for each
* revision.
*
* @param bool True to load and attach diff IDs.
* @return this
* @task config
*/
public function needDiffIDs($need_diff_ids) {
$this->needDiffIDs = $need_diff_ids;
return $this;
}
/* -( Query Execution )---------------------------------------------------- */
/**
* Execute the query as configured, returning matching
* @{class:DifferentialRevision} objects.
*
* @return list List of matching DifferentialRevision objects.
* @task exec
*/
public function execute() {
$table = new DifferentialRevision();
$conn_r = $table->establishConnection('r');
if ($this->shouldUseResponsibleFastPath()) {
$data = $this->loadDataUsingResponsibleFastPath();
} else {
$data = $this->loadData();
}
$revisions = $table->loadAllFromArray($data);
if ($revisions) {
if ($this->needRelationships) {
$this->loadRelationships($conn_r, $revisions);
}
if ($this->needCommitPHIDs) {
$this->loadCommitPHIDs($conn_r, $revisions);
}
$need_active = $this->needActiveDiffs ||
$this->branches;
$need_ids = $need_active ||
$this->needDiffIDs;
if ($need_ids) {
$this->loadDiffIDs($conn_r, $revisions);
}
if ($need_active) {
$this->loadActiveDiffs($conn_r, $revisions);
}
if ($this->branches) {
// TODO: We could filter this in SQL instead and might get better
// performance in some cases.
$branch_map = array_fill_keys($this->branches, true);
foreach ($revisions as $key => $revision) {
$diff = $revision->getActiveDiff();
if (!$diff) {
unset($revisions[$key]);
continue;
}
// TODO: Old arc uploaded the wrong branch name for Mercurial (i.e.,
// with a trailing "\n"). Once the arc version gets bumped, do a
// migration and remove this.
$branch = trim($diff->getBranch());
if (!$diff || empty($branch_map[$branch])) {
unset($revisions[$key]);
continue;
}
}
}
}
return $revisions;
}
/**
* Determine if we should execute an optimized, fast-path query to fetch
* open revisions for one responsible user. This is used by the Differential
* dashboard and much faster when executed as a UNION ALL than with JOIN
* and WHERE, which is why we special case it.
*/
private function shouldUseResponsibleFastPath() {
if ((count($this->responsibles) == 1) &&
($this->status == self::STATUS_OPEN) &&
($this->order == self::ORDER_MODIFIED) &&
!$this->offset &&
!$this->limit &&
!$this->subscribers &&
!$this->reviewers &&
!$this->ccs &&
!$this->authors &&
!$this->revIDs &&
!$this->commitHashes &&
!$this->phids) {
return true;
}
return false;
}
private function loadDataUsingResponsibleFastPath() {
$table = new DifferentialRevision();
$conn_r = $table->establishConnection('r');
$responsible_phid = reset($this->responsibles);
$open_statuses = array(
ArcanistDifferentialRevisionStatus::NEEDS_REVIEW,
ArcanistDifferentialRevisionStatus::NEEDS_REVISION,
ArcanistDifferentialRevisionStatus::ACCEPTED,
);
return queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE authorPHID = %s AND status IN (%Ld)
UNION ALL
SELECT r.* FROM %T r JOIN %T rel
ON rel.revisionID = r.id
AND rel.relation = %s
AND rel.objectPHID = %s
WHERE r.status IN (%Ld) ORDER BY dateModified DESC',
$table->getTableName(),
$responsible_phid,
$open_statuses,
$table->getTableName(),
DifferentialRevision::RELATIONSHIP_TABLE,
DifferentialRevision::RELATION_REVIEWER,
$responsible_phid,
$open_statuses);
}
private function loadData() {
$table = new DifferentialRevision();
$conn_r = $table->establishConnection('r');
$select = qsprintf(
$conn_r,
'SELECT r.* FROM %T r',
$table->getTableName());
$joins = $this->buildJoinsClause($conn_r);
$where = $this->buildWhereClause($conn_r);
$group_by = $this->buildGroupByClause($conn_r);
$order_by = $this->buildOrderByClause($conn_r);
$limit = '';
if ($this->offset || $this->limit) {
$limit = qsprintf(
$conn_r,
'LIMIT %d, %d',
(int)$this->offset,
$this->limit);
}
return queryfx_all(
$conn_r,
'%Q %Q %Q %Q %Q %Q',
$select,
$joins,
$where,
$group_by,
$order_by,
$limit);
}
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
private function buildJoinsClause($conn_r) {
$joins = array();
if ($this->pathIDs) {
$path_table = new DifferentialAffectedPath();
$joins[] = qsprintf(
$conn_r,
'JOIN %T p ON p.revisionID = r.id',
$path_table->getTableName());
}
if ($this->commitHashes) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T hash_rel ON hash_rel.revisionID = r.id',
ArcanistDifferentialRevisionHash::TABLE_NAME);
}
if ($this->ccs) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T cc_rel ON cc_rel.revisionID = r.id '.
'AND cc_rel.relation = %s '.
'AND cc_rel.objectPHID in (%Ls)',
DifferentialRevision::RELATIONSHIP_TABLE,
DifferentialRevision::RELATION_SUBSCRIBED,
$this->ccs);
}
if ($this->reviewers) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T reviewer_rel ON reviewer_rel.revisionID = r.id '.
'AND reviewer_rel.relation = %s '.
'AND reviewer_rel.objectPHID in (%Ls)',
DifferentialRevision::RELATIONSHIP_TABLE,
DifferentialRevision::RELATION_REVIEWER,
$this->reviewers);
}
if ($this->subscribers) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T sub_rel ON sub_rel.revisionID = r.id '.
'AND sub_rel.relation IN (%Ls) '.
'AND sub_rel.objectPHID in (%Ls)',
DifferentialRevision::RELATIONSHIP_TABLE,
array(
DifferentialRevision::RELATION_SUBSCRIBED,
DifferentialRevision::RELATION_REVIEWER,
),
$this->subscribers);
}
if ($this->responsibles) {
$joins[] = qsprintf(
$conn_r,
'LEFT JOIN %T responsibles_rel ON responsibles_rel.revisionID = r.id '.
'AND responsibles_rel.relation = %s '.
'AND responsibles_rel.objectPHID in (%Ls)',
DifferentialRevision::RELATIONSHIP_TABLE,
DifferentialRevision::RELATION_REVIEWER,
$this->responsibles);
}
+ if ($this->draftAuthors) {
+ $joins[] = qsprintf(
+ $conn_r,
+ 'JOIN %T inline_comment ON inline_comment.revisionID = r.id '.
+ 'AND inline_comment.commentID is NULL',
+ id(new DifferentialInlineComment())->getTableName());
+ }
+
$joins = implode(' ', $joins);
return $joins;
}
/**
* @task internal
*/
private function buildWhereClause($conn_r) {
$where = array();
if ($this->pathIDs) {
$path_clauses = array();
$repo_info = igroup($this->pathIDs, 'repositoryID');
foreach ($repo_info as $repository_id => $paths) {
$path_clauses[] = qsprintf(
$conn_r,
'(repositoryID = %d AND pathID IN (%Ld))',
$repository_id,
ipull($paths, 'pathID'));
}
$path_clauses = '('.implode(' OR ', $path_clauses).')';
$where[] = $path_clauses;
}
if ($this->authors) {
$where[] = qsprintf(
$conn_r,
'authorPHID IN (%Ls)',
$this->authors);
}
+ if ($this->draftAuthors) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'inline_comment.authorPHID IN (%Ls)',
+ $this->draftAuthors);
+ }
if ($this->revIDs) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->revIDs);
}
if ($this->commitHashes) {
$hash_clauses = array();
foreach ($this->commitHashes as $info) {
list($type, $hash) = $info;
$hash_clauses[] = qsprintf(
$conn_r,
'(hash_rel.type = %s AND hash_rel.hash = %s)',
$type,
$hash);
}
$hash_clauses = '('.implode(' OR ', $hash_clauses).')';
$where[] = $hash_clauses;
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
if ($this->responsibles) {
$where[] = qsprintf(
$conn_r,
'(responsibles_rel.objectPHID IS NOT NULL OR r.authorPHID IN (%Ls))',
$this->responsibles);
}
switch ($this->status) {
case self::STATUS_ANY:
break;
case self::STATUS_OPEN:
$where[] = qsprintf(
$conn_r,
'status IN (%Ld)',
array(
ArcanistDifferentialRevisionStatus::NEEDS_REVIEW,
ArcanistDifferentialRevisionStatus::NEEDS_REVISION,
ArcanistDifferentialRevisionStatus::ACCEPTED,
));
break;
case self::STATUS_ACCEPTED:
$where[] = qsprintf(
$conn_r,
'status IN (%Ld)',
array(
ArcanistDifferentialRevisionStatus::ACCEPTED,
));
break;
case self::STATUS_COMMITTED:
$where[] = qsprintf(
$conn_r,
'status IN (%Ld)',
array(
ArcanistDifferentialRevisionStatus::COMMITTED,
));
break;
case self::STATUS_ABANDONED:
$where[] = qsprintf(
$conn_r,
'status IN (%Ld)',
array(
ArcanistDifferentialRevisionStatus::ABANDONED,
));
break;
default:
throw new Exception(
"Unknown revision status filter constant '{$this->status}'!");
}
if ($where) {
$where = 'WHERE '.implode(' AND ', $where);
} else {
$where = '';
}
return $where;
}
/**
* @task internal
*/
private function buildGroupByClause($conn_r) {
$join_triggers = array_merge(
$this->pathIDs,
$this->ccs,
$this->reviewers,
$this->subscribers,
$this->responsibles);
$needs_distinct = (count($join_triggers) > 1);
if ($needs_distinct) {
return 'GROUP BY r.id';
} else {
return '';
}
}
/**
* @task internal
*/
private function buildOrderByClause($conn_r) {
switch ($this->order) {
case self::ORDER_MODIFIED:
return 'ORDER BY r.dateModified DESC';
case self::ORDER_CREATED:
return 'ORDER BY r.dateCreated DESC';
case self::ORDER_PATH_MODIFIED:
if (!$this->pathIDs) {
throw new Exception(
"To use ORDER_PATH_MODIFIED, you must specify withPath().");
}
return 'ORDER BY p.epoch DESC';
default:
throw new Exception("Unknown query order constant '{$this->order}'.");
}
}
private function loadRelationships($conn_r, array $revisions) {
$relationships = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE revisionID in (%Ld) ORDER BY sequence',
DifferentialRevision::RELATIONSHIP_TABLE,
mpull($revisions, 'getID'));
$relationships = igroup($relationships, 'revisionID');
foreach ($revisions as $revision) {
$revision->attachRelationships(
idx(
$relationships,
$revision->getID(),
array()));
}
}
private function loadCommitPHIDs($conn_r, array $revisions) {
$commit_phids = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE revisionID IN (%Ld)',
DifferentialRevision::TABLE_COMMIT,
mpull($revisions, 'getID'));
$commit_phids = igroup($commit_phids, 'revisionID');
foreach ($revisions as $revision) {
$phids = idx($commit_phids, $revision->getID(), array());
$phids = ipull($phids, 'commitPHID');
$revision->attachCommitPHIDs($phids);
}
}
private function loadDiffIDs($conn_r, array $revisions) {
$diff_table = new DifferentialDiff();
$diff_ids = queryfx_all(
$conn_r,
'SELECT revisionID, id FROM %T WHERE revisionID IN (%Ld)
ORDER BY id DESC',
$diff_table->getTableName(),
mpull($revisions, 'getID'));
$diff_ids = igroup($diff_ids, 'revisionID');
foreach ($revisions as $revision) {
$ids = idx($diff_ids, $revision->getID(), array());
$ids = ipull($ids, 'id');
$revision->attachDiffIDs($ids);
}
}
private function loadActiveDiffs($conn_r, array $revisions) {
$diff_table = new DifferentialDiff();
$load_ids = array();
foreach ($revisions as $revision) {
$diffs = $revision->getDiffIDs();
if ($diffs) {
$load_ids[] = max($diffs);
}
}
$active_diffs = array();
if ($load_ids) {
$active_diffs = $diff_table->loadAllWhere(
'id IN (%Ld)',
$load_ids);
}
$active_diffs = mpull($active_diffs, null, 'getRevisionID');
foreach ($revisions as $revision) {
$revision->attachActiveDiff(idx($active_diffs, $revision->getID()));
}
}
public static function splitResponsible(array $revisions, $user_phid) {
$active = array();
$waiting = array();
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
// Bucket revisions into $active (revisions you need to do something
// about) and $waiting (revisions you're waiting on someone else to do
// something about).
foreach ($revisions as $revision) {
$needs_review = ($revision->getStatus() == $status_review);
$filter_is_author = ($revision->getAuthorPHID() == $user_phid);
// If exactly one of "needs review" and "the user is the author" is
// true, the user needs to act on it. Otherwise, they're waiting on
// it.
if ($needs_review ^ $filter_is_author) {
$active[] = $revision;
} else {
$waiting[] = $revision;
}
}
return array($active, $waiting);
}
}
diff --git a/src/applications/differential/query/revision/__init__.php b/src/applications/differential/query/revision/__init__.php
index 175d01879b..04b88010cc 100644
--- a/src/applications/differential/query/revision/__init__.php
+++ b/src/applications/differential/query/revision/__init__.php
@@ -1,21 +1,22 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('arcanist', 'differential/constants/revisionhash');
phutil_require_module('arcanist', 'differential/constants/revisionstatus');
phutil_require_module('phabricator', 'applications/differential/storage/affectedpath');
phutil_require_module('phabricator', 'applications/differential/storage/diff');
+phutil_require_module('phabricator', 'applications/differential/storage/inlinecomment');
phutil_require_module('phabricator', 'applications/differential/storage/revision');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'utils');
phutil_require_source('DifferentialRevisionQuery.php');

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 17:48 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127134
Default Alt Text
(38 KB)

Event Timeline