Browse Source

support for gitlab subgroups, closes #6349

Rob Bast 8 years ago
parent
commit
e2eb8f2201

+ 43 - 12
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 = '#^(?:(?P<scheme>https?)://(?P<domain>.+?)/|git@(?P<domain2>[^:]+):)(?P<owner>[^/]+)/(?P<repo>[^/]+?)(?:\.git|/)?$#';
+    const URL_REGEX = '#^(?:(?P<scheme>https?)://(?P<domain>.+?)/|git@(?P<domain2>[^:]+):)(?P<parts>.+)/(?P<repo>[^/]+?)(?:\.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('<warning>Failed to download ' . $this->owner . '/' . $this->repository . ':' . $e->getMessage() . '</warning>');
+                    $this->io->writeError('<warning>Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . '</warning>');
                     $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata (<info>'.$this->url.'</info>)');
 
                     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;
+    }
 }

+ 77 - 3
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 = <<<JSON
+{
+    "id": 17,
+    "default_branch": "mymaster",
+    "public": false,
+    "http_url_to_repo": "https://gitlab.com/mygroup/mysubgroup/my-pro.ject",
+    "ssh_url_to_repo": "git@gitlab.com:mygroup/mysubgroup/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/mysubgroup/my-pro.ject",
+    "web_url": "https://gitlab.com/mygroup/mysubgroup/my-pro.ject"
+}
+JSON;
+
+        $this->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 = <<<JSON
+{
+    "id": 17,
+    "default_branch": "mymaster",
+    "public": false,
+    "http_url_to_repo": "https://mycompany.com/gitlab/mygroup/mysubgroup/my-pro.ject",
+    "ssh_url_to_repo": "git@mycompany.com:mygroup/mysubgroup/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/mysubgroup/my-pro.ject",
+    "web_url": "https://mycompany.com/gitlab/mygroup/mysubgroup/my-pro.ject"
 }
 JSON;