@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 694 lines 18 kB view raw
1<?php 2 3/** 4 * @extends PhabricatorCursorPagedPolicyAwareQuery<PhabricatorRepository> 5 */ 6final class PhabricatorRepositoryQuery 7 extends PhabricatorCursorPagedPolicyAwareQuery { 8 9 private $ids; 10 private $phids; 11 private $callsigns; 12 private $types; 13 private $uuids; 14 private $uris; 15 private $slugs; 16 private $almanacServicePHIDs; 17 18 private $numericIdentifiers; 19 private $callsignIdentifiers; 20 private $phidIdentifiers; 21 private $monogramIdentifiers; 22 private $slugIdentifiers; 23 24 private $identifierMap; 25 26 const STATUS_OPEN = 'status-open'; 27 const STATUS_CLOSED = 'status-closed'; 28 const STATUS_ALL = 'status-all'; 29 private $status = self::STATUS_ALL; 30 31 const HOSTED_PHABRICATOR = 'hosted-phab'; 32 const HOSTED_REMOTE = 'hosted-remote'; 33 const HOSTED_ALL = 'hosted-all'; 34 private $hosted = self::HOSTED_ALL; 35 36 private $needMostRecentCommits; 37 private $needCommitCounts; 38 private $needProjectPHIDs; 39 private $needURIs; 40 private $needProfileImage; 41 42 public function withIDs(array $ids) { 43 $this->ids = $ids; 44 return $this; 45 } 46 47 public function withPHIDs(array $phids) { 48 $this->phids = $phids; 49 return $this; 50 } 51 52 public function withCallsigns(array $callsigns) { 53 $this->callsigns = $callsigns; 54 return $this; 55 } 56 57 public function withIdentifiers(array $identifiers) { 58 $identifiers = array_fuse($identifiers); 59 60 $ids = array(); 61 $callsigns = array(); 62 $phids = array(); 63 $monograms = array(); 64 $slugs = array(); 65 66 foreach ($identifiers as $identifier) { 67 if ($identifier === null) { 68 continue; 69 } 70 71 if (ctype_digit((string)$identifier)) { 72 $ids[$identifier] = $identifier; 73 continue; 74 } 75 76 if (preg_match('/^(r[A-Z]+|R[1-9]\d*)\z/', $identifier)) { 77 $monograms[$identifier] = $identifier; 78 continue; 79 } 80 81 $repository_type = PhabricatorRepositoryRepositoryPHIDType::TYPECONST; 82 if (phid_get_type($identifier) === $repository_type) { 83 $phids[$identifier] = $identifier; 84 continue; 85 } 86 87 if (preg_match('/^[A-Z]+\z/', $identifier)) { 88 $callsigns[$identifier] = $identifier; 89 continue; 90 } 91 92 $slugs[$identifier] = $identifier; 93 } 94 95 $this->numericIdentifiers = $ids; 96 $this->callsignIdentifiers = $callsigns; 97 $this->phidIdentifiers = $phids; 98 $this->monogramIdentifiers = $monograms; 99 $this->slugIdentifiers = $slugs; 100 101 return $this; 102 } 103 104 public function withStatus($status) { 105 $this->status = $status; 106 return $this; 107 } 108 109 public function withHosted($hosted) { 110 $this->hosted = $hosted; 111 return $this; 112 } 113 114 public function withTypes(array $types) { 115 $this->types = $types; 116 return $this; 117 } 118 119 public function withUUIDs(array $uuids) { 120 $this->uuids = $uuids; 121 return $this; 122 } 123 124 public function withURIs(array $uris) { 125 $this->uris = $uris; 126 return $this; 127 } 128 129 public function withSlugs(array $slugs) { 130 $this->slugs = $slugs; 131 return $this; 132 } 133 134 public function withAlmanacServicePHIDs(array $phids) { 135 $this->almanacServicePHIDs = $phids; 136 return $this; 137 } 138 139 public function needCommitCounts($need_counts) { 140 $this->needCommitCounts = $need_counts; 141 return $this; 142 } 143 144 public function needMostRecentCommits($need_commits) { 145 $this->needMostRecentCommits = $need_commits; 146 return $this; 147 } 148 149 public function needProjectPHIDs($need_phids) { 150 $this->needProjectPHIDs = $need_phids; 151 return $this; 152 } 153 154 public function needURIs($need_uris) { 155 $this->needURIs = $need_uris; 156 return $this; 157 } 158 159 public function needProfileImage($need) { 160 $this->needProfileImage = $need; 161 return $this; 162 } 163 164 public function getBuiltinOrders() { 165 return array( 166 'committed' => array( 167 'vector' => array('committed', 'id'), 168 'name' => pht('Most Recent Commit'), 169 ), 170 'name' => array( 171 'vector' => array('name', 'id'), 172 'name' => pht('Name'), 173 ), 174 'callsign' => array( 175 'vector' => array('callsign'), 176 'name' => pht('Callsign'), 177 ), 178 'size' => array( 179 'vector' => array('size', 'id'), 180 'name' => pht('Size'), 181 ), 182 ) + parent::getBuiltinOrders(); 183 } 184 185 public function getIdentifierMap() { 186 if ($this->identifierMap === null) { 187 throw new PhutilInvalidStateException('execute'); 188 } 189 return $this->identifierMap; 190 } 191 192 protected function willExecute() { 193 $this->identifierMap = array(); 194 } 195 196 public function newResultObject() { 197 return new PhabricatorRepository(); 198 } 199 200 protected function loadPage() { 201 $table = $this->newResultObject(); 202 $data = $this->loadStandardPageRows($table); 203 $repositories = $table->loadAllFromArray($data); 204 205 if ($this->needCommitCounts) { 206 $sizes = ipull($data, 'size', 'id'); 207 foreach ($repositories as $id => $repository) { 208 $repository->attachCommitCount(nonempty($sizes[$id], 0)); 209 } 210 } 211 212 if ($this->needMostRecentCommits) { 213 $commit_ids = ipull($data, 'lastCommitID', 'id'); 214 $commit_ids = array_filter($commit_ids); 215 if ($commit_ids) { 216 $commits = id(new DiffusionCommitQuery()) 217 ->setViewer($this->getViewer()) 218 ->withIDs($commit_ids) 219 ->needCommitData(true) 220 ->needIdentities(true) 221 ->execute(); 222 } else { 223 $commits = array(); 224 } 225 foreach ($repositories as $id => $repository) { 226 $commit = null; 227 if (idx($commit_ids, $id)) { 228 $commit = idx($commits, $commit_ids[$id]); 229 } 230 $repository->attachMostRecentCommit($commit); 231 } 232 } 233 234 return $repositories; 235 } 236 237 /** 238 * @param array<PhabricatorRepository> $repositories 239 */ 240 protected function willFilterPage(array $repositories) { 241 assert_instances_of($repositories, PhabricatorRepository::class); 242 243 // TODO: Denormalize repository status into the PhabricatorRepository 244 // table so we can do this filtering in the database. 245 foreach ($repositories as $key => $repo) { 246 $status = $this->status; 247 switch ($status) { 248 case self::STATUS_OPEN: 249 if (!$repo->isTracked()) { 250 unset($repositories[$key]); 251 } 252 break; 253 case self::STATUS_CLOSED: 254 if ($repo->isTracked()) { 255 unset($repositories[$key]); 256 } 257 break; 258 case self::STATUS_ALL: 259 break; 260 default: 261 throw new Exception("Unknown status '{$status}'!"); 262 } 263 264 // TODO: This should also be denormalized. 265 $hosted = $this->hosted; 266 switch ($hosted) { 267 case self::HOSTED_PHABRICATOR: 268 if (!$repo->isHosted()) { 269 unset($repositories[$key]); 270 } 271 break; 272 case self::HOSTED_REMOTE: 273 if ($repo->isHosted()) { 274 unset($repositories[$key]); 275 } 276 break; 277 case self::HOSTED_ALL: 278 break; 279 default: 280 throw new Exception(pht("Unknown hosted failed '%s'!", $hosted)); 281 } 282 } 283 284 // Build the identifierMap 285 if ($this->numericIdentifiers) { 286 foreach ($this->numericIdentifiers as $id) { 287 if (isset($repositories[$id])) { 288 $this->identifierMap[$id] = $repositories[$id]; 289 } 290 } 291 } 292 293 if ($this->callsignIdentifiers) { 294 $repository_callsigns = mpull($repositories, null, 'getCallsign'); 295 296 foreach ($this->callsignIdentifiers as $callsign) { 297 if (isset($repository_callsigns[$callsign])) { 298 $this->identifierMap[$callsign] = $repository_callsigns[$callsign]; 299 } 300 } 301 } 302 303 if ($this->phidIdentifiers) { 304 $repository_phids = mpull($repositories, null, 'getPHID'); 305 306 foreach ($this->phidIdentifiers as $phid) { 307 if (isset($repository_phids[$phid])) { 308 $this->identifierMap[$phid] = $repository_phids[$phid]; 309 } 310 } 311 } 312 313 if ($this->monogramIdentifiers) { 314 $monogram_map = array(); 315 foreach ($repositories as $repository) { 316 foreach ($repository->getAllMonograms() as $monogram) { 317 $monogram_map[$monogram] = $repository; 318 } 319 } 320 321 foreach ($this->monogramIdentifiers as $monogram) { 322 if (isset($monogram_map[$monogram])) { 323 $this->identifierMap[$monogram] = $monogram_map[$monogram]; 324 } 325 } 326 } 327 328 if ($this->slugIdentifiers) { 329 $slug_map = array(); 330 foreach ($repositories as $repository) { 331 $slug = $repository->getRepositorySlug(); 332 if ($slug === null) { 333 continue; 334 } 335 336 $normal = phutil_utf8_strtolower($slug); 337 $slug_map[$normal] = $repository; 338 } 339 340 foreach ($this->slugIdentifiers as $slug) { 341 $normal = phutil_utf8_strtolower($slug); 342 if (isset($slug_map[$normal])) { 343 $this->identifierMap[$slug] = $slug_map[$normal]; 344 } 345 } 346 } 347 348 return $repositories; 349 } 350 351 protected function didFilterPage(array $repositories) { 352 if ($this->needProjectPHIDs) { 353 $type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 354 355 $edge_query = id(new PhabricatorEdgeQuery()) 356 ->withSourcePHIDs(mpull($repositories, 'getPHID')) 357 ->withEdgeTypes(array($type_project)); 358 $edge_query->execute(); 359 360 foreach ($repositories as $repository) { 361 $project_phids = $edge_query->getDestinationPHIDs( 362 array( 363 $repository->getPHID(), 364 )); 365 $repository->attachProjectPHIDs($project_phids); 366 } 367 } 368 369 $viewer = $this->getViewer(); 370 371 if ($this->needURIs) { 372 $uris = id(new PhabricatorRepositoryURIQuery()) 373 ->setViewer($viewer) 374 ->withRepositories($repositories) 375 ->execute(); 376 $uri_groups = mgroup($uris, 'getRepositoryPHID'); 377 foreach ($repositories as $repository) { 378 $repository_uris = idx($uri_groups, $repository->getPHID(), array()); 379 $repository->attachURIs($repository_uris); 380 } 381 } 382 383 if ($this->needProfileImage) { 384 $default = null; 385 386 $file_phids = mpull($repositories, 'getProfileImagePHID'); 387 $file_phids = array_filter($file_phids); 388 if ($file_phids) { 389 $files = id(new PhabricatorFileQuery()) 390 ->setParentQuery($this) 391 ->setViewer($this->getViewer()) 392 ->withPHIDs($file_phids) 393 ->execute(); 394 $files = mpull($files, null, 'getPHID'); 395 } else { 396 $files = array(); 397 } 398 399 foreach ($repositories as $repository) { 400 $file = idx($files, $repository->getProfileImagePHID()); 401 if (!$file) { 402 if (!$default) { 403 $default = PhabricatorFile::loadBuiltin( 404 $this->getViewer(), 405 'repo/code.png'); 406 } 407 $file = $default; 408 } 409 $repository->attachProfileImageFile($file); 410 } 411 } 412 413 return $repositories; 414 } 415 416 protected function getPrimaryTableAlias() { 417 return 'r'; 418 } 419 420 public function getOrderableColumns() { 421 return parent::getOrderableColumns() + array( 422 'committed' => array( 423 'table' => 's', 424 'column' => 'epoch', 425 'type' => 'int', 426 'null' => 'tail', 427 ), 428 'callsign' => array( 429 'table' => 'r', 430 'column' => 'callsign', 431 'type' => 'string', 432 'unique' => true, 433 'reverse' => true, 434 'null' => 'tail', 435 ), 436 'name' => array( 437 'table' => 'r', 438 'column' => 'name', 439 'type' => 'string', 440 'reverse' => true, 441 ), 442 'size' => array( 443 'table' => 's', 444 'column' => 'size', 445 'type' => 'int', 446 'null' => 'tail', 447 ), 448 ); 449 } 450 451 protected function newPagingMapFromCursorObject( 452 PhabricatorQueryCursor $cursor, 453 array $keys) { 454 455 $repository = $cursor->getObject(); 456 457 $map = array( 458 'id' => (int)$repository->getID(), 459 'callsign' => $repository->getCallsign(), 460 'name' => $repository->getName(), 461 ); 462 463 if (isset($keys['committed'])) { 464 $map['committed'] = $cursor->getRawRowProperty('epoch'); 465 } 466 467 if (isset($keys['size'])) { 468 $map['size'] = $cursor->getRawRowProperty('size'); 469 } 470 471 return $map; 472 } 473 474 protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) { 475 $parts = parent::buildSelectClauseParts($conn); 476 477 if ($this->shouldJoinSummaryTable()) { 478 $parts[] = qsprintf($conn, 's.*'); 479 } 480 481 return $parts; 482 } 483 484 protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { 485 $joins = parent::buildJoinClauseParts($conn); 486 487 if ($this->shouldJoinSummaryTable()) { 488 $joins[] = qsprintf( 489 $conn, 490 'LEFT JOIN %T s ON r.id = s.repositoryID', 491 PhabricatorRepository::TABLE_SUMMARY); 492 } 493 494 if ($this->shouldJoinURITable()) { 495 $joins[] = qsprintf( 496 $conn, 497 'LEFT JOIN %R uri ON r.phid = uri.repositoryPHID', 498 new PhabricatorRepositoryURIIndex()); 499 } 500 501 return $joins; 502 } 503 504 protected function shouldGroupQueryResultRows() { 505 if ($this->shouldJoinURITable()) { 506 return true; 507 } 508 509 return parent::shouldGroupQueryResultRows(); 510 } 511 512 private function shouldJoinURITable() { 513 return ($this->uris !== null); 514 } 515 516 private function shouldJoinSummaryTable() { 517 if ($this->needCommitCounts) { 518 return true; 519 } 520 521 if ($this->needMostRecentCommits) { 522 return true; 523 } 524 525 $vector = $this->getOrderVector(); 526 if ($vector->containsKey('committed')) { 527 return true; 528 } 529 530 if ($vector->containsKey('size')) { 531 return true; 532 } 533 534 return false; 535 } 536 537 protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 538 $where = parent::buildWhereClauseParts($conn); 539 540 if ($this->ids !== null) { 541 $where[] = qsprintf( 542 $conn, 543 'r.id IN (%Ld)', 544 $this->ids); 545 } 546 547 if ($this->phids !== null) { 548 $where[] = qsprintf( 549 $conn, 550 'r.phid IN (%Ls)', 551 $this->phids); 552 } 553 554 if ($this->callsigns !== null) { 555 $where[] = qsprintf( 556 $conn, 557 'r.callsign IN (%Ls)', 558 $this->callsigns); 559 } 560 561 if ($this->numericIdentifiers || 562 $this->callsignIdentifiers || 563 $this->phidIdentifiers || 564 $this->monogramIdentifiers || 565 $this->slugIdentifiers) { 566 $identifier_clause = array(); 567 568 if ($this->numericIdentifiers) { 569 $identifier_clause[] = qsprintf( 570 $conn, 571 'r.id IN (%Ld)', 572 $this->numericIdentifiers); 573 } 574 575 if ($this->callsignIdentifiers) { 576 $identifier_clause[] = qsprintf( 577 $conn, 578 'r.callsign IN (%Ls)', 579 $this->callsignIdentifiers); 580 } 581 582 if ($this->phidIdentifiers) { 583 $identifier_clause[] = qsprintf( 584 $conn, 585 'r.phid IN (%Ls)', 586 $this->phidIdentifiers); 587 } 588 589 if ($this->monogramIdentifiers) { 590 $monogram_callsigns = array(); 591 $monogram_ids = array(); 592 593 foreach ($this->monogramIdentifiers as $identifier) { 594 if ($identifier[0] == 'r') { 595 $monogram_callsigns[] = substr($identifier, 1); 596 } else { 597 $monogram_ids[] = substr($identifier, 1); 598 } 599 } 600 601 if ($monogram_ids) { 602 $identifier_clause[] = qsprintf( 603 $conn, 604 'r.id IN (%Ld)', 605 $monogram_ids); 606 } 607 608 if ($monogram_callsigns) { 609 $identifier_clause[] = qsprintf( 610 $conn, 611 'r.callsign IN (%Ls)', 612 $monogram_callsigns); 613 } 614 } 615 616 if ($this->slugIdentifiers) { 617 $identifier_clause[] = qsprintf( 618 $conn, 619 'r.repositorySlug IN (%Ls)', 620 $this->slugIdentifiers); 621 } 622 623 $where[] = qsprintf($conn, '%LO', $identifier_clause); 624 } 625 626 if ($this->types) { 627 $where[] = qsprintf( 628 $conn, 629 'r.versionControlSystem IN (%Ls)', 630 $this->types); 631 } 632 633 if ($this->uuids) { 634 $where[] = qsprintf( 635 $conn, 636 'r.uuid IN (%Ls)', 637 $this->uuids); 638 } 639 640 if ($this->slugs !== null) { 641 $where[] = qsprintf( 642 $conn, 643 'r.repositorySlug IN (%Ls)', 644 $this->slugs); 645 } 646 647 if ($this->uris !== null) { 648 $try_uris = $this->getNormalizedURIs(); 649 $try_uris = array_fuse($try_uris); 650 651 $where[] = qsprintf( 652 $conn, 653 'uri.repositoryURI IN (%Ls)', 654 $try_uris); 655 } 656 657 if ($this->almanacServicePHIDs !== null) { 658 $where[] = qsprintf( 659 $conn, 660 'r.almanacServicePHID IN (%Ls)', 661 $this->almanacServicePHIDs); 662 } 663 664 return $where; 665 } 666 667 public function getQueryApplicationClass() { 668 return PhabricatorDiffusionApplication::class; 669 } 670 671 private function getNormalizedURIs() { 672 $normalized_uris = array(); 673 674 // Since we don't know which type of repository this URI is in the general 675 // case, just generate all the normalizations. We could refine this in some 676 // cases: if the query specifies VCS types, or the URI is a git-style URI 677 // or an `svn+ssh` URI, we could deduce how to normalize it. However, this 678 // would be more complicated and it's not clear if it matters in practice. 679 680 $domain_map = PhabricatorRepositoryURI::getURINormalizerDomainMap(); 681 682 $types = ArcanistRepositoryURINormalizer::getAllURITypes(); 683 foreach ($this->uris as $uri) { 684 foreach ($types as $type) { 685 $normalized_uri = new ArcanistRepositoryURINormalizer($type, $uri); 686 $normalized_uri->setDomainMap($domain_map); 687 $normalized_uris[] = $normalized_uri->getNormalizedURI(); 688 } 689 } 690 691 return array_unique($normalized_uris); 692 } 693 694}