SvnDownloader.php 5.7 KB

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