Initial GitLab Driver
This is a proof of concept, and mostly done to gather feedback on the structure of the driver and to see if this is something that Composer should include in core. Various review changes based on Stof comments. * Remove cleanup() as it is implemented by the abstract class. * Remove wrong comment in getReferences * Implement getSource (as GitHubDriver does) * Finish phpDocs for methods.pull/3765/head
parent
eadc167b12
commit
782c6303bc
|
@ -69,6 +69,12 @@ abstract class BaseIO implements IOInterface
|
|||
}
|
||||
}
|
||||
|
||||
if ($tokens = $config->get('gitlab-tokens')) {
|
||||
foreach ($tokens as $domain => $token) {
|
||||
$this->setAuthentication($domain, $token, 'gitlab-private-token');
|
||||
}
|
||||
}
|
||||
|
||||
// reload http basic credentials from config if available
|
||||
if ($creds = $config->get('http-basic')) {
|
||||
foreach ($creds as $domain => $cred) {
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
|
||||
namespace Composer\Repository\Vcs;
|
||||
|
||||
use Composer\Config;
|
||||
use Composer\Cache;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Downloader\TransportException;
|
||||
|
||||
/**
|
||||
* Simplistic driver for GitLab currently only supports the api, not local checkouts.
|
||||
*/
|
||||
class GitLabDriver extends VcsDriver
|
||||
{
|
||||
protected $owner;
|
||||
protected $repository;
|
||||
protected $originUrl;
|
||||
protected $cache;
|
||||
protected $rootIdentifier;
|
||||
protected $infoCache = array();
|
||||
|
||||
/**
|
||||
* Extracts information from the repository url.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match);
|
||||
|
||||
$this->owner = $match[3];
|
||||
$this->repository = $match[4];
|
||||
$this->originUrl = !empty($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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the composer.json file from the project by a identifier.
|
||||
*
|
||||
* if specific keys arent present it will try and infer them by default values.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getComposerInformation($identifier)
|
||||
{
|
||||
if (isset($this->infoCache[$identifier])) {
|
||||
return $this->infoCache[$identifier];
|
||||
}
|
||||
|
||||
if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier)) {
|
||||
return $this->infoCache[$identifier] = JsonFile::parseJson($res, $res);
|
||||
}
|
||||
|
||||
$composer = $this->fetchComposerFile($identifier);
|
||||
|
||||
if (empty($composer['content']) || $composer['encoding'] !== 'base64' || !($composer = base64_decode($composer['content']))) {
|
||||
throw new \RuntimeException('Could not retrieve composer.json from GitLab#'.$identifier);
|
||||
}
|
||||
|
||||
$composer = JsonFile::parseJson($composer);
|
||||
|
||||
if (!isset($composer['time'])) {
|
||||
$resource = $this->getApiUrl().'/repository/commits/'.urlencode($identifier);
|
||||
$commit = JsonFile::parseJson($this->getContents($resource), $resource);
|
||||
|
||||
$composer['time'] = $commit['committed_date'];
|
||||
}
|
||||
|
||||
if (preg_match('{[a-f0-9]{40}}i', $identifier)) {
|
||||
$this->cache->write($identifier, json_encode($composer));
|
||||
}
|
||||
|
||||
$this->infoCache[$identifier] = $composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hasComposerFile($identifier)
|
||||
{
|
||||
try {
|
||||
$this->fetchComposerFile($identifier);
|
||||
|
||||
return true;
|
||||
} catch (TransportException $e) {
|
||||
if ($e->getCode() !== 404) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRepositoryUrl()
|
||||
{
|
||||
return 'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return $this->getRepositoryUrl() . '.git';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getDist($identifier)
|
||||
{
|
||||
$url = $this->getApiUrl().'/repository/archive?sha='.$identifier;
|
||||
|
||||
return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getSource($identifier)
|
||||
{
|
||||
return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootIdentifier()
|
||||
{
|
||||
return $this->rootIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getBranches()
|
||||
{
|
||||
return $this->getReferences('branches');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getTags()
|
||||
{
|
||||
return $this->getReferences('tags');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches composer.json file from the repository through api
|
||||
*
|
||||
* @param string $identifier
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchComposerFile($identifier)
|
||||
{
|
||||
$resource = $this->getApiUrl() . '/repository/files?file_path=composer.json&ref='.$identifier;
|
||||
|
||||
return JsonFile::parseJson($this->getContents($resource), $resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Root url
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getApiUrl()
|
||||
{
|
||||
// this needs to be https, but our install is running http
|
||||
return 'http://'.$this->originUrl.'/api/v3/projects/'.$this->owner.'%2F'.$this->repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return string[] where keys are named references like tags or branches and the value a sha
|
||||
*/
|
||||
protected function getReferences($type)
|
||||
{
|
||||
$resource = $this->getApiUrl().'/repository/'.$type;
|
||||
|
||||
$data = JsonFile::parseJson($this->getContents($resource), $resource);
|
||||
|
||||
$references = array();
|
||||
|
||||
foreach ($data as $datum) {
|
||||
$references[$datum['name']] = $datum['commit']['id'];
|
||||
}
|
||||
|
||||
return $references;
|
||||
}
|
||||
|
||||
protected function fetchRootIdentifier()
|
||||
{
|
||||
// we need to fetch the default branch from the api
|
||||
$resource = $this->getApiUrl();
|
||||
|
||||
$project = JsonFile::parseJson($this->getContents($resource), $resource);
|
||||
|
||||
$this->rootIdentifier = $project['default_branch'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the config `gitlab-domains` to see if the driver supports the url for the
|
||||
* repository given.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function supports(IOInterface $io, Config $config, $url, $deep = false)
|
||||
{
|
||||
if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$originUrl = empty($matches[2]) ? $matches[3] : $matches[2];
|
||||
|
||||
if (!in_array($originUrl, (array) $config->get('gitlab-domains'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!extension_loaded('openssl')) {
|
||||
if ($io->isVerbose()) {
|
||||
$io->write('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ class VcsRepository extends ArrayRepository
|
|||
{
|
||||
$this->drivers = $drivers ?: array(
|
||||
'github' => 'Composer\Repository\Vcs\GitHubDriver',
|
||||
'gitlab' => 'Composer\Repository\Vcs\GitLabDriver',
|
||||
'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver',
|
||||
'git' => 'Composer\Repository\Vcs\GitDriver',
|
||||
'hg-bitbucket' => 'Composer\Repository\Vcs\HgBitbucketDriver',
|
||||
|
|
|
@ -148,10 +148,17 @@ class RemoteFilesystem
|
|||
if ($this->io->isDebug()) {
|
||||
$this->io->writeError((substr($fileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $fileUrl);
|
||||
}
|
||||
|
||||
if (isset($options['github-token'])) {
|
||||
$fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token'];
|
||||
unset($options['github-token']);
|
||||
}
|
||||
|
||||
if (isset($options['gitlab-token'])) {
|
||||
$fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'private_token='.$options['gitlab-token'];
|
||||
unset($options['gitlab-token']);
|
||||
}
|
||||
|
||||
if (isset($options['http'])) {
|
||||
$options['http']['ignore_errors'] = true;
|
||||
}
|
||||
|
@ -410,7 +417,9 @@ class RemoteFilesystem
|
|||
$auth = $this->io->getAuthentication($originUrl);
|
||||
if ('github.com' === $originUrl && 'x-oauth-basic' === $auth['password']) {
|
||||
$options['github-token'] = $auth['username'];
|
||||
} else {
|
||||
} elseif ($auth['password'] === 'gitlab-private-token') {
|
||||
$options['gitlab-token'] = $auth['username'];
|
||||
}else {
|
||||
$authStr = base64_encode($auth['username'] . ':' . $auth['password']);
|
||||
$headers[] = 'Authorization: Basic '.$authStr;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Test\Repository\Vcs;
|
||||
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\Repository\Vcs\GitLabDriver;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Config;
|
||||
|
||||
class GitLabDriverTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
$this->config = new Config;
|
||||
|
||||
$this->io = $this->getMock('Composer\IO\IOInterface');
|
||||
|
||||
$this->process = $this->getMock('Composer\Util\ProcessExecutor');
|
||||
|
||||
}
|
||||
|
||||
public function testInterfaceIsComplete()
|
||||
{
|
||||
$remoteFilesystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem')
|
||||
->setConstructorArgs(array($this->io))
|
||||
->getMock();
|
||||
|
||||
$driver = new GitLabDriver(array('url' => 'http://google.com'), $this->io, $this->config, $this->process, $remoteFilesystem);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue