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

Support an "Ancestors Of: ..." constraint in commit queries

Summary:
Ref T13137. See PHI609. An install would like to filter audit requests on a particular branch, e.g. "master".

This is difficult in the general case because we can not apply this constraint efficiently under every conceivable data shape, but we can do a reasonable job in most practical cases.

See T13137#238822 for more detailed discussion on the approach here.

This is a bit rough, but should do the job for now.

Test Plan:
- Filtered commits by various branches, e.g. "master"; "lfs". Saw correct-seeming results.
- Stubbed out the "just list everything" path to hit the `diffusion.internal.ancestors` path, saw the same correct-seeming results.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13137

Differential Revision: https://secure.phabricator.com/D19431

+211 -1
+2
src/__phutil_library_map__.php
··· 816 816 'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php', 817 817 'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php', 818 818 'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php', 819 + 'DiffusionInternalAncestorsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php', 819 820 'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php', 820 821 'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php', 821 822 'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php', ··· 6149 6150 'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 6150 6151 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 6151 6152 'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', 6153 + 'DiffusionInternalAncestorsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 6152 6154 'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 6153 6155 'DiffusionLastModifiedController' => 'DiffusionController', 6154 6156 'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
+11
src/applications/audit/query/PhabricatorCommitSearchEngine.php
··· 53 53 $query->withUnreachable($map['unreachable']); 54 54 } 55 55 56 + if ($map['ancestorsOf']) { 57 + $query->withAncestorsOf($map['ancestorsOf']); 58 + } 59 + 56 60 return $query; 57 61 } 58 62 ··· 103 107 pht( 104 108 'Find or exclude unreachable commits which are not ancestors of '. 105 109 'any branch, tag, or ref.')), 110 + id(new PhabricatorSearchStringListField()) 111 + ->setLabel(pht('Ancestors Of')) 112 + ->setKey('ancestorsOf') 113 + ->setDescription( 114 + pht( 115 + 'Find commits which are ancestors of a particular ref, '. 116 + 'like "master".')), 106 117 ); 107 118 } 108 119
+51
src/applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php
··· 1 + <?php 2 + 3 + final class DiffusionInternalAncestorsConduitAPIMethod 4 + extends DiffusionQueryConduitAPIMethod { 5 + 6 + public function isInternalAPI() { 7 + return true; 8 + } 9 + 10 + public function getAPIMethodName() { 11 + return 'diffusion.internal.ancestors'; 12 + } 13 + 14 + public function getMethodDescription() { 15 + return pht('Internal method for filtering ref ancestors.'); 16 + } 17 + 18 + protected function defineReturnType() { 19 + return 'list<string>'; 20 + } 21 + 22 + protected function defineCustomParamTypes() { 23 + return array( 24 + 'ref' => 'required string', 25 + 'commits' => 'required list<string>', 26 + ); 27 + } 28 + 29 + protected function getResult(ConduitAPIRequest $request) { 30 + $drequest = $this->getDiffusionRequest(); 31 + $repository = $drequest->getRepository(); 32 + 33 + $commits = $request->getValue('commits'); 34 + $ref = $request->getValue('ref'); 35 + 36 + $graph = new PhabricatorGitGraphStream($repository, $ref); 37 + 38 + $keep = array(); 39 + foreach ($commits as $identifier) { 40 + try { 41 + $graph->getCommitDate($identifier); 42 + $keep[] = $identifier; 43 + } catch (Exception $ex) { 44 + // Not an ancestor. 45 + } 46 + } 47 + 48 + return $keep; 49 + } 50 + 51 + }
+147 -1
src/applications/diffusion/query/DiffusionCommitQuery.php
··· 22 22 private $epochMin; 23 23 private $epochMax; 24 24 private $importing; 25 + private $ancestorsOf; 25 26 26 27 private $needCommitData; 27 28 private $needDrafts; 29 + 30 + private $mustFilterRefs = false; 31 + private $refRepository; 28 32 29 33 public function withIDs(array $ids) { 30 34 $this->ids = $ids; ··· 92 96 } 93 97 94 98 public function withRepositoryIDs(array $repository_ids) { 95 - $this->repositoryIDs = $repository_ids; 99 + $this->repositoryIDs = array_unique($repository_ids); 96 100 return $this; 97 101 } 98 102 ··· 149 153 150 154 public function withImporting($importing) { 151 155 $this->importing = $importing; 156 + return $this; 157 + } 158 + 159 + public function withAncestorsOf(array $refs) { 160 + $this->ancestorsOf = $refs; 152 161 return $this; 153 162 } 154 163 ··· 307 316 protected function didFilterPage(array $commits) { 308 317 $viewer = $this->getViewer(); 309 318 319 + if ($this->mustFilterRefs) { 320 + // If this flag is set, the query has an "Ancestors Of" constraint and 321 + // at least one of the constraining refs had too many ancestors for us 322 + // to apply the constraint with a big "commitIdentifier IN (%Ls)" clause. 323 + // We're going to filter each page and hope we get a full result set 324 + // before the query overheats. 325 + 326 + $ancestor_list = mpull($commits, 'getCommitIdentifier'); 327 + $ancestor_list = array_values($ancestor_list); 328 + 329 + foreach ($this->ancestorsOf as $ref) { 330 + try { 331 + $ancestor_list = DiffusionQuery::callConduitWithDiffusionRequest( 332 + $viewer, 333 + DiffusionRequest::newFromDictionary( 334 + array( 335 + 'repository' => $this->refRepository, 336 + 'user' => $viewer, 337 + )), 338 + 'diffusion.internal.ancestors', 339 + array( 340 + 'ref' => $ref, 341 + 'commits' => $ancestor_list, 342 + )); 343 + } catch (ConduitClientException $ex) { 344 + throw new PhabricatorSearchConstraintException( 345 + $ex->getMessage()); 346 + } 347 + 348 + if (!$ancestor_list) { 349 + break; 350 + } 351 + } 352 + 353 + $ancestor_list = array_fuse($ancestor_list); 354 + foreach ($commits as $key => $commit) { 355 + $identifier = $commit->getCommitIdentifier(); 356 + if (!isset($ancestor_list[$identifier])) { 357 + $this->didRejectResult($commit); 358 + unset($commits[$key]); 359 + } 360 + } 361 + 362 + if (!$commits) { 363 + return $commits; 364 + } 365 + } 366 + 310 367 if ($this->needCommitData) { 311 368 $data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( 312 369 'commitID in (%Ld)', ··· 362 419 $repository_ids = array_merge($repository_ids, $this->repositoryIDs); 363 420 } 364 421 $this->withRepositoryIDs($repository_ids); 422 + } 423 + 424 + if ($this->ancestorsOf !== null) { 425 + if (count($this->repositoryIDs) !== 1) { 426 + throw new PhabricatorSearchConstraintException( 427 + pht( 428 + 'To search for commits which are ancestors of particular refs, '. 429 + 'you must constrain the search to exactly one repository.')); 430 + } 431 + 432 + $repository_id = head($this->repositoryIDs); 433 + $history_limit = $this->getRawResultLimit() * 32; 434 + $viewer = $this->getViewer(); 435 + 436 + $repository = id(new PhabricatorRepositoryQuery()) 437 + ->setViewer($viewer) 438 + ->withIDs(array($repository_id)) 439 + ->executeOne(); 440 + 441 + if (!$repository) { 442 + throw new PhabricatorEmptyQueryException(); 443 + } 444 + 445 + if ($repository->isSVN()) { 446 + throw new PhabricatorSearchConstraintException( 447 + pht( 448 + 'Subversion does not support searching for ancestors of '. 449 + 'a particular ref. This operation is not meaningful in '. 450 + 'Subversion.')); 451 + } 452 + 453 + if ($repository->isHg()) { 454 + throw new PhabricatorSearchConstraintException( 455 + pht( 456 + 'Mercurial does not currently support searching for ancestors of '. 457 + 'a particular ref.')); 458 + } 459 + 460 + $can_constrain = true; 461 + $history_identifiers = array(); 462 + foreach ($this->ancestorsOf as $key => $ref) { 463 + try { 464 + $raw_history = DiffusionQuery::callConduitWithDiffusionRequest( 465 + $viewer, 466 + DiffusionRequest::newFromDictionary( 467 + array( 468 + 'repository' => $repository, 469 + 'user' => $viewer, 470 + )), 471 + 'diffusion.historyquery', 472 + array( 473 + 'commit' => $ref, 474 + 'limit' => $history_limit, 475 + )); 476 + } catch (ConduitClientException $ex) { 477 + throw new PhabricatorSearchConstraintException( 478 + $ex->getMessage()); 479 + } 480 + 481 + $ref_identifiers = array(); 482 + foreach ($raw_history['pathChanges'] as $change) { 483 + $ref_identifiers[] = $change['commitIdentifier']; 484 + } 485 + 486 + // If this ref had fewer total commits than the limit, we're safe to 487 + // apply the constraint as a large `IN (...)` query for a list of 488 + // commit identifiers. This is efficient. 489 + if ($history_limit) { 490 + if (count($ref_identifiers) >= $history_limit) { 491 + $can_constrain = false; 492 + break; 493 + } 494 + } 495 + 496 + $history_identifiers += array_fuse($ref_identifiers); 497 + } 498 + 499 + // If all refs had a small number of ancestors, we can just put the 500 + // constraint into the query here and we're done. Otherwise, we need 501 + // to filter each page after it comes out of the MySQL layer. 502 + if ($can_constrain) { 503 + $where[] = qsprintf( 504 + $conn, 505 + 'commit.commitIdentifier IN (%Ls)', 506 + $history_identifiers); 507 + } else { 508 + $this->mustFilterRefs = true; 509 + $this->refRepository = $repository; 510 + } 365 511 } 366 512 367 513 if ($this->ids !== null) {