diff --git a/resources/sql/autopatches/20191028.uriindex.01.rebuild.php b/resources/sql/autopatches/20191028.uriindex.01.rebuild.php new file mode 100644 index 0000000000..c9bd3d97ca --- /dev/null +++ b/resources/sql/autopatches/20191028.uriindex.01.rebuild.php @@ -0,0 +1,4 @@ +getNormalizedPath() == $norm_b->getNormalizedPath()) { * // URIs appear to point at the same repository. * } else { * // URIs are very unlikely to be the same repository. * } * * Because a repository can be hosted at arbitrarily many arbitrary URIs, there * is no way to completely prevent false negatives by only examining URIs * (that is, repositories with totally different URIs could really be the same). * However, normalization is relatively aggressive and false negatives should * be rare: if normalization says two URIs are different repositories, they * probably are. * * @task normal Normalizing URIs */ final class PhabricatorRepositoryURINormalizer extends Phobject { const TYPE_GIT = 'git'; const TYPE_SVN = 'svn'; const TYPE_MERCURIAL = 'hg'; private $type; private $uri; public function __construct($type, $uri) { switch ($type) { case self::TYPE_GIT: case self::TYPE_SVN: case self::TYPE_MERCURIAL: break; default: throw new Exception(pht('Unknown URI type "%s"!', $type)); } $this->type = $type; $this->uri = $uri; } public static function getAllURITypes() { return array( self::TYPE_GIT, self::TYPE_SVN, self::TYPE_MERCURIAL, ); } /* -( Normalizing URIs )--------------------------------------------------- */ /** * @task normal */ public function getPath() { switch ($this->type) { case self::TYPE_GIT: $uri = new PhutilURI($this->uri); return $uri->getPath(); case self::TYPE_SVN: case self::TYPE_MERCURIAL: $uri = new PhutilURI($this->uri); if ($uri->getProtocol()) { return $uri->getPath(); } return $this->uri; } } public function getNormalizedURI() { return $this->getNormalizedDomain().'/'.$this->getNormalizedPath(); } /** * @task normal */ public function getNormalizedPath() { $path = $this->getPath(); $path = trim($path, '/'); switch ($this->type) { case self::TYPE_GIT: $path = preg_replace('/\.git$/', '', $path); break; case self::TYPE_SVN: case self::TYPE_MERCURIAL: break; } // If this is a Phabricator URI, strip it down to the callsign. We mutably // allow you to clone repositories as "/diffusion/X/anything.git", for // example. $matches = null; if (preg_match('@^(diffusion/(?:[A-Z]+|\d+))@', $path, $matches)) { $path = $matches[1]; } return $path; } public function getNormalizedDomain() { $domain = null; $uri = new PhutilURI($this->uri); $domain = $uri->getDomain(); if (!strlen($domain)) { - $domain = ''; + return ''; } - return phutil_utf8_strtolower($domain); + $domain = phutil_utf8_strtolower($domain); + + // See T13435. If the domain for a repository URI is same as the install + // base URI, store it as a "" token instead of the actual domain + // so that the index does not fall out of date if the install moves. + + $base_uri = PhabricatorEnv::getURI('/'); + $base_uri = new PhutilURI($base_uri); + $base_domain = $base_uri->getDomain(); + $base_domain = phutil_utf8_strtolower($base_domain); + if ($domain === $base_domain) { + return ''; + } + + // Likewise, store a token for the "SSH Host" domain so it can be changed + // without requiring an index rebuild. + + $ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host'); + if (strlen($ssh_host)) { + $ssh_host = phutil_utf8_strtolower($ssh_host); + if ($domain === $ssh_host) { + return ''; + } + } + + return $domain; } } diff --git a/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php b/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php index 81dd735562..8ab54a23a4 100644 --- a/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php +++ b/src/applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php @@ -1,51 +1,81 @@ 'path', 'https://user@domain.com/path.git' => 'path', 'git@domain.com:path.git' => 'path', 'ssh://user@gitserv002.com/path.git' => 'path', 'ssh://htaft@domain.com/path.git' => 'path', 'ssh://user@domain.com/bananas.git' => 'bananas', 'git@domain.com:bananas.git' => 'bananas', 'user@domain.com:path/repo' => 'path/repo', 'user@domain.com:path/repo/' => 'path/repo', 'file:///path/to/local/repo.git' => 'path/to/local/repo', '/path/to/local/repo.git' => 'path/to/local/repo', 'ssh://something.com/diffusion/X/anything.git' => 'diffusion/X', 'ssh://something.com/diffusion/X/' => 'diffusion/X', ); $type_git = PhabricatorRepositoryURINormalizer::TYPE_GIT; foreach ($cases as $input => $expect) { $normal = new PhabricatorRepositoryURINormalizer($type_git, $input); $this->assertEqual( $expect, $normal->getNormalizedPath(), pht('Normalized Git path for "%s".', $input)); } } + public function testDomainURINormalizer() { + $base_domain = 'base.phabricator.example.com'; + $ssh_domain = 'ssh.phabricator.example.com'; + + $env = PhabricatorEnv::beginScopedEnv(); + $env->overrideEnvConfig('phabricator.base-uri', 'http://'.$base_domain); + $env->overrideEnvConfig('diffusion.ssh-host', $ssh_domain); + + $cases = array( + '/' => '', + '/path/to/local/repo.git' => '', + 'ssh://user@domain.com/path.git' => 'domain.com', + 'ssh://user@DOMAIN.COM/path.git' => 'domain.com', + 'http://'.$base_domain.'/diffusion/X/' => '', + 'ssh://'.$ssh_domain.'/diffusion/X/' => '', + 'git@'.$ssh_domain.':bananas.git' => '', + ); + + $type_git = PhabricatorRepositoryURINormalizer::TYPE_GIT; + + foreach ($cases as $input => $expect) { + $normal = new PhabricatorRepositoryURINormalizer($type_git, $input); + + $this->assertEqual( + $expect, + $normal->getNormalizedDomain(), + pht('Normalized domain for "%s".', $input)); + } + } + public function testSVNURINormalizer() { $cases = array( 'file:///path/to/repo' => 'path/to/repo', 'file:///path/to/repo/' => 'path/to/repo', ); $type_svn = PhabricatorRepositoryURINormalizer::TYPE_SVN; foreach ($cases as $input => $expect) { $normal = new PhabricatorRepositoryURINormalizer($type_svn, $input); $this->assertEqual( $expect, $normal->getNormalizedPath(), pht('Normalized SVN path for "%s".', $input)); } } }