From e2eb8f2201118260c951d6cefe88bfef8888da27 Mon Sep 17 00:00:00 2001 From: Rob Bast Date: Mon, 24 Apr 2017 10:08:31 +0200 Subject: [PATCH] support for gitlab subgroups, closes #6349 --- src/Composer/Repository/Vcs/GitLabDriver.php | 55 ++++++++++--- .../Test/Repository/Vcs/GitLabDriverTest.php | 80 ++++++++++++++++++- 2 files changed, 120 insertions(+), 15 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index dd2a74494..66eda8145 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -29,7 +29,7 @@ use Composer\Util\GitLab; class GitLabDriver extends VcsDriver { private $scheme; - private $owner; + private $namespace; private $repository; /** @@ -66,7 +66,7 @@ class GitLabDriver extends VcsDriver */ private $isPrivate = true; - const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)/|git@(?P[^:]+):)(?P[^/]+)/(?P[^/]+?)(?:\.git|/)?$#'; + const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\.git|/)?$#'; /** * Extracts information from the repository url. @@ -81,12 +81,19 @@ class GitLabDriver extends VcsDriver throw new \InvalidArgumentException('The URL provided is invalid. It must be the HTTP URL of a GitLab project.'); } - $this->scheme = !empty($match['scheme']) ? $match['scheme'] : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https'); - $this->originUrl = !empty($match['domain']) ? $match['domain'] : $match['domain2']; - $this->owner = $match['owner']; + $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2']; + $configuredDomains = $this->config->get('gitlab-domains'); + $urlParts = explode('/', $match['parts']); + + $this->scheme = !empty($match['scheme']) + ? $match['scheme'] + : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https') + ; + $this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts); + $this->namespace = implode('/', $urlParts); $this->repository = preg_replace('#(\.git)$#', '', $match['repo']); - $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository); $this->fetchProject(); } @@ -241,7 +248,7 @@ class GitLabDriver extends VcsDriver */ public function getApiUrl() { - return $this->scheme.'://'.$this->originUrl.'/api/v3/projects/'.$this->urlEncodeAll($this->owner).'%2F'.$this->urlEncodeAll($this->repository); + return $this->scheme.'://'.$this->originUrl.'/api/v3/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); } /** @@ -326,12 +333,12 @@ class GitLabDriver extends VcsDriver */ protected function generateSshUrl() { - return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; + return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git'; } protected function generatePublicUrl() { - return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + return 'https://' . $this->originUrl . '/'.$this->namespace.'/'.$this->repository.'.git'; } protected function setupGitDriver($url) @@ -386,7 +393,7 @@ class GitLabDriver extends VcsDriver if (!$this->io->isInteractive()) { return $this->attemptCloneFallback(); } - $this->io->writeError('Failed to download ' . $this->owner . '/' . $this->repository . ':' . $e->getMessage() . ''); + $this->io->writeError('Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . ''); $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata ('.$this->url.')'); return parent::getContents($url); @@ -421,9 +428,10 @@ class GitLabDriver extends VcsDriver } $scheme = !empty($match['scheme']) ? $match['scheme'] : null; - $originUrl = !empty($match['domain']) ? $match['domain'] : $match['domain2']; + $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2']; + $urlParts = explode('/', $match['parts']); - if (!in_array($originUrl, (array) $config->get('gitlab-domains'))) { + if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts)) { return false; } @@ -435,4 +443,27 @@ class GitLabDriver extends VcsDriver return true; } + + /** + * @param array $configuredDomains + * @param string $guessedDomain + * @param array $urlParts + * @return bool|string + */ + private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts) + { + if (in_array($guessedDomain, $configuredDomains)) { + return $guessedDomain; + } + + while (null !== ($part = array_shift($urlParts))) { + $guessedDomain .= '/' . $part; + + if (in_array($guessedDomain, $configuredDomains)) { + return $guessedDomain; + } + } + + return false; + } } diff --git a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php index fa0cd6ac2..fe2b3bf7c 100644 --- a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php @@ -35,7 +35,11 @@ class GitLabDriverTest extends TestCase $this->config->merge(array( 'config' => array( 'home' => $this->home, - 'gitlab-domains' => array('mycompany.com/gitlab', 'gitlab.com'), + 'gitlab-domains' => array( + 'mycompany.com/gitlab', + 'othercompany.com/nested/gitlab', + 'gitlab.com', + ), ), )); @@ -285,6 +289,10 @@ JSON; array('http://example.com/foo/bar', false), array('http://mycompany.com/gitlab/mygroup/myproject', true), array('https://mycompany.com/gitlab/mygroup/myproject', extension_loaded('openssl')), + array('http://othercompany.com/nested/gitlab/mygroup/myproject', true), + array('https://othercompany.com/nested/gitlab/mygroup/myproject', extension_loaded('openssl')), + array('http://gitlab.com/mygroup/mysubgroup/mysubsubgroup/myproject', true), + array('https://gitlab.com/mygroup/mysubgroup/mysubsubgroup/myproject', extension_loaded('openssl')), ); } @@ -298,14 +306,80 @@ JSON; "id": 17, "default_branch": "mymaster", "public": false, - "http_url_to_repo": "https://gitlab.com/mygroup/my-pro.ject", + "http_url_to_repo": "https://gitlab.com/gitlab/mygroup/my-pro.ject", "ssh_url_to_repo": "git@gitlab.com:mygroup/my-pro.ject.git", "last_activity_at": "2014-12-01T09:17:51.000+01:00", "name": "My Project", "name_with_namespace": "My Group / My Project", "path": "myproject", "path_with_namespace": "mygroup/my-pro.ject", - "web_url": "https://gitlab.com/mygroup/my-pro.ject" + "web_url": "https://gitlab.com/gitlab/mygroup/my-pro.ject" +} +JSON; + + $this->remoteFilesystem + ->getContents('mycompany.com/gitlab', $apiUrl, false) + ->willReturn($projectData) + ->shouldBeCalledTimes(1) + ; + + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->remoteFilesystem->reveal()); + $driver->initialize(); + + $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); + } + + public function testGitlabSubGroup() + { + $url = 'https://gitlab.com/mygroup/mysubgroup/myproject'; + $apiUrl = 'https://gitlab.com/api/v3/projects/mygroup%2Fmysubgroup%2Fmyproject'; + + $projectData = <<remoteFilesystem + ->getContents('gitlab.com', $apiUrl, false) + ->willReturn($projectData) + ->shouldBeCalledTimes(1) + ; + + $driver = new GitLabDriver(array('url' => $url), $this->io->reveal(), $this->config, $this->process->reveal(), $this->remoteFilesystem->reveal()); + $driver->initialize(); + + $this->assertEquals($apiUrl, $driver->getApiUrl(), 'API URL is derived from the repository URL'); + } + + public function testGitlabSubDirectorySubGroup() + { + $url = 'https://mycompany.com/gitlab/mygroup/mysubgroup/myproject'; + $apiUrl = 'https://mycompany.com/gitlab/api/v3/projects/mygroup%2Fmysubgroup%2Fmyproject'; + + $projectData = <<