Verify that SVN repository roots really are repository roots
Summary: Fixes T3238. Ref T4327. Although the instructions are fairly clear on this, it's easy to miss them. Make sure the root the user enters matches the real root. Test Plan: Added unit tests. Used `bin/repository discover` to hit the check explicitly. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T3238, T4327 Differential Revision: https://secure.phabricator.com/D8020
This commit is contained in:
		| @@ -473,8 +473,10 @@ final class DiffusionRepositoryCreateController | |||||||
|         "| `svn://svn.example.net/svnroot/` |\n". |         "| `svn://svn.example.net/svnroot/` |\n". | ||||||
|         "| `file:///local/path/to/svnroot/` |\n". |         "| `file:///local/path/to/svnroot/` |\n". | ||||||
|         "\n\n". |         "\n\n". | ||||||
|         "Make sure you specify the root of the repository, not a ". |         "You **MUST** specify the root of the repository, not a ". | ||||||
|         "subdirectory."); |         "subdirectory. (If you want to import only part of a Subversion ". | ||||||
|  |         "repository, use the //Import Only// option at the end of this ". | ||||||
|  |         "workflow.)"); | ||||||
|     } else { |     } else { | ||||||
|       throw new Exception("Unsupported VCS!"); |       throw new Exception("Unsupported VCS!"); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -39,12 +39,15 @@ | |||||||
| final class PhabricatorRepositoryURINormalizer extends Phobject { | final class PhabricatorRepositoryURINormalizer extends Phobject { | ||||||
|  |  | ||||||
|   const TYPE_GIT = 'git'; |   const TYPE_GIT = 'git'; | ||||||
|  |   const TYPE_SVN = 'svn'; | ||||||
|  |  | ||||||
|   private $type; |   private $type; | ||||||
|   private $uri; |   private $uri; | ||||||
|  |  | ||||||
|   public function __construct($type, $uri) { |   public function __construct($type, $uri) { | ||||||
|     switch ($type) { |     switch ($type) { | ||||||
|       case self::TYPE_GIT: |       case self::TYPE_GIT: | ||||||
|  |       case self::TYPE_SVN: | ||||||
|         break; |         break; | ||||||
|       default: |       default: | ||||||
|         throw new Exception(pht('Unknown URI type "%s"!')); |         throw new Exception(pht('Unknown URI type "%s"!')); | ||||||
| @@ -74,6 +77,13 @@ final class PhabricatorRepositoryURINormalizer extends Phobject { | |||||||
|           return $uri->getPath(); |           return $uri->getPath(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         return $this->uri; | ||||||
|  |       case self::TYPE_SVN: | ||||||
|  |         $uri = new PhutilURI($this->uri); | ||||||
|  |         if ($uri->getProtocol()) { | ||||||
|  |           return $uri->getPath(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return $this->uri; |         return $this->uri; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -90,6 +100,8 @@ final class PhabricatorRepositoryURINormalizer extends Phobject { | |||||||
|       case self::TYPE_GIT: |       case self::TYPE_GIT: | ||||||
|         $path = preg_replace('/\.git$/', '', $path); |         $path = preg_replace('/\.git$/', '', $path); | ||||||
|         break; |         break; | ||||||
|  |       case self::TYPE_SVN: | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return $path; |     return $path; | ||||||
|   | |||||||
| @@ -25,7 +25,25 @@ final class PhabricatorRepositoryURINormalizerTestCase | |||||||
|       $this->assertEqual( |       $this->assertEqual( | ||||||
|         $expect, |         $expect, | ||||||
|         $normal->getNormalizedPath(), |         $normal->getNormalizedPath(), | ||||||
|         pht('Normalized path for "%s".', $input)); |         pht('Normalized Git path 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)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -225,6 +225,10 @@ final class PhabricatorRepositoryDiscoveryEngine | |||||||
|   private function discoverSubversionCommits() { |   private function discoverSubversionCommits() { | ||||||
|     $repository = $this->getRepository(); |     $repository = $this->getRepository(); | ||||||
|  |  | ||||||
|  |     if (!$repository->isHosted()) { | ||||||
|  |       $this->verifySubversionRoot($repository); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     $upper_bound = null; |     $upper_bound = null; | ||||||
|     $limit = 1; |     $limit = 1; | ||||||
|     $refs = array(); |     $refs = array(); | ||||||
| @@ -289,6 +293,43 @@ final class PhabricatorRepositoryDiscoveryEngine | |||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   private function verifySubversionRoot(PhabricatorRepository $repository) { | ||||||
|  |     list($xml) = $repository->execxRemoteCommand( | ||||||
|  |       'info --xml %s', | ||||||
|  |       $repository->getSubversionPathURI()); | ||||||
|  |  | ||||||
|  |     $xml = phutil_utf8ize($xml); | ||||||
|  |     $xml = new SimpleXMLElement($xml); | ||||||
|  |  | ||||||
|  |     $remote_root = (string)($xml->entry[0]->repository[0]->root[0]); | ||||||
|  |     $expect_root = $repository->getSubversionPathURI(); | ||||||
|  |  | ||||||
|  |     $normal_type_svn = PhabricatorRepositoryURINormalizer::TYPE_SVN; | ||||||
|  |  | ||||||
|  |     $remote_normal = id(new PhabricatorRepositoryURINormalizer( | ||||||
|  |       $normal_type_svn, | ||||||
|  |       $remote_root))->getNormalizedPath(); | ||||||
|  |  | ||||||
|  |     $expect_normal = id(new PhabricatorRepositoryURINormalizer( | ||||||
|  |       $normal_type_svn, | ||||||
|  |       $expect_root))->getNormalizedPath(); | ||||||
|  |  | ||||||
|  |     if ($remote_normal != $expect_normal) { | ||||||
|  |       throw new Exception( | ||||||
|  |         pht( | ||||||
|  |           'Repository "%s" does not have a correctly configured remote URI. '. | ||||||
|  |           'The remote URI for a Subversion repository MUST point at the '. | ||||||
|  |           'repository root. The root for this repository is "%s", but the '. | ||||||
|  |           'configured URI is "%s". To resolve this error, set the remote URI '. | ||||||
|  |           'to point at the repository root. If you want to import only part '. | ||||||
|  |           'of a Subversion repository, use the "Import Only" option.', | ||||||
|  |           $repository->getCallsign(), | ||||||
|  |           $remote_root, | ||||||
|  |           $expect_root)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
| /* -(  Discovering Mercurial Repositories  )--------------------------------- */ | /* -(  Discovering Mercurial Repositories  )--------------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,14 @@ abstract class PhabricatorWorkingCopyTestCase extends PhabricatorTestCase { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected function buildBareRepository($callsign) { |   protected function buildBareRepository($callsign) { | ||||||
|  |     $existing_repository = id(new PhabricatorRepositoryQuery()) | ||||||
|  |       ->withCallsigns(array($callsign)) | ||||||
|  |       ->setViewer(PhabricatorUser::getOmnipotentUser()) | ||||||
|  |       ->executeOne(); | ||||||
|  |     if ($existing_repository) { | ||||||
|  |       $existing_repository->delete(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     $data_dir = dirname(__FILE__).'/data/'; |     $data_dir = dirname(__FILE__).'/data/'; | ||||||
|  |  | ||||||
|     $types = array( |     $types = array( | ||||||
| @@ -84,7 +92,7 @@ abstract class PhabricatorWorkingCopyTestCase extends PhabricatorTestCase { | |||||||
|  |  | ||||||
|     id(new PhabricatorRepositoryDiscoveryEngine()) |     id(new PhabricatorRepositoryDiscoveryEngine()) | ||||||
|       ->setRepository($repository) |       ->setRepository($repository) | ||||||
|       ->discoverCommits($repository); |       ->discoverCommits(); | ||||||
|  |  | ||||||
|     return $repository; |     return $repository; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -962,7 +962,7 @@ final class PhabricatorChangeParserTestCase | |||||||
|  |  | ||||||
|     id(new PhabricatorRepositoryDiscoveryEngine()) |     id(new PhabricatorRepositoryDiscoveryEngine()) | ||||||
|       ->setRepository($repository) |       ->setRepository($repository) | ||||||
|       ->discoverCommits($repository); |       ->discoverCommits(); | ||||||
|  |  | ||||||
|     $viewer = PhabricatorUser::getOmnipotentUser(); |     $viewer = PhabricatorUser::getOmnipotentUser(); | ||||||
|  |  | ||||||
| @@ -1056,6 +1056,54 @@ final class PhabricatorChangeParserTestCase | |||||||
|       )); |       )); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public function testSubversionValidRootParser() { | ||||||
|  |     // First, automatically configure the root correctly. | ||||||
|  |     $repository = $this->buildBareRepository('CHD'); | ||||||
|  |     id(new PhabricatorRepositoryPullEngine()) | ||||||
|  |       ->setRepository($repository) | ||||||
|  |       ->pullRepository(); | ||||||
|  |  | ||||||
|  |     $caught = null; | ||||||
|  |     try { | ||||||
|  |       id(new PhabricatorRepositoryDiscoveryEngine()) | ||||||
|  |         ->setRepository($repository) | ||||||
|  |         ->discoverCommits(); | ||||||
|  |     } catch (Exception $ex) { | ||||||
|  |       $caught = $ex; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $this->assertEqual( | ||||||
|  |       false, | ||||||
|  |       ($caught instanceof Exception), | ||||||
|  |       pht('Natural SVN root should work properly.')); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     // This time, artificially break the root. We expect this to fail. | ||||||
|  |     $repository = $this->buildBareRepository('CHD'); | ||||||
|  |     $repository->setDetail( | ||||||
|  |       'remote-uri', | ||||||
|  |       $repository->getDetail('remote-uri').'trunk/'); | ||||||
|  |  | ||||||
|  |     id(new PhabricatorRepositoryPullEngine()) | ||||||
|  |       ->setRepository($repository) | ||||||
|  |       ->pullRepository(); | ||||||
|  |  | ||||||
|  |     $caught = null; | ||||||
|  |     try { | ||||||
|  |       id(new PhabricatorRepositoryDiscoveryEngine()) | ||||||
|  |         ->setRepository($repository) | ||||||
|  |         ->discoverCommits(); | ||||||
|  |     } catch (Exception $ex) { | ||||||
|  |       $caught = $ex; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $this->assertEqual( | ||||||
|  |       true, | ||||||
|  |       ($caught instanceof Exception), | ||||||
|  |       pht('Artificial SVN root should fail.')); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|   private function expectChanges( |   private function expectChanges( | ||||||
|     PhabricatorRepository $repository, |     PhabricatorRepository $repository, | ||||||
|     array $commits, |     array $commits, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley