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

Add "Fetch Rules" to observed Git repositories

Summary:
Depends on D20421. Ref T13277. I'd generally like to move away from "Track Only".

Some of the use cases for "Track Only" (or adjacent to "Track Only") are better resolved with "Fetch Rules" -- basically, rules to fetch only some subset of refs from the observed remote.

Add configurable "Fetch Rules" for Git repositories. For example, if you only want to fetch `master`, you can now speify:

```
refs/heads/master
```

If you only want to fetch branches and tags, you can use:

```
refs/heads/*
refs/tags/*
```

In theory, this is slightly less powerful in the general case than "Track Only", but gives us better behavior in some cases (e.g., when the remote has 50K random temporary branches). In practice, I think this and a better "Autoclose Only" will let us move away from "Track Only", get default behavior which is better aligned with what users actually expect, and dodge all the "track tags/refs" questions.

Test Plan: Configured repositories with "Fetch Refs" rules, used `bin/repository pull --verbose --trace ...` to run pulls, saw expected pull/fetch behavior.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13277

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

+202 -10
+2
src/__phutil_library_map__.php
··· 4350 4350 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', 4351 4351 'PhabricatorRepositoryEnormousTransaction' => 'applications/repository/xaction/PhabricatorRepositoryEnormousTransaction.php', 4352 4352 'PhabricatorRepositoryFerretEngine' => 'applications/repository/search/PhabricatorRepositoryFerretEngine.php', 4353 + 'PhabricatorRepositoryFetchRefsTransaction' => 'applications/repository/xaction/PhabricatorRepositoryFetchRefsTransaction.php', 4353 4354 'PhabricatorRepositoryFilesizeLimitTransaction' => 'applications/repository/xaction/PhabricatorRepositoryFilesizeLimitTransaction.php', 4354 4355 'PhabricatorRepositoryFulltextEngine' => 'applications/repository/search/PhabricatorRepositoryFulltextEngine.php', 4355 4356 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php', ··· 10603 10604 'PhabricatorRepositoryEngine' => 'Phobject', 10604 10605 'PhabricatorRepositoryEnormousTransaction' => 'PhabricatorRepositoryTransactionType', 10605 10606 'PhabricatorRepositoryFerretEngine' => 'PhabricatorFerretEngine', 10607 + 'PhabricatorRepositoryFetchRefsTransaction' => 'PhabricatorRepositoryTransactionType', 10606 10608 'PhabricatorRepositoryFilesizeLimitTransaction' => 'PhabricatorRepositoryTransactionType', 10607 10609 'PhabricatorRepositoryFulltextEngine' => 'PhabricatorFulltextEngine', 10608 10610 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
+12
src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php
··· 212 212 ->setObject($object) 213 213 ->execute(); 214 214 215 + $fetch_value = $object->getFetchRules(); 215 216 $track_value = $object->getTrackOnlyRules(); 216 217 $autoclose_value = $object->getAutocloseOnlyRules(); 217 218 ··· 364 365 ->setConduitDescription(pht('Set the default branch name.')) 365 366 ->setConduitTypeDescription(pht('New default branch name.')) 366 367 ->setValue($object->getDetail('default-branch')), 368 + id(new PhabricatorTextAreaEditField()) 369 + ->setIsStringList(true) 370 + ->setKey('fetchRefs') 371 + ->setLabel(pht('Fetch Refs')) 372 + ->setTransactionType( 373 + PhabricatorRepositoryFetchRefsTransaction::TRANSACTIONTYPE) 374 + ->setIsCopyable(true) 375 + ->setDescription(pht('Fetch only these refs.')) 376 + ->setConduitDescription(pht('Set the fetched refs.')) 377 + ->setConduitTypeDescription(pht('New fetched refs.')) 378 + ->setValue($fetch_value), 367 379 id(new PhabricatorTextAreaEditField()) 368 380 ->setIsStringList(true) 369 381 ->setKey('trackOnly')
+11
src/applications/diffusion/management/DiffusionRepositoryBranchesManagementPanel.php
··· 36 36 protected function getEditEngineFieldKeys() { 37 37 return array( 38 38 'defaultBranch', 39 + 'fetchRefs', 39 40 'trackOnly', 40 41 'autocloseOnly', 41 42 ); ··· 77 78 $repository->getDetail('default-branch'), 78 79 phutil_tag('em', array(), $repository->getDefaultBranch())); 79 80 $view->addProperty(pht('Default Branch'), $default_branch); 81 + 82 + if ($repository->supportsFetchRules()) { 83 + $fetch_only = $repository->getFetchRules(); 84 + if ($fetch_only) { 85 + $fetch_display = implode(', ', $fetch_only); 86 + } else { 87 + $fetch_display = phutil_tag('em', array(), pht('Fetch All Refs')); 88 + } 89 + $view->addProperty(pht('Fetch Refs'), $fetch_display); 90 + } 80 91 81 92 $track_only_rules = $repository->getTrackOnlyRules(); 82 93 $track_only_rules = implode(', ', $track_only_rules);
+67 -10
src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
··· 339 339 throw new Exception($message); 340 340 } 341 341 342 - $remote_refs = $this->loadGitRemoteRefs($repository); 343 - $local_refs = $this->loadGitLocalRefs($repository); 342 + // Load the refs we're planning to fetch from the remote repository. 343 + $remote_refs = $this->loadGitRemoteRefs( 344 + $repository, 345 + $repository->getRemoteURIEnvelope()); 346 + 347 + // Load the refs we're planning to fetch from the local repository, by 348 + // using the local working copy path as the "remote" repository URI. 349 + $local_refs = $this->loadGitRemoteRefs( 350 + $repository, 351 + new PhutilOpaqueEnvelope($path)); 352 + 344 353 if ($remote_refs === $local_refs) { 345 354 $this->log( 346 355 pht( ··· 350 359 } 351 360 352 361 $this->logRefDifferences($remote_refs, $local_refs); 362 + 363 + $fetch_rules = $this->getGitFetchRules($repository); 353 364 354 365 $future = $repository->getRemoteCommandFuture( 355 - 'fetch %P %s --prune', 366 + 'fetch --prune -- %P %Ls', 356 367 $repository->getRemoteURIEnvelope(), 357 - '+refs/*:refs/*'); 368 + $fetch_rules); 358 369 359 370 $future 360 371 ->setCWD($path) 361 372 ->resolvex(); 362 373 } 363 374 375 + private function getGitRefRules(PhabricatorRepository $repository) { 376 + $ref_rules = $repository->getFetchRules($repository); 377 + 378 + if (!$ref_rules) { 379 + $ref_rules = array( 380 + 'refs/*', 381 + ); 382 + } 383 + 384 + return $ref_rules; 385 + } 386 + 387 + private function getGitFetchRules(PhabricatorRepository $repository) { 388 + $ref_rules = $this->getGitRefRules($repository); 389 + 390 + // Rewrite each ref rule "X" into "+X:X". 391 + 392 + // The "X" means "fetch ref X". 393 + // The "...:X" means "...and copy it into local ref X". 394 + // The "+..." means "...and overwrite the local ref if it already exists". 395 + 396 + $fetch_rules = array(); 397 + foreach ($ref_rules as $key => $ref_rule) { 398 + $fetch_rules[] = sprintf( 399 + '+%s:%s', 400 + $ref_rule, 401 + $ref_rule); 402 + } 403 + 404 + return $fetch_rules; 405 + } 364 406 365 407 /** 366 408 * @task git ··· 378 420 $this->installHook($root.$path); 379 421 } 380 422 381 - private function loadGitRemoteRefs(PhabricatorRepository $repository) { 382 - $remote_envelope = $repository->getRemoteURIEnvelope(); 423 + private function loadGitRemoteRefs( 424 + PhabricatorRepository $repository, 425 + PhutilOpaqueEnvelope $remote_envelope) { 426 + 427 + $ref_rules = $this->getGitRefRules($repository); 383 428 384 429 // NOTE: "git ls-remote" does not support "--" until circa January 2016. 385 - // See T12416. None of the flags to "ls-remote" appear dangerous, and 386 - // other checks make it difficult to configure a suspicious remote URI. 430 + // See T12416. None of the flags to "ls-remote" appear dangerous, but 431 + // refuse to list any refs beginning with "-" just in case. 432 + 433 + foreach ($ref_rules as $ref_rule) { 434 + if (preg_match('/^-/', $ref_rule)) { 435 + throw new Exception( 436 + pht( 437 + 'Refusing to list potentially dangerous ref ("%s") beginning '. 438 + 'with "-".', 439 + $ref_rule)); 440 + } 441 + } 442 + 387 443 list($stdout) = $repository->execxRemoteCommand( 388 - 'ls-remote %P', 389 - $remote_envelope); 444 + 'ls-remote %P %Ls', 445 + $remote_envelope, 446 + $ref_rules); 390 447 391 448 // Empty repositories don't have any refs. 392 449 if (!strlen(rtrim($stdout))) {
+16
src/applications/repository/storage/PhabricatorRepository.php
··· 1213 1213 return $this; 1214 1214 } 1215 1215 1216 + public function supportsFetchRules() { 1217 + if ($this->isGit()) { 1218 + return true; 1219 + } 1220 + 1221 + return false; 1222 + } 1223 + 1224 + public function getFetchRules() { 1225 + return $this->getDetail('fetch-rules', array()); 1226 + } 1227 + 1228 + public function setFetchRules(array $rules) { 1229 + return $this->setDetail('fetch-rules', $rules); 1230 + } 1231 + 1216 1232 1217 1233 /* -( Repository URI Management )------------------------------------------ */ 1218 1234
+94
src/applications/repository/xaction/PhabricatorRepositoryFetchRefsTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorRepositoryFetchRefsTransaction 4 + extends PhabricatorRepositoryTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'fetch-refs'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getFetchRules(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setFetchRules($value); 14 + } 15 + 16 + public function getTitle() { 17 + $old = $this->getOldValue(); 18 + $new = $this->getNewValue(); 19 + 20 + if (!$new) { 21 + return pht( 22 + '%s set this repository to fetch all refs.', 23 + $this->renderAuthor()); 24 + } else if (!$old) { 25 + return pht( 26 + '%s set this repository to fetch refs: %s.', 27 + $this->renderAuthor(), 28 + $this->renderValue(implode(', ', $new))); 29 + } else { 30 + return pht( 31 + '%s changed fetched refs from %s to %s.', 32 + $this->renderAuthor(), 33 + $this->renderValue(implode(', ', $old)), 34 + $this->renderValue(implode(', ', $new))); 35 + } 36 + } 37 + 38 + public function validateTransactions($object, array $xactions) { 39 + $errors = array(); 40 + 41 + foreach ($xactions as $xaction) { 42 + $new_value = $xaction->getNewValue(); 43 + 44 + if (!is_array($new_value) || !phutil_is_natural_list($new_value)) { 45 + $errors[] = $this->newInvalidError( 46 + pht( 47 + 'Fetch rules must be a list of strings, got "%s".', 48 + phutil_describe_type($new_value)), 49 + $xaction); 50 + continue; 51 + } 52 + 53 + foreach ($new_value as $idx => $rule) { 54 + if (!is_string($rule)) { 55 + $errors[] = $this->newInvalidError( 56 + pht( 57 + 'Fetch rule (at index "%s") must be a string, got "%s".', 58 + $idx, 59 + phutil_describe_type($rule)), 60 + $xaction); 61 + continue; 62 + } 63 + 64 + if (!strlen($rule)) { 65 + $errors[] = $this->newInvalidError( 66 + pht( 67 + 'Fetch rule (at index "%s") is empty. Fetch rules must '. 68 + 'contain text.', 69 + $idx), 70 + $xaction); 71 + continue; 72 + } 73 + 74 + // Since we fetch ref "X" as "+X:X", don't allow rules to include 75 + // colons. This is specific to Git and may not be relevant if 76 + // Mercurial repositories eventually get fetch rules. 77 + if (preg_match('/:/', $rule)) { 78 + $errors[] = $this->newInvalidError( 79 + pht( 80 + 'Fetch rule ("%s", at index "%s") is invalid: fetch rules '. 81 + 'must not contain colons.', 82 + $rule, 83 + $idx), 84 + $xaction); 85 + continue; 86 + } 87 + 88 + } 89 + } 90 + 91 + return $errors; 92 + } 93 + 94 + }