@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3/**
4 * @extends PhabricatorCursorPagedPolicyAwareQuery<PhabricatorRepositoryCommit>
5 */
6final class DiffusionCommitQuery
7 extends PhabricatorCursorPagedPolicyAwareQuery {
8
9 private $ids;
10 private $phids;
11 private $authorPHIDs;
12 private $defaultRepository;
13 private $identifiers;
14 private $repositoryIDs;
15 private $repositoryPHIDs;
16 private $identifierMap;
17 private $responsiblePHIDs;
18 private $statuses;
19 private $packagePHIDs;
20 private $unreachable;
21 private $permanent;
22
23 private $needAuditRequests;
24 private $needAuditAuthority;
25 private $auditIDs;
26 private $auditorPHIDs;
27 private $epochMin;
28 private $epochMax;
29 private $importing;
30 private $ancestorsOf;
31
32 private $needCommitData;
33 private $needDrafts;
34 private $needIdentities;
35
36 private $mustFilterRefs = false;
37 private $refRepository;
38
39 public function withIDs(array $ids) {
40 $this->ids = $ids;
41 return $this;
42 }
43
44 public function withPHIDs(array $phids) {
45 $this->phids = $phids;
46 return $this;
47 }
48
49 public function withAuthorPHIDs(array $phids) {
50 $this->authorPHIDs = $phids;
51 return $this;
52 }
53
54 /**
55 * Load commits by partial or full identifiers, e.g. "rXab82393", "rX1234",
56 * or "a9caf12". When an identifier matches multiple commits, they will all
57 * be returned; callers should be prepared to deal with more results than
58 * they queried for.
59 */
60 public function withIdentifiers(array $identifiers) {
61 // Some workflows (like blame lookups) can pass in large numbers of
62 // duplicate identifiers. We only care about unique identifiers, so
63 // get rid of duplicates immediately.
64 $identifiers = array_fuse($identifiers);
65
66 $this->identifiers = $identifiers;
67 return $this;
68 }
69
70 /**
71 * Look up commits in a specific repository. This is a shorthand for calling
72 * @{method:withDefaultRepository} and @{method:withRepositoryIDs}.
73 */
74 public function withRepository(PhabricatorRepository $repository) {
75 $this->withDefaultRepository($repository);
76 $this->withRepositoryIDs(array($repository->getID()));
77 return $this;
78 }
79
80 /**
81 * Look up commits in a specific repository. Prefer
82 * @{method:withRepositoryIDs}; the underlying table is keyed by ID such
83 * that this method requires a separate initial query to map PHID to ID.
84 */
85 public function withRepositoryPHIDs(array $phids) {
86 $this->repositoryPHIDs = $phids;
87 return $this;
88 }
89
90 /**
91 * If a default repository is provided, ambiguous commit identifiers will
92 * be assumed to belong to the default repository.
93 *
94 * For example, "r123" appearing in a commit message in repository X is
95 * likely to be unambiguously "rX123". Normally the reference would be
96 * considered ambiguous, but if you provide a default repository it will
97 * be correctly resolved.
98 */
99 public function withDefaultRepository(PhabricatorRepository $repository) {
100 $this->defaultRepository = $repository;
101 return $this;
102 }
103
104 public function withRepositoryIDs(array $repository_ids) {
105 $this->repositoryIDs = array_unique($repository_ids);
106 return $this;
107 }
108
109 public function needCommitData($need) {
110 $this->needCommitData = $need;
111 return $this;
112 }
113
114 public function needDrafts($need) {
115 $this->needDrafts = $need;
116 return $this;
117 }
118
119 public function needIdentities($need) {
120 $this->needIdentities = $need;
121 return $this;
122 }
123
124 public function needAuditRequests($need) {
125 $this->needAuditRequests = $need;
126 return $this;
127 }
128
129 /**
130 * @param array<PhabricatorUser> $users
131 */
132 public function needAuditAuthority(array $users) {
133 assert_instances_of($users, PhabricatorUser::class);
134 $this->needAuditAuthority = $users;
135 return $this;
136 }
137
138 public function withAuditIDs(array $ids) {
139 $this->auditIDs = $ids;
140 return $this;
141 }
142
143 public function withAuditorPHIDs(array $auditor_phids) {
144 $this->auditorPHIDs = $auditor_phids;
145 return $this;
146 }
147
148 public function withResponsiblePHIDs(array $responsible_phids) {
149 $this->responsiblePHIDs = $responsible_phids;
150 return $this;
151 }
152
153 public function withPackagePHIDs(array $package_phids) {
154 $this->packagePHIDs = $package_phids;
155 return $this;
156 }
157
158 public function withUnreachable($unreachable) {
159 $this->unreachable = $unreachable;
160 return $this;
161 }
162
163 public function withPermanent($permanent) {
164 $this->permanent = $permanent;
165 return $this;
166 }
167
168 public function withStatuses(array $statuses) {
169 $this->statuses = $statuses;
170 return $this;
171 }
172
173 public function withEpochRange($min, $max) {
174 $this->epochMin = $min;
175 $this->epochMax = $max;
176 return $this;
177 }
178
179 public function withImporting($importing) {
180 $this->importing = $importing;
181 return $this;
182 }
183
184 public function withAncestorsOf(array $refs) {
185 $this->ancestorsOf = $refs;
186 return $this;
187 }
188
189 public function getIdentifierMap() {
190 if ($this->identifierMap === null) {
191 throw new Exception(
192 pht(
193 'You must %s the query before accessing the identifier map.',
194 'execute()'));
195 }
196 return $this->identifierMap;
197 }
198
199 protected function getPrimaryTableAlias() {
200 return 'commit';
201 }
202
203 protected function willExecute() {
204 if ($this->identifierMap === null) {
205 $this->identifierMap = array();
206 }
207 }
208
209 public function newResultObject() {
210 return new PhabricatorRepositoryCommit();
211 }
212
213 protected function loadPage() {
214 $table = $this->newResultObject();
215 $conn = $table->establishConnection('r');
216
217 $empty_exception = null;
218 $subqueries = array();
219 if ($this->responsiblePHIDs) {
220 $base_authors = $this->authorPHIDs;
221 $base_auditors = $this->auditorPHIDs;
222
223 $responsible_phids = $this->responsiblePHIDs;
224 if ($base_authors) {
225 $all_authors = array_merge($base_authors, $responsible_phids);
226 } else {
227 $all_authors = $responsible_phids;
228 }
229
230 if ($base_auditors) {
231 $all_auditors = array_merge($base_auditors, $responsible_phids);
232 } else {
233 $all_auditors = $responsible_phids;
234 }
235
236 $this->authorPHIDs = $all_authors;
237 $this->auditorPHIDs = $base_auditors;
238 try {
239 $subqueries[] = $this->buildStandardPageQuery(
240 $conn,
241 $table->getTableName());
242 } catch (PhabricatorEmptyQueryException $ex) {
243 $empty_exception = $ex;
244 }
245
246 $this->authorPHIDs = $base_authors;
247 $this->auditorPHIDs = $all_auditors;
248 try {
249 $subqueries[] = $this->buildStandardPageQuery(
250 $conn,
251 $table->getTableName());
252 } catch (PhabricatorEmptyQueryException $ex) {
253 $empty_exception = $ex;
254 }
255 } else {
256 $subqueries[] = $this->buildStandardPageQuery(
257 $conn,
258 $table->getTableName());
259 }
260
261 if (!$subqueries && $empty_exception) {
262 throw $empty_exception;
263 }
264
265 if (count($subqueries) > 1) {
266 $unions = null;
267 foreach ($subqueries as $subquery) {
268 if (!$unions) {
269 $unions = qsprintf(
270 $conn,
271 '(%Q)',
272 $subquery);
273 continue;
274 }
275
276 $unions = qsprintf(
277 $conn,
278 '%Q UNION DISTINCT (%Q)',
279 $unions,
280 $subquery);
281 }
282
283 $query = qsprintf(
284 $conn,
285 '%Q %Q %Q',
286 $unions,
287 $this->buildOrderClause($conn, true),
288 $this->buildLimitClause($conn));
289 } else {
290 $query = head($subqueries);
291 }
292
293 $rows = queryfx_all($conn, '%Q', $query);
294 $rows = $this->didLoadRawRows($rows);
295
296 return $table->loadAllFromArray($rows);
297 }
298
299 protected function willFilterPage(array $commits) {
300 $repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID');
301 $repos = id(new PhabricatorRepositoryQuery())
302 ->setViewer($this->getViewer())
303 ->withIDs($repository_ids)
304 ->execute();
305
306 $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
307 $result = array();
308
309 foreach ($commits as $key => $commit) {
310 $repo = idx($repos, $commit->getRepositoryID());
311 if ($repo) {
312 $commit->attachRepository($repo);
313 } else {
314 $this->didRejectResult($commit);
315 unset($commits[$key]);
316 continue;
317 }
318
319 // Build the identifierMap
320 if ($this->identifiers !== null) {
321 $ids = $this->identifiers;
322 $prefixes = array(
323 'r'.$commit->getRepository()->getCallsign(),
324 'r'.$commit->getRepository()->getCallsign().':',
325 'R'.$commit->getRepository()->getID().':',
326 '', // No prefix is valid too and will only match the commitIdentifier
327 );
328 $suffix = $commit->getCommitIdentifier();
329
330 if ($commit->getRepository()->isSVN()) {
331 foreach ($prefixes as $prefix) {
332 if (isset($ids[$prefix.$suffix])) {
333 $result[$prefix.$suffix][] = $commit;
334 }
335 }
336 } else {
337 // This awkward construction is so we can link the commits up in O(N)
338 // time instead of O(N^2).
339 for ($ii = $min_qualified; $ii <= strlen($suffix); $ii++) {
340 $part = substr($suffix, 0, $ii);
341 foreach ($prefixes as $prefix) {
342 if (isset($ids[$prefix.$part])) {
343 $result[$prefix.$part][] = $commit;
344 }
345 }
346 }
347 }
348 }
349 }
350
351 if ($result) {
352 foreach ($result as $identifier => $matching_commits) {
353 if (count($matching_commits) == 1) {
354 $result[$identifier] = head($matching_commits);
355 } else {
356 // This reference is ambiguous (it matches more than one commit) so
357 // don't link it.
358 unset($result[$identifier]);
359 }
360 }
361 $this->identifierMap += $result;
362 }
363
364 return $commits;
365 }
366
367 protected function didFilterPage(array $commits) {
368 $viewer = $this->getViewer();
369
370 if ($this->mustFilterRefs) {
371 // If this flag is set, the query has an "Ancestors Of" constraint and
372 // at least one of the constraining refs had too many ancestors for us
373 // to apply the constraint with a big "commitIdentifier IN (%Ls)" clause.
374 // We're going to filter each page and hope we get a full result set
375 // before the query overheats.
376
377 $ancestor_list = mpull($commits, 'getCommitIdentifier');
378 $ancestor_list = array_values($ancestor_list);
379
380 foreach ($this->ancestorsOf as $ref) {
381 try {
382 $ancestor_list = DiffusionQuery::callConduitWithDiffusionRequest(
383 $viewer,
384 DiffusionRequest::newFromDictionary(
385 array(
386 'repository' => $this->refRepository,
387 'user' => $viewer,
388 )),
389 'diffusion.internal.ancestors',
390 array(
391 'ref' => $ref,
392 'commits' => $ancestor_list,
393 ));
394 } catch (ConduitClientException $ex) {
395 throw new PhabricatorSearchConstraintException(
396 $ex->getMessage());
397 }
398
399 if (!$ancestor_list) {
400 break;
401 }
402 }
403
404 $ancestor_list = array_fuse($ancestor_list);
405 foreach ($commits as $key => $commit) {
406 $identifier = $commit->getCommitIdentifier();
407 if (!isset($ancestor_list[$identifier])) {
408 $this->didRejectResult($commit);
409 unset($commits[$key]);
410 }
411 }
412
413 if (!$commits) {
414 return $commits;
415 }
416 }
417
418 if ($this->needCommitData) {
419 $data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
420 'commitID in (%Ld)',
421 mpull($commits, 'getID'));
422 $data = mpull($data, null, 'getCommitID');
423 foreach ($commits as $commit) {
424 $commit_data = idx($data, $commit->getID());
425 if (!$commit_data) {
426 $commit_data = new PhabricatorRepositoryCommitData();
427 }
428 $commit->attachCommitData($commit_data);
429 }
430 }
431
432 if ($this->needAuditRequests) {
433 $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere(
434 'commitPHID IN (%Ls)',
435 mpull($commits, 'getPHID'));
436
437 $requests = mgroup($requests, 'getCommitPHID');
438 foreach ($commits as $commit) {
439 $audit_requests = idx($requests, $commit->getPHID(), array());
440 $commit->attachAudits($audit_requests);
441 foreach ($audit_requests as $audit_request) {
442 $audit_request->attachCommit($commit);
443 }
444 }
445 }
446
447 if ($this->needIdentities) {
448 $identity_phids = array_unique(array_merge(
449 mpull($commits, 'getAuthorIdentityPHID'),
450 mpull($commits, 'getCommitterIdentityPHID')));
451
452 $data = id(new PhabricatorRepositoryIdentityQuery())
453 ->withPHIDs($identity_phids)
454 ->setViewer($this->getViewer())
455 ->execute();
456 $data = mpull($data, null, 'getPHID');
457
458 foreach ($commits as $commit) {
459 $author_identity = idx($data, $commit->getAuthorIdentityPHID());
460 $committer_identity = idx($data, $commit->getCommitterIdentityPHID());
461 $commit->attachIdentities($author_identity, $committer_identity);
462 }
463 }
464
465 if ($this->needDrafts) {
466 PhabricatorDraftEngine::attachDrafts(
467 $viewer,
468 $commits);
469 }
470
471 if ($this->needAuditAuthority) {
472 $authority_users = $this->needAuditAuthority;
473
474 // NOTE: This isn't very efficient since we're running two queries per
475 // user, but there's currently no way to figure out authority for
476 // multiple users in one query. Today, we only ever request authority for
477 // a single user and single commit, so this has no practical impact.
478
479 // NOTE: We're querying with the viewership of query viewer, not the
480 // actual users. If the viewer can't see a project or package, they
481 // won't be able to see who has authority on it. This is safer than
482 // showing them true authority, and should never matter today, but it
483 // also doesn't seem like a significant disclosure and might be
484 // reasonable to adjust later if it causes something weird or confusing
485 // to happen.
486
487 $authority_map = array();
488 foreach ($authority_users as $authority_user) {
489 $authority_phid = $authority_user->getPHID();
490 if (!$authority_phid) {
491 continue;
492 }
493
494 $result_phids = array();
495
496 // Users have authority over themselves.
497 $result_phids[] = $authority_phid;
498
499 // Users have authority over packages they own.
500 $owned_packages = id(new PhabricatorOwnersPackageQuery())
501 ->setViewer($viewer)
502 ->withAuthorityPHIDs(array($authority_phid))
503 ->execute();
504 foreach ($owned_packages as $package) {
505 $result_phids[] = $package->getPHID();
506 }
507
508 // Users have authority over projects they're members of.
509 $projects = id(new PhabricatorProjectQuery())
510 ->setViewer($viewer)
511 ->withMemberPHIDs(array($authority_phid))
512 ->execute();
513 foreach ($projects as $project) {
514 $result_phids[] = $project->getPHID();
515 }
516
517 $result_phids = array_fuse($result_phids);
518
519 foreach ($commits as $commit) {
520 $attach_phids = $result_phids;
521
522 // NOTE: When modifying your own commits, you act only on behalf of
523 // yourself, not your packages or projects. The idea here is that you
524 // can't accept your own commits. In the future, this might change or
525 // depend on configuration.
526 $author_phid = $commit->getAuthorPHID();
527 if ($author_phid == $authority_phid) {
528 $attach_phids = array($author_phid);
529 $attach_phids = array_fuse($attach_phids);
530 }
531
532 $commit->attachAuditAuthority($authority_user, $attach_phids);
533 }
534 }
535 }
536
537 return $commits;
538 }
539
540 protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
541 $where = parent::buildWhereClauseParts($conn);
542
543 if ($this->repositoryPHIDs !== null) {
544 $map_repositories = id(new PhabricatorRepositoryQuery())
545 ->setViewer($this->getViewer())
546 ->withPHIDs($this->repositoryPHIDs)
547 ->execute();
548
549 if (!$map_repositories) {
550 throw new PhabricatorEmptyQueryException();
551 }
552 $repository_ids = mpull($map_repositories, 'getID');
553 if ($this->repositoryIDs !== null) {
554 $repository_ids = array_merge($repository_ids, $this->repositoryIDs);
555 }
556 $this->withRepositoryIDs($repository_ids);
557 }
558
559 if ($this->ancestorsOf !== null) {
560 if ($this->repositoryIDs === null || count($this->repositoryIDs) !== 1) {
561 throw new PhabricatorSearchConstraintException(
562 pht(
563 'To search for commits which are ancestors of particular refs, '.
564 'you must constrain the search to exactly one repository.'));
565 }
566
567 $repository_id = head($this->repositoryIDs);
568 $history_limit = $this->getRawResultLimit() * 32;
569 $viewer = $this->getViewer();
570
571 $repository = id(new PhabricatorRepositoryQuery())
572 ->setViewer($viewer)
573 ->withIDs(array($repository_id))
574 ->executeOne();
575
576 if (!$repository) {
577 throw new PhabricatorEmptyQueryException();
578 }
579
580 if ($repository->isSVN()) {
581 throw new PhabricatorSearchConstraintException(
582 pht(
583 'Subversion does not support searching for ancestors of '.
584 'a particular ref. This operation is not meaningful in '.
585 'Subversion.'));
586 }
587
588 if ($repository->isHg()) {
589 throw new PhabricatorSearchConstraintException(
590 pht(
591 'Mercurial does not currently support searching for ancestors of '.
592 'a particular ref.'));
593 }
594
595 $can_constrain = true;
596 $history_identifiers = array();
597 foreach ($this->ancestorsOf as $key => $ref) {
598 try {
599 $raw_history = DiffusionQuery::callConduitWithDiffusionRequest(
600 $viewer,
601 DiffusionRequest::newFromDictionary(
602 array(
603 'repository' => $repository,
604 'user' => $viewer,
605 )),
606 'diffusion.historyquery',
607 array(
608 'commit' => $ref,
609 'limit' => $history_limit,
610 ));
611 } catch (ConduitClientException $ex) {
612 throw new PhabricatorSearchConstraintException(
613 $ex->getMessage());
614 }
615
616 $ref_identifiers = array();
617 foreach ($raw_history['pathChanges'] as $change) {
618 $ref_identifiers[] = $change['commitIdentifier'];
619 }
620
621 // If this ref had fewer total commits than the limit, we're safe to
622 // apply the constraint as a large `IN (...)` query for a list of
623 // commit identifiers. This is efficient.
624 if ($history_limit) {
625 if (count($ref_identifiers) >= $history_limit) {
626 $can_constrain = false;
627 break;
628 }
629 }
630
631 $history_identifiers += array_fuse($ref_identifiers);
632 }
633
634 // If all refs had a small number of ancestors, we can just put the
635 // constraint into the query here and we're done. Otherwise, we need
636 // to filter each page after it comes out of the MySQL layer.
637 if ($can_constrain) {
638 $where[] = qsprintf(
639 $conn,
640 'commit.commitIdentifier IN (%Ls)',
641 $history_identifiers);
642 } else {
643 $this->mustFilterRefs = true;
644 $this->refRepository = $repository;
645 }
646 }
647
648 if ($this->ids !== null) {
649 $where[] = qsprintf(
650 $conn,
651 'commit.id IN (%Ld)',
652 $this->ids);
653 }
654
655 if ($this->phids !== null) {
656 $where[] = qsprintf(
657 $conn,
658 'commit.phid IN (%Ls)',
659 $this->phids);
660 }
661
662 if ($this->repositoryIDs !== null) {
663 $where[] = qsprintf(
664 $conn,
665 'commit.repositoryID IN (%Ld)',
666 $this->repositoryIDs);
667 }
668
669 if ($this->authorPHIDs !== null) {
670 $author_phids = $this->authorPHIDs;
671 if ($author_phids) {
672 $author_phids = $this->selectPossibleAuthors($author_phids);
673 if (!$author_phids) {
674 throw new PhabricatorEmptyQueryException(
675 pht('Author PHIDs contain no possible authors.'));
676 }
677 }
678
679 $where[] = qsprintf(
680 $conn,
681 'commit.authorPHID IN (%Ls)',
682 $author_phids);
683 }
684
685 if ($this->epochMin !== null) {
686 $where[] = qsprintf(
687 $conn,
688 'commit.epoch >= %d',
689 $this->epochMin);
690 }
691
692 if ($this->epochMax !== null) {
693 $where[] = qsprintf(
694 $conn,
695 'commit.epoch <= %d',
696 $this->epochMax);
697 }
698
699 if ($this->importing !== null) {
700 if ($this->importing) {
701 $where[] = qsprintf(
702 $conn,
703 '(commit.importStatus & %d) != %d',
704 PhabricatorRepositoryCommit::IMPORTED_ALL,
705 PhabricatorRepositoryCommit::IMPORTED_ALL);
706 } else {
707 $where[] = qsprintf(
708 $conn,
709 '(commit.importStatus & %d) = %d',
710 PhabricatorRepositoryCommit::IMPORTED_ALL,
711 PhabricatorRepositoryCommit::IMPORTED_ALL);
712 }
713 }
714
715 if ($this->identifiers !== null) {
716 $min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH;
717 $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
718
719 $refs = array();
720 $bare = array();
721 foreach ($this->identifiers as $identifier) {
722 $matches = null;
723 preg_match('/^(?:[rR]([A-Z]+:?|[0-9]+:))?(.*)$/',
724 $identifier, $matches);
725 $repo = nonempty(rtrim($matches[1], ':'), null);
726 $commit_identifier = nonempty($matches[2], null);
727
728 if ($repo === null) {
729 if ($this->defaultRepository) {
730 $repo = $this->defaultRepository->getPHID();
731 }
732 }
733
734 if ($repo === null) {
735 if (strlen($commit_identifier) < $min_unqualified) {
736 continue;
737 }
738 $bare[] = $commit_identifier;
739 } else {
740 $refs[] = array(
741 'repository' => $repo,
742 'identifier' => $commit_identifier,
743 );
744 }
745 }
746
747 $sql = array();
748
749 foreach ($bare as $identifier) {
750 $sql[] = qsprintf(
751 $conn,
752 '(commit.commitIdentifier LIKE %> AND '.
753 'LENGTH(commit.commitIdentifier) = 40)',
754 $identifier);
755 }
756
757 if ($refs) {
758 $repositories = ipull($refs, 'repository');
759
760 $repos = id(new PhabricatorRepositoryQuery())
761 ->setViewer($this->getViewer())
762 ->withIdentifiers($repositories);
763 $repos->execute();
764
765 $repos = $repos->getIdentifierMap();
766 foreach ($refs as $key => $ref) {
767 $repo = idx($repos, $ref['repository']);
768 if (!$repo) {
769 continue;
770 }
771
772 if ($repo->isSVN()) {
773 if (!ctype_digit((string)$ref['identifier'])) {
774 continue;
775 }
776 $sql[] = qsprintf(
777 $conn,
778 '(commit.repositoryID = %d AND commit.commitIdentifier = %s)',
779 $repo->getID(),
780 // NOTE: Because the 'commitIdentifier' column is a string, MySQL
781 // ignores the index if we hand it an integer. Hand it a string.
782 // See T3377.
783 (int)$ref['identifier']);
784 } else {
785 if (!phutil_nonempty_string($ref['identifier']) ||
786 strlen($ref['identifier']) < $min_qualified) {
787 continue;
788 }
789
790 $identifier = $ref['identifier'];
791 if (strlen($identifier) == 40) {
792 // MySQL seems to do slightly better with this version if the
793 // clause, so issue it if we have a full commit hash.
794 $sql[] = qsprintf(
795 $conn,
796 '(commit.repositoryID = %d
797 AND commit.commitIdentifier = %s)',
798 $repo->getID(),
799 $identifier);
800 } else {
801 $sql[] = qsprintf(
802 $conn,
803 '(commit.repositoryID = %d
804 AND commit.commitIdentifier LIKE %>)',
805 $repo->getID(),
806 $identifier);
807 }
808 }
809 }
810 }
811
812 if (!$sql) {
813 // If we discarded all possible identifiers (e.g., they all referenced
814 // bogus repositories or were all too short), make sure the query finds
815 // nothing.
816 throw new PhabricatorEmptyQueryException(
817 pht('No commit identifiers.'));
818 }
819
820 $where[] = qsprintf($conn, '%LO', $sql);
821 }
822
823 if ($this->auditIDs !== null) {
824 $where[] = qsprintf(
825 $conn,
826 'auditor.id IN (%Ld)',
827 $this->auditIDs);
828 }
829
830 if ($this->auditorPHIDs !== null) {
831 $where[] = qsprintf(
832 $conn,
833 'auditor.auditorPHID IN (%Ls)',
834 $this->auditorPHIDs);
835 }
836
837 if ($this->statuses !== null) {
838 $statuses = DiffusionCommitAuditStatus::newModernKeys(
839 $this->statuses);
840
841 $where[] = qsprintf(
842 $conn,
843 'commit.auditStatus IN (%Ls)',
844 $statuses);
845 }
846
847 if ($this->packagePHIDs !== null) {
848 $where[] = qsprintf(
849 $conn,
850 'package.dst IN (%Ls)',
851 $this->packagePHIDs);
852 }
853
854 if ($this->unreachable !== null) {
855 if ($this->unreachable) {
856 $where[] = qsprintf(
857 $conn,
858 '(commit.importStatus & %d) = %d',
859 PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE,
860 PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE);
861 } else {
862 $where[] = qsprintf(
863 $conn,
864 '(commit.importStatus & %d) = 0',
865 PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE);
866 }
867 }
868
869 if ($this->permanent !== null) {
870 if ($this->permanent) {
871 $where[] = qsprintf(
872 $conn,
873 '(commit.importStatus & %d) = %d',
874 PhabricatorRepositoryCommit::IMPORTED_PERMANENT,
875 PhabricatorRepositoryCommit::IMPORTED_PERMANENT);
876 } else {
877 $where[] = qsprintf(
878 $conn,
879 '(commit.importStatus & %d) = 0',
880 PhabricatorRepositoryCommit::IMPORTED_PERMANENT);
881 }
882 }
883
884 return $where;
885 }
886
887 protected function didFilterResults(array $filtered) {
888 if ($this->identifierMap) {
889 foreach ($this->identifierMap as $name => $commit) {
890 if (isset($filtered[$commit->getPHID()])) {
891 unset($this->identifierMap[$name]);
892 }
893 }
894 }
895 }
896
897 private function shouldJoinAuditor() {
898 return ($this->auditIDs || $this->auditorPHIDs);
899 }
900
901 private function shouldJoinOwners() {
902 return (bool)$this->packagePHIDs;
903 }
904
905 protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
906 $join = parent::buildJoinClauseParts($conn);
907 $audit_request = new PhabricatorRepositoryAuditRequest();
908
909 if ($this->shouldJoinAuditor()) {
910 $join[] = qsprintf(
911 $conn,
912 'JOIN %T auditor ON commit.phid = auditor.commitPHID',
913 $audit_request->getTableName());
914 }
915
916 if ($this->shouldJoinOwners()) {
917 $join[] = qsprintf(
918 $conn,
919 'JOIN %T package ON commit.phid = package.src
920 AND package.type = %s',
921 PhabricatorEdgeConfig::TABLE_NAME_EDGE,
922 DiffusionCommitHasPackageEdgeType::EDGECONST);
923 }
924
925 return $join;
926 }
927
928 protected function shouldGroupQueryResultRows() {
929 if ($this->shouldJoinAuditor()) {
930 return true;
931 }
932
933 if ($this->shouldJoinOwners()) {
934 return true;
935 }
936
937 return parent::shouldGroupQueryResultRows();
938 }
939
940 public function getQueryApplicationClass() {
941 return PhabricatorDiffusionApplication::class;
942 }
943
944 public function getOrderableColumns() {
945 return parent::getOrderableColumns() + array(
946 'epoch' => array(
947 'table' => $this->getPrimaryTableAlias(),
948 'column' => 'epoch',
949 'type' => 'int',
950 'reverse' => false,
951 ),
952 );
953 }
954
955 protected function newPagingMapFromPartialObject($object) {
956 return array(
957 'id' => (int)$object->getID(),
958 'epoch' => (int)$object->getEpoch(),
959 );
960 }
961
962 public function getBuiltinOrders() {
963 $parent = parent::getBuiltinOrders();
964
965 // Rename the default ID-based orders.
966 $parent['importnew'] = array(
967 'name' => pht('Import Date (Newest First)'),
968 ) + $parent['newest'];
969
970 $parent['importold'] = array(
971 'name' => pht('Import Date (Oldest First)'),
972 ) + $parent['oldest'];
973
974 return array(
975 'newest' => array(
976 'vector' => array('epoch', 'id'),
977 'name' => pht('Commit Date (Newest First)'),
978 ),
979 'oldest' => array(
980 'vector' => array('-epoch', '-id'),
981 'name' => pht('Commit Date (Oldest First)'),
982 ),
983 ) + $parent;
984 }
985
986 private function selectPossibleAuthors(array $phids) {
987 // See PHI1057. Select PHIDs which might possibly be commit authors from
988 // a larger list of PHIDs. This primarily filters out packages and projects
989 // from "Responsible Users: ..." queries. Our goal in performing this
990 // filtering is to improve the performance of the final query.
991
992 foreach ($phids as $key => $phid) {
993 if (phid_get_type($phid) !== PhabricatorPeopleUserPHIDType::TYPECONST) {
994 unset($phids[$key]);
995 }
996 }
997
998 return $phids;
999 }
1000
1001
1002}