From a4af559ca8d249d629afc7d4111080bbedad0e75 Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Tue, 27 Dec 2016 00:39:02 +0100 Subject: [PATCH 01/12] Store access-token for re-use Store the Bitbucket access-token (and the expiration time) so it can be re-used within the time it is valid. The Bitbucket::requestToken and Bitbucket::getToken now only return the access-token and not all other parameters it receives from the Bitbucket API. --- src/Composer/Config.php | 2 + src/Composer/Util/Bitbucket.php | 84 ++++++++++++--- src/Composer/Util/Git.php | 10 +- src/Composer/Util/RemoteFilesystem.php | 6 +- tests/Composer/Test/Util/BitbucketTest.php | 116 ++++++++++----------- 5 files changed, 134 insertions(+), 84 deletions(-) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 2888b8d29..fcf00c2f2 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -79,7 +79,9 @@ class Config private $config; private $baseDir; private $repositories; + /** @var ConfigSourceInterface */ private $configSource; + /** @var ConfigSourceInterface */ private $authConfigSource; private $useEnvironment; private $warnedHosts = array(); diff --git a/src/Composer/Util/Bitbucket.php b/src/Composer/Util/Bitbucket.php index f8b460103..152fe5fa0 100644 --- a/src/Composer/Util/Bitbucket.php +++ b/src/Composer/Util/Bitbucket.php @@ -27,6 +27,7 @@ class Bitbucket private $process; private $remoteFilesystem; private $token = array(); + private $time; const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token'; @@ -37,21 +38,26 @@ class Bitbucket * @param Config $config The composer configuration * @param ProcessExecutor $process Process instance, injectable for mocking * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking + * @param int $time Timestamp, injectable for mocking */ - public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null) + public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null, $time = null) { $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor; $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); + $this->time = $time; } /** - * @return array + * @return string */ public function getToken() { - return $this->token; + if (! isset($this->token['access_token'])) { + return ''; + } + return $this->token['access_token']; } /** @@ -109,6 +115,8 @@ class Bitbucket throw $e; } + + return true; } /** @@ -151,16 +159,13 @@ class Bitbucket $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); - $this->requestAccessToken($originUrl); + if (! $this->requestAccessToken($originUrl)) { + return false; + } // store value in user config - $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl); + $this->storeInAuthConfig($originUrl, $consumerKey, $consumerSecret); - $consumer = array( - "consumer-key" => $consumerKey, - "consumer-secret" => $consumerSecret, - ); - $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer); // Remove conflicting basic auth credentials (if available) $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl); @@ -175,17 +180,66 @@ class Bitbucket * @param string $originUrl * @param string $consumerKey * @param string $consumerSecret - * @return array + * @return string */ public function requestToken($originUrl, $consumerKey, $consumerSecret) { - if (!empty($this->token)) { - return $this->token; + if (!empty($this->token) || $this->getTokenFromConfig($originUrl)) { + return $this->token['access_token']; } $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); - $this->requestAccessToken($originUrl); + if (! $this->requestAccessToken($originUrl)) { + return ''; + } - return $this->token; + $this->storeInAuthConfig($originUrl, $consumerKey, $consumerSecret); + + return $this->token['access_token']; + } + + /** + * Store the new/updated credentials to the configuration + * @param string $originUrl + * @param string $consumerKey + * @param string $consumerSecret + */ + private function storeInAuthConfig($originUrl, $consumerKey, $consumerSecret) + { + $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl); + + $time = null === $this->time ? time() : $this->time; + $consumer = array( + "consumer-key" => $consumerKey, + "consumer-secret" => $consumerSecret, + "access-token" => $this->token['access_token'], + "access-token-expiration" => $time + $this->token['expires_in'] + ); + + $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer); + } + + /** + * @param string $originUrl + * @return bool + */ + private function getTokenFromConfig($originUrl) + { + $authConfig = $this->config->get('bitbucket-oauth'); + + if (empty($authConfig) || + ! isset($authConfig[$originUrl]) || + ! isset($authConfig[$originUrl]['access-token']) || + ! isset($authConfig[$originUrl]['access-token-expiration']) || + time() > $authConfig[$originUrl]['access-token-expiration'] + ) { + return false; + } + + $this->token = array( + 'access_token' => $authConfig[$originUrl]['access-token'] + ); + + return true; } } diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index c7e892244..90722bc5a 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -122,17 +122,17 @@ class Git if (!$bitbucketUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { $bitbucketUtil->authorizeOAuthInteractively($match[1], $message); - $token = $bitbucketUtil->getToken(); - $this->io->setAuthentication($match[1], 'x-token-auth', $token['access_token']); + $access_token = $bitbucketUtil->getToken(); + $this->io->setAuthentication($match[1], 'x-token-auth', $access_token); } } else { //We're authenticating with a locally stored consumer. $auth = $this->io->getAuthentication($match[1]); //We already have an access_token from a previous request. if ($auth['username'] !== 'x-token-auth') { - $token = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']); - if (!empty($token)) { - $this->io->setAuthentication($match[1], 'x-token-auth', $token['access_token']); + $access_token = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']); + if (! empty($access_token)) { + $this->io->setAuthentication($match[1], 'x-token-auth', $access_token); } } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 5b137686d..4437f79ea 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -601,9 +601,9 @@ class RemoteFilesystem $auth = $this->io->getAuthentication($this->originUrl); if ($auth['username'] !== 'x-token-auth') { $bitbucketUtil = new Bitbucket($this->io, $this->config); - $token = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']); - if (! empty($token)) { - $this->io->setAuthentication($this->originUrl, 'x-token-auth', $token['access_token']); + $access_token = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']); + if (! empty($access_token)) { + $this->io->setAuthentication($this->originUrl, 'x-token-auth', $access_token); $askForOAuthToken = false; } } else { diff --git a/tests/Composer/Test/Util/BitbucketTest.php b/tests/Composer/Test/Util/BitbucketTest.php index 45b4ce752..e9ca9e269 100644 --- a/tests/Composer/Test/Util/BitbucketTest.php +++ b/tests/Composer/Test/Util/BitbucketTest.php @@ -35,6 +35,8 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase private $config; /** @type Bitbucket */ private $bitbucket; + /** @var int */ + private $time; protected function setUp() { @@ -52,7 +54,9 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase $this->config = $this->getMock('Composer\Config'); - $this->bitbucket = new Bitbucket($this->io, $this->config, null, $this->rfs); + $this->time = time(); + + $this->bitbucket = new Bitbucket($this->io, $this->config, null, $this->rfs, $this->time); } public function testRequestAccessTokenWithValidOAuthConsumer() @@ -82,14 +86,15 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase ) ); + $this->config->expects($this->once()) + ->method('get') + ->with('bitbucket-oauth') + ->willReturn(null); + + $this->setExpectationsForStoringAccessToken(); + $this->assertEquals( - array( - 'access_token' => $this->token, - 'scopes' => 'repository', - 'expires_in' => 3600, - 'refresh_token' => 'refreshtoken', - 'token_type' => 'bearer' - ), + $this->token, $this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret) ); } @@ -133,7 +138,12 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase ) ); - $this->assertEquals(array(), $this->bitbucket->requestToken($this->origin, $this->username, $this->password)); + $this->config->expects($this->once()) + ->method('get') + ->with('bitbucket-oauth') + ->willReturn(null); + + $this->assertEquals('', $this->bitbucket->requestToken($this->origin, $this->username, $this->password)); } public function testUsernamePasswordAuthenticationFlow() @@ -161,67 +171,51 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase $this->isFalse(), $this->anything() ) - ->willReturn(sprintf('{}', $this->token)) - ; - - $authJson = $this->getAuthJsonMock(); - $this->config - ->expects($this->exactly(3)) - ->method('getAuthConfigSource') - ->willReturn($authJson) - ; - $this->config - ->expects($this->once()) - ->method('getConfigSource') - ->willReturn($this->getConfJsonMock()) - ; - - $authJson->expects($this->once()) - ->method('addConfigSetting') - ->with( - 'bitbucket-oauth.'.$this->origin, - array( - 'consumer-key' => $this->consumer_key, - 'consumer-secret' => $this->consumer_secret + ->willReturn( + sprintf( + '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refresh_token", "token_type": "bearer"}', + $this->token ) - ); + ) + ; - $authJson->expects($this->once()) - ->method('removeConfigSetting') - ->with('http-basic.'.$this->origin); + $this->setExpectationsForStoringAccessToken(true); $this->assertTrue($this->bitbucket->authorizeOAuthInteractively($this->origin, $this->message)); } - private function getAuthJsonMock() + private function setExpectationsForStoringAccessToken($removeBasicAuth = false) { - $authjson = $this - ->getMockBuilder('Composer\Config\JsonConfigSource') - ->disableOriginalConstructor() - ->getMock() - ; - $authjson - ->expects($this->atLeastOnce()) - ->method('getName') - ->willReturn('auth.json') - ; + $configSourceMock = $this->getMock('Composer\Config\ConfigSourceInterface'); + $this->config->expects($this->once()) + ->method('getConfigSource') + ->willReturn($configSourceMock); - return $authjson; - } - - private function getConfJsonMock() - { - $confjson = $this - ->getMockBuilder('Composer\Config\JsonConfigSource') - ->disableOriginalConstructor() - ->getMock() - ; - $confjson - ->expects($this->atLeastOnce()) + $configSourceMock->expects($this->once()) ->method('removeConfigSetting') - ->with('bitbucket-oauth.'.$this->origin) - ; + ->with('bitbucket-oauth.' . $this->origin); - return $confjson; + $authConfigSourceMock = $this->getMock('Composer\Config\ConfigSourceInterface'); + $this->config->expects($this->atLeastOnce()) + ->method('getAuthConfigSource') + ->willReturn($authConfigSourceMock); + + $authConfigSourceMock->expects($this->once()) + ->method('addConfigSetting') + ->with( + 'bitbucket-oauth.' . $this->origin, + array( + "consumer-key" => $this->consumer_key, + "consumer-secret" => $this->consumer_secret, + "access-token" => $this->token, + "access-token-expiration" => $this->time + 3600 + ) + ); + + if ($removeBasicAuth) { + $authConfigSourceMock->expects($this->once()) + ->method('removeConfigSetting') + ->with('http-basic.' . $this->origin); + } } } From 512750a20e390a6a52f2c86a5f7fa6733f3217d4 Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Tue, 27 Dec 2016 12:48:54 +0100 Subject: [PATCH 02/12] Add more tests to cover the new functionality. --- tests/Composer/Test/Util/BitbucketTest.php | 71 ++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/Composer/Test/Util/BitbucketTest.php b/tests/Composer/Test/Util/BitbucketTest.php index e9ca9e269..62bae0633 100644 --- a/tests/Composer/Test/Util/BitbucketTest.php +++ b/tests/Composer/Test/Util/BitbucketTest.php @@ -99,6 +99,77 @@ class BitbucketTest extends \PHPUnit_Framework_TestCase ); } + public function testRequestAccessTokenWithValidOAuthConsumerAndValidStoredAccessToken() + { + $this->config->expects($this->once()) + ->method('get') + ->with('bitbucket-oauth') + ->willReturn( + array( + $this->origin => array( + 'access-token' => $this->token, + 'access-token-expiration' => $this->time + 1800, + 'consumer-key' => $this->consumer_key, + 'consumer-secret' => $this->consumer_secret + ) + ) + ); + + $this->assertEquals( + $this->token, + $this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret) + ); + } + + public function testRequestAccessTokenWithValidOAuthConsumerAndExpiredAccessToken() + { + $this->config->expects($this->once()) + ->method('get') + ->with('bitbucket-oauth') + ->willReturn( + array( + $this->origin => array( + 'access-token' => 'randomExpiredToken', + 'access-token-expiration' => $this->time - 400, + 'consumer-key' => $this->consumer_key, + 'consumer-secret' => $this->consumer_secret + ) + ) + ); + + $this->io->expects($this->once()) + ->method('setAuthentication') + ->with($this->origin, $this->consumer_key, $this->consumer_secret); + + $this->rfs->expects($this->once()) + ->method('getContents') + ->with( + $this->origin, + Bitbucket::OAUTH2_ACCESS_TOKEN_URL, + false, + array( + 'retry-auth-failure' => false, + 'http' => array( + 'method' => 'POST', + 'content' => 'grant_type=client_credentials', + ) + ) + ) + ->willReturn( + sprintf( + '{"access_token": "%s", "scopes": "repository", "expires_in": 3600, "refresh_token": "refreshtoken", "token_type": "bearer"}', + $this->token + ) + ); + + $this->setExpectationsForStoringAccessToken(); + + $this->assertEquals( + $this->token, + $this->bitbucket->requestToken($this->origin, $this->consumer_key, $this->consumer_secret) + ); + } + public function testRequestAccessTokenWithUsernameAndPassword() { $this->io->expects($this->once()) From b3b05949bbb5c07a03a19ec679f146dedcca1f18 Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Tue, 27 Dec 2016 16:29:39 +0100 Subject: [PATCH 03/12] Implement most desirable Authorization method. As per https://developer.atlassian.com/bitbucket/api/2/reference/meta/authentication#make-requests adding the OAuth access token in the Authorization header is desired above adding it to the URL. --- src/Composer/Util/RemoteFilesystem.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 4437f79ea..b6fd65a46 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -245,14 +245,6 @@ class RemoteFilesystem unset($options['gitlab-token']); } - if (isset($options['bitbucket-token'])) { - // skip using the token for BitBucket downloads as these are not working with auth - if (!$this->isPublicBitBucketDownload($origFileUrl)) { - $fileUrl .= (false === strpos($fileUrl,'?') ? '?' : '&') . 'access_token=' . $options['bitbucket-token']; - } - unset($options['bitbucket-token']); - } - if (isset($options['http'])) { $options['http']['ignore_errors'] = true; } @@ -730,7 +722,9 @@ class RemoteFilesystem } elseif ('bitbucket.org' === $originUrl && $this->fileUrl !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL && 'x-token-auth' === $auth['username'] ) { - $options['bitbucket-token'] = $auth['password']; + if (!$this->isPublicBitBucketDownload($this->fileUrl)) { + $headers[] = 'Authorization: Bearer ' . $auth['password']; + } } else { $authStr = base64_encode($auth['username'] . ':' . $auth['password']); $headers[] = 'Authorization: Basic '.$authStr; From d25c483231a7ad8854996e968cbb0a7b99b3341b Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Tue, 27 Dec 2016 22:16:32 +0100 Subject: [PATCH 04/12] Implement Bitbucket API version 2.0 (where applicable). --- .../Repository/Vcs/BitbucketDriver.php | 161 +++++++++++++++++- .../Repository/Vcs/GitBitbucketDriver.php | 66 +++---- .../Repository/Vcs/HgBitbucketDriver.php | 75 ++------ .../Repository/Vcs/GitBitbucketDriverTest.php | 161 +++++++++--------- 4 files changed, 280 insertions(+), 183 deletions(-) diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 8a778f7eb..51eee5358 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -18,6 +18,12 @@ abstract class BitbucketDriver extends VcsDriver protected $tags; protected $branches; protected $infoCache = array(); + protected $branchesUrl = ''; + protected $tagsUrl = ''; + protected $homeUrl = ''; + protected $website = ''; + protected $cloneHttpsUrl = ''; + protected $cloneSshUrl = ''; /** * @var VcsDriver @@ -44,6 +50,40 @@ abstract class BitbucketDriver extends VcsDriver ); } + /** + * {@inheritDoc} + */ + public function getUrl() + { + return $this->cloneHttpsUrl; + } + + /** + * @return array + */ + protected function getRepoData() + { + $resource = sprintf( + 'https://api.bitbucket.org/2.0/repositories/%s/%s?%s', + $this->owner, + $this->repository, + http_build_query( + array('fields' => '-project,-owner') + ) + ); + + $repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); + $this->parseCloneUrls($repoData['links']['clone']); + + $this->hasIssues = !empty($repoData['has_issues']); + $this->branchesUrl = $repoData['links']['branches']['href']; + $this->tagsUrl = $repoData['links']['tags']['href']; + $this->homeUrl = $repoData['links']['html']['href']; + $this->website = $repoData['website']; + + return $repoData; + } + /** * {@inheritDoc} */ @@ -102,6 +142,9 @@ abstract class BitbucketDriver extends VcsDriver $this->repository ); } + if (!isset($composer['homepage'])) { + $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website; + } $this->infoCache[$identifier] = $composer; @@ -122,8 +165,13 @@ abstract class BitbucketDriver extends VcsDriver return $this->fallbackDriver->getFileContent($file, $identifier); } - $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/' - . $this->owner . '/' . $this->repository . '/src/' . $identifier . '/' . $file; + $resource = sprintf( + 'https://api.bitbucket.org/1.0/repositories/%s/%s/src/%s/%s', + $this->owner, + $this->repository, + $identifier, + $file + ); $fileData = JsonFile::parseJson($this->getContents($resource), $resource); if (!is_array($fileData) || ! array_key_exists('data', $fileData)) { return null; @@ -148,6 +196,89 @@ abstract class BitbucketDriver extends VcsDriver return new \DateTime($changeset['timestamp']); } + /** + * {@inheritDoc} + */ + public function getDist($identifier) + { + $url = sprintf( + 'https://bitbucket.org/%s/%s/get/%s.zip', + $this->owner, + $this->repository, + $identifier + ); + + return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); + } + + /** + * {@inheritDoc} + */ + public function getTags() + { + if (null === $this->tags) { + $this->tags = array(); + $resource = sprintf( + '%s?%s', + $this->tagsUrl, + http_build_query( + array( + 'pagelen' => 100, + 'fields' => 'values.name,values.target.hash,next' + ) + ) + ); + $hasNext = true; + while ($hasNext) { + $tagsData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + foreach ($tagsData['values'] as $data) { + $this->tags[$data['name']] = $data['target']['hash']; + } + if (empty($tagsData['next'])) { + $hasNext = false; + } else { + $resource = $tagsData['next']; + } + } + } + + return $this->tags; + } + + /** + * {@inheritDoc} + */ + public function getBranches() + { + if (null === $this->branches) { + $this->branches = array(); + $resource = sprintf( + '%s?%s', + $this->branchesUrl, + http_build_query( + array( + 'pagelen' => 100, + 'fields' => 'values.name,values.target.hash,next' + ) + ) + ); + $hasNext = true; + while ($hasNext) { + $branchData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + foreach ($branchData['values'] as $data) { + $this->branches[$data['name']] = $data['target']['hash']; + } + if (empty($branchData['next'])) { + $hasNext = false; + } else { + $resource = $branchData['next']; + } + } + } + + return $this->branches; + } + /** * Get the remote content. * @@ -184,7 +315,10 @@ abstract class BitbucketDriver extends VcsDriver * * @return string */ - abstract protected function generateSshUrl(); + protected function generateSshUrl() + { + return $this->cloneSshUrl; + } protected function attemptCloneFallback() { @@ -202,4 +336,25 @@ abstract class BitbucketDriver extends VcsDriver } abstract protected function setupFallbackDriver($url); + + /** + * @param array $cloneLinks + * @return void + */ + protected function parseCloneUrls(array $cloneLinks) + { + foreach ($cloneLinks as $cloneLink) { + if ($cloneLink['name'] === 'https') { + // Format: https://(user@)bitbucket.org/{user}/{repo} + // Strip username from URL (only present in clone URL's for private repositories) + $this->cloneHttpsUrl = preg_replace('/https:\/\/([^@]+@)?/', 'https://', $cloneLink['href']); + } elseif ($cloneLink['name'] === 'ssh') { + // Format: ssh://{git or hg}@bitbucket.org/{user}/{repo} + // Replace for git URL's + $href = preg_replace('/ssh:\/\/git@bitbucket\.org\//', 'git@bitbucket.org:', $cloneLink['href']); + // Replace for hg URL's + $this->cloneSshUrl = str_replace('ssh://', '', $href); + } + } + } } diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 2874fe77d..d128ad6ab 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -21,9 +21,6 @@ use Composer\IO\IOInterface; */ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface { - - - /** * {@inheritDoc} */ @@ -34,10 +31,22 @@ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface } if (null === $this->rootIdentifier) { - $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository; - $repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); - $this->hasIssues = !empty($repoData['has_issues']); - $this->rootIdentifier = !empty($repoData['main_branch']) ? $repoData['main_branch'] : 'master'; + $repoData = $this->getRepoData(); + + if ($repoData['scm'] !== 'git') { + throw new \RuntimeException( + $this->url.' does not appear to be a git repository, use '. + $this->cloneHttpsUrl.' if this is a mercurial bitbucket repository' + ); + } + + $resource = sprintf( + 'https://api.bitbucket.org/1.0/repositories/%s/%s/main-branch', + $this->owner, + $this->repository + ); + $main_branch_data = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); + $this->rootIdentifier = !empty($main_branch_data['name']) ? $main_branch_data['name'] : 'master'; } return $this->rootIdentifier; @@ -52,7 +61,7 @@ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface return $this->fallbackDriver->getUrl(); } - return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + return parent::getUrl(); } /** @@ -67,17 +76,6 @@ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier); } - /** - * {@inheritDoc} - */ - public function getDist($identifier) - { - $url = $this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/get/'.$identifier.'.zip'; - - return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); - } - - /** * {@inheritDoc} */ @@ -87,16 +85,7 @@ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface return $this->fallbackDriver->getTags(); } - if (null === $this->tags) { - $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'; - $tagsData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); - $this->tags = array(); - foreach ($tagsData as $tag => $data) { - $this->tags[$tag] = $data['raw_node']; - } - } - - return $this->tags; + return parent::getTags(); } /** @@ -108,16 +97,7 @@ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface return $this->fallbackDriver->getBranches(); } - if (null === $this->branches) { - $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'; - $branchData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); - $this->branches = array(); - foreach ($branchData as $branch => $data) { - $this->branches[$branch] = $data['raw_node']; - } - } - - return $this->branches; + return parent::getBranches(); } /** @@ -152,12 +132,4 @@ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface ); $this->fallbackDriver->initialize(); } - - /** - * {@inheritdoc} - */ - protected function generateSshUrl() - { - return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; - } } diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index c456121c0..573259e2e 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -21,33 +21,33 @@ use Composer\IO\IOInterface; */ class HgBitbucketDriver extends BitbucketDriver { - /** * {@inheritDoc} */ public function getRootIdentifier() { if (null === $this->rootIdentifier) { - $resource = $this->getScheme() . '://bitbucket.org/api/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'; - $repoData = JsonFile::parseJson($this->getContents($resource), $resource); - if (array() === $repoData || !isset($repoData['tip'])) { - throw new \RuntimeException($this->url.' does not appear to be a mercurial repository, use '.$this->url.'.git if this is a git bitbucket repository'); + $repoData = $this->getRepoData(); + + if ($repoData['scm'] !== 'hg') { + throw new \RuntimeException( + $this->url.' does not appear to be a mercurial repository, use '. + $this->cloneHttpsUrl.' if this is a git bitbucket repository' + ); } - $this->hasIssues = !empty($repoData['has_issues']); - $this->rootIdentifier = $repoData['tip']['raw_node']; + + $resource = sprintf( + 'https://api.bitbucket.org/1.0/repositories/%s/%s/main-branch', + $this->owner, + $this->repository + ); + $main_branch_data = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); + $this->rootIdentifier = !empty($main_branch_data['name']) ? $main_branch_data['name'] : 'default'; } return $this->rootIdentifier; } - /** - * {@inheritDoc} - */ - public function getUrl() - { - return $this->url; - } - /** * {@inheritDoc} */ @@ -56,51 +56,20 @@ class HgBitbucketDriver extends BitbucketDriver return array('type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier); } - /** - * {@inheritDoc} - */ - public function getDist($identifier) - { - $url = $this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/get/'.$identifier.'.zip'; - - return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); - } - /** * {@inheritDoc} */ public function getTags() { - if (null === $this->tags) { - $resource = $this->getScheme() . '://bitbucket.org/api/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'; - $tagsData = JsonFile::parseJson($this->getContents($resource), $resource); - $this->tags = array(); - foreach ($tagsData as $tag => $data) { - $this->tags[$tag] = $data['raw_node']; - } + parent::getTags(); + + if (isset($this->tags['tip'])) { unset($this->tags['tip']); } return $this->tags; } - /** - * {@inheritDoc} - */ - public function getBranches() - { - if (null === $this->branches) { - $resource = $this->getScheme() . '://bitbucket.org/api/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'; - $branchData = JsonFile::parseJson($this->getContents($resource), $resource); - $this->branches = array(); - foreach ($branchData as $branch => $data) { - $this->branches[$branch] = $data['raw_node']; - } - } - - return $this->branches; - } - /** * {@inheritDoc} */ @@ -130,12 +99,4 @@ class HgBitbucketDriver extends BitbucketDriver ); $this->fallbackDriver->initialize(); } - - /** - * {@inheritdoc} - */ - protected function generateSshUrl() - { - return 'hg@' . $this->originUrl . '/' . $this->owner.'/'.$this->repository; - } } diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index 75f7be119..e744ce241 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -76,69 +76,96 @@ class GitBitbucketDriverTest extends TestCase return $driver; } - public function testGetRootIdentifier() + public function testGetRootIdentifierWrongScmType() { - $driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git')); + $this->setExpectedException( + '\RuntimeException', + 'https://bitbucket.org/user/repo.git does not appear to be a git repository, use https://bitbucket.org/user/repo if this is a mercurial bitbucket repository' + ); - $this->rfs->expects($this->any()) + $this->rfs->expects($this->once()) ->method('getContents') ->with( $this->originUrl, - 'https://api.bitbucket.org/1.0/repositories/user/repo', + 'https://api.bitbucket.org/2.0/repositories/user/repo?fields=-project%2C-owner', false ) ->willReturn( - '{"scm": "git", "has_wiki": false, "last_updated": "2016-05-17T13:20:21.993", "no_forks": true, "forks_count": 0, "created_on": "2015-02-18T16:22:24.688", "owner": "user", "logo": "https://bitbucket.org/user/repo/avatar/32/?ts=1463484021", "email_mailinglist": "", "is_mq": false, "size": 9975494, "read_only": false, "fork_of": null, "mq_of": null, "followers_count": 0, "state": "available", "utc_created_on": "2015-02-18 15:22:24+00:00", "website": "", "description": "", "has_issues": false, "is_fork": false, "slug": "repo", "is_private": true, "name": "repo", "language": "php", "utc_last_updated": "2016-05-17 11:20:21+00:00", "no_public_forks": true, "creator": null, "resource_uri": "/1.0/repositories/user/repo"}' + '{"scm":"hg","website":"","has_wiki":false,"name":"repo","links":{"branches":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/branches"},"tags":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/tags"},"clone":[{"href":"https:\/\/user@bitbucket.org\/user\/repo","name":"https"},{"href":"ssh:\/\/hg@bitbucket.org\/user\/repo","name":"ssh"}],"html":{"href":"https:\/\/bitbucket.org\/user\/repo"}},"language":"php","created_on":"2015-02-18T16:22:24.688+00:00","updated_on":"2016-05-17T13:20:21.993+00:00","is_private":true,"has_issues":false}' ); - $this->assertEquals( - 'master', - $driver->getRootIdentifier() - ); + $driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git')); + + $driver->getRootIdentifier(); } - public function testGetParams() - { - $url = 'https://bitbucket.org/user/repo.git'; - $driver = $this->getDriver(array('url' => $url)); - - $this->assertEquals($url, $driver->getUrl()); - - $this->assertEquals( - array( - 'type' => 'zip', - 'url' => 'https://bitbucket.org/user/repo/get/reference.zip', - 'reference' => 'reference', - 'shasum' => '' - ), - $driver->getDist('reference') - ); - - $this->assertEquals( - array('type' => 'git', 'url' => $url, 'reference' => 'reference'), - $driver->getSource('reference') - ); - } - - public function testGetComposerInformation() + public function testDriver() { $driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git')); $this->rfs->expects($this->any()) ->method('getContents') ->withConsecutive( - array('bitbucket.org', 'https://api.bitbucket.org/1.0/repositories/user/repo/src/master/composer.json', false), - array('bitbucket.org', 'https://api.bitbucket.org/1.0/repositories/user/repo/changesets/master', false), - array('bitbucket.org', 'https://api.bitbucket.org/1.0/repositories/user/repo/tags', false), - array('bitbucket.org', 'https://api.bitbucket.org/1.0/repositories/user/repo/branches', false) + array( + $this->originUrl, + 'https://api.bitbucket.org/2.0/repositories/user/repo?fields=-project%2C-owner', + false + ), + array( + $this->originUrl, + 'https://api.bitbucket.org/1.0/repositories/user/repo/main-branch', + false + ), + array( + $this->originUrl, + 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/tags?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cnext', + false + ), + array( + $this->originUrl, + 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/branches?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cnext', + false + ), + array( + $this->originUrl, + 'https://api.bitbucket.org/1.0/repositories/user/repo/src/master/composer.json', + false + ), + array( + $this->originUrl, + 'https://api.bitbucket.org/1.0/repositories/user/repo/changesets/master', + false + ) ) ->willReturnOnConsecutiveCalls( + '{"scm":"git","website":"","has_wiki":false,"name":"repo","links":{"branches":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/branches"},"tags":{"href":"https:\/\/api.bitbucket.org\/2.0\/repositories\/user\/repo\/refs\/tags"},"clone":[{"href":"https:\/\/user@bitbucket.org\/user\/repo.git","name":"https"},{"href":"ssh:\/\/git@bitbucket.org\/user\/repo.git","name":"ssh"}],"html":{"href":"https:\/\/bitbucket.org\/user\/repo"}},"language":"php","created_on":"2015-02-18T16:22:24.688+00:00","updated_on":"2016-05-17T13:20:21.993+00:00","is_private":true,"has_issues":false}', + '{"name": "master"}', + '{"values":[{"name":"1.0.1","target":{"hash":"9b78a3932143497c519e49b8241083838c8ff8a1"}},{"name":"1.0.0","target":{"hash":"d3393d514318a9267d2f8ebbf463a9aaa389f8eb"}}]}', + '{"values":[{"name":"master","target":{"hash":"937992d19d72b5116c3e8c4a04f960e5fa270b22"}}]}', '{"node": "937992d19d72", "path": "composer.json", "data": "{\n \"name\": \"user/repo\",\n \"description\": \"test repo\",\n \"license\": \"GPL\",\n \"authors\": [\n {\n \"name\": \"Name\",\n \"email\": \"local@domain.tld\"\n }\n ],\n \"require\": {\n \"creator/package\": \"^1.0\"\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"~4.8\"\n }\n}\n", "size": 269}', - '{"node": "937992d19d72", "files": [{"type": "modified", "file": "path/to/file"}], "raw_author": "User ", "utctimestamp": "2016-05-17 11:19:52+00:00", "author": "user", "timestamp": "2016-05-17 13:19:52", "raw_node": "937992d19d72b5116c3e8c4a04f960e5fa270b22", "parents": ["71e195a33361"], "branch": "master", "message": "Commit message\n", "revision": null, "size": -1}', - '{}', - '{"master": {"node": "937992d19d72", "files": [{"type": "modified", "file": "path/to/file"}], "raw_author": "User ", "utctimestamp": "2016-05-17 11:19:52+00:00", "author": "user", "timestamp": "2016-05-17 13:19:52", "raw_node": "937992d19d72b5116c3e8c4a04f960e5fa270b22", "parents": ["71e195a33361"], "branch": "master", "message": "Commit message\n", "revision": null, "size": -1}}' + '{"node": "937992d19d72", "files": [{"type": "modified", "file": "path/to/file"}], "raw_author": "User ", "utctimestamp": "2016-05-17 11:19:52+00:00", "author": "user", "timestamp": "2016-05-17 13:19:52", "raw_node": "937992d19d72b5116c3e8c4a04f960e5fa270b22", "parents": ["71e195a33361"], "branch": "master", "message": "Commit message\n", "revision": null, "size": -1}' ); + $this->assertEquals( + 'master', + $driver->getRootIdentifier() + ); + + $this->assertEquals( + array( + '1.0.1' => '9b78a3932143497c519e49b8241083838c8ff8a1', + '1.0.0' => 'd3393d514318a9267d2f8ebbf463a9aaa389f8eb' + ), + $driver->getTags() + ); + + $this->assertEquals( + array( + 'master' => '937992d19d72b5116c3e8c4a04f960e5fa270b22' + ), + $driver->getBranches() + ); + $this->assertEquals( array( 'name' => 'user/repo', @@ -159,56 +186,38 @@ class GitBitbucketDriverTest extends TestCase 'time' => '2016-05-17 13:19:52', 'support' => array( 'source' => 'https://bitbucket.org/user/repo/src/937992d19d72b5116c3e8c4a04f960e5fa270b22/?at=master' - ) + ), + 'homepage' => 'https://bitbucket.org/user/repo' ), $driver->getComposerInformation('master') ); + + return $driver; } - public function testGetTags() + /** + * @depends testDriver + * @param \Composer\Repository\Vcs\VcsDriverInterface $driver + */ + public function testGetParams($driver) { - $driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git')); + $url = 'https://bitbucket.org/user/repo.git'; - $this->rfs->expects($this->once()) - ->method('getContents') - ->with( - 'bitbucket.org', - 'https://api.bitbucket.org/1.0/repositories/user/repo/tags', - false - ) - ->willReturn( - '{"1.0.1": {"node": "9b78a3932143", "files": [{"type": "modified", "file": "path/to/file"}], "branches": [], "raw_author": "User ", "utctimestamp": "2015-04-16 14:50:40+00:00", "author": "user", "timestamp": "2015-04-16 16:50:40", "raw_node": "9b78a3932143497c519e49b8241083838c8ff8a1", "parents": ["84531c04dbfc", "50c2a4635ad0"], "branch": null, "message": "Commit message\n", "revision": null, "size": -1}, "1.0.0": {"node": "d3393d514318", "files": [{"type": "modified", "file": "path/to/file2"}], "branches": [], "raw_author": "User ", "utctimestamp": "2015-04-16 09:31:45+00:00", "author": "user", "timestamp": "2015-04-16 11:31:45", "raw_node": "d3393d514318a9267d2f8ebbf463a9aaa389f8eb", "parents": ["5a29a73cd1a0"], "branch": null, "message": "Commit message\n", "revision": null, "size": -1}}' - ); + $this->assertEquals($url, $driver->getUrl()); $this->assertEquals( array( - '1.0.1' => '9b78a3932143497c519e49b8241083838c8ff8a1', - '1.0.0' => 'd3393d514318a9267d2f8ebbf463a9aaa389f8eb' + 'type' => 'zip', + 'url' => 'https://bitbucket.org/user/repo/get/reference.zip', + 'reference' => 'reference', + 'shasum' => '' ), - $driver->getTags() + $driver->getDist('reference') ); - } - - public function testGetBranches() - { - $driver = $this->getDriver(array('url' => 'https://bitbucket.org/user/repo.git')); - - $this->rfs->expects($this->once()) - ->method('getContents') - ->with( - 'bitbucket.org', - 'https://api.bitbucket.org/1.0/repositories/user/repo/branches', - false - ) - ->willReturn( - '{"master": {"node": "937992d19d72", "files": [{"type": "modified", "file": "path/to/file"}], "raw_author": "User ", "utctimestamp": "2016-05-17 11:19:52+00:00", "author": "user", "timestamp": "2016-05-17 13:19:52", "raw_node": "937992d19d72b5116c3e8c4a04f960e5fa270b22", "parents": ["71e195a33361"], "branch": "master", "message": "Commit message\n", "revision": null, "size": -1}}' - ); $this->assertEquals( - array( - 'master' => '937992d19d72b5116c3e8c4a04f960e5fa270b22' - ), - $driver->getBranches() + array('type' => 'git', 'url' => $url, 'reference' => 'reference'), + $driver->getSource('reference') ); } From 3eeb6214eb0c9b2a17593583e78edac504c1dcf7 Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Wed, 28 Dec 2016 00:10:27 +0100 Subject: [PATCH 05/12] Fix RemoteFilesystem::isPublicBitBucketDownload The access token was added to requests to third party hosts the bitbucket api is redirecting to. --- src/Composer/Util/RemoteFilesystem.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index b6fd65a46..2d6f76b32 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -997,6 +997,13 @@ class RemoteFilesystem */ private function isPublicBitBucketDownload($urlToBitBucketFile) { + $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST); + if (strpos($domain, 'bitbucket.org') === false) { + // Bitbucket downloads are hosted on amazonaws. + // We do not need to authenticate there at all + return true; + } + $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever} From 7ae4ed1ec88c8b76731a8933344b6536ba7f03f3 Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Wed, 28 Dec 2016 22:43:24 +0100 Subject: [PATCH 06/12] Improve fetching single files via bitbucket API. The former implementation used the 'src' endpoint which returned some meta data as well. This has been replaced with the 'raw' endpoint which does not return the meta data and does not need an extra JSON decode step. --- src/Composer/Repository/Vcs/BitbucketDriver.php | 8 ++------ .../Test/Repository/Vcs/GitBitbucketDriverTest.php | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 51eee5358..8a2e78a98 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -166,18 +166,14 @@ abstract class BitbucketDriver extends VcsDriver } $resource = sprintf( - 'https://api.bitbucket.org/1.0/repositories/%s/%s/src/%s/%s', + 'https://api.bitbucket.org/1.0/repositories/%s/%s/raw/%s/%s', $this->owner, $this->repository, $identifier, $file ); - $fileData = JsonFile::parseJson($this->getContents($resource), $resource); - if (!is_array($fileData) || ! array_key_exists('data', $fileData)) { - return null; - } - return $fileData['data']; + return $this->getContents($resource); } /** diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index e744ce241..30ec81130 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -128,7 +128,7 @@ class GitBitbucketDriverTest extends TestCase ), array( $this->originUrl, - 'https://api.bitbucket.org/1.0/repositories/user/repo/src/master/composer.json', + 'https://api.bitbucket.org/1.0/repositories/user/repo/raw/master/composer.json', false ), array( @@ -142,7 +142,7 @@ class GitBitbucketDriverTest extends TestCase '{"name": "master"}', '{"values":[{"name":"1.0.1","target":{"hash":"9b78a3932143497c519e49b8241083838c8ff8a1"}},{"name":"1.0.0","target":{"hash":"d3393d514318a9267d2f8ebbf463a9aaa389f8eb"}}]}', '{"values":[{"name":"master","target":{"hash":"937992d19d72b5116c3e8c4a04f960e5fa270b22"}}]}', - '{"node": "937992d19d72", "path": "composer.json", "data": "{\n \"name\": \"user/repo\",\n \"description\": \"test repo\",\n \"license\": \"GPL\",\n \"authors\": [\n {\n \"name\": \"Name\",\n \"email\": \"local@domain.tld\"\n }\n ],\n \"require\": {\n \"creator/package\": \"^1.0\"\n },\n \"require-dev\": {\n \"phpunit/phpunit\": \"~4.8\"\n }\n}\n", "size": 269}', + '{"name": "user/repo","description": "test repo","license": "GPL","authors": [{"name": "Name","email": "local@domain.tld"}],"require": {"creator/package": "^1.0"},"require-dev": {"phpunit/phpunit": "~4.8"}}', '{"node": "937992d19d72", "files": [{"type": "modified", "file": "path/to/file"}], "raw_author": "User ", "utctimestamp": "2016-05-17 11:19:52+00:00", "author": "user", "timestamp": "2016-05-17 13:19:52", "raw_node": "937992d19d72b5116c3e8c4a04f960e5fa270b22", "parents": ["71e195a33361"], "branch": "master", "message": "Commit message\n", "revision": null, "size": -1}' ); From bea4ec7f8878a1fc982dbf2304a17c2a12adf78d Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Wed, 28 Dec 2016 23:04:40 +0100 Subject: [PATCH 07/12] Some refactoring after testing hg protocol. - Revert deletion of generateSshUrl() as this is needed when falling back on the GitDriver or HgDriver. - Implement clean way to fallback from BitbucketDriver to GitDriver or HgDriver after previous changes. - Implement fallback in HgBitbucketDriver like in GitBitbucketDriver. --- .../Repository/Vcs/BitbucketDriver.php | 88 +++++++++++++++---- .../Vcs/BitbucketFallbackException.php | 17 ++++ .../Repository/Vcs/GitBitbucketDriver.php | 76 ++++------------ .../Repository/Vcs/HgBitbucketDriver.php | 55 +++++------- 4 files changed, 129 insertions(+), 107 deletions(-) create mode 100644 src/Composer/Repository/Vcs/BitbucketFallbackException.php diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 8a2e78a98..00401e253 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -1,5 +1,15 @@ + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Composer\Repository\Vcs; use Composer\Cache; @@ -23,12 +33,13 @@ abstract class BitbucketDriver extends VcsDriver protected $homeUrl = ''; protected $website = ''; protected $cloneHttpsUrl = ''; - protected $cloneSshUrl = ''; /** * @var VcsDriver */ protected $fallbackDriver; + /** @var string|null if set either git or hg */ + protected $vcsType; /** * {@inheritDoc} @@ -55,11 +66,18 @@ abstract class BitbucketDriver extends VcsDriver */ public function getUrl() { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getUrl(); + } + return $this->cloneHttpsUrl; } /** - * @return array + * Attempts to fetch the repository data via the BitBucket API and + * sets some parameters which are used in other methods + * + * @return void */ protected function getRepoData() { @@ -73,6 +91,9 @@ abstract class BitbucketDriver extends VcsDriver ); $repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); + if ($this->fallbackDriver) { + throw new BitbucketFallbackException(); + } $this->parseCloneUrls($repoData['links']['clone']); $this->hasIssues = !empty($repoData['has_issues']); @@ -80,8 +101,7 @@ abstract class BitbucketDriver extends VcsDriver $this->tagsUrl = $repoData['links']['tags']['href']; $this->homeUrl = $repoData['links']['html']['href']; $this->website = $repoData['website']; - - return $repoData; + $this->vcsType = $repoData['scm']; } /** @@ -173,7 +193,7 @@ abstract class BitbucketDriver extends VcsDriver $file ); - return $this->getContents($resource); + return $this->getContentsWithOAuthCredentials($resource); } /** @@ -187,16 +207,32 @@ abstract class BitbucketDriver extends VcsDriver $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/' . $this->owner . '/' . $this->repository . '/changesets/' . $identifier; - $changeset = JsonFile::parseJson($this->getContents($resource), $resource); + $changeset = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); return new \DateTime($changeset['timestamp']); } + /** + * {@inheritDoc} + */ + public function getSource($identifier) + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getSource($identifier); + } + + return array('type' => $this->vcsType, 'url' => $this->getUrl(), 'reference' => $identifier); + } + /** * {@inheritDoc} */ public function getDist($identifier) { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getDist($identifier); + } + $url = sprintf( 'https://bitbucket.org/%s/%s/get/%s.zip', $this->owner, @@ -212,6 +248,10 @@ abstract class BitbucketDriver extends VcsDriver */ public function getTags() { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getTags(); + } + if (null === $this->tags) { $this->tags = array(); $resource = sprintf( @@ -236,6 +276,9 @@ abstract class BitbucketDriver extends VcsDriver $resource = $tagsData['next']; } } + if ($this->vcsType === 'hg') { + unset($this->tags['tip']); + } } return $this->tags; @@ -246,6 +289,10 @@ abstract class BitbucketDriver extends VcsDriver */ public function getBranches() { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getBranches(); + } + if (null === $this->branches) { $this->branches = array(); $resource = sprintf( @@ -311,10 +358,7 @@ abstract class BitbucketDriver extends VcsDriver * * @return string */ - protected function generateSshUrl() - { - return $this->cloneSshUrl; - } + abstract protected function generateSshUrl(); protected function attemptCloneFallback() { @@ -331,6 +375,10 @@ abstract class BitbucketDriver extends VcsDriver } } + /** + * @param string $url + * @return void + */ abstract protected function setupFallbackDriver($url); /** @@ -344,13 +392,21 @@ abstract class BitbucketDriver extends VcsDriver // Format: https://(user@)bitbucket.org/{user}/{repo} // Strip username from URL (only present in clone URL's for private repositories) $this->cloneHttpsUrl = preg_replace('/https:\/\/([^@]+@)?/', 'https://', $cloneLink['href']); - } elseif ($cloneLink['name'] === 'ssh') { - // Format: ssh://{git or hg}@bitbucket.org/{user}/{repo} - // Replace for git URL's - $href = preg_replace('/ssh:\/\/git@bitbucket\.org\//', 'git@bitbucket.org:', $cloneLink['href']); - // Replace for hg URL's - $this->cloneSshUrl = str_replace('ssh://', '', $href); } } } + + /** + * @return array|null + */ + protected function getMainBranchData() + { + $resource = sprintf( + 'https://api.bitbucket.org/1.0/repositories/%s/%s/main-branch', + $this->owner, + $this->repository + ); + + return JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + } } diff --git a/src/Composer/Repository/Vcs/BitbucketFallbackException.php b/src/Composer/Repository/Vcs/BitbucketFallbackException.php new file mode 100644 index 000000000..063bb4c10 --- /dev/null +++ b/src/Composer/Repository/Vcs/BitbucketFallbackException.php @@ -0,0 +1,17 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +class BitbucketFallbackException extends \RuntimeException +{ +} diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index d128ad6ab..292ab01b1 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -13,13 +13,12 @@ namespace Composer\Repository\Vcs; use Composer\Config; -use Composer\Json\JsonFile; use Composer\IO\IOInterface; /** * @author Per Bernhardt */ -class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface +class GitBitbucketDriver extends BitbucketDriver { /** * {@inheritDoc} @@ -31,75 +30,26 @@ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface } if (null === $this->rootIdentifier) { - $repoData = $this->getRepoData(); + try { + $this->getRepoData(); + } catch (BitbucketFallbackException $e) { + return $this->fallbackDriver->getRootIdentifier(); + } - if ($repoData['scm'] !== 'git') { + if ($this->vcsType !== 'git') { throw new \RuntimeException( $this->url.' does not appear to be a git repository, use '. $this->cloneHttpsUrl.' if this is a mercurial bitbucket repository' ); } - $resource = sprintf( - 'https://api.bitbucket.org/1.0/repositories/%s/%s/main-branch', - $this->owner, - $this->repository - ); - $main_branch_data = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); + $main_branch_data = $this->getMainBranchData(); $this->rootIdentifier = !empty($main_branch_data['name']) ? $main_branch_data['name'] : 'master'; } return $this->rootIdentifier; } - /** - * {@inheritDoc} - */ - public function getUrl() - { - if ($this->fallbackDriver) { - return $this->fallbackDriver->getUrl(); - } - - return parent::getUrl(); - } - - /** - * {@inheritDoc} - */ - public function getSource($identifier) - { - if ($this->fallbackDriver) { - return $this->fallbackDriver->getSource($identifier); - } - - return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier); - } - - /** - * {@inheritDoc} - */ - public function getTags() - { - if ($this->fallbackDriver) { - return $this->fallbackDriver->getTags(); - } - - return parent::getTags(); - } - - /** - * {@inheritDoc} - */ - public function getBranches() - { - if ($this->fallbackDriver) { - return $this->fallbackDriver->getBranches(); - } - - return parent::getBranches(); - } - /** * {@inheritDoc} */ @@ -119,7 +69,7 @@ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface } /** - * @param string $url + * {@inheritdoc} */ protected function setupFallbackDriver($url) { @@ -132,4 +82,12 @@ class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface ); $this->fallbackDriver->initialize(); } + + /** + * {@inheritdoc} + */ + protected function generateSshUrl() + { + return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; + } } diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index 573259e2e..cf76a5115 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -13,7 +13,6 @@ namespace Composer\Repository\Vcs; use Composer\Config; -use Composer\Json\JsonFile; use Composer\IO\IOInterface; /** @@ -26,50 +25,31 @@ class HgBitbucketDriver extends BitbucketDriver */ public function getRootIdentifier() { - if (null === $this->rootIdentifier) { - $repoData = $this->getRepoData(); + if ($this->fallbackDriver) { + return $this->fallbackDriver->getRootIdentifier(); + } - if ($repoData['scm'] !== 'hg') { + if (null === $this->rootIdentifier) { + try { + $this->getRepoData(); + } catch (BitbucketFallbackException $e) { + return $this->fallbackDriver->getRootIdentifier(); + } + + if ($this->vcsType !== 'hg') { throw new \RuntimeException( $this->url.' does not appear to be a mercurial repository, use '. $this->cloneHttpsUrl.' if this is a git bitbucket repository' ); } - $resource = sprintf( - 'https://api.bitbucket.org/1.0/repositories/%s/%s/main-branch', - $this->owner, - $this->repository - ); - $main_branch_data = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); + $main_branch_data = $this->getMainBranchData(); $this->rootIdentifier = !empty($main_branch_data['name']) ? $main_branch_data['name'] : 'default'; } return $this->rootIdentifier; } - /** - * {@inheritDoc} - */ - public function getSource($identifier) - { - return array('type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier); - } - - /** - * {@inheritDoc} - */ - public function getTags() - { - parent::getTags(); - - if (isset($this->tags['tip'])) { - unset($this->tags['tip']); - } - - return $this->tags; - } - /** * {@inheritDoc} */ @@ -88,6 +68,9 @@ class HgBitbucketDriver extends BitbucketDriver return true; } + /** + * {@inheritdoc} + */ protected function setupFallbackDriver($url) { $this->fallbackDriver = new HgDriver( @@ -99,4 +82,12 @@ class HgBitbucketDriver extends BitbucketDriver ); $this->fallbackDriver->initialize(); } + + /** + * {@inheritdoc} + */ + protected function generateSshUrl() + { + return 'ssh://hg@' . $this->originUrl . '/' . $this->owner.'/'.$this->repository; + } } From 046b1184dcf50b191daa5a96ecdc351d7db791eb Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Sun, 22 Jan 2017 14:58:35 +0100 Subject: [PATCH 08/12] Change getChangeDate call Use the v2.0 commit resource instead of the v1.0 changeset resource. --- src/Composer/Repository/Vcs/BitbucketDriver.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 00401e253..3fbfb53fe 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -205,11 +205,15 @@ abstract class BitbucketDriver extends VcsDriver return $this->fallbackDriver->getChangeDate($identifier); } - $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/' - . $this->owner . '/' . $this->repository . '/changesets/' . $identifier; - $changeset = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); + $resource = sprintf( + 'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date', + $this->owner, + $this->repository, + $identifier + ); + $commit = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); - return new \DateTime($changeset['timestamp']); + return new \DateTime($commit['date']); } /** From 5dbdefdd72f2234dfd2995a8e6bbd491e7225b61 Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Sun, 22 Jan 2017 15:55:17 +0100 Subject: [PATCH 09/12] Implement ordering in requesting tags and branches. Update unit test with latest changes. --- composer.lock | 2 +- src/Composer/Repository/Vcs/BitbucketDriver.php | 6 ++++-- .../Test/Repository/Vcs/GitBitbucketDriverTest.php | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index dc6d7afec..20483e67b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "e18501d127e13e3619f80abbcf372c81", + "content-hash": "cc935b611f9e58595c17efc6e7eff79a", "packages": [ { "name": "composer/ca-bundle", diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 3fbfb53fe..1c0b63cb5 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -264,7 +264,8 @@ abstract class BitbucketDriver extends VcsDriver http_build_query( array( 'pagelen' => 100, - 'fields' => 'values.name,values.target.hash,next' + 'fields' => 'values.name,values.target.hash,next', + 'sort' => '-target.date' ) ) ); @@ -305,7 +306,8 @@ abstract class BitbucketDriver extends VcsDriver http_build_query( array( 'pagelen' => 100, - 'fields' => 'values.name,values.target.hash,next' + 'fields' => 'values.name,values.target.hash,next', + 'sort' => '-target.date' ) ) ); diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index 30ec81130..58e17ab9b 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -118,12 +118,12 @@ class GitBitbucketDriverTest extends TestCase ), array( $this->originUrl, - 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/tags?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cnext', + 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/tags?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cnext&sort=-target.date', false ), array( $this->originUrl, - 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/branches?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cnext', + 'https://api.bitbucket.org/2.0/repositories/user/repo/refs/branches?pagelen=100&fields=values.name%2Cvalues.target.hash%2Cnext&sort=-target.date', false ), array( @@ -133,7 +133,7 @@ class GitBitbucketDriverTest extends TestCase ), array( $this->originUrl, - 'https://api.bitbucket.org/1.0/repositories/user/repo/changesets/master', + 'https://api.bitbucket.org/2.0/repositories/user/repo/commit/master?fields=date', false ) ) @@ -143,7 +143,7 @@ class GitBitbucketDriverTest extends TestCase '{"values":[{"name":"1.0.1","target":{"hash":"9b78a3932143497c519e49b8241083838c8ff8a1"}},{"name":"1.0.0","target":{"hash":"d3393d514318a9267d2f8ebbf463a9aaa389f8eb"}}]}', '{"values":[{"name":"master","target":{"hash":"937992d19d72b5116c3e8c4a04f960e5fa270b22"}}]}', '{"name": "user/repo","description": "test repo","license": "GPL","authors": [{"name": "Name","email": "local@domain.tld"}],"require": {"creator/package": "^1.0"},"require-dev": {"phpunit/phpunit": "~4.8"}}', - '{"node": "937992d19d72", "files": [{"type": "modified", "file": "path/to/file"}], "raw_author": "User ", "utctimestamp": "2016-05-17 11:19:52+00:00", "author": "user", "timestamp": "2016-05-17 13:19:52", "raw_node": "937992d19d72b5116c3e8c4a04f960e5fa270b22", "parents": ["71e195a33361"], "branch": "master", "message": "Commit message\n", "revision": null, "size": -1}' + '{"date": "2016-05-17T13:19:52+00:00"}' ); $this->assertEquals( From 4377ba2bcbd62ad52ece7743d72b84f6c1dea9ea Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Wed, 25 Jan 2017 21:18:19 +0100 Subject: [PATCH 10/12] Implement changes after review by stof. - Use camelCase variable names. - Set 3rd argument of http_build_query - Remove obsolete checks --- src/Composer/Repository/Vcs/BitbucketDriver.php | 12 +++++++++--- src/Composer/Repository/Vcs/GitBitbucketDriver.php | 4 ++-- src/Composer/Repository/Vcs/HgBitbucketDriver.php | 4 ++-- src/Composer/Util/Bitbucket.php | 4 +--- src/Composer/Util/Git.php | 10 +++++----- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 1c0b63cb5..e2497aa72 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -86,7 +86,9 @@ abstract class BitbucketDriver extends VcsDriver $this->owner, $this->repository, http_build_query( - array('fields' => '-project,-owner') + array('fields' => '-project,-owner'), + null, + '&' ) ); @@ -266,7 +268,9 @@ abstract class BitbucketDriver extends VcsDriver 'pagelen' => 100, 'fields' => 'values.name,values.target.hash,next', 'sort' => '-target.date' - ) + ), + null, + '&' ) ); $hasNext = true; @@ -308,7 +312,9 @@ abstract class BitbucketDriver extends VcsDriver 'pagelen' => 100, 'fields' => 'values.name,values.target.hash,next', 'sort' => '-target.date' - ) + ), + null, + '&' ) ); $hasNext = true; diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 292ab01b1..82fe69028 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -43,8 +43,8 @@ class GitBitbucketDriver extends BitbucketDriver ); } - $main_branch_data = $this->getMainBranchData(); - $this->rootIdentifier = !empty($main_branch_data['name']) ? $main_branch_data['name'] : 'master'; + $mainBranchData = $this->getMainBranchData(); + $this->rootIdentifier = !empty($mainBranchData['name']) ? $mainBranchData['name'] : 'master'; } return $this->rootIdentifier; diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index cf76a5115..e908f9ce6 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -43,8 +43,8 @@ class HgBitbucketDriver extends BitbucketDriver ); } - $main_branch_data = $this->getMainBranchData(); - $this->rootIdentifier = !empty($main_branch_data['name']) ? $main_branch_data['name'] : 'default'; + $mainBranchData = $this->getMainBranchData(); + $this->rootIdentifier = !empty($mainBranchData['name']) ? $mainBranchData['name'] : 'default'; } return $this->rootIdentifier; diff --git a/src/Composer/Util/Bitbucket.php b/src/Composer/Util/Bitbucket.php index 152fe5fa0..e1f319768 100644 --- a/src/Composer/Util/Bitbucket.php +++ b/src/Composer/Util/Bitbucket.php @@ -227,9 +227,7 @@ class Bitbucket { $authConfig = $this->config->get('bitbucket-oauth'); - if (empty($authConfig) || - ! isset($authConfig[$originUrl]) || - ! isset($authConfig[$originUrl]['access-token']) || + if (! isset($authConfig[$originUrl]['access-token']) || ! isset($authConfig[$originUrl]['access-token-expiration']) || time() > $authConfig[$originUrl]['access-token-expiration'] ) { diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 90722bc5a..97b36ef19 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -122,17 +122,17 @@ class Git if (!$bitbucketUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { $bitbucketUtil->authorizeOAuthInteractively($match[1], $message); - $access_token = $bitbucketUtil->getToken(); - $this->io->setAuthentication($match[1], 'x-token-auth', $access_token); + $accessToken = $bitbucketUtil->getToken(); + $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken); } } else { //We're authenticating with a locally stored consumer. $auth = $this->io->getAuthentication($match[1]); //We already have an access_token from a previous request. if ($auth['username'] !== 'x-token-auth') { - $access_token = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']); - if (! empty($access_token)) { - $this->io->setAuthentication($match[1], 'x-token-auth', $access_token); + $accessToken = $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']); + if (! empty($accessToken)) { + $this->io->setAuthentication($match[1], 'x-token-auth', $accessToken); } } } From 3ccaac619b9853a0bf6edcc59ea448b8c1f1ade8 Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Sun, 29 Jan 2017 15:35:50 +0100 Subject: [PATCH 11/12] Refactor the getRepoData method to not throw an Exception --- src/Composer/Repository/Vcs/BitbucketDriver.php | 5 +++-- .../Vcs/BitbucketFallbackException.php | 17 ----------------- .../Repository/Vcs/GitBitbucketDriver.php | 4 +--- .../Repository/Vcs/HgBitbucketDriver.php | 4 +--- 4 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 src/Composer/Repository/Vcs/BitbucketFallbackException.php diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index e2497aa72..ba1851f53 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -77,7 +77,7 @@ abstract class BitbucketDriver extends VcsDriver * Attempts to fetch the repository data via the BitBucket API and * sets some parameters which are used in other methods * - * @return void + * @return bool */ protected function getRepoData() { @@ -94,7 +94,7 @@ abstract class BitbucketDriver extends VcsDriver $repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource); if ($this->fallbackDriver) { - throw new BitbucketFallbackException(); + return false; } $this->parseCloneUrls($repoData['links']['clone']); @@ -104,6 +104,7 @@ abstract class BitbucketDriver extends VcsDriver $this->homeUrl = $repoData['links']['html']['href']; $this->website = $repoData['website']; $this->vcsType = $repoData['scm']; + return true; } /** diff --git a/src/Composer/Repository/Vcs/BitbucketFallbackException.php b/src/Composer/Repository/Vcs/BitbucketFallbackException.php deleted file mode 100644 index 063bb4c10..000000000 --- a/src/Composer/Repository/Vcs/BitbucketFallbackException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Repository\Vcs; - -class BitbucketFallbackException extends \RuntimeException -{ -} diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 82fe69028..c8a5c9905 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -30,9 +30,7 @@ class GitBitbucketDriver extends BitbucketDriver } if (null === $this->rootIdentifier) { - try { - $this->getRepoData(); - } catch (BitbucketFallbackException $e) { + if (! $this->getRepoData()) { return $this->fallbackDriver->getRootIdentifier(); } diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index e908f9ce6..8324f22ac 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -30,9 +30,7 @@ class HgBitbucketDriver extends BitbucketDriver } if (null === $this->rootIdentifier) { - try { - $this->getRepoData(); - } catch (BitbucketFallbackException $e) { + if (! $this->getRepoData()) { return $this->fallbackDriver->getRootIdentifier(); } From 966d0bec10d11b2ac141ab73b9418c9a90830528 Mon Sep 17 00:00:00 2001 From: Stefan Grootscholten Date: Sun, 29 Jan 2017 15:55:37 +0100 Subject: [PATCH 12/12] One more non-camelCase variable. --- src/Composer/Util/RemoteFilesystem.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 2d6f76b32..a6c93afe8 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -593,9 +593,9 @@ class RemoteFilesystem $auth = $this->io->getAuthentication($this->originUrl); if ($auth['username'] !== 'x-token-auth') { $bitbucketUtil = new Bitbucket($this->io, $this->config); - $access_token = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']); - if (! empty($access_token)) { - $this->io->setAuthentication($this->originUrl, 'x-token-auth', $access_token); + $accessToken = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']); + if (! empty($accessToken)) { + $this->io->setAuthentication($this->originUrl, 'x-token-auth', $accessToken); $askForOAuthToken = false; } } else {