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

Move repository URI normalization out of PullLocalDaemon

Summary: Ref T4327. Moves us one small step forward toward testable change parsers by separating out this unrelated logic from the PullLocal daemon. We will also probably want to run this logic so we can do remote path lookups to limit the role of Arcanist Projects in the future, which is why I made the URI type (here, only "git") a parameter rather than calling this a `GitURINormalizer` or something.

Test Plan:
- Ran unit tests.
- Ran `repository discover` on a correctly-configured remote repository.
- Ran `repository discover` on an incorrectly-configured remote, got an error message.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T4327

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

+144 -127
+4
src/__phutil_library_map__.php
··· 1881 1881 'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php', 1882 1882 'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php', 1883 1883 'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php', 1884 + 'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php', 1885 + 'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php', 1884 1886 'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php', 1885 1887 'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php', 1886 1888 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php', ··· 4551 4553 'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase', 4552 4554 'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction', 4553 4555 'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4556 + 'PhabricatorRepositoryURINormalizer' => 'Phobject', 4557 + 'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase', 4554 4558 'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase', 4555 4559 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO', 4556 4560 'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
+11 -43
src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
··· 719 719 $valid = false; 720 720 $exists = false; 721 721 } else { 722 - $valid = self::isSameGitOrigin($remote_uri, $expect_remote); 722 + $normal_type_git = PhabricatorRepositoryURINormalizer::TYPE_GIT; 723 + 724 + $remote_normal = id(new PhabricatorRepositoryURINormalizer( 725 + $normal_type_git, 726 + $remote_uri))->getNormalizedPath(); 727 + 728 + $expect_normal = id(new PhabricatorRepositoryURINormalizer( 729 + $normal_type_git, 730 + $expect_remote))->getNormalizedPath(); 731 + 732 + $valid = ($remote_normal == $expect_normal); 723 733 $exists = true; 724 734 } 725 735 ··· 767 777 } 768 778 } 769 779 } 770 - 771 - 772 - 773 - /** 774 - * @task git 775 - */ 776 - public static function isSameGitOrigin($remote, $expect) { 777 - $remote_path = self::getPathFromGitURI($remote); 778 - $expect_path = self::getPathFromGitURI($expect); 779 - 780 - $remote_match = self::executeGitNormalizePath($remote_path); 781 - $expect_match = self::executeGitNormalizePath($expect_path); 782 - 783 - return ($remote_match == $expect_match); 784 - } 785 - 786 - private static function getPathFromGitURI($raw_uri) { 787 - $uri = new PhutilURI($raw_uri); 788 - if ($uri->getProtocol()) { 789 - return $uri->getPath(); 790 - } 791 - 792 - $uri = new PhutilGitURI($raw_uri); 793 - if ($uri->getDomain()) { 794 - return $uri->getPath(); 795 - } 796 - 797 - return $raw_uri; 798 - } 799 - 800 - 801 - /** 802 - * @task git 803 - */ 804 - private static function executeGitNormalizePath($path) { 805 - // Strip away "/" and ".git", so similar paths correctly match. 806 - 807 - $path = trim($path, '/'); 808 - $path = preg_replace('/\.git$/', '', $path); 809 - return $path; 810 - } 811 - 812 780 813 781 private function pushToMirrors(PhabricatorRepository $repository) { 814 782 if (!$repository->canMirror()) {
+98
src/applications/repository/data/PhabricatorRepositoryURINormalizer.php
··· 1 + <?php 2 + 3 + /** 4 + * Normalize repository URIs. For example, these URIs are generally equivalent 5 + * and all point at the same repository: 6 + * 7 + * ssh://user@host/repo 8 + * ssh://user@host/repo/ 9 + * ssh://user@host:22/repo 10 + * user@host:/repo 11 + * ssh://user@host/repo.git 12 + * 13 + * This class can be used to normalize URIs like this, in order to detect 14 + * alternate spellings of the same repository URI. In particular, the 15 + * @{method:getNormalizedPath} method will return: 16 + * 17 + * repo 18 + * 19 + * ...for all of these URIs. Generally, usage looks like this: 20 + * 21 + * $norm_a = new PhabricatorRepositoryURINormalizer($type, $uri_a); 22 + * $norm_b = new PhabricatorRepositoryURINormalizer($type, $uri_b); 23 + * 24 + * if ($norm_a->getNormalizedPath() == $norm_b->getNormalizedPath()) { 25 + * // URIs appear to point at the same repository. 26 + * } else { 27 + * // URIs are very unlikely to be the same repository. 28 + * } 29 + * 30 + * Because a repository can be hosted at arbitrarly many arbitrary URIs, there 31 + * is no way to completely prevent false negatives by only examining URIs 32 + * (that is, repositories with totally different URIs could really be the same). 33 + * However, normalization is relatively agressive and false negatives should 34 + * be rare: if normalization says two URIs are different repositories, they 35 + * probably are. 36 + * 37 + * @task normal Normalizing URIs 38 + */ 39 + final class PhabricatorRepositoryURINormalizer extends Phobject { 40 + 41 + const TYPE_GIT = 'git'; 42 + private $type; 43 + private $uri; 44 + 45 + public function __construct($type, $uri) { 46 + switch ($type) { 47 + case self::TYPE_GIT: 48 + break; 49 + default: 50 + throw new Exception(pht('Unknown URI type "%s"!')); 51 + } 52 + 53 + $this->type = $type; 54 + $this->uri = $uri; 55 + } 56 + 57 + 58 + /* -( Normalizing URIs )--------------------------------------------------- */ 59 + 60 + 61 + /** 62 + * @task normal 63 + */ 64 + public function getPath() { 65 + switch ($this->type) { 66 + case self::TYPE_GIT: 67 + $uri = new PhutilURI($this->uri); 68 + if ($uri->getProtocol()) { 69 + return $uri->getPath(); 70 + } 71 + 72 + $uri = new PhutilGitURI($this->uri); 73 + if ($uri->getDomain()) { 74 + return $uri->getPath(); 75 + } 76 + 77 + return $this->uri; 78 + } 79 + } 80 + 81 + 82 + /** 83 + * @task normal 84 + */ 85 + public function getNormalizedPath() { 86 + $path = $this->getPath(); 87 + $path = trim($path, '/'); 88 + 89 + switch ($this->type) { 90 + case self::TYPE_GIT: 91 + $path = preg_replace('/\.git$/', '', $path); 92 + break; 93 + } 94 + 95 + return $path; 96 + } 97 + 98 + }
+31
src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php
··· 1 + <?php 2 + 3 + final class PhabricatorRepositoryURINormalizerTestCase 4 + extends PhabricatorTestCase { 5 + 6 + public function testGitURINormalizer() { 7 + $cases = array( 8 + 'ssh://user@domain.com/path.git' => 'path', 9 + 'https://user@domain.com/path.git' => 'path', 10 + 'git@domain.com:path.git' => 'path', 11 + 'ssh://user@gitserv002.com/path.git' => 'path', 12 + 'ssh://htaft@domain.com/path.git' => 'path', 13 + 'ssh://user@domain.com/bananas.git' => 'bananas', 14 + 'git@domain.com:bananas.git' => 'bananas', 15 + 'user@domain.com:path/repo' => 'path/repo', 16 + 'user@domain.com:path/repo/' => 'path/repo', 17 + 'file:///path/to/local/repo.git' => 'path/to/local/repo', 18 + '/path/to/local/repo.git' => 'path/to/local/repo', 19 + ); 20 + 21 + $type_git = PhabricatorRepositoryURINormalizer::TYPE_GIT; 22 + 23 + foreach ($cases as $input => $expect) { 24 + $normal = new PhabricatorRepositoryURINormalizer($type_git, $input); 25 + $this->assertEqual( 26 + $expect, 27 + $normal->getNormalizedPath(), 28 + pht('Normalized path for "%s".', $input)); 29 + } 30 + } 31 + }
-84
src/applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php
··· 24 24 $this->assertEqual(array(), $refs); 25 25 } 26 26 27 - public function testExecuteGitVerifySameOrigin() { 28 - $cases = array( 29 - array( 30 - 'ssh://user@domain.com/path.git', 31 - 'ssh://user@domain.com/path.git', 32 - true, 33 - 'Identical paths should pass.', 34 - ), 35 - array( 36 - 'ssh://user@domain.com/path.git', 37 - 'https://user@domain.com/path.git', 38 - true, 39 - 'Protocol changes should pass.', 40 - ), 41 - array( 42 - 'ssh://user@domain.com/path.git', 43 - 'git@domain.com:path.git', 44 - true, 45 - 'Git implicit SSH should pass.', 46 - ), 47 - array( 48 - 'ssh://user@gitserv001.com/path.git', 49 - 'ssh://user@gitserv002.com/path.git', 50 - true, 51 - 'Domain changes should pass.', 52 - ), 53 - array( 54 - 'ssh://alincoln@domain.com/path.git', 55 - 'ssh://htaft@domain.com/path.git', 56 - true, 57 - 'User/auth changes should pass.', 58 - ), 59 - array( 60 - 'ssh://user@domain.com/apples.git', 61 - 'ssh://user@domain.com/bananas.git', 62 - false, 63 - 'Path changes should fail.', 64 - ), 65 - array( 66 - 'ssh://user@domain.com/apples.git', 67 - 'git@domain.com:bananas.git', 68 - false, 69 - 'Git implicit SSH path changes should fail.', 70 - ), 71 - array( 72 - 'user@domain.com:path/repo.git', 73 - 'user@domain.com:path/repo', 74 - true, 75 - 'Optional .git extension should not prevent matches.', 76 - ), 77 - array( 78 - 'user@domain.com:path/repo/', 79 - 'user@domain.com:path/repo', 80 - true, 81 - 'Optional trailing slash should not prevent matches.', 82 - ), 83 - array( 84 - 'file:///path/to/local/repo.git', 85 - 'file:///path/to/local/repo.git', 86 - true, 87 - 'file:// protocol should be supported.', 88 - ), 89 - array( 90 - '/path/to/local/repo.git', 91 - 'file:///path/to/local/repo.git', 92 - true, 93 - 'Implicit file:// protocol should be recognized.', 94 - ), 95 - ); 96 - 97 - foreach ($cases as $case) { 98 - list($remote, $config, $expect, $message) = $case; 99 - 100 - $this->assertEqual( 101 - $expect, 102 - PhabricatorRepositoryPullLocalDaemon::isSameGitOrigin( 103 - $remote, 104 - $config, 105 - '(a test case)'), 106 - "Verification that '{$remote}' and '{$config}' are the same origin ". 107 - "had a different outcome than expected: {$message}"); 108 - } 109 - } 110 - 111 27 }