diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php new file mode 100644 index 000000000..a1d9a8e26 --- /dev/null +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -0,0 +1,205 @@ +url, $match); + $this->owner = $match[1]; + $this->repository = $match[2]; + $this->originUrl = 'bitbucket.org'; + $this->cache = new Cache( + $this->io, + implode('/', array( + $this->config->get('cache-repo-dir'), + $this->originUrl, + $this->owner, + $this->repository + )) + ); + } + + /** + * {@inheritDoc} + */ + public function getComposerInformation($identifier) + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + return $this->infoCache[$identifier] = JsonFile::parseJson($res); + } + + $composer = $this->getBaseComposerInformation($identifier); + + // specials for bitbucket + if (!isset($composer['support']['source'])) { + $label = array_search( + $identifier, + $this->getTags() + ) ?: array_search( + $identifier, + $this->getBranches() + ) ?: $identifier; + + if (array_key_exists($label, $tags = $this->getTags())) { + $hash = $tags[$label]; + } elseif (array_key_exists($label, $branches = $this->getBranches())) { + $hash = $branches[$label]; + } + + if (! isset($hash)) { + $composer['support']['source'] = sprintf( + 'https://%s/%s/%s/src', + $this->originUrl, + $this->owner, + $this->repository + ); + } else { + $composer['support']['source'] = sprintf( + 'https://%s/%s/%s/src/%s/?at=%s', + $this->originUrl, + $this->owner, + $this->repository, + $hash, + $label + ); + } + } + if (!isset($composer['support']['issues']) && $this->hasIssues) { + $composer['support']['issues'] = sprintf( + 'https://%s/%s/%s/issues', + $this->originUrl, + $this->owner, + $this->repository + ); + } + + $this->infoCache[$identifier] = $composer; + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, json_encode($composer)); + } + } + + return $this->infoCache[$identifier]; + } + + /** + * {@inheritdoc} + */ + public function getFileContent($file, $identifier) + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getFileContent($file, $identifier); + } + + $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/' + . $this->owner . '/' . $this->repository . '/src/' . $identifier . '/' . $file; + $fileData = JsonFile::parseJson($this->getContents($resource), $resource); + if (!is_array($fileData) || ! array_key_exists('data', $fileData)) { + return null; + } + + return $fileData['data']; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + if ($this->fallbackDriver) { + return $this->fallbackDriver->getChangeDate($identifier); + } + + $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/' + . $this->owner . '/' . $this->repository . '/changesets/' . $identifier; + $changeset = JsonFile::parseJson($this->getContents($resource), $resource); + + return new \DateTime($changeset['timestamp']); + } + + /** + * Get the remote content. + * + * @param string $url The URL of content + * @param bool $fetchingRepoData + * + * @return mixed The result + */ + protected function getContentsWithOAuthCredentials($url, $fetchingRepoData = false) + { + try { + return parent::getContents($url); + } catch (TransportException $e) { + $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->remoteFilesystem); + + if (403 === $e->getCode()) { + if (!$this->io->hasAuthentication($this->originUrl) + && $bitbucketUtil->authorizeOAuth($this->originUrl) + ) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive() && $fetchingRepoData) { + return $this->attemptCloneFallback(); + } + } + + throw $e; + } + } + + /** + * Generate an SSH URL + * + * @return string + */ + abstract protected function generateSshUrl(); + + protected function attemptCloneFallback() + { + try { + $this->setupFallbackDriver($this->generateSshUrl()); + } catch (\RuntimeException $e) { + $this->fallbackDriver = null; + + $this->io->writeError( + 'Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode' + . ' so that you can enter your Bitbucket OAuth consumer credentials' + ); + throw $e; + } + } + + abstract protected function setupFallbackDriver($url); +} diff --git a/src/Composer/Repository/Vcs/FossilDriver.php b/src/Composer/Repository/Vcs/FossilDriver.php index 58fc9eb1b..b992db7ec 100644 --- a/src/Composer/Repository/Vcs/FossilDriver.php +++ b/src/Composer/Repository/Vcs/FossilDriver.php @@ -122,29 +122,27 @@ class FossilDriver extends VcsDriver } /** - * {@inheritDoc} + * {@inheritdoc} */ - public function getComposerInformation($identifier) + public function getFileContent($file, $identifier) { - if (!isset($this->infoCache[$identifier])) { - $command = sprintf('fossil cat -r %s composer.json', ProcessExecutor::escape($identifier)); - $this->process->execute($command, $composer, $this->checkoutDir); - - if (trim($composer) === '') { - return; - } + $command = sprintf('fossil cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); + $this->process->execute($command, $content, $this->checkoutDir); - $composer = JsonFile::parseJson(trim($composer), $identifier); - - if (empty($composer['time'])) { - $this->process->execute(sprintf('fossil finfo composer.json | head -n 2 | tail -n 1 | awk \'{print $1}\''), $output, $this->checkoutDir); - $date = new \DateTime(trim($output), new \DateTimeZone('UTC')); - $composer['time'] = $date->format('Y-m-d H:i:s'); - } - $this->infoCache[$identifier] = $composer; + if (!trim($content)) { + return null; } - return $this->infoCache[$identifier]; + return $content; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + $this->process->execute(sprintf('fossil finfo composer.json | head -n 2 | tail -n 1 | awk \'{print $1}\''), $output, $this->checkoutDir); + return new \DateTime(trim($output), new \DateTimeZone('UTC')); } /** diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 72af4183e..2874fe77d 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -12,53 +12,25 @@ namespace Composer\Repository\Vcs; -use Composer\Cache; use Composer\Config; -use Composer\Downloader\TransportException; use Composer\Json\JsonFile; use Composer\IO\IOInterface; -use Composer\Util\Bitbucket; /** * @author Per Bernhardt */ -class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface +class GitBitbucketDriver extends BitbucketDriver implements VcsDriverInterface { - /** - * @var Cache - */ - protected $cache; - protected $owner; - protected $repository; - protected $tags; - protected $branches; - protected $rootIdentifier; - protected $infoCache = array(); - private $hasIssues; - /** - * @var GitDriver - */ - private $gitDriver; - /** - * {@inheritDoc} - */ - public function initialize() - { - preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match); - $this->owner = $match[1]; - $this->repository = $match[2]; - $this->originUrl = 'bitbucket.org'; - $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); - } + /** * {@inheritDoc} */ public function getRootIdentifier() { - if ($this->gitDriver) { - return $this->gitDriver->getRootIdentifier(); + if ($this->fallbackDriver) { + return $this->fallbackDriver->getRootIdentifier(); } if (null === $this->rootIdentifier) { @@ -76,8 +48,8 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface */ public function getUrl() { - if ($this->gitDriver) { - return $this->gitDriver->getUrl(); + if ($this->fallbackDriver) { + return $this->fallbackDriver->getUrl(); } return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; @@ -88,8 +60,8 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface */ public function getSource($identifier) { - if ($this->gitDriver) { - return $this->gitDriver->getSource($identifier); + if ($this->fallbackDriver) { + return $this->fallbackDriver->getSource($identifier); } return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier); @@ -105,76 +77,14 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); } - /** - * {@inheritDoc} - */ - public function getComposerInformation($identifier) - { - if ($this->gitDriver) { - return $this->gitDriver->getComposerInformation($identifier); - } - - if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier)) { - $this->infoCache[$identifier] = JsonFile::parseJson($res); - } - - if (!isset($this->infoCache[$identifier])) { - $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/src/'.$identifier.'/composer.json'; - $file = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); - if (!is_array($file) || ! array_key_exists('data', $file)) { - return array(); - } - - $composer = JsonFile::parseJson($file['data'], $resource); - - if (empty($composer['time'])) { - $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier; - $changeset = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource); - $composer['time'] = $changeset['timestamp']; - } - if (!isset($composer['support']['source'])) { - $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier; - - if (array_key_exists($label, $tags = $this->getTags())) { - $hash = $tags[$label]; - } elseif (array_key_exists($label, $branches = $this->getBranches())) { - $hash = $branches[$label]; - } - - if (! isset($hash)) { - $composer['support']['source'] = sprintf('https://%s/%s/%s/src', $this->originUrl, $this->owner, $this->repository); - } else { - $composer['support']['source'] = sprintf( - 'https://%s/%s/%s/src/%s/?at=%s', - $this->originUrl, - $this->owner, - $this->repository, - $hash, - $label - ); - } - } - if (!isset($composer['support']['issues']) && $this->hasIssues) { - $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); - } - - if (preg_match('{[a-f0-9]{40}}i', $identifier)) { - $this->cache->write($identifier, json_encode($composer)); - } - - $this->infoCache[$identifier] = $composer; - } - - return $this->infoCache[$identifier]; - } /** * {@inheritDoc} */ public function getTags() { - if ($this->gitDriver) { - return $this->gitDriver->getTags(); + if ($this->fallbackDriver) { + return $this->fallbackDriver->getTags(); } if (null === $this->tags) { @@ -194,8 +104,8 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface */ public function getBranches() { - if ($this->gitDriver) { - return $this->gitDriver->getBranches(); + if ($this->fallbackDriver) { + return $this->fallbackDriver->getBranches(); } if (null === $this->branches) { @@ -228,75 +138,26 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface return true; } - protected function attemptCloneFallback() - { - try { - $this->setupGitDriver($this->generateSshUrl()); - - return; - } catch (\RuntimeException $e) { - $this->gitDriver = null; - - $this->io->writeError('Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your Bitbucket OAuth consumer credentials'); - throw $e; - } - } - - /** - * Generate an SSH URL - * - * @return string - */ - private function generateSshUrl() - { - return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; - } - - /** - * Get the remote content. - * - * @param string $url The URL of content - * @param bool $fetchingRepoData - * - * @return mixed The result - */ - protected function getContentsWithOAuthCredentials($url, $fetchingRepoData = false) - { - try { - return parent::getContents($url); - } catch (TransportException $e) { - $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->remoteFilesystem); - - switch ($e->getCode()) { - case 403: - if (!$this->io->hasAuthentication($this->originUrl) && $bitbucketUtil->authorizeOAuth($this->originUrl)) { - return parent::getContents($url); - } - - if (!$this->io->isInteractive() && $fetchingRepoData) { - return $this->attemptCloneFallback(); - } - - throw $e; - - default: - throw $e; - } - } - } - /** * @param string $url */ - private function setupGitDriver($url) + protected function setupFallbackDriver($url) { - $this->gitDriver = new GitDriver( + $this->fallbackDriver = new GitDriver( array('url' => $url), $this->io, $this->config, $this->process, $this->remoteFilesystem ); - $this->gitDriver->initialize(); + $this->fallbackDriver->initialize(); + } + + /** + * {@inheritdoc} + */ + protected function generateSshUrl() + { + return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; } } diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index bacad4d2c..7093ce94d 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -120,38 +120,30 @@ class GitDriver extends VcsDriver } /** - * {@inheritDoc} + * {@inheritdoc} */ - public function getComposerInformation($identifier) + public function getFileContent($file, $identifier) { - if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier)) { - $this->infoCache[$identifier] = JsonFile::parseJson($res); - } - - if (!isset($this->infoCache[$identifier])) { - $resource = sprintf('%s:composer.json', ProcessExecutor::escape($identifier)); - $this->process->execute(sprintf('git show %s', $resource), $composer, $this->repoDir); - - if (!trim($composer)) { - return; - } - - $composer = JsonFile::parseJson($composer, $resource); - - if (empty($composer['time'])) { - $this->process->execute(sprintf('git log -1 --format=%%at %s', ProcessExecutor::escape($identifier)), $output, $this->repoDir); - $date = new \DateTime('@'.trim($output), new \DateTimeZone('UTC')); - $composer['time'] = $date->format('Y-m-d H:i:s'); - } + $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); + $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir); - if (preg_match('{[a-f0-9]{40}}i', $identifier)) { - $this->cache->write($identifier, json_encode($composer)); - } - - $this->infoCache[$identifier] = $composer; + if (!trim($content)) { + return null; } - return $this->infoCache[$identifier]; + return $content; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + $this->process->execute(sprintf( + 'git log -1 --format=%%at %s', + ProcessExecutor::escape($identifier) + ), $output, $this->repoDir); + return new \DateTime('@'.trim($output), new \DateTimeZone('UTC')); } /** diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 04d7321f5..b33b99916 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -146,50 +146,23 @@ class GitHubDriver extends VcsDriver return $this->gitDriver->getComposerInformation($identifier); } - if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier)) { - $this->infoCache[$identifier] = JsonFile::parseJson($res); - } - if (!isset($this->infoCache[$identifier])) { - $notFoundRetries = 2; - while ($notFoundRetries) { - try { - $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/composer.json?ref='.urlencode($identifier); - $resource = JsonFile::parseJson($this->getContents($resource)); - if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($composer = base64_decode($resource['content']))) { - throw new \RuntimeException('Could not retrieve composer.json for '.$identifier); - } - break; - } catch (TransportException $e) { - if (404 !== $e->getCode()) { - throw $e; - } - - // TODO should be removed when possible - // retry fetching if github returns a 404 since they happen randomly - $notFoundRetries--; - $composer = null; - } + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + return $this->infoCache[$identifier] = JsonFile::parseJson($res); } - if ($composer) { - $composer = JsonFile::parseJson($composer, $resource); + $composer = $this->getBaseComposerInformation($identifier); - if (empty($composer['time'])) { - $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://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label); - } - if (!isset($composer['support']['issues']) && $this->hasIssues) { - $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); - } + // specials for github + if (!isset($composer['support']['source'])) { + $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier; + $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://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); } - if ($composer && preg_match('{[a-f0-9]{40}}i', $identifier)) { + if ($this->shouldCache($identifier)) { $this->cache->write($identifier, json_encode($composer)); } @@ -199,6 +172,54 @@ class GitHubDriver extends VcsDriver return $this->infoCache[$identifier]; } + /** + * {@inheritdoc} + */ + public function getFileContent($file, $identifier) + { + if ($this->gitDriver) { + return $this->gitDriver->getFileContent($file, $identifier); + } + + $notFoundRetries = 2; + while ($notFoundRetries) { + try { + + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); + $resource = JsonFile::parseJson($this->getContents($resource)); + if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) { + throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); + } + + return $content; + } catch (TransportException $e) { + if (404 !== $e->getCode()) { + throw $e; + } + + // TODO should be removed when possible + // retry fetching if github returns a 404 since they happen randomly + $notFoundRetries--; + return null; + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) { + if ($this->gitDriver) { + return $this->gitDriver->getChangeDate($identifier); + } + + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier); + $commit = JsonFile::parseJson($this->getContents($resource), $resource); + return new \DateTime($commit['commit']['committer']['date']); + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index ed91056d4..5f3538869 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -32,9 +32,6 @@ class GitLabDriver extends VcsDriver private $owner; private $repository; - private $cache; - private $infoCache = array(); - /** * @var array Project data returned by GitLab API */ @@ -99,13 +96,9 @@ class GitLabDriver extends VcsDriver } /** - * 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} + * {@inheritdoc} */ - public function getComposerInformation($identifier) + public function getFileContent($file, $identifier) { // Convert the root identifier to a cachable commit id if (!preg_match('{[a-f0-9]{40}}i', $identifier)) { @@ -115,34 +108,33 @@ class GitLabDriver extends VcsDriver } } - 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); - } + $resource = $this->getApiUrl().'/repository/blobs/'.$identifier.'?filepath=' . $file; try { - $composer = $this->fetchComposerFile($identifier); + $content = $this->getContents($resource); } catch (TransportException $e) { if ($e->getCode() !== 404) { throw $e; } - $composer = false; + return null; } - if ($composer && !isset($composer['time']) && isset($this->commits[$identifier])) { - $composer['time'] = $this->commits[$identifier]['committed_date']; - } + return $content; + } - if (preg_match('{[a-f0-9]{40}}i', $identifier)) { - $this->cache->write($identifier, json_encode($composer)); + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + if (isset($this->commits[$identifier])) { + return new \DateTime($this->commits[$identifier]['committed_date']); } - return $this->infoCache[$identifier] = $composer; + return new \DateTime(); } + /** * {@inheritDoc} */ @@ -209,20 +201,6 @@ class GitLabDriver extends VcsDriver return $this->tags; } - /** - * Fetches composer.json file from the repository through api. - * - * @param string $identifier - * - * @return array - */ - protected function fetchComposerFile($identifier) - { - $resource = $this->getApiUrl().'/repository/blobs/'.$identifier.'?filepath=composer.json'; - - return JsonFile::parseJson($this->getContents($resource), $resource); - } - /** * @return string Base URL for GitLab API v3 */ diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index eb6808601..c456121c0 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -12,7 +12,6 @@ namespace Composer\Repository\Vcs; -use Composer\Cache; use Composer\Config; use Composer\Json\JsonFile; use Composer\IO\IOInterface; @@ -20,27 +19,8 @@ use Composer\IO\IOInterface; /** * @author Per Bernhardt */ -class HgBitbucketDriver extends VcsDriver +class HgBitbucketDriver extends BitbucketDriver { - protected $cache; - protected $owner; - protected $repository; - protected $tags; - protected $branches; - protected $rootIdentifier; - protected $infoCache = array(); - - /** - * {@inheritDoc} - */ - public function initialize() - { - preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match); - $this->owner = $match[1]; - $this->repository = $match[2]; - $this->originUrl = 'bitbucket.org'; - $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); - } /** * {@inheritDoc} @@ -53,6 +33,7 @@ class HgBitbucketDriver extends VcsDriver 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'); } + $this->hasIssues = !empty($repoData['has_issues']); $this->rootIdentifier = $repoData['tip']['raw_node']; } @@ -85,46 +66,6 @@ class HgBitbucketDriver extends VcsDriver return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); } - /** - * {@inheritDoc} - */ - public function getComposerInformation($identifier) - { - if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier)) { - $this->infoCache[$identifier] = JsonFile::parseJson($res); - } - - if (!isset($this->infoCache[$identifier])) { - $resource = $this->getScheme() . '://bitbucket.org/api/1.0/repositories/'.$this->owner.'/'.$this->repository.'/src/'.$identifier.'/composer.json'; - $repoData = JsonFile::parseJson($this->getContents($resource), $resource); - - // Bitbucket does not send different response codes for found and - // not found files, so we have to check the response structure. - // found: {node: ..., data: ..., size: ..., ...} - // not found: {node: ..., files: [...], directories: [...], ...} - - if (!array_key_exists('data', $repoData)) { - return; - } - - $composer = JsonFile::parseJson($repoData['data'], $resource); - - if (empty($composer['time'])) { - $resource = $this->getScheme() . '://bitbucket.org/api/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier; - $changeset = JsonFile::parseJson($this->getContents($resource), $resource); - $composer['time'] = $changeset['timestamp']; - } - - if (preg_match('{[a-f0-9]{40}}i', $identifier)) { - $this->cache->write($identifier, json_encode($composer)); - } - - $this->infoCache[$identifier] = $composer; - } - - return $this->infoCache[$identifier]; - } - /** * {@inheritDoc} */ @@ -177,4 +118,24 @@ class HgBitbucketDriver extends VcsDriver return true; } + + protected function setupFallbackDriver($url) + { + $this->fallbackDriver = new HgDriver( + array('url' => $url), + $this->io, + $this->config, + $this->process, + $this->remoteFilesystem + ); + $this->fallbackDriver->initialize(); + } + + /** + * {@inheritdoc} + */ + protected function generateSshUrl() + { + return 'hg@' . $this->originUrl . '/' . $this->owner.'/'.$this->repository; + } } diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 4dc103d38..bad52bed3 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -17,6 +17,7 @@ use Composer\Json\JsonFile; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\IO\IOInterface; +use Symfony\Component\Process\Process; /** * @author Per Bernhardt @@ -114,28 +115,34 @@ class HgDriver extends VcsDriver } /** - * {@inheritDoc} + * {@inheritdoc} */ - public function getComposerInformation($identifier) + public function getFileContent($file, $identifier) { - if (!isset($this->infoCache[$identifier])) { - $this->process->execute(sprintf('hg cat -r %s composer.json', ProcessExecutor::escape($identifier)), $composer, $this->repoDir); - - if (!trim($composer)) { - return; - } + $resource = sprintf('hg cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); + $this->process->execute(sprintf('hg cat -r %s', $resource), $content, $this->repoDir); - $composer = JsonFile::parseJson($composer, $identifier); - - if (empty($composer['time'])) { - $this->process->execute(sprintf('hg log --template "{date|rfc3339date}" -r %s', ProcessExecutor::escape($identifier)), $output, $this->repoDir); - $date = new \DateTime(trim($output), new \DateTimeZone('UTC')); - $composer['time'] = $date->format('Y-m-d H:i:s'); - } - $this->infoCache[$identifier] = $composer; + if (!trim($content)) { + return; } - return $this->infoCache[$identifier]; + return $content; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + $this->process->execute( + sprintf( + 'hg log --template "{date|rfc3339date}" -r %s', + ProcessExecutor::escape($identifier) + ), + $output, + $this->repoDir + ); + return new \DateTime(trim($output), new \DateTimeZone('UTC')); } /** diff --git a/src/Composer/Repository/Vcs/PerforceDriver.php b/src/Composer/Repository/Vcs/PerforceDriver.php index 01d4a52d8..7aaa6efb4 100644 --- a/src/Composer/Repository/Vcs/PerforceDriver.php +++ b/src/Composer/Repository/Vcs/PerforceDriver.php @@ -24,10 +24,8 @@ class PerforceDriver extends VcsDriver { protected $depot; protected $branch; + /** @var Perforce */ protected $perforce; - protected $composerInfo; - protected $composerInfoIdentifier; - /** * {@inheritDoc} */ @@ -59,19 +57,21 @@ class PerforceDriver extends VcsDriver $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io); } + /** - * {@inheritDoc} + * {@inheritdoc} */ - public function getComposerInformation($identifier) + public function getFileContent($file, $identifier) { - if (!empty($this->composerInfoIdentifier)) { - if (strcmp($identifier, $this->composerInfoIdentifier) === 0) { - return $this->composerInfo; - } - } - $composer_info = $this->perforce->getComposerInformation($identifier); + return $this->perforce->getFileContent($file, $identifier); + } - return $composer_info; + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + return null; } /** @@ -138,10 +138,10 @@ class PerforceDriver extends VcsDriver */ public function hasComposerFile($identifier) { - $this->composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier); - $this->composerInfoIdentifier = $identifier; + $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier); + $composerInfoIdentifier = $identifier; - return !empty($this->composerInfo); + return !empty($composerInfo); } /** diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index c602ddaa3..f2d6a4754 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -116,54 +116,80 @@ class SvnDriver extends VcsDriver } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getComposerInformation($identifier) + { + if (!isset($this->infoCache[$identifier])) { + if ($res = $this->cache->read($identifier.'.json')) { + return $this->infoCache[$identifier] = JsonFile::parseJson($res); + } + + $composer = $this->getBaseComposerInformation($identifier); + + $this->cache->write($identifier.'.json', json_encode($composer)); + + $this->infoCache[$identifier] = $composer; + } + + + return $this->infoCache[$identifier]; + } + + /** + * @param string $file + * @param string $identifier + */ + public function getFileContent($file, $identifier) { $identifier = '/' . trim($identifier, '/') . '/'; - if ($res = $this->cache->read($identifier.'.json')) { - $this->infoCache[$identifier] = JsonFile::parseJson($res); + preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match); + if (!empty($match[2])) { + $path = $match[1]; + $rev = $match[2]; + } else { + $path = $identifier; + $rev = ''; } - if (!isset($this->infoCache[$identifier])) { - preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match); - if (!empty($match[2])) { - $path = $match[1]; - $rev = $match[2]; - } else { - $path = $identifier; - $rev = ''; + try { + $resource = $path.$file; + $output = $this->execute('svn cat', $this->baseUrl . $resource . $rev); + if (!trim($output)) { + return null; } + } catch (\RuntimeException $e) { + throw new TransportException($e->getMessage()); + } - try { - $resource = $path.'composer.json'; - $output = $this->execute('svn cat', $this->baseUrl . $resource . $rev); - if (!trim($output)) { - return; - } - } catch (\RuntimeException $e) { - throw new TransportException($e->getMessage()); - } + return $output; + } - $composer = JsonFile::parseJson($output, $this->baseUrl . $resource . $rev); + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + $identifier = '/' . trim($identifier, '/') . '/'; - if (empty($composer['time'])) { - $output = $this->execute('svn info', $this->baseUrl . $path . $rev); - foreach ($this->process->splitLines($output) as $line) { - if ($line && preg_match('{^Last Changed Date: ([^(]+)}', $line, $match)) { - $date = new \DateTime($match[1], new \DateTimeZone('UTC')); - $composer['time'] = $date->format('Y-m-d H:i:s'); - break; - } - } - } + preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match); + if (!empty($match[2])) { + $path = $match[1]; + $rev = $match[2]; + } else { + $path = $identifier; + $rev = ''; + } - $this->cache->write($identifier.'.json', json_encode($composer)); - $this->infoCache[$identifier] = $composer; + $output = $this->execute('svn info', $this->baseUrl . $path . $rev); + foreach ($this->process->splitLines($output) as $line) { + if ($line && preg_match('{^Last Changed Date: ([^(]+)}', $line, $match)) { + return new \DateTime($match[1], new \DateTimeZone('UTC')); + } } - return $this->infoCache[$identifier]; + return null; } /** diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 56f06a580..c72a6c6a8 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -12,10 +12,12 @@ namespace Composer\Repository\Vcs; +use Composer\Cache; use Composer\Downloader\TransportException; use Composer\Config; use Composer\Factory; use Composer\IO\IOInterface; +use Composer\Json\JsonFile; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Composer\Util\Filesystem; @@ -41,6 +43,10 @@ abstract class VcsDriver implements VcsDriverInterface protected $process; /** @var RemoteFilesystem */ protected $remoteFilesystem; + /** @var array */ + protected $infoCache = array(); + /** @var Cache */ + protected $cache; /** * Constructor. @@ -66,6 +72,57 @@ abstract class VcsDriver implements VcsDriverInterface $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); } + /** + * Returns whether or not the given $identifier should be cached or not. + * + * @param string $identifier + * @return boolean + */ + protected function shouldCache($identifier) + { + return $this->cache && preg_match('{[a-f0-9]{40}}i', $identifier); + } + + /** + * {@inheritdoc} + */ + public function getComposerInformation($identifier) + { + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + return $this->infoCache[$identifier] = JsonFile::parseJson($res); + } + + $composer = $this->getBaseComposerInformation($identifier); + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, json_encode($composer)); + } + + $this->infoCache[$identifier] = $composer; + } + + + return $this->infoCache[$identifier]; + } + + protected function getBaseComposerInformation($identifier) + { + $composerFileContent = $this->getFileContent('composer.json', $identifier); + + if (!$composerFileContent) { + return null; + } + + $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json'); + + if (empty($composer['time']) && $changeDate = $this->getChangeDate($identifier)) { + $composer['time'] = $changeDate->format('Y-m-d H:i:s'); + } + + return $composer; + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/VcsDriverInterface.php b/src/Composer/Repository/Vcs/VcsDriverInterface.php index 307d63ef6..5f77b3161 100644 --- a/src/Composer/Repository/Vcs/VcsDriverInterface.php +++ b/src/Composer/Repository/Vcs/VcsDriverInterface.php @@ -33,6 +33,23 @@ interface VcsDriverInterface */ public function getComposerInformation($identifier); + /** + * Return the content of $file or null if the file does not exist. + * + * @param string $file + * @param string $identifier + * @return string + */ + public function getFileContent($file, $identifier); + + /** + * Get the changedate for $identifier. + * + * @param string $identifier + * @return \DateTime + */ + public function getChangeDate($identifier); + /** * Return the root identifier (trunk, master, default/tip ..) * diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index 632238985..f3c20fce3 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -304,7 +304,9 @@ class Perforce public function connectClient() { - $p4CreateClientCommand = $this->generateP4Command('client -i < ' . str_replace(" ", "\\ ", $this->getP4ClientSpec())); + $p4CreateClientCommand = $this->generateP4Command( + 'client -i < ' . str_replace(" ", "\\ ", $this->getP4ClientSpec()) + ); $this->executeCommand($p4CreateClientCommand); } @@ -397,55 +399,54 @@ class Perforce public function getComposerInformation($identifier) { - $index = strpos($identifier, '@'); - if ($index === false) { - $composerJson = $identifier. '/composer.json'; + $composerFileContent = $this->getFileContent('composer.json', $identifier); - return $this->getComposerInformationFromPath($composerJson); + if (!$composerFileContent) { + return; } - return $this->getComposerInformationFromLabel($identifier, $index); + return json_decode($composerFileContent, true); } - public function getComposerInformationFromPath($composerJson) + public function getFileContent($file, $identifier) { - $command = $this->generateP4Command(' print ' . $composerJson); + $path = $this->getFilePath($file, $identifier); + + $command = $this->generateP4Command(' print ' . $path); $this->executeCommand($command); $result = $this->commandResult; - $index = strpos($result, '{'); - if ($index === false) { - return ''; - } - if ($index >= 0) { - $rawData = substr($result, $index); - $composer_info = json_decode($rawData, true); - return $composer_info; + if (!trim($result)) { + return null; } - return ''; + return $result; } - public function getComposerInformationFromLabel($identifier, $index) + public function getFilePath($file, $identifier) { - $composerJsonPath = substr($identifier, 0, $index) . '/composer.json' . substr($identifier, $index); - $command = $this->generateP4Command(' files ' . $composerJsonPath, false); - $this->executeCommand($command); - $result = $this->commandResult; - $index2 = strpos($result, 'no such file(s).'); - if ($index2 === false) { - $index3 = strpos($result, 'change'); - if (!($index3 === false)) { - $phrase = trim(substr($result, $index3)); - $fields = explode(' ', $phrase); - $id = $fields[1]; - $composerJson = substr($identifier, 0, $index) . '/composer.json@' . $id; - - return $this->getComposerInformationFromPath($composerJson); + $index = strpos($identifier, '@'); + if ($index === false) { + $path = $identifier. '/' . $file; + + return $path; + } else { + $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index); + $command = $this->generateP4Command(' files ' . $path, false); + $this->executeCommand($command); + $result = $this->commandResult; + $index2 = strpos($result, 'no such file(s).'); + if ($index2 === false) { + $index3 = strpos($result, 'change'); + if ($index3 !== false) { + $phrase = trim(substr($result, $index3)); + $fields = explode(' ', $phrase); + return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1]; + } } } - return ""; + return null; } public function getBranches()