Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/query/DifferentialRevisionQuery.php
12262 views
1
<?php
2
3
/**
4
* @task config Query Configuration
5
* @task exec Query Execution
6
* @task internal Internals
7
*/
8
final class DifferentialRevisionQuery
9
extends PhabricatorCursorPagedPolicyAwareQuery {
10
11
private $authors = array();
12
private $draftAuthors = array();
13
private $ccs = array();
14
private $reviewers = array();
15
private $revIDs = array();
16
private $commitHashes = array();
17
private $phids = array();
18
private $responsibles = array();
19
private $branches = array();
20
private $repositoryPHIDs;
21
private $updatedEpochMin;
22
private $updatedEpochMax;
23
private $statuses;
24
private $isOpen;
25
private $createdEpochMin;
26
private $createdEpochMax;
27
private $noReviewers;
28
private $paths;
29
30
const ORDER_MODIFIED = 'order-modified';
31
const ORDER_CREATED = 'order-created';
32
33
private $needActiveDiffs = false;
34
private $needDiffIDs = false;
35
private $needCommitPHIDs = false;
36
private $needHashes = false;
37
private $needReviewers = false;
38
private $needReviewerAuthority;
39
private $needDrafts;
40
private $needFlags;
41
42
43
/* -( Query Configuration )------------------------------------------------ */
44
45
/**
46
* Find revisions affecting one or more items in a list of paths.
47
*
48
* @param list<string> List of file paths.
49
* @return this
50
* @task config
51
*/
52
public function withPaths(array $paths) {
53
$this->paths = $paths;
54
return $this;
55
}
56
57
/**
58
* Filter results to revisions authored by one of the given PHIDs. Calling
59
* this function will clear anything set by previous calls to
60
* @{method:withAuthors}.
61
*
62
* @param array List of PHIDs of authors
63
* @return this
64
* @task config
65
*/
66
public function withAuthors(array $author_phids) {
67
$this->authors = $author_phids;
68
return $this;
69
}
70
71
/**
72
* Filter results to revisions which CC one of the listed people. Calling this
73
* function will clear anything set by previous calls to @{method:withCCs}.
74
*
75
* @param array List of PHIDs of subscribers.
76
* @return this
77
* @task config
78
*/
79
public function withCCs(array $cc_phids) {
80
$this->ccs = $cc_phids;
81
return $this;
82
}
83
84
/**
85
* Filter results to revisions that have one of the provided PHIDs as
86
* reviewers. Calling this function will clear anything set by previous calls
87
* to @{method:withReviewers}.
88
*
89
* @param array List of PHIDs of reviewers
90
* @return this
91
* @task config
92
*/
93
public function withReviewers(array $reviewer_phids) {
94
if ($reviewer_phids === array()) {
95
throw new Exception(
96
pht(
97
'Empty "withReviewers()" constraint is invalid. Provide one or '.
98
'more values, or remove the constraint.'));
99
}
100
101
$with_none = false;
102
103
foreach ($reviewer_phids as $key => $phid) {
104
switch ($phid) {
105
case DifferentialNoReviewersDatasource::FUNCTION_TOKEN:
106
$with_none = true;
107
unset($reviewer_phids[$key]);
108
break;
109
default:
110
break;
111
}
112
}
113
114
$this->noReviewers = $with_none;
115
if ($reviewer_phids) {
116
$this->reviewers = array_values($reviewer_phids);
117
}
118
119
return $this;
120
}
121
122
/**
123
* Filter results to revisions that have one of the provided commit hashes.
124
* Calling this function will clear anything set by previous calls to
125
* @{method:withCommitHashes}.
126
*
127
* @param array List of pairs <Class
128
* ArcanistDifferentialRevisionHash::HASH_$type constant,
129
* hash>
130
* @return this
131
* @task config
132
*/
133
public function withCommitHashes(array $commit_hashes) {
134
$this->commitHashes = $commit_hashes;
135
return $this;
136
}
137
138
public function withStatuses(array $statuses) {
139
$this->statuses = $statuses;
140
return $this;
141
}
142
143
public function withIsOpen($is_open) {
144
$this->isOpen = $is_open;
145
return $this;
146
}
147
148
149
/**
150
* Filter results to revisions on given branches.
151
*
152
* @param list List of branch names.
153
* @return this
154
* @task config
155
*/
156
public function withBranches(array $branches) {
157
$this->branches = $branches;
158
return $this;
159
}
160
161
162
/**
163
* Filter results to only return revisions whose ids are in the given set.
164
*
165
* @param array List of revision ids
166
* @return this
167
* @task config
168
*/
169
public function withIDs(array $ids) {
170
$this->revIDs = $ids;
171
return $this;
172
}
173
174
175
/**
176
* Filter results to only return revisions whose PHIDs are in the given set.
177
*
178
* @param array List of revision PHIDs
179
* @return this
180
* @task config
181
*/
182
public function withPHIDs(array $phids) {
183
$this->phids = $phids;
184
return $this;
185
}
186
187
188
/**
189
* Given a set of users, filter results to return only revisions they are
190
* responsible for (i.e., they are either authors or reviewers).
191
*
192
* @param array List of user PHIDs.
193
* @return this
194
* @task config
195
*/
196
public function withResponsibleUsers(array $responsible_phids) {
197
$this->responsibles = $responsible_phids;
198
return $this;
199
}
200
201
202
public function withRepositoryPHIDs(array $repository_phids) {
203
$this->repositoryPHIDs = $repository_phids;
204
return $this;
205
}
206
207
public function withUpdatedEpochBetween($min, $max) {
208
$this->updatedEpochMin = $min;
209
$this->updatedEpochMax = $max;
210
return $this;
211
}
212
213
public function withCreatedEpochBetween($min, $max) {
214
$this->createdEpochMin = $min;
215
$this->createdEpochMax = $max;
216
return $this;
217
}
218
219
220
/**
221
* Set whether or not the query should load the active diff for each
222
* revision.
223
*
224
* @param bool True to load and attach diffs.
225
* @return this
226
* @task config
227
*/
228
public function needActiveDiffs($need_active_diffs) {
229
$this->needActiveDiffs = $need_active_diffs;
230
return $this;
231
}
232
233
234
/**
235
* Set whether or not the query should load the associated commit PHIDs for
236
* each revision.
237
*
238
* @param bool True to load and attach diffs.
239
* @return this
240
* @task config
241
*/
242
public function needCommitPHIDs($need_commit_phids) {
243
$this->needCommitPHIDs = $need_commit_phids;
244
return $this;
245
}
246
247
248
/**
249
* Set whether or not the query should load associated diff IDs for each
250
* revision.
251
*
252
* @param bool True to load and attach diff IDs.
253
* @return this
254
* @task config
255
*/
256
public function needDiffIDs($need_diff_ids) {
257
$this->needDiffIDs = $need_diff_ids;
258
return $this;
259
}
260
261
262
/**
263
* Set whether or not the query should load associated commit hashes for each
264
* revision.
265
*
266
* @param bool True to load and attach commit hashes.
267
* @return this
268
* @task config
269
*/
270
public function needHashes($need_hashes) {
271
$this->needHashes = $need_hashes;
272
return $this;
273
}
274
275
276
/**
277
* Set whether or not the query should load associated reviewers.
278
*
279
* @param bool True to load and attach reviewers.
280
* @return this
281
* @task config
282
*/
283
public function needReviewers($need_reviewers) {
284
$this->needReviewers = $need_reviewers;
285
return $this;
286
}
287
288
289
/**
290
* Request information about the viewer's authority to act on behalf of each
291
* reviewer. In particular, they have authority to act on behalf of projects
292
* they are a member of.
293
*
294
* @param bool True to load and attach authority.
295
* @return this
296
* @task config
297
*/
298
public function needReviewerAuthority($need_reviewer_authority) {
299
$this->needReviewerAuthority = $need_reviewer_authority;
300
return $this;
301
}
302
303
public function needFlags($need_flags) {
304
$this->needFlags = $need_flags;
305
return $this;
306
}
307
308
public function needDrafts($need_drafts) {
309
$this->needDrafts = $need_drafts;
310
return $this;
311
}
312
313
314
/* -( Query Execution )---------------------------------------------------- */
315
316
317
public function newResultObject() {
318
return new DifferentialRevision();
319
}
320
321
322
/**
323
* Execute the query as configured, returning matching
324
* @{class:DifferentialRevision} objects.
325
*
326
* @return list List of matching DifferentialRevision objects.
327
* @task exec
328
*/
329
protected function loadPage() {
330
$data = $this->loadData();
331
$data = $this->didLoadRawRows($data);
332
$table = $this->newResultObject();
333
return $table->loadAllFromArray($data);
334
}
335
336
protected function willFilterPage(array $revisions) {
337
$viewer = $this->getViewer();
338
339
$repository_phids = mpull($revisions, 'getRepositoryPHID');
340
$repository_phids = array_filter($repository_phids);
341
342
$repositories = array();
343
if ($repository_phids) {
344
$repositories = id(new PhabricatorRepositoryQuery())
345
->setViewer($this->getViewer())
346
->withPHIDs($repository_phids)
347
->execute();
348
$repositories = mpull($repositories, null, 'getPHID');
349
}
350
351
// If a revision is associated with a repository:
352
//
353
// - the viewer must be able to see the repository; or
354
// - the viewer must have an automatic view capability.
355
//
356
// In the latter case, we'll load the revision but not load the repository.
357
358
$can_view = PhabricatorPolicyCapability::CAN_VIEW;
359
foreach ($revisions as $key => $revision) {
360
$repo_phid = $revision->getRepositoryPHID();
361
if (!$repo_phid) {
362
// The revision has no associated repository. Attach `null` and move on.
363
$revision->attachRepository(null);
364
continue;
365
}
366
367
$repository = idx($repositories, $repo_phid);
368
if ($repository) {
369
// The revision has an associated repository, and the viewer can see
370
// it. Attach it and move on.
371
$revision->attachRepository($repository);
372
continue;
373
}
374
375
if ($revision->hasAutomaticCapability($can_view, $viewer)) {
376
// The revision has an associated repository which the viewer can not
377
// see, but the viewer has an automatic capability on this revision.
378
// Load the revision without attaching a repository.
379
$revision->attachRepository(null);
380
continue;
381
}
382
383
if ($this->getViewer()->isOmnipotent()) {
384
// The viewer is omnipotent. Allow the revision to load even without
385
// a repository.
386
$revision->attachRepository(null);
387
continue;
388
}
389
390
// The revision has an associated repository, and the viewer can't see
391
// it, and the viewer has no special capabilities. Filter out this
392
// revision.
393
$this->didRejectResult($revision);
394
unset($revisions[$key]);
395
}
396
397
if (!$revisions) {
398
return array();
399
}
400
401
$table = new DifferentialRevision();
402
$conn_r = $table->establishConnection('r');
403
404
if ($this->needCommitPHIDs) {
405
$this->loadCommitPHIDs($revisions);
406
}
407
408
$need_active = $this->needActiveDiffs;
409
$need_ids = $need_active || $this->needDiffIDs;
410
411
if ($need_ids) {
412
$this->loadDiffIDs($conn_r, $revisions);
413
}
414
415
if ($need_active) {
416
$this->loadActiveDiffs($conn_r, $revisions);
417
}
418
419
if ($this->needHashes) {
420
$this->loadHashes($conn_r, $revisions);
421
}
422
423
if ($this->needReviewers || $this->needReviewerAuthority) {
424
$this->loadReviewers($conn_r, $revisions);
425
}
426
427
return $revisions;
428
}
429
430
protected function didFilterPage(array $revisions) {
431
$viewer = $this->getViewer();
432
433
if ($this->needFlags) {
434
$flags = id(new PhabricatorFlagQuery())
435
->setViewer($viewer)
436
->withOwnerPHIDs(array($viewer->getPHID()))
437
->withObjectPHIDs(mpull($revisions, 'getPHID'))
438
->execute();
439
$flags = mpull($flags, null, 'getObjectPHID');
440
foreach ($revisions as $revision) {
441
$revision->attachFlag(
442
$viewer,
443
idx($flags, $revision->getPHID()));
444
}
445
}
446
447
if ($this->needDrafts) {
448
PhabricatorDraftEngine::attachDrafts(
449
$viewer,
450
$revisions);
451
}
452
453
return $revisions;
454
}
455
456
private function loadData() {
457
$table = $this->newResultObject();
458
$conn = $table->establishConnection('r');
459
460
$selects = array();
461
462
// NOTE: If the query includes "responsiblePHIDs", we execute it as a
463
// UNION of revisions they own and revisions they're reviewing. This has
464
// much better performance than doing it with JOIN/WHERE.
465
if ($this->responsibles) {
466
$basic_authors = $this->authors;
467
$basic_reviewers = $this->reviewers;
468
469
try {
470
// Build the query where the responsible users are authors.
471
$this->authors = array_merge($basic_authors, $this->responsibles);
472
473
$this->reviewers = $basic_reviewers;
474
$selects[] = $this->buildSelectStatement($conn);
475
476
// Build the query where the responsible users are reviewers, or
477
// projects they are members of are reviewers.
478
$this->authors = $basic_authors;
479
$this->reviewers = array_merge($basic_reviewers, $this->responsibles);
480
$selects[] = $this->buildSelectStatement($conn);
481
482
// Put everything back like it was.
483
$this->authors = $basic_authors;
484
$this->reviewers = $basic_reviewers;
485
} catch (Exception $ex) {
486
$this->authors = $basic_authors;
487
$this->reviewers = $basic_reviewers;
488
throw $ex;
489
}
490
} else {
491
$selects[] = $this->buildSelectStatement($conn);
492
}
493
494
if (count($selects) > 1) {
495
$unions = null;
496
foreach ($selects as $select) {
497
if (!$unions) {
498
$unions = $select;
499
continue;
500
}
501
502
$unions = qsprintf(
503
$conn,
504
'%Q UNION DISTINCT %Q',
505
$unions,
506
$select);
507
}
508
509
$query = qsprintf(
510
$conn,
511
'%Q %Q %Q',
512
$unions,
513
$this->buildOrderClause($conn, true),
514
$this->buildLimitClause($conn));
515
} else {
516
$query = head($selects);
517
}
518
519
return queryfx_all($conn, '%Q', $query);
520
}
521
522
private function buildSelectStatement(AphrontDatabaseConnection $conn_r) {
523
$table = new DifferentialRevision();
524
525
$select = $this->buildSelectClause($conn_r);
526
527
$from = qsprintf(
528
$conn_r,
529
'FROM %T r',
530
$table->getTableName());
531
532
$joins = $this->buildJoinsClause($conn_r);
533
$where = $this->buildWhereClause($conn_r);
534
$group_by = $this->buildGroupClause($conn_r);
535
$having = $this->buildHavingClause($conn_r);
536
537
$order_by = $this->buildOrderClause($conn_r);
538
539
$limit = $this->buildLimitClause($conn_r);
540
541
return qsprintf(
542
$conn_r,
543
'(%Q %Q %Q %Q %Q %Q %Q %Q)',
544
$select,
545
$from,
546
$joins,
547
$where,
548
$group_by,
549
$having,
550
$order_by,
551
$limit);
552
}
553
554
555
/* -( Internals )---------------------------------------------------------- */
556
557
558
/**
559
* @task internal
560
*/
561
private function buildJoinsClause(AphrontDatabaseConnection $conn) {
562
$joins = array();
563
564
if ($this->paths) {
565
$path_table = new DifferentialAffectedPath();
566
$joins[] = qsprintf(
567
$conn,
568
'JOIN %R paths ON paths.revisionID = r.id',
569
$path_table);
570
}
571
572
if ($this->commitHashes) {
573
$joins[] = qsprintf(
574
$conn,
575
'JOIN %T hash_rel ON hash_rel.revisionID = r.id',
576
ArcanistDifferentialRevisionHash::TABLE_NAME);
577
}
578
579
if ($this->ccs) {
580
$joins[] = qsprintf(
581
$conn,
582
'JOIN %T e_ccs ON e_ccs.src = r.phid '.
583
'AND e_ccs.type = %s '.
584
'AND e_ccs.dst in (%Ls)',
585
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
586
PhabricatorObjectHasSubscriberEdgeType::EDGECONST,
587
$this->ccs);
588
}
589
590
if ($this->reviewers) {
591
$joins[] = qsprintf(
592
$conn,
593
'LEFT JOIN %T reviewer ON reviewer.revisionPHID = r.phid
594
AND reviewer.reviewerStatus != %s
595
AND reviewer.reviewerPHID in (%Ls)',
596
id(new DifferentialReviewer())->getTableName(),
597
DifferentialReviewerStatus::STATUS_RESIGNED,
598
$this->reviewers);
599
}
600
601
if ($this->noReviewers) {
602
$joins[] = qsprintf(
603
$conn,
604
'LEFT JOIN %T no_reviewer ON no_reviewer.revisionPHID = r.phid
605
AND no_reviewer.reviewerStatus != %s',
606
id(new DifferentialReviewer())->getTableName(),
607
DifferentialReviewerStatus::STATUS_RESIGNED);
608
}
609
610
if ($this->draftAuthors) {
611
$joins[] = qsprintf(
612
$conn,
613
'JOIN %T has_draft ON has_draft.srcPHID = r.phid
614
AND has_draft.type = %s
615
AND has_draft.dstPHID IN (%Ls)',
616
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
617
PhabricatorObjectHasDraftEdgeType::EDGECONST,
618
$this->draftAuthors);
619
}
620
621
$joins[] = $this->buildJoinClauseParts($conn);
622
623
return $this->formatJoinClause($conn, $joins);
624
}
625
626
627
/**
628
* @task internal
629
*/
630
protected function buildWhereClause(AphrontDatabaseConnection $conn) {
631
$viewer = $this->getViewer();
632
$where = array();
633
634
if ($this->paths !== null) {
635
$paths = $this->paths;
636
637
$path_map = id(new DiffusionPathIDQuery($paths))
638
->loadPathIDs();
639
640
if (!$path_map) {
641
// If none of the paths have entries in the PathID table, we can not
642
// possibly find any revisions affecting them.
643
throw new PhabricatorEmptyQueryException();
644
}
645
646
$where[] = qsprintf(
647
$conn,
648
'paths.pathID IN (%Ld)',
649
array_fuse($path_map));
650
651
// If we have repository PHIDs, additionally constrain this query to
652
// try to help MySQL execute it efficiently.
653
if ($this->repositoryPHIDs !== null) {
654
$repositories = id(new PhabricatorRepositoryQuery())
655
->setViewer($viewer)
656
->setParentQuery($this)
657
->withPHIDs($this->repositoryPHIDs)
658
->execute();
659
660
if (!$repositories) {
661
throw new PhabricatorEmptyQueryException();
662
}
663
664
$repository_ids = mpull($repositories, 'getID');
665
666
$where[] = qsprintf(
667
$conn,
668
'paths.repositoryID IN (%Ld)',
669
$repository_ids);
670
}
671
}
672
673
if ($this->authors) {
674
$where[] = qsprintf(
675
$conn,
676
'r.authorPHID IN (%Ls)',
677
$this->authors);
678
}
679
680
if ($this->revIDs) {
681
$where[] = qsprintf(
682
$conn,
683
'r.id IN (%Ld)',
684
$this->revIDs);
685
}
686
687
if ($this->repositoryPHIDs) {
688
$where[] = qsprintf(
689
$conn,
690
'r.repositoryPHID IN (%Ls)',
691
$this->repositoryPHIDs);
692
}
693
694
if ($this->commitHashes) {
695
$hash_clauses = array();
696
foreach ($this->commitHashes as $info) {
697
list($type, $hash) = $info;
698
$hash_clauses[] = qsprintf(
699
$conn,
700
'(hash_rel.type = %s AND hash_rel.hash = %s)',
701
$type,
702
$hash);
703
}
704
$hash_clauses = qsprintf($conn, '%LO', $hash_clauses);
705
$where[] = $hash_clauses;
706
}
707
708
if ($this->phids) {
709
$where[] = qsprintf(
710
$conn,
711
'r.phid IN (%Ls)',
712
$this->phids);
713
}
714
715
if ($this->branches) {
716
$where[] = qsprintf(
717
$conn,
718
'r.branchName in (%Ls)',
719
$this->branches);
720
}
721
722
if ($this->updatedEpochMin !== null) {
723
$where[] = qsprintf(
724
$conn,
725
'r.dateModified >= %d',
726
$this->updatedEpochMin);
727
}
728
729
if ($this->updatedEpochMax !== null) {
730
$where[] = qsprintf(
731
$conn,
732
'r.dateModified <= %d',
733
$this->updatedEpochMax);
734
}
735
736
if ($this->createdEpochMin !== null) {
737
$where[] = qsprintf(
738
$conn,
739
'r.dateCreated >= %d',
740
$this->createdEpochMin);
741
}
742
743
if ($this->createdEpochMax !== null) {
744
$where[] = qsprintf(
745
$conn,
746
'r.dateCreated <= %d',
747
$this->createdEpochMax);
748
}
749
750
if ($this->statuses !== null) {
751
$where[] = qsprintf(
752
$conn,
753
'r.status in (%Ls)',
754
$this->statuses);
755
}
756
757
if ($this->isOpen !== null) {
758
if ($this->isOpen) {
759
$statuses = DifferentialLegacyQuery::getModernValues(
760
DifferentialLegacyQuery::STATUS_OPEN);
761
} else {
762
$statuses = DifferentialLegacyQuery::getModernValues(
763
DifferentialLegacyQuery::STATUS_CLOSED);
764
}
765
$where[] = qsprintf(
766
$conn,
767
'r.status in (%Ls)',
768
$statuses);
769
}
770
771
$reviewer_subclauses = array();
772
773
if ($this->noReviewers) {
774
$reviewer_subclauses[] = qsprintf(
775
$conn,
776
'no_reviewer.reviewerPHID IS NULL');
777
}
778
779
if ($this->reviewers) {
780
$reviewer_subclauses[] = qsprintf(
781
$conn,
782
'reviewer.reviewerPHID IS NOT NULL');
783
}
784
785
if ($reviewer_subclauses) {
786
$where[] = qsprintf($conn, '%LO', $reviewer_subclauses);
787
}
788
789
$where[] = $this->buildWhereClauseParts($conn);
790
791
return $this->formatWhereClause($conn, $where);
792
}
793
794
795
/**
796
* @task internal
797
*/
798
protected function shouldGroupQueryResultRows() {
799
800
if ($this->paths) {
801
// (If we have exactly one repository and exactly one path, we don't
802
// technically need to group, but it's simpler to always group.)
803
return true;
804
}
805
806
if (count($this->ccs) > 1) {
807
return true;
808
}
809
810
if (count($this->reviewers) > 1) {
811
return true;
812
}
813
814
if (count($this->commitHashes) > 1) {
815
return true;
816
}
817
818
if ($this->noReviewers) {
819
return true;
820
}
821
822
return parent::shouldGroupQueryResultRows();
823
}
824
825
public function getBuiltinOrders() {
826
$orders = parent::getBuiltinOrders() + array(
827
'updated' => array(
828
'vector' => array('updated', 'id'),
829
'name' => pht('Date Updated (Latest First)'),
830
'aliases' => array(self::ORDER_MODIFIED),
831
),
832
'outdated' => array(
833
'vector' => array('-updated', '-id'),
834
'name' => pht('Date Updated (Oldest First)'),
835
),
836
);
837
838
// Alias the "newest" builtin to the historical key for it.
839
$orders['newest']['aliases'][] = self::ORDER_CREATED;
840
841
return $orders;
842
}
843
844
protected function getDefaultOrderVector() {
845
return array('updated', 'id');
846
}
847
848
public function getOrderableColumns() {
849
return array(
850
'updated' => array(
851
'table' => $this->getPrimaryTableAlias(),
852
'column' => 'dateModified',
853
'type' => 'int',
854
),
855
) + parent::getOrderableColumns();
856
}
857
858
protected function newPagingMapFromPartialObject($object) {
859
return array(
860
'id' => (int)$object->getID(),
861
'updated' => (int)$object->getDateModified(),
862
);
863
}
864
865
private function loadCommitPHIDs(array $revisions) {
866
assert_instances_of($revisions, 'DifferentialRevision');
867
868
if (!$revisions) {
869
return;
870
}
871
872
$revisions = mpull($revisions, null, 'getPHID');
873
874
$edge_query = id(new PhabricatorEdgeQuery())
875
->withSourcePHIDs(array_keys($revisions))
876
->withEdgeTypes(
877
array(
878
DifferentialRevisionHasCommitEdgeType::EDGECONST,
879
));
880
$edge_query->execute();
881
882
foreach ($revisions as $phid => $revision) {
883
$commit_phids = $edge_query->getDestinationPHIDs(array($phid));
884
$revision->attachCommitPHIDs($commit_phids);
885
}
886
}
887
888
private function loadDiffIDs($conn_r, array $revisions) {
889
assert_instances_of($revisions, 'DifferentialRevision');
890
891
$diff_table = new DifferentialDiff();
892
893
$diff_ids = queryfx_all(
894
$conn_r,
895
'SELECT revisionID, id FROM %T WHERE revisionID IN (%Ld)
896
ORDER BY id DESC',
897
$diff_table->getTableName(),
898
mpull($revisions, 'getID'));
899
$diff_ids = igroup($diff_ids, 'revisionID');
900
901
foreach ($revisions as $revision) {
902
$ids = idx($diff_ids, $revision->getID(), array());
903
$ids = ipull($ids, 'id');
904
$revision->attachDiffIDs($ids);
905
}
906
}
907
908
private function loadActiveDiffs($conn_r, array $revisions) {
909
assert_instances_of($revisions, 'DifferentialRevision');
910
911
$diff_table = new DifferentialDiff();
912
913
$load_ids = array();
914
foreach ($revisions as $revision) {
915
$diffs = $revision->getDiffIDs();
916
if ($diffs) {
917
$load_ids[] = max($diffs);
918
}
919
}
920
921
$active_diffs = array();
922
if ($load_ids) {
923
$active_diffs = $diff_table->loadAllWhere(
924
'id IN (%Ld)',
925
$load_ids);
926
}
927
928
$active_diffs = mpull($active_diffs, null, 'getRevisionID');
929
foreach ($revisions as $revision) {
930
$revision->attachActiveDiff(idx($active_diffs, $revision->getID()));
931
}
932
}
933
934
private function loadHashes(
935
AphrontDatabaseConnection $conn_r,
936
array $revisions) {
937
assert_instances_of($revisions, 'DifferentialRevision');
938
939
$data = queryfx_all(
940
$conn_r,
941
'SELECT * FROM %T WHERE revisionID IN (%Ld)',
942
'differential_revisionhash',
943
mpull($revisions, 'getID'));
944
945
$data = igroup($data, 'revisionID');
946
foreach ($revisions as $revision) {
947
$hashes = idx($data, $revision->getID(), array());
948
$list = array();
949
foreach ($hashes as $hash) {
950
$list[] = array($hash['type'], $hash['hash']);
951
}
952
$revision->attachHashes($list);
953
}
954
}
955
956
private function loadReviewers(
957
AphrontDatabaseConnection $conn,
958
array $revisions) {
959
960
assert_instances_of($revisions, 'DifferentialRevision');
961
962
$reviewer_table = new DifferentialReviewer();
963
$reviewer_rows = queryfx_all(
964
$conn,
965
'SELECT * FROM %T WHERE revisionPHID IN (%Ls)
966
ORDER BY id ASC',
967
$reviewer_table->getTableName(),
968
mpull($revisions, 'getPHID'));
969
$reviewer_list = $reviewer_table->loadAllFromArray($reviewer_rows);
970
$reviewer_map = mgroup($reviewer_list, 'getRevisionPHID');
971
972
foreach ($reviewer_map as $key => $reviewers) {
973
$reviewer_map[$key] = mpull($reviewers, null, 'getReviewerPHID');
974
}
975
976
$viewer = $this->getViewer();
977
$viewer_phid = $viewer->getPHID();
978
979
$allow_key = 'differential.allow-self-accept';
980
$allow_self = PhabricatorEnv::getEnvConfig($allow_key);
981
982
// Figure out which of these reviewers the viewer has authority to act as.
983
if ($this->needReviewerAuthority && $viewer_phid) {
984
$authority = $this->loadReviewerAuthority(
985
$revisions,
986
$reviewer_map,
987
$allow_self);
988
}
989
990
foreach ($revisions as $revision) {
991
$reviewers = idx($reviewer_map, $revision->getPHID(), array());
992
foreach ($reviewers as $reviewer_phid => $reviewer) {
993
if ($this->needReviewerAuthority) {
994
if (!$viewer_phid) {
995
// Logged-out users never have authority.
996
$has_authority = false;
997
} else if ((!$allow_self) &&
998
($revision->getAuthorPHID() == $viewer_phid)) {
999
// The author can never have authority unless we allow self-accept.
1000
$has_authority = false;
1001
} else {
1002
// Otherwise, look up whether the viewer has authority.
1003
$has_authority = isset($authority[$reviewer_phid]);
1004
}
1005
1006
$reviewer->attachAuthority($viewer, $has_authority);
1007
}
1008
1009
$reviewers[$reviewer_phid] = $reviewer;
1010
}
1011
1012
$revision->attachReviewers($reviewers);
1013
}
1014
}
1015
1016
private function loadReviewerAuthority(
1017
array $revisions,
1018
array $reviewers,
1019
$allow_self) {
1020
1021
$revision_map = mpull($revisions, null, 'getPHID');
1022
$viewer_phid = $this->getViewer()->getPHID();
1023
1024
// Find all the project/package reviewers which the user may have authority
1025
// over.
1026
$project_phids = array();
1027
$package_phids = array();
1028
$project_type = PhabricatorProjectProjectPHIDType::TYPECONST;
1029
$package_type = PhabricatorOwnersPackagePHIDType::TYPECONST;
1030
1031
foreach ($reviewers as $revision_phid => $reviewer_list) {
1032
if (!$allow_self) {
1033
if ($revision_map[$revision_phid]->getAuthorPHID() == $viewer_phid) {
1034
// If self-review isn't permitted, the user will never have
1035
// authority over projects on revisions they authored because you
1036
// can't accept your own revisions, so we don't need to load any
1037
// data about these reviewers.
1038
continue;
1039
}
1040
}
1041
1042
foreach ($reviewer_list as $reviewer_phid => $reviewer) {
1043
$phid_type = phid_get_type($reviewer_phid);
1044
if ($phid_type == $project_type) {
1045
$project_phids[] = $reviewer_phid;
1046
}
1047
if ($phid_type == $package_type) {
1048
$package_phids[] = $reviewer_phid;
1049
}
1050
}
1051
}
1052
1053
// The viewer has authority over themselves.
1054
$user_authority = array_fuse(array($viewer_phid));
1055
1056
// And over any projects they are a member of.
1057
$project_authority = array();
1058
if ($project_phids) {
1059
$project_authority = id(new PhabricatorProjectQuery())
1060
->setViewer($this->getViewer())
1061
->withPHIDs($project_phids)
1062
->withMemberPHIDs(array($viewer_phid))
1063
->execute();
1064
$project_authority = mpull($project_authority, 'getPHID');
1065
$project_authority = array_fuse($project_authority);
1066
}
1067
1068
// And over any packages they own.
1069
$package_authority = array();
1070
if ($package_phids) {
1071
$package_authority = id(new PhabricatorOwnersPackageQuery())
1072
->setViewer($this->getViewer())
1073
->withPHIDs($package_phids)
1074
->withAuthorityPHIDs(array($viewer_phid))
1075
->execute();
1076
$package_authority = mpull($package_authority, 'getPHID');
1077
$package_authority = array_fuse($package_authority);
1078
}
1079
1080
return $user_authority + $project_authority + $package_authority;
1081
}
1082
1083
public function getQueryApplicationClass() {
1084
return 'PhabricatorDifferentialApplication';
1085
}
1086
1087
protected function getPrimaryTableAlias() {
1088
return 'r';
1089
}
1090
1091
}
1092
1093