1
0
Fork 0

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
Henrik Bjørnskov 2014-10-21 10:25:24 +02:00 committed by Jérôme Tamarelle
parent eadc167b12
commit 782c6303bc
5 changed files with 293 additions and 1 deletions

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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',

View File

@ -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;
}

View File

@ -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);
}
}