|
@@ -4,10 +4,12 @@ namespace Composer\Repository\Vcs;
|
|
|
|
|
|
use Composer\Json\JsonFile;
|
|
|
use Composer\Util\ProcessExecutor;
|
|
|
+use Composer\Util\Svn as SvnUtil;
|
|
|
use Composer\IO\IOInterface;
|
|
|
|
|
|
/**
|
|
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
|
|
+ * @author Till Klampaeckel <till@php.net>
|
|
|
*/
|
|
|
class SvnDriver extends VcsDriver
|
|
|
{
|
|
@@ -17,29 +19,93 @@ class SvnDriver extends VcsDriver
|
|
|
protected $infoCache = array();
|
|
|
|
|
|
/**
|
|
|
- * @var boolean $useAuth Contains credentials, or not?
|
|
|
+ * Contains credentials, or not?
|
|
|
+ * @var boolean
|
|
|
*/
|
|
|
protected $useAuth = false;
|
|
|
|
|
|
/**
|
|
|
- * @var string $svnUsername
|
|
|
+ * To determine if we should cache the credentials supplied by the user. By default: no cache.
|
|
|
+ * @var boolean
|
|
|
+ */
|
|
|
+ protected $useCache = false;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var string
|
|
|
*/
|
|
|
protected $svnUsername = '';
|
|
|
|
|
|
/**
|
|
|
- * @var string $svnPassword
|
|
|
+ * @var string
|
|
|
*/
|
|
|
protected $svnPassword = '';
|
|
|
|
|
|
+ /**
|
|
|
+ * @var \Composer\Util\Svn
|
|
|
+ */
|
|
|
+ protected $util;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param string $url
|
|
|
+ * @param IOInterface $io
|
|
|
+ * @param ProcessExecutor $process
|
|
|
+ *
|
|
|
+ * @return $this
|
|
|
+ */
|
|
|
public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
|
|
|
{
|
|
|
+ $url = self::fixSvnUrl($url);
|
|
|
parent::__construct($this->baseUrl = rtrim($url, '/'), $io, $process);
|
|
|
|
|
|
if (false !== ($pos = strrpos($url, '/trunk'))) {
|
|
|
$this->baseUrl = substr($url, 0, $pos);
|
|
|
}
|
|
|
+ $this->util = new SvnUtil($this->baseUrl, $io);
|
|
|
+ $this->useAuth = $this->util->hasAuth();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Execute an SVN command and try to fix up the process with credentials
|
|
|
+ * if necessary. The command is 'fixed up' with {@link self::getSvnCommand()}.
|
|
|
+ *
|
|
|
+ * @param string $command The svn command to run.
|
|
|
+ * @param string $url The SVN URL.
|
|
|
+ *
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ public function execute($command, $url)
|
|
|
+ {
|
|
|
+ $svnCommand = $this->util->getCommand($command, $url);
|
|
|
+
|
|
|
+ $status = $this->process->execute(
|
|
|
+ $svnCommand,
|
|
|
+ $output
|
|
|
+ );
|
|
|
+
|
|
|
+ if (0 === $status) {
|
|
|
+ return $output;
|
|
|
+ }
|
|
|
|
|
|
- $this->detectSvnAuth();
|
|
|
+ // this could be any failure, since SVN exits with 1 always
|
|
|
+ if (!$this->io->isInteractive()) {
|
|
|
+ return $output;
|
|
|
+ }
|
|
|
+
|
|
|
+ // the error is not auth-related
|
|
|
+ if (strpos($output, 'authorization failed:') === false) {
|
|
|
+ return $output;
|
|
|
+ }
|
|
|
+
|
|
|
+ // no authorization has been detected so far
|
|
|
+ if (!$this->useAuth) {
|
|
|
+ $this->useAuth = $this->util->doAuthDance()->hasAuth();
|
|
|
+
|
|
|
+ // restart the process
|
|
|
+ $output = $this->execute($command, $url);
|
|
|
+ } else {
|
|
|
+ $this->io->write("Authorization failed: {$svnCommand}");
|
|
|
+ }
|
|
|
+ return $output;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -98,30 +164,15 @@ class SvnDriver extends VcsDriver
|
|
|
$rev = '';
|
|
|
}
|
|
|
|
|
|
- $this->process->execute(
|
|
|
- sprintf(
|
|
|
- 'svn cat --non-interactive %s %s',
|
|
|
- $this->getSvnCredentialString(),
|
|
|
- escapeshellarg($this->baseUrl.$identifier.'composer.json'.$rev)
|
|
|
- ),
|
|
|
- $composer
|
|
|
- );
|
|
|
-
|
|
|
- if (!trim($composer)) {
|
|
|
+ $output = $this->execute('svn cat', $this->baseUrl . $identifier . 'composer.json' . $rev);
|
|
|
+ if (!trim($output)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- $composer = JsonFile::parseJson($composer);
|
|
|
+ $composer = JsonFile::parseJson($output);
|
|
|
|
|
|
if (!isset($composer['time'])) {
|
|
|
- $this->process->execute(
|
|
|
- sprintf(
|
|
|
- 'svn info %s %s',
|
|
|
- $this->getSvnCredentialString(),
|
|
|
- escapeshellarg($this->baseUrl.$identifier.$rev)
|
|
|
- ),
|
|
|
- $output
|
|
|
- );
|
|
|
+ $output = $this->execute('svn info', $this->baseUrl . $identifier . $rev);
|
|
|
foreach ($this->process->splitLines($output) as $line) {
|
|
|
if ($line && preg_match('{^Last Changed Date: ([^(]+)}', $line, $match)) {
|
|
|
$date = new \DateTime($match[1]);
|
|
@@ -142,18 +193,14 @@ class SvnDriver extends VcsDriver
|
|
|
public function getTags()
|
|
|
{
|
|
|
if (null === $this->tags) {
|
|
|
- $this->process->execute(
|
|
|
- sprintf(
|
|
|
- 'svn ls --non-interactive %s %s',
|
|
|
- $this->getSvnCredentialString(),
|
|
|
- escapeshellarg($this->baseUrl.'/tags')
|
|
|
- ),
|
|
|
- $output
|
|
|
- );
|
|
|
$this->tags = array();
|
|
|
- foreach ($this->process->splitLines($output) as $tag) {
|
|
|
- if ($tag) {
|
|
|
- $this->tags[rtrim($tag, '/')] = '/tags/'.$tag;
|
|
|
+
|
|
|
+ $output = $this->execute('svn ls', $this->baseUrl . '/tags');
|
|
|
+ if ($output) {
|
|
|
+ foreach ($this->process->splitLines($output) as $tag) {
|
|
|
+ if ($tag) {
|
|
|
+ $this->tags[rtrim($tag, '/')] = '/tags/'.$tag;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -167,71 +214,45 @@ class SvnDriver extends VcsDriver
|
|
|
public function getBranches()
|
|
|
{
|
|
|
if (null === $this->branches) {
|
|
|
- $this->process->execute(
|
|
|
- sprintf(
|
|
|
- 'svn ls --verbose --non-interactive %s %s',
|
|
|
- $this->getSvnCredentialString(),
|
|
|
- escapeshellarg($this->baseUrl.'/')
|
|
|
- ),
|
|
|
- $output
|
|
|
- );
|
|
|
-
|
|
|
$this->branches = array();
|
|
|
- foreach ($this->process->splitLines($output) as $line) {
|
|
|
- preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match);
|
|
|
- if ($match[2] === 'trunk/') {
|
|
|
- $this->branches['trunk'] = '/trunk/@'.$match[1];
|
|
|
- break;
|
|
|
+
|
|
|
+ $output = $this->execute('svn ls --verbose', $this->baseUrl . '/');
|
|
|
+ if ($output) {
|
|
|
+ foreach ($this->process->splitLines($output) as $line) {
|
|
|
+ $line = trim($line);
|
|
|
+ if ($line && preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
|
|
|
+ if (isset($match[1]) && isset($match[2]) && $match[2] === 'trunk/') {
|
|
|
+ $this->branches['trunk'] = '/trunk/@'.$match[1];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
unset($output);
|
|
|
|
|
|
- $this->process->execute(
|
|
|
- sprintf(
|
|
|
- 'svn ls --verbose --non-interactive %s',
|
|
|
- $this->getSvnCredentialString(),
|
|
|
- escapeshellarg($this->baseUrl.'/branches')
|
|
|
- ),
|
|
|
- $output
|
|
|
- );
|
|
|
- foreach ($this->process->splitLines(trim($output)) as $line) {
|
|
|
- preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match);
|
|
|
- if ($match[2] === './') {
|
|
|
- continue;
|
|
|
+ $output = $this->execute('svn ls --verbose', $this->baseUrl . '/branches');
|
|
|
+ if ($output) {
|
|
|
+ foreach ($this->process->splitLines(trim($output)) as $line) {
|
|
|
+ $line = trim($line);
|
|
|
+ if ($line && preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
|
|
|
+ if (isset($match[1]) && isset($match[2]) && $match[2] !== './') {
|
|
|
+ $this->branches[rtrim($match[2], '/')] = '/branches/'.$match[2].'@'.$match[1];
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- $this->branches[rtrim($match[2], '/')] = '/branches/'.$match[2].'@'.$match[1];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $this->branches;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Return the credential string for the svn command.
|
|
|
- *
|
|
|
- * --no-auth-cache when credentials are present
|
|
|
- *
|
|
|
- * @return string
|
|
|
- */
|
|
|
- public function getSvnCredentialString()
|
|
|
- {
|
|
|
- if ($this->useAuth !== true) {
|
|
|
- return '';
|
|
|
- }
|
|
|
- $str = ' --no-auth-cache --username %s --password %s ';
|
|
|
- return sprintf(
|
|
|
- $str,
|
|
|
- escapeshellarg($this->svnUsername),
|
|
|
- escapeshellarg($this->svnPassword)
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* {@inheritDoc}
|
|
|
*/
|
|
|
public static function supports($url, $deep = false)
|
|
|
{
|
|
|
- if (preg_match('#(^svn://|//svn\.)#i', $url)) {
|
|
|
+ $url = self::fixSvnUrl($url);
|
|
|
+ if (preg_match('#((^svn://)|(^svn\+ssh://)|(^file:///)|(^http)|(svn\.))#i', $url)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
@@ -242,37 +263,34 @@ class SvnDriver extends VcsDriver
|
|
|
$processExecutor = new ProcessExecutor();
|
|
|
|
|
|
$exit = $processExecutor->execute(
|
|
|
- sprintf(
|
|
|
- 'svn info --non-interactive %s %s 2>/dev/null',
|
|
|
- $this->getSvnCredentialString(),
|
|
|
- escapeshellarg($url)
|
|
|
- ),
|
|
|
- $ignored
|
|
|
+ "svn info --non-interactive {$url}",
|
|
|
+ $ignoredOutput
|
|
|
);
|
|
|
- return $exit === 0;
|
|
|
+
|
|
|
+ if ($exit === 0) {
|
|
|
+ // This is definitely a Subversion repository.
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (preg_match('/authorization failed/i', $processExecutor->getErrorOutput())) {
|
|
|
+ // This is likely a remote Subversion repository that requires
|
|
|
+ // authentication. We will handle actual authentication later.
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * This is quick and dirty - thoughts?
|
|
|
+ * An absolute path (leading '/') is converted to a file:// url.
|
|
|
+ *
|
|
|
+ * @param string $url
|
|
|
*
|
|
|
- * @return void
|
|
|
- * @uses parent::$baseUrl
|
|
|
- * @uses self::$useAuth, self::$svnUsername, self::$svnPassword
|
|
|
- * @see self::__construct()
|
|
|
+ * @return string
|
|
|
*/
|
|
|
- protected function detectSvnAuth()
|
|
|
+ protected static function fixSvnUrl($url)
|
|
|
{
|
|
|
- $uri = parse_url($this->baseUrl);
|
|
|
- if (empty($uri['user'])) {
|
|
|
- return;
|
|
|
+ if (strpos($url, '/', 0) === 0) {
|
|
|
+ $url = 'file://' . $url;
|
|
|
}
|
|
|
-
|
|
|
- $this->svnUsername = $uri['user'];
|
|
|
-
|
|
|
- if (!empty($uri['pass'])) {
|
|
|
- $this->svnPassword = $uri['pass'];
|
|
|
- }
|
|
|
-
|
|
|
- $this->useAuth = true;
|
|
|
+ return $url;
|
|
|
}
|
|
|
}
|