* 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\Json\JsonFile; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; use Composer\IO\IOInterface; /** * @author Per Bernhardt */ class HgDriver extends VcsDriver { protected $tags; protected $branches; protected $rootIdentifier; protected $repoDir; protected $infoCache = array(); /** * {@inheritDoc} */ public function initialize() { if (static::isLocalUrl($this->url)) { $this->repoDir = str_replace('file://', '', $this->url); } else { $this->repoDir = $this->config->get('home') . '/cache.hg/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '/'; $fs = new Filesystem(); $fs->ensureDirectoryExists(dirname($this->repoDir)); if (!is_writable(dirname($this->repoDir))) { throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.dirname($this->repoDir).'" directory is not writable by the current user.'); } // update the repo if it is a valid hg repository if (is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) { if (0 !== $this->process->execute('hg pull -u', $output, $this->repoDir)) { $this->io->write('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); } } else { // clean up directory and do a fresh clone into it $fs->removeDirectory($this->repoDir); if (0 !== $this->process->execute(sprintf('hg clone %s %s', escapeshellarg($this->url), escapeshellarg($this->repoDir)), $output, $cacheDir)) { $output = $this->process->getErrorOutput(); if (0 !== $this->process->execute('hg --version', $ignoredOutput)) { throw new \RuntimeException('Failed to clone '.$this->url.', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); } throw new \RuntimeException('Failed to clone '.$this->url.', could not read packages from it' . "\n\n" .$output); } } } $this->getTags(); $this->getBranches(); } /** * {@inheritDoc} */ public function getRootIdentifier() { if (null === $this->rootIdentifier) { $this->process->execute(sprintf('hg tip --template "{node}"'), $output, $this->repoDir); $output = $this->process->splitLines($output); $this->rootIdentifier = $output[0]; } return $this->rootIdentifier; } /** * {@inheritDoc} */ public function getUrl() { return $this->url; } /** * {@inheritDoc} */ public function getSource($identifier) { $label = array_search($identifier, (array) $this->tags) ? : $identifier; return array('type' => 'hg', 'url' => $this->getUrl(), 'reference' => $label); } /** * {@inheritDoc} */ public function getDist($identifier) { return null; } /** * {@inheritDoc} */ public function getComposerInformation($identifier) { if (!isset($this->infoCache[$identifier])) { $this->process->execute(sprintf('hg cat -r %s composer.json', escapeshellarg($identifier)), $composer, $this->repoDir); if (!trim($composer)) { return; } $composer = JsonFile::parseJson($composer, $identifier); if (!isset($composer['time'])) { $this->process->execute(sprintf('hg log --template "{date|rfc822date}" -r %s', escapeshellarg($identifier)), $output, $this->repoDir); $date = new \DateTime(trim($output)); $composer['time'] = $date->format('Y-m-d H:i:s'); } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } /** * {@inheritDoc} */ public function getTags() { if (null === $this->tags) { $tags = array(); $this->process->execute('hg tags', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $tag) { if ($tag && preg_match('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) { $tags[$match[1]] = $match[2]; } } unset($tags['tip']); $this->tags = $tags; } return $this->tags; } /** * {@inheritDoc} */ public function getBranches() { if (null === $this->branches) { $branches = array(); $bookmarks = array(); $this->process->execute('hg branches', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && preg_match('(^([^\s]+)\s+\d+:(.*)$)', $branch, $match)) { $branches[$match[1]] = $match[2]; } } $this->process->execute('hg bookmarks', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && preg_match('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match)) { $bookmarks[$match[1]] = $match[2]; } } // Branches will have preference over bookmarks $this->branches = array_merge($bookmarks, $branches); } return $this->branches; } /** * {@inheritDoc} */ public static function supports(IOInterface $io, $url, $deep = false) { if (preg_match('#(^(?:https?|ssh)://(?:[^@]@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) { return true; } // local filesystem if (static::isLocalUrl($url)) { if (!is_dir($url)) { throw new \RuntimeException('Directory does not exist: '.$url); } $process = new ProcessExecutor(); $url = str_replace('file://', '', $url); // check whether there is a hg repo in that path if ($process->execute('hg summary', $output, $url) === 0) { return true; } } if (!$deep) { return false; } $processExecutor = new ProcessExecutor(); $exit = $processExecutor->execute(sprintf('hg identify %s', escapeshellarg($url)), $ignored); return $exit === 0; } }