SvnDownloader.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Downloader;
  12. use Composer\Package\PackageInterface;
  13. use Composer\Util\Svn as SvnUtil;
  14. use Composer\Repository\VcsRepository;
  15. use Composer\Util\ProcessExecutor;
  16. /**
  17. * @author Ben Bieker <mail@ben-bieker.de>
  18. * @author Till Klampaeckel <till@php.net>
  19. */
  20. class SvnDownloader extends VcsDownloader
  21. {
  22. protected $cacheCredentials = true;
  23. /**
  24. * {@inheritDoc}
  25. */
  26. protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
  27. {
  28. }
  29. /**
  30. * {@inheritDoc}
  31. */
  32. protected function doInstall(PackageInterface $package, $path, $url)
  33. {
  34. SvnUtil::cleanEnv();
  35. $ref = $package->getSourceReference();
  36. $repo = $package->getRepository();
  37. if ($repo instanceof VcsRepository) {
  38. $repoConfig = $repo->getRepoConfig();
  39. if (array_key_exists('svn-cache-credentials', $repoConfig)) {
  40. $this->cacheCredentials = (bool) $repoConfig['svn-cache-credentials'];
  41. }
  42. }
  43. $this->io->writeError(" Checking out ".$package->getSourceReference());
  44. $this->execute($package, $url, "svn co", sprintf("%s/%s", $url, $ref), null, $path);
  45. }
  46. /**
  47. * {@inheritDoc}
  48. */
  49. protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
  50. {
  51. SvnUtil::cleanEnv();
  52. $ref = $target->getSourceReference();
  53. if (!$this->hasMetadataRepository($path)) {
  54. throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
  55. }
  56. $util = new SvnUtil($url, $this->io, $this->config);
  57. $flags = "";
  58. if (version_compare($util->binaryVersion(), '1.7.0', '>=')) {
  59. $flags .= ' --ignore-ancestry';
  60. }
  61. $this->io->writeError(" Checking out " . $ref);
  62. $this->execute($target, $url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path);
  63. }
  64. /**
  65. * {@inheritDoc}
  66. */
  67. public function getLocalChanges(PackageInterface $package, $path)
  68. {
  69. if (!$this->hasMetadataRepository($path)) {
  70. return null;
  71. }
  72. $this->process->execute('svn status --ignore-externals', $output, $path);
  73. return preg_match('{^ *[^X ] +}m', $output) ? $output : null;
  74. }
  75. /**
  76. * Execute an SVN command and try to fix up the process with credentials
  77. * if necessary.
  78. *
  79. * @param string $baseUrl Base URL of the repository
  80. * @param string $command SVN command to run
  81. * @param string $url SVN url
  82. * @param string $cwd Working directory
  83. * @param string $path Target for a checkout
  84. * @throws \RuntimeException
  85. * @return string
  86. */
  87. protected function execute(PackageInterface $package, $baseUrl, $command, $url, $cwd = null, $path = null)
  88. {
  89. $util = new SvnUtil($baseUrl, $this->io, $this->config);
  90. $util->setCacheCredentials($this->cacheCredentials);
  91. try {
  92. return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
  93. } catch (\RuntimeException $e) {
  94. throw new \RuntimeException(
  95. $package->getPrettyName().' could not be downloaded, '.$e->getMessage()
  96. );
  97. }
  98. }
  99. /**
  100. * {@inheritDoc}
  101. */
  102. protected function cleanChanges(PackageInterface $package, $path, $update)
  103. {
  104. if (!$changes = $this->getLocalChanges($package, $path)) {
  105. return;
  106. }
  107. if (!$this->io->isInteractive()) {
  108. if (true === $this->config->get('discard-changes')) {
  109. return $this->discardChanges($path);
  110. }
  111. return parent::cleanChanges($package, $path, $update);
  112. }
  113. $changes = array_map(function ($elem) {
  114. return ' '.$elem;
  115. }, preg_split('{\s*\r?\n\s*}', $changes));
  116. $countChanges = count($changes);
  117. $this->io->writeError(sprintf(' <error>'.$package->getPrettyName().' has modified file%s:</error>', $countChanges === 1 ? '' : 's'));
  118. $this->io->writeError(array_slice($changes, 0, 10));
  119. if ($countChanges > 10) {
  120. $remainingChanges = $countChanges - 10;
  121. $this->io->writeError(
  122. sprintf(
  123. ' <info>'.$remainingChanges.' more file%s modified, choose "v" to view the full list</info>',
  124. $remainingChanges === 1 ? '' : 's'
  125. )
  126. );
  127. }
  128. while (true) {
  129. switch ($this->io->ask(' <info>Discard changes [y,n,v,?]?</info> ', '?')) {
  130. case 'y':
  131. $this->discardChanges($path);
  132. break 2;
  133. case 'n':
  134. throw new \RuntimeException('Update aborted');
  135. case 'v':
  136. $this->io->writeError($changes);
  137. break;
  138. case '?':
  139. default:
  140. $this->io->writeError(array(
  141. ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'),
  142. ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up',
  143. ' v - view modified files',
  144. ' ? - print help',
  145. ));
  146. break;
  147. }
  148. }
  149. }
  150. /**
  151. * {@inheritDoc}
  152. */
  153. protected function getCommitLogs($fromReference, $toReference, $path)
  154. {
  155. if (preg_match('{@(\d+)$}', $fromReference) && preg_match('{@(\d+)$}', $toReference)) {
  156. // retrieve the svn base url from the checkout folder
  157. $command = sprintf('svn info --non-interactive --xml %s', ProcessExecutor::escape($path));
  158. if (0 !== $this->process->execute($command, $output, $path)) {
  159. throw new \RuntimeException(
  160. 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()
  161. );
  162. }
  163. $urlPattern = '#<url>(.*)</url>#';
  164. if (preg_match($urlPattern, $output, $matches)) {
  165. $baseUrl = $matches[1];
  166. } else {
  167. throw new \RuntimeException(
  168. 'Unable to determine svn url for path '. $path
  169. );
  170. }
  171. // strip paths from references and only keep the actual revision
  172. $fromRevision = preg_replace('{.*@(\d+)$}', '$1', $fromReference);
  173. $toRevision = preg_replace('{.*@(\d+)$}', '$1', $toReference);
  174. $command = sprintf('svn log -r%s:%s --incremental', ProcessExecutor::escape($fromRevision), ProcessExecutor::escape($toRevision));
  175. $util = new SvnUtil($baseUrl, $this->io, $this->config);
  176. $util->setCacheCredentials($this->cacheCredentials);
  177. try {
  178. return $util->executeLocal($command, $path, null, $this->io->isVerbose());
  179. } catch (\RuntimeException $e) {
  180. throw new \RuntimeException(
  181. 'Failed to execute ' . $command . "\n\n".$e->getMessage()
  182. );
  183. }
  184. }
  185. return "Could not retrieve changes between $fromReference and $toReference due to missing revision information";
  186. }
  187. protected function discardChanges($path)
  188. {
  189. if (0 !== $this->process->execute('svn revert -R .', $output, $path)) {
  190. throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
  191. }
  192. }
  193. /**
  194. * {@inheritDoc}
  195. */
  196. protected function hasMetadataRepository($path)
  197. {
  198. return is_dir($path.'/.svn');
  199. }
  200. }