SvnDriver.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. <?php
  2. namespace Composer\Repository\Vcs;
  3. use Composer\Json\JsonFile;
  4. use Composer\Util\ProcessExecutor;
  5. use Composer\IO\IOInterface;
  6. /**
  7. * @author Jordi Boggiano <j.boggiano@seld.be>
  8. */
  9. class SvnDriver extends VcsDriver implements VcsDriverInterface
  10. {
  11. protected $baseUrl;
  12. protected $tags;
  13. protected $branches;
  14. protected $infoCache = array();
  15. /**
  16. * @var boolean $useAuth Contains credentials, or not?
  17. */
  18. protected $useAuth = false;
  19. /**
  20. * @var boolean $useCache To determine if we should cache the credentials
  21. * supplied by the user. By default: no cache.
  22. * @see self::getSvnAuthCache()
  23. */
  24. protected $useCache = false;
  25. /**
  26. * @var string $svnUsername
  27. */
  28. protected $svnUsername = '';
  29. /**
  30. * @var string $svnPassword
  31. */
  32. protected $svnPassword = '';
  33. /**
  34. * __construct
  35. *
  36. * @param string $url
  37. * @param IOInterface $io
  38. * @param ProcessExecutor $process
  39. *
  40. * @return $this
  41. * @uses self::detectSvnAuth()
  42. */
  43. public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
  44. {
  45. parent::__construct($this->baseUrl = rtrim($url, '/'), $io, $process);
  46. if (false !== ($pos = strrpos($url, '/trunk'))) {
  47. $this->baseUrl = substr($url, 0, $pos);
  48. }
  49. $this->detectSvnAuth();
  50. }
  51. /**
  52. * Execute an SVN command and try to fix up the process with credentials
  53. * if necessary. The command is 'fixed up' with {@link self::getSvnCommand()}.
  54. *
  55. * @param string $command The svn command to run.
  56. * @param string $url The SVN URL.
  57. *
  58. * @return string
  59. * @uses self::getSvnCommand()
  60. * @uses parent::$process
  61. * @see ProcessExecutor::execute()
  62. */
  63. public function execute($command, $url)
  64. {
  65. $svnCommand = $this->getSvnCommand($command, $url);
  66. $status = $this->process->execute(
  67. $svnCommand,
  68. $output
  69. );
  70. // this could be any failure
  71. if ($status > 0 && $this->io->isInteractive()) {
  72. // the error is not auth-related
  73. if (strpos($output, 'authorization failed:') === false) {
  74. return $output;
  75. }
  76. // no authorization has been detected so far
  77. if (!$this->useAuth) {
  78. $this->io->write("The Subversion server ({$this->baseUrl}) requested credentials:");
  79. $this->svnUsername = $this->io->ask("Username");
  80. $this->svnPassword = $this->io->ask("Password");
  81. $this->useAuth = true;
  82. static $cacheTrueAnswers = array('yes', 'y', 'true', 'ja', 'si', 'da');
  83. $cacheAnswer = strtolower(trim($this->io->ask("Should Subversion cache these credentials?", 'no')));
  84. if (in_array($cacheAnswer, $cacheTrueAnswers)) {
  85. $this->useCache = true;
  86. }
  87. // restart the process
  88. $output = $this->execute($command, $url);
  89. } else {
  90. $this->io->write("Authorization failed: {$svnCommand}");
  91. }
  92. }
  93. return $output;
  94. }
  95. /**
  96. * {@inheritDoc}
  97. */
  98. public function initialize()
  99. {
  100. $this->getBranches();
  101. $this->getTags();
  102. }
  103. /**
  104. * {@inheritDoc}
  105. */
  106. public function getRootIdentifier()
  107. {
  108. return 'trunk';
  109. }
  110. /**
  111. * {@inheritDoc}
  112. */
  113. public function getUrl()
  114. {
  115. return $this->url;
  116. }
  117. /**
  118. * {@inheritDoc}
  119. */
  120. public function getSource($identifier)
  121. {
  122. return array('type' => 'svn', 'url' => $this->baseUrl, 'reference' => $identifier);
  123. }
  124. /**
  125. * {@inheritDoc}
  126. */
  127. public function getDist($identifier)
  128. {
  129. return null;
  130. }
  131. /**
  132. * {@inheritDoc}
  133. */
  134. public function getComposerInformation($identifier)
  135. {
  136. $identifier = '/' . trim($identifier, '/') . '/';
  137. if (!isset($this->infoCache[$identifier])) {
  138. preg_match('{^(.+?)(@\d+)?$}', $identifier, $match);
  139. if (!empty($match[2])) {
  140. $identifier = $match[1];
  141. $rev = $match[2];
  142. } else {
  143. $rev = '';
  144. }
  145. $output = $this->execute('svn cat', $this->baseUrl . $identifier . 'composer.json' . $rev);
  146. if (!trim($output)) {
  147. throw new \UnexpectedValueException(
  148. 'Failed to retrieve composer information for identifier ' . $identifier . ' in ' . $this->getUrl()
  149. );
  150. }
  151. $composer = JsonFile::parseJson($output);
  152. if (!isset($composer['time'])) {
  153. $output = $this->execute('svn info', $this->baseUrl . $identifier . $rev);
  154. foreach ($this->process->splitLines($output) as $line) {
  155. if ($line && preg_match('{^Last Changed Date: ([^(]+)}', $line, $match)) {
  156. $date = new \DateTime($match[1]);
  157. $composer['time'] = $date->format('Y-m-d H:i:s');
  158. break;
  159. }
  160. }
  161. }
  162. $this->infoCache[$identifier] = $composer;
  163. }
  164. return $this->infoCache[$identifier];
  165. }
  166. /**
  167. * {@inheritDoc}
  168. */
  169. public function getTags()
  170. {
  171. if (null === $this->tags) {
  172. $output = $this->execute('svn ls', $this->baseUrl . '/tags');
  173. $this->tags = array();
  174. foreach ($this->process->splitLines($output) as $tag) {
  175. if ($tag) {
  176. $this->tags[rtrim($tag, '/')] = '/tags/'.$tag;
  177. }
  178. }
  179. }
  180. return $this->tags;
  181. }
  182. /**
  183. * {@inheritDoc}
  184. */
  185. public function getBranches()
  186. {
  187. if (null === $this->branches) {
  188. $output = $this->execute('svn ls --verbose', $this->baseUrl . '/');
  189. $this->branches = array();
  190. foreach ($this->process->splitLines($output) as $line) {
  191. preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match);
  192. if ($match[2] === 'trunk/') {
  193. $this->branches['trunk'] = '/trunk/@'.$match[1];
  194. break;
  195. }
  196. }
  197. unset($output);
  198. $output = $this->execute('svn ls --verbose', $this->baseUrl . '/branches');
  199. foreach ($this->process->splitLines(trim($output)) as $line) {
  200. preg_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match);
  201. if ($match[2] === './') {
  202. continue;
  203. }
  204. $this->branches[rtrim($match[2], '/')] = '/branches/'.$match[2].'@'.$match[1];
  205. }
  206. }
  207. return $this->branches;
  208. }
  209. /**
  210. * Return the no-auth-cache switch.
  211. *
  212. * @return string
  213. */
  214. public function getSvnAuthCache()
  215. {
  216. if (!$this->useCache) {
  217. return '--no-auth-cache ';
  218. }
  219. return '';
  220. }
  221. /**
  222. * A method to create the svn commands run.
  223. *
  224. * @string $cmd Usually 'svn ls' or something like that.
  225. * @string $url Repo URL.
  226. * @string $pipe Optional pipe for the output.
  227. *
  228. * @return string
  229. */
  230. public function getSvnCommand($cmd, $url, $pipe = null)
  231. {
  232. $cmd = sprintf('%s %s%s %s',
  233. $cmd,
  234. $this->getSvnInteractiveSetting(),
  235. $this->getSvnCredentialString(),
  236. escapeshellarg($url)
  237. );
  238. if ($pipe !== null) {
  239. $cmd .= ' ' . $pipe;
  240. }
  241. return $cmd;
  242. }
  243. /**
  244. * Return the credential string for the svn command.
  245. *
  246. * Adds --no-auth-cache when credentials are present.
  247. *
  248. * @return string
  249. * @uses self::$useAuth
  250. */
  251. public function getSvnCredentialString()
  252. {
  253. if ($this->useAuth !== true) {
  254. return '';
  255. }
  256. $str = ' %s--username %s --password %s ';
  257. return sprintf(
  258. $str,
  259. $this->getSvnAuthCache(),
  260. escapeshellarg($this->svnUsername),
  261. escapeshellarg($this->svnPassword)
  262. );
  263. }
  264. /**
  265. * Always run commands 'non-interactive':
  266. *
  267. * It's easier to spot potential issues (e.g. auth-failure) because
  268. * non-interactive svn fails fast and does not wait for user input.
  269. *
  270. * @return string
  271. */
  272. public function getSvnInteractiveSetting()
  273. {
  274. return '--non-interactive ';
  275. }
  276. /**
  277. * {@inheritDoc}
  278. */
  279. public function hasComposerFile($identifier)
  280. {
  281. try {
  282. $this->getComposerInformation($identifier);
  283. return true;
  284. } catch (\Exception $e) {
  285. }
  286. return false;
  287. }
  288. /**
  289. * {@inheritDoc}
  290. */
  291. public static function supports($url, $deep = false)
  292. {
  293. if (preg_match('#(^svn://|//svn\.)#i', $url)) {
  294. return true;
  295. }
  296. if (!$deep) {
  297. return false;
  298. }
  299. $processExecutor = new ProcessExecutor();
  300. $exit = $processExecutor->execute(
  301. $this->getSvnCommand('svn info', $url, '2>/dev/null'),
  302. $ignoredOutput
  303. );
  304. return $exit === 0;
  305. }
  306. /**
  307. * This is quick and dirty - thoughts?
  308. *
  309. * @return void
  310. * @uses parent::$baseUrl
  311. * @uses self::$useAuth, self::$svnUsername, self::$svnPassword
  312. * @see self::__construct()
  313. */
  314. protected function detectSvnAuth()
  315. {
  316. $uri = parse_url($this->baseUrl);
  317. if (empty($uri['user'])) {
  318. return;
  319. }
  320. $this->svnUsername = $uri['user'];
  321. if (!empty($uri['pass'])) {
  322. $this->svnPassword = $uri['pass'];
  323. }
  324. $this->useAuth = true;
  325. }
  326. }