@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
at upstream/main 1002 lines 29 kB view raw
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}