From 597f834ae998ea80797879f4259e8e6accff4a4b Mon Sep 17 00:00:00 2001 From: Thomas Flori Date: Fri, 11 Nov 2016 09:06:03 +0100 Subject: [PATCH] add getFileContent function This function is very similar to a part from getComposerInformation - so we can use this function in getComposerInformation too. And because it is almost everywhere the same we can put it to abstract class. By implementing getComposerInformation in abstract class we need to add the getChangeDate to interface too. Only Problem: perforce seems not to support a ChangeDate. For this we use 'now' to have at least something. --- .../Repository/Vcs/BitbucketDriver.php | 184 ++++++++++++++++++ src/Composer/Repository/Vcs/FossilDriver.php | 34 ++-- .../Repository/Vcs/GitBitbucketDriver.php | 155 +++------------ src/Composer/Repository/Vcs/GitDriver.php | 46 ++--- src/Composer/Repository/Vcs/GitHubDriver.php | 106 ++++++---- src/Composer/Repository/Vcs/GitLabDriver.php | 49 ++++- .../Repository/Vcs/HgBitbucketDriver.php | 73 ++----- src/Composer/Repository/Vcs/HgDriver.php | 34 ++-- .../Repository/Vcs/PerforceDriver.php | 16 ++ src/Composer/Repository/Vcs/SvnDriver.php | 59 ++++++ src/Composer/Repository/Vcs/VcsDriver.php | 28 +++ .../Repository/Vcs/VcsDriverInterface.php | 17 ++ src/Composer/Util/Perforce.php | 42 ++++ 13 files changed, 547 insertions(+), 296 deletions(-) create mode 100644 src/Composer/Repository/Vcs/BitbucketDriver.php diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php new file mode 100644 index 000000000..4e3a55f26 --- /dev/null +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -0,0 +1,184 @@ +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 getComposerInformation($identifier) + { + if ($this->sshDriver) { + return $this->sshDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + + $composer = parent::getComposerInformation($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; + } + + return $this->infoCache[$identifier]; + } + + /** + * {@inheritdoc} + */ + public function getFileContent($file, $identifier) { + if ($this->sshDriver) { + return $this->sshDriver->getFileContent($file, $identifier); + } + + if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier . ':' . $file)) { + return $res; + } + + $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; + } + + if (preg_match('{[a-f0-9]{40}}i', $identifier)) { + $this->cache->write($identifier . ':' . $file, $fileData['data']); + } + + return $fileData['data']; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) { + if ($this->sshDriver) { + return $this->sshDriver->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); + + 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; + } + } + } + + /** + * Generate an SSH URL + * + * @return string + */ + protected function generateSshUrl() + { + return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; + } + + protected function attemptCloneFallback() + { + try { + $this->setupSshDriver($this->generateSshUrl()); + + return; + } catch (\RuntimeException $e) { + $this->sshDriver = 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 setupSshDriver($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); + $command = sprintf('fossil cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); + $this->process->execute($command, $content, $this->checkoutDir); - if (trim($composer) === '') { - return; - } - - $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..03a9fddaf 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -22,43 +22,18 @@ 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->sshDriver) { + return $this->sshDriver->getRootIdentifier(); } if (null === $this->rootIdentifier) { @@ -76,8 +51,8 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface */ public function getUrl() { - if ($this->gitDriver) { - return $this->gitDriver->getUrl(); + if ($this->sshDriver) { + return $this->sshDriver->getUrl(); } return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; @@ -88,8 +63,8 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface */ public function getSource($identifier) { - if ($this->gitDriver) { - return $this->gitDriver->getSource($identifier); + if ($this->sshDriver) { + return $this->sshDriver->getSource($identifier); } return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier); @@ -105,76 +80,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->sshDriver) { + return $this->sshDriver->getTags(); } if (null === $this->tags) { @@ -194,8 +107,8 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface */ public function getBranches() { - if ($this->gitDriver) { - return $this->gitDriver->getBranches(); + if ($this->sshDriver) { + return $this->sshDriver->getBranches(); } if (null === $this->branches) { @@ -228,28 +141,19 @@ 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 + * @param string $url */ - private function generateSshUrl() + protected function setupSshDriver($url) { - return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; + $this->sshDriver = new GitDriver( + array('url' => $url), + $this->io, + $this->config, + $this->process, + $this->remoteFilesystem + ); + $this->sshDriver->initialize(); } /** @@ -284,19 +188,4 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface } } } - - /** - * @param string $url - */ - private function setupGitDriver($url) - { - $this->gitDriver = new GitDriver( - array('url' => $url), - $this->io, - $this->config, - $this->process, - $this->remoteFilesystem - ); - $this->gitDriver->initialize(); - } } diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index bacad4d2c..685088c4b 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -120,38 +120,34 @@ 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 (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier . ':' . $file)) { + return $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); + $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); + $this->process->execute(sprintf('git show %s', $resource), $content, $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'); - } - - 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]; + if (preg_match('{[a-f0-9]{40}}i', $identifier)) { + $this->cache->write($identifier . ':' . $file, $content); + } + + 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..04ac5d7cd 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -146,51 +146,17 @@ 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; - } + $composer = parent::getComposerInformation($identifier); + + // 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 ($composer) { - $composer = JsonFile::parseJson($composer, $resource); - - 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); - } - } - - if ($composer && preg_match('{[a-f0-9]{40}}i', $identifier)) { - $this->cache->write($identifier, json_encode($composer)); + 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; @@ -199,6 +165,62 @@ class GitHubDriver extends VcsDriver return $this->infoCache[$identifier]; } + /** + * {@inheritdoc} + */ + public function getFileContent($file, $identifier) + { + if ($this->gitDriver) { + return $this->gitDriver->getFileContent($file, $identifier); + } + + if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier . ':' . $file)) { + return $res; + } + + $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); + } + + if ($content && preg_match('{[a-f0-9]{40}}i', $identifier)) { + $this->cache->write($identifier . ':' . $file, $content); + } + + 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..2ad00003a 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -33,7 +33,6 @@ class GitLabDriver extends VcsDriver private $repository; private $cache; - private $infoCache = array(); /** * @var array Project data returned by GitLab API @@ -143,6 +142,54 @@ class GitLabDriver extends VcsDriver return $this->infoCache[$identifier] = $composer; } + /** + * {@inheritdoc} + */ + public function getFileContent($file, $identifier) + { + // Convert the root identifier to a cachable commit id + if (!preg_match('{[a-f0-9]{40}}i', $identifier)) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + + if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier . ':' . $file)) { + return $res; + } + + $resource = $this->getApiUrl().'/repository/blobs/'.$identifier.'?filepath=' . $file; + + try { + $content = $this->getContents($resource); + } catch (TransportException $e) { + if ($e->getCode() !== 404) { + throw $e; + } + return null; + } + + if (preg_match('{[a-f0-9]{40}}i', $identifier)) { + $this->cache->write($identifier . ':' . $file, $content); + } + + return $content; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + if (isset($this->commits[$identifier])) { + return new \DateTime($this->commits[$identifier]['committed_date']); + } + + return new \DateTime(); + } + + /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index eb6808601..d2d7cc1be 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/HgBitbucketDriver.php @@ -20,27 +20,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 +34,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 +67,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 +119,15 @@ class HgBitbucketDriver extends VcsDriver return true; } + + protected function setupSshDriver($url) { + $this->sshDriver = new HgDriver( + array('url' => $url), + $this->io, + $this->config, + $this->process, + $this->remoteFilesystem + ); + $this->sshDriver->initialize(); + } } diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 4dc103d38..451cec7ff 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,27 @@ 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); + $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); - if (!trim($composer)) { - return; - } - - $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..744b4264f 100644 --- a/src/Composer/Repository/Vcs/PerforceDriver.php +++ b/src/Composer/Repository/Vcs/PerforceDriver.php @@ -24,6 +24,7 @@ class PerforceDriver extends VcsDriver { protected $depot; protected $branch; + /** @var Perforce */ protected $perforce; protected $composerInfo; protected $composerInfoIdentifier; @@ -74,6 +75,21 @@ class PerforceDriver extends VcsDriver return $composer_info; } + /** + * {@inheritdoc} + */ + public function getFileContent($file, $identifier) + { + return $this->perforce->getFileContent($file, $identifier); + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) { + return new \DateTime(); + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index c602ddaa3..bffe23b46 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -166,6 +166,65 @@ class SvnDriver extends VcsDriver return $this->infoCache[$identifier]; } + /** + * @param string $file + * @param string $identifier + */ + public function getFileContent($file, $identifier) + { + $identifier = '/' . trim($identifier, '/') . '/'; + + if ($res = $this->cache->read($identifier . ':' . $file)) { + return $res; + } + + 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()); + } + + $this->cache->write($identifier . ':' . $file, $output); + + return $output; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) { + $identifier = '/' . trim($identifier, '/') . '/'; + + preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match); + if (!empty($match[2])) { + $path = $match[1]; + $rev = $match[2]; + } else { + $path = $identifier; + $rev = ''; + } + + $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')); + } + } + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 56f06a580..37e9fc01f 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -16,6 +16,7 @@ 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 +42,8 @@ abstract class VcsDriver implements VcsDriverInterface protected $process; /** @var RemoteFilesystem */ protected $remoteFilesystem; + /** @var array */ + protected $infoCache = array(); /** * Constructor. @@ -66,6 +69,31 @@ abstract class VcsDriver implements VcsDriverInterface $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config); } + /** + * {@inheritdoc} + */ + public function getComposerInformation($identifier) + { + if (!isset($this->infoCache[$identifier])) { + $composerFileContent = $this->getFileContent('composer.json', $identifier); + + if (!$composerFileContent) { + return null; + } + + $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json'); + + if (empty($composer['time'])) { + $composer['time'] = $this->getChangeDate($identifier)->format('Y-m-d H:i:s'); + } + + $this->infoCache[$identifier] = $composer; + } + + + return $this->infoCache[$identifier]; + } + /** * {@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..86fd472cd 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -407,6 +407,48 @@ class Perforce return $this->getComposerInformationFromLabel($identifier, $index); } + public function getFileContent($file, $identifier) { + $path = $this->getFilePath($file, $identifier); + + $command = $this->generateP4Command(' print ' . $path); + $this->executeCommand($command); + $result = $this->commandResult; + + if (!trim($result)) { + return null; + } + + return $result; + } + + public function getFilePath($file, $identifier) { + $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); + $id = $fields[1]; + $path = substr($identifier, 0, $index) . '/' . $file . '@' . $id; + + return $path; + } + } + } + + return null; + } + public function getComposerInformationFromPath($composerJson) { $command = $this->generateP4Command(' print ' . $composerJson);