SvnDownloader.php 6.8 KB

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