Browse Source

Merge pull request #2375 from gena01/github-ee

Fixing #728 Adding basic support for Github Enterprise
Jordi Boggiano 11 years ago
parent
commit
d3ff302194

+ 2 - 0
doc/04-schema.md

@@ -655,6 +655,8 @@ The following options are supported:
 * **prepend-autoloader:** Defaults to `true`. If false, the composer autoloader
   will not be prepended to existing autoloaders. This is sometimesrequired to fix
   interoperability issues with other autoloaders.
+* **github-domains:** Defaults to `["github.com"]`. A list of domains to use in
+  github mode. This is used for GitHub Enterprise setups.
 * **notify-on-install:** Defaults to `true`. Composer allows repositories to
   define a notification URL, so that they get notified whenever a package from
   that repository is installed. This option allows you to disable that behaviour.

+ 7 - 0
res/composer-schema.json

@@ -179,6 +179,13 @@
                 "prepend-autoloader": {
                     "type": "boolean",
                     "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
+                },
+                "github-domains": {
+                    "type": "array",
+                    "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",
+                    "items": {
+                        "type": "string"
+                    }
                 }
             }
         },

+ 12 - 0
src/Composer/Command/ConfigCommand.php

@@ -308,6 +308,18 @@ EOT
                     return $vals;
                 }
             ),
+            'github-domains' => array(
+                function ($vals) {
+                    if (!is_array($vals)) {
+                        return 'array expected';
+                    }
+
+                    return true;
+                },
+                function ($vals) {
+                    return $vals;
+                }
+            ),
         );
 
         foreach ($uniqueConfigValues as $name => $callbacks) {

+ 1 - 0
src/Composer/Config.php

@@ -36,6 +36,7 @@ class Config
         'cache-files-maxsize' => '300MiB',
         'discard-changes' => false,
         'prepend-autoloader' => true,
+        'github-domains' => array('github.com'),
     );
 
     public static $defaultRepositories = array(

+ 10 - 5
src/Composer/Downloader/GitDownloader.php

@@ -293,7 +293,7 @@ class GitDownloader extends VcsDownloader
         }
 
         // public github, autoswitch protocols
-        if (preg_match('{^(?:https?|git)(://github.com/.*)}', $url, $match)) {
+        if (preg_match('{^(?:https?|git)(://'.$this->getGitHubDomainsRegex().'/.*)}', $url, $match)) {
             $protocols = $this->config->get('github-protocols');
             if (!is_array($protocols)) {
                 throw new \RuntimeException('Config value "github-protocols" must be an array, got '.gettype($protocols));
@@ -317,7 +317,7 @@ class GitDownloader extends VcsDownloader
         $command = call_user_func($commandCallable, $url);
         if (0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
             // private github repository without git access, try https with auth
-            if (preg_match('{^git@(github.com):(.+?)\.git$}i', $url, $match)) {
+            if (preg_match('{^git@'.$this->getGitHubDomainsRegex().':(.+?)\.git$}i', $url, $match)) {
                 if (!$this->io->hasAuthentication($match[1])) {
                     $gitHubUtil = new GitHub($this->io, $this->config, $this->process);
                     $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';
@@ -368,6 +368,11 @@ class GitDownloader extends VcsDownloader
         }
     }
 
+    protected function getGitHubDomainsRegex()
+    {
+        return '('.implode('|', array_map('preg_quote', $this->config->get('github-domains'))).')';
+    }
+
     protected function throwException($message, $url)
     {
         if (0 !== $this->process->execute('git --version', $ignoredOutput)) {
@@ -385,11 +390,11 @@ class GitDownloader extends VcsDownloader
     protected function setPushUrl(PackageInterface $package, $path)
     {
         // set push url for github projects
-        if (preg_match('{^(?:https?|git)://github.com/([^/]+)/([^/]+?)(?:\.git)?$}', $package->getSourceUrl(), $match)) {
+        if (preg_match('{^(?:https?|git)://'.$this->getGitHubDomainsRegex().'/([^/]+)/([^/]+?)(?:\.git)?$}', $package->getSourceUrl(), $match)) {
             $protocols = $this->config->get('github-protocols');
-            $pushUrl = 'git@github.com:'.$match[1].'/'.$match[2].'.git';
+            $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git';
             if ($protocols[0] !== 'git') {
-                $pushUrl = 'https://github.com/'.$match[1].'/'.$match[2].'.git';
+                $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
             }
             $cmd = sprintf('git remote set-url --push origin %s', escapeshellarg($pushUrl));
             $this->process->execute($cmd, $ignoredOutput, $path);

+ 2 - 1
src/Composer/Repository/Vcs/GitBitbucketDriver.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Repository\Vcs;
 
+use Composer\Config;
 use Composer\Json\JsonFile;
 use Composer\IO\IOInterface;
 
@@ -140,7 +141,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
     /**
      * {@inheritDoc}
      */
-    public static function supports(IOInterface $io, $url, $deep = false)
+    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
         if (!preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
             return false;

+ 2 - 1
src/Composer/Repository/Vcs/GitDriver.php

@@ -18,6 +18,7 @@ use Composer\Util\Filesystem;
 use Composer\Util\Git as GitUtil;
 use Composer\IO\IOInterface;
 use Composer\Cache;
+use Composer\Config;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
@@ -211,7 +212,7 @@ class GitDriver extends VcsDriver
     /**
      * {@inheritDoc}
      */
-    public static function supports(IOInterface $io, $url, $deep = false)
+    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
         if (preg_match('#(^git://|\.git$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) {
             return true;

+ 37 - 16
src/Composer/Repository/Vcs/GitHubDriver.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Repository\Vcs;
 
+use Composer\Config;
 use Composer\Downloader\TransportException;
 use Composer\Json\JsonFile;
 use Composer\Cache;
@@ -45,10 +46,10 @@ class GitHubDriver extends VcsDriver
      */
     public function initialize()
     {
-        preg_match('#^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $this->url, $match);
-        $this->owner = $match[1];
-        $this->repository = $match[2];
-        $this->originUrl = 'github.com';
+        preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git)?$#', $this->url, $match);
+        $this->owner = $match[3];
+        $this->repository = $match[4];
+        $this->originUrl = isset($match[1]) ? $match[1] : $match[2];
         $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);
 
         $this->fetchRootIdentifier();
@@ -75,7 +76,21 @@ class GitHubDriver extends VcsDriver
             return $this->gitDriver->getUrl();
         }
 
-        return 'https://github.com/'.$this->owner.'/'.$this->repository.'.git';
+        return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function getApiUrl()
+    {
+        if ('github.com' === $this->originUrl) {
+            $apiUrl = 'api.github.com';
+        } else {
+            $apiUrl = $this->originUrl . '/api/v3';
+        }
+
+        return 'https://' . $apiUrl;
     }
 
     /**
@@ -105,7 +120,8 @@ class GitHubDriver extends VcsDriver
         if ($this->gitDriver) {
             return $this->gitDriver->getDist($identifier);
         }
-        $url = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier;
+
+        $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier;
 
         return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '');
     }
@@ -127,7 +143,7 @@ class GitHubDriver extends VcsDriver
             $notFoundRetries = 2;
             while ($notFoundRetries) {
                 try {
-                    $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/contents/composer.json?ref='.urlencode($identifier);
+                    $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/composer.json?ref='.urlencode($identifier);
                     $composer = JsonFile::parseJson($this->getContents($resource));
                     if (empty($composer['content']) || $composer['encoding'] !== 'base64' || !($composer = base64_decode($composer['content']))) {
                         throw new \RuntimeException('Could not retrieve composer.json from '.$resource);
@@ -149,16 +165,16 @@ class GitHubDriver extends VcsDriver
                 $composer = JsonFile::parseJson($composer, $resource);
 
                 if (!isset($composer['time'])) {
-                    $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
+                    $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
                     $commit = JsonFile::parseJson($this->getContents($resource), $resource);
                     $composer['time'] = $commit['commit']['committer']['date'];
                 }
                 if (!isset($composer['support']['source'])) {
                     $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier;
-                    $composer['support']['source'] = sprintf('https://github.com/%s/%s/tree/%s', $this->owner, $this->repository, $label);
+                    $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label);
                 }
                 if (!isset($composer['support']['issues']) && $this->hasIssues) {
-                    $composer['support']['issues'] = sprintf('https://github.com/%s/%s/issues', $this->owner, $this->repository);
+                    $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository);
                 }
             }
 
@@ -181,7 +197,7 @@ class GitHubDriver extends VcsDriver
             return $this->gitDriver->getTags();
         }
         if (null === $this->tags) {
-            $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags';
+            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags';
             $tagsData = JsonFile::parseJson($this->getContents($resource), $resource);
             $this->tags = array();
             foreach ($tagsData as $tag) {
@@ -201,7 +217,7 @@ class GitHubDriver extends VcsDriver
             return $this->gitDriver->getBranches();
         }
         if (null === $this->branches) {
-            $resource = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads';
+            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads';
             $branchData = JsonFile::parseJson($this->getContents($resource), $resource);
             $this->branches = array();
             foreach ($branchData as $branch) {
@@ -216,9 +232,14 @@ class GitHubDriver extends VcsDriver
     /**
      * {@inheritDoc}
      */
-    public static function supports(IOInterface $io, $url, $deep = false)
+    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
-        if (!preg_match('#^((?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $url)) {
+        if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git)?$#', $url, $matches)) {
+            return false;
+        }
+
+        $originUrl = isset($matches[2]) ? $matches[2] : $matches[3];
+        if (!in_array($originUrl, $config->get('github-domains'))) {
             return false;
         }
 
@@ -240,7 +261,7 @@ class GitHubDriver extends VcsDriver
      */
     protected function generateSshUrl()
     {
-        return 'git@github.com:'.$this->owner.'/'.$this->repository.'.git';
+        return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git';
     }
 
     /**
@@ -357,7 +378,7 @@ class GitHubDriver extends VcsDriver
      */
     protected function fetchRootIdentifier()
     {
-        $repoDataUrl = 'https://api.github.com/repos/'.$this->owner.'/'.$this->repository;
+        $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository;
 
         $repoData = JsonFile::parseJson($this->getContents($repoDataUrl, true), $repoDataUrl);
         if (null === $repoData && null !== $this->gitDriver) {

+ 2 - 1
src/Composer/Repository/Vcs/HgBitbucketDriver.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Repository\Vcs;
 
+use Composer\Config;
 use Composer\Json\JsonFile;
 use Composer\IO\IOInterface;
 
@@ -150,7 +151,7 @@ class HgBitbucketDriver extends VcsDriver
     /**
      * {@inheritDoc}
      */
-    public static function supports(IOInterface $io, $url, $deep = false)
+    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
         if (!preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) {
             return false;

+ 2 - 1
src/Composer/Repository/Vcs/HgDriver.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Repository\Vcs;
 
+use Composer\Config;
 use Composer\Json\JsonFile;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\Filesystem;
@@ -189,7 +190,7 @@ class HgDriver extends VcsDriver
     /**
      * {@inheritDoc}
      */
-    public static function supports(IOInterface $io, $url, $deep = false)
+    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
         if (preg_match('#(^(?:https?|ssh)://(?:[^@]@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) {
             return true;

+ 2 - 1
src/Composer/Repository/Vcs/PerforceDriver.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Repository\Vcs;
 
+use Composer\Config;
 use Composer\IO\IOInterface;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\Perforce;
@@ -158,7 +159,7 @@ class PerforceDriver extends VcsDriver
     /**
      * {@inheritDoc}
      */
-    public static function supports(IOInterface $io, $url, $deep = false)
+    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
         if ($deep || preg_match('#\b(perforce|p4)\b#i', $url)) {
             return Perforce::checkServerExists($url, new ProcessExecutor);

+ 2 - 1
src/Composer/Repository/Vcs/SvnDriver.php

@@ -13,6 +13,7 @@
 namespace Composer\Repository\Vcs;
 
 use Composer\Cache;
+use Composer\Config;
 use Composer\Json\JsonFile;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\Filesystem;
@@ -241,7 +242,7 @@ class SvnDriver extends VcsDriver
     /**
      * {@inheritDoc}
      */
-    public static function supports(IOInterface $io, $url, $deep = false)
+    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
         $url = self::normalizeUrl($url);
         if (preg_match('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) {

+ 6 - 4
src/Composer/Repository/Vcs/VcsDriverInterface.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Repository\Vcs;
 
+use Composer\Config;
 use Composer\IO\IOInterface;
 
 /**
@@ -90,10 +91,11 @@ interface VcsDriverInterface
     /**
      * Checks if this driver can handle a given url
      *
-     * @param  IOInterface $io   IO instance
-     * @param  string      $url
-     * @param  bool        $deep unless true, only shallow checks (url matching typically) should be done
+     * @param  IOInterface $io     IO instance
+     * @param  Config      $config current $config
+     * @param  string      $url    URL to validate/check
+     * @param  bool        $deep   unless true, only shallow checks (url matching typically) should be done
      * @return bool
      */
-    public static function supports(IOInterface $io, $url, $deep = false);
+    public static function supports(IOInterface $io, Config $config, $url, $deep = false);
 }

+ 2 - 2
src/Composer/Repository/VcsRepository.php

@@ -80,7 +80,7 @@ class VcsRepository extends ArrayRepository
         }
 
         foreach ($this->drivers as $driver) {
-            if ($driver::supports($this->io, $this->url)) {
+            if ($driver::supports($this->io, $this->config, $this->url)) {
                 $driver = new $driver($this->repoConfig, $this->io, $this->config);
                 $driver->initialize();
 
@@ -89,7 +89,7 @@ class VcsRepository extends ArrayRepository
         }
 
         foreach ($this->drivers as $driver) {
-            if ($driver::supports($this->io, $this->url, true)) {
+            if ($driver::supports($this->io, $this->config, $this->url, true)) {
                 $driver = new $driver($this->repoConfig, $this->io, $this->config);
                 $driver->initialize();
 

+ 4 - 2
src/Composer/Util/GitHub.php

@@ -51,7 +51,7 @@ class GitHub
      */
     public function authorizeOAuth($originUrl)
     {
-        if ('github.com' !== $originUrl) {
+        if (!in_array($originUrl, $this->config->get('github-domains'))) {
             return false;
         }
 
@@ -78,6 +78,8 @@ class GitHub
     {
         $attemptCounter = 0;
 
+        $apiUrl = ('github.com' === $originUrl) ? 'api.github.com' : $originUrl . '/api/v3';
+
         if ($message) {
             $this->io->write($message);
         }
@@ -95,7 +97,7 @@ class GitHub
                     $appName .= ' on ' . trim($output);
                 }
 
-                $contents = JsonFile::parseJson($this->remoteFilesystem->getContents($originUrl, 'https://api.github.com/authorizations', false, array(
+                $contents = JsonFile::parseJson($this->remoteFilesystem->getContents($originUrl, 'https://'. $apiUrl . '/authorizations', false, array(
                     'http' => array(
                         'method' => 'POST',
                         'follow_location' => false,

+ 1 - 4
tests/Composer/Test/Downloader/GitDownloaderTest.php

@@ -24,10 +24,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
         $executor = $executor ?: $this->getMock('Composer\Util\ProcessExecutor');
         $filesystem = $filesystem ?: $this->getMock('Composer\Util\Filesystem');
         if (!$config) {
-            $config = $this->getMock('Composer\Config');
-            $config->expects($this->any())
-                ->method('has')
-                ->will($this->returnValue(false));
+            $config = new Config();
         }
 
         return new GitDownloader($io, $config, $executor, $filesystem);

+ 4 - 5
tests/Composer/Test/Repository/Vcs/PerforceDriverTest.php

@@ -104,7 +104,6 @@ class PerforceDriverTest extends \PHPUnit_Framework_TestCase
 
     public function testHasComposerFile()
     {
-        $this->setUp();
         $repoConfig = array(
             'url'    => 'TEST_PERFORCE_URL',
             'depot'  => 'TEST_DEPOT_CONFIG',
@@ -131,17 +130,17 @@ class PerforceDriverTest extends \PHPUnit_Framework_TestCase
         $result = $driver->hasComposerFile($identifier);
         $this->assertTrue($result);
     }
-    
+
     /**
      * Test that supports() simply return false.
-     * 
+     *
      * @covers \Composer\Repository\Vcs\PerforceDriver::supports
-     * 
+     *
      * @return void
      */
     public function testSupportsReturnsFalseNoDeepCheck()
     {
         $this->expectOutputString('');
-        $this->assertFalse(PerforceDriver::supports($this->io, 'existing.url'));
+        $this->assertFalse(PerforceDriver::supports($this->io, $this->config, 'existing.url'));
     }
 }

+ 3 - 5
tests/Composer/Test/Repository/Vcs/SvnDriverTest.php

@@ -80,10 +80,8 @@ class SvnDriverTest extends \PHPUnit_Framework_TestCase
      */
     public function testSupport($url, $assertion)
     {
-        if ($assertion === true) {
-            $this->assertTrue(SvnDriver::supports($this->getMock('Composer\IO\IOInterface'), $url));
-        } else {
-            $this->assertFalse(SvnDriver::supports($this->getMock('Composer\IO\IOInterface'), $url));
-        }
+        $config = new Config();
+        $result = SvnDriver::supports($this->getMock('Composer\IO\IOInterface'), $config, $url);
+        $this->assertEquals($assertion, $result);
     }
 }