VcsDownloader.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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\Config;
  13. use Composer\Package\PackageInterface;
  14. use Composer\Util\ProcessExecutor;
  15. use Composer\IO\IOInterface;
  16. use Composer\Util\Filesystem;
  17. /**
  18. * @author Jordi Boggiano <j.boggiano@seld.be>
  19. */
  20. abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface
  21. {
  22. protected $io;
  23. protected $config;
  24. protected $process;
  25. protected $filesystem;
  26. public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
  27. {
  28. $this->io = $io;
  29. $this->config = $config;
  30. $this->process = $process ?: new ProcessExecutor($io);
  31. $this->filesystem = $fs ?: new Filesystem;
  32. }
  33. /**
  34. * {@inheritDoc}
  35. */
  36. public function getInstallationSource()
  37. {
  38. return 'source';
  39. }
  40. /**
  41. * {@inheritDoc}
  42. */
  43. public function download(PackageInterface $package, $path)
  44. {
  45. if (!$package->getSourceReference()) {
  46. throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
  47. }
  48. $this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
  49. $this->filesystem->emptyDirectory($path);
  50. $urls = $package->getSourceUrls();
  51. while ($url = array_shift($urls)) {
  52. try {
  53. if (Filesystem::isLocalPath($url)) {
  54. $url = realpath($url);
  55. }
  56. $this->doDownload($package, $path, $url);
  57. break;
  58. } catch (\Exception $e) {
  59. if ($this->io->isDebug()) {
  60. $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage());
  61. } elseif (count($urls)) {
  62. $this->io->writeError(' Failed, trying the next URL');
  63. }
  64. if (!count($urls)) {
  65. throw $e;
  66. }
  67. }
  68. }
  69. $this->io->writeError('');
  70. }
  71. /**
  72. * {@inheritDoc}
  73. */
  74. public function update(PackageInterface $initial, PackageInterface $target, $path)
  75. {
  76. if (!$target->getSourceReference()) {
  77. throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information');
  78. }
  79. $name = $target->getName();
  80. if ($initial->getPrettyVersion() == $target->getPrettyVersion()) {
  81. if ($target->getSourceType() === 'svn') {
  82. $from = $initial->getSourceReference();
  83. $to = $target->getSourceReference();
  84. } else {
  85. $from = substr($initial->getSourceReference(), 0, 7);
  86. $to = substr($target->getSourceReference(), 0, 7);
  87. }
  88. $name .= ' '.$initial->getPrettyVersion();
  89. } else {
  90. $from = $initial->getFullPrettyVersion();
  91. $to = $target->getFullPrettyVersion();
  92. }
  93. $this->io->writeError(" - Updating <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");
  94. $this->cleanChanges($initial, $path, true);
  95. $urls = $target->getSourceUrls();
  96. while ($url = array_shift($urls)) {
  97. try {
  98. if (Filesystem::isLocalPath($url)) {
  99. $url = realpath($url);
  100. }
  101. $this->doUpdate($initial, $target, $path, $url);
  102. break;
  103. } catch (\Exception $e) {
  104. if ($this->io->isDebug()) {
  105. $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage());
  106. } elseif (count($urls)) {
  107. $this->io->writeError(' Failed, trying the next URL');
  108. } else {
  109. // in case of failed update, try to reapply the changes before aborting
  110. $this->reapplyChanges($path);
  111. throw $e;
  112. }
  113. }
  114. }
  115. $this->reapplyChanges($path);
  116. // print the commit logs if in verbose mode
  117. if ($this->io->isVerbose()) {
  118. $message = 'Pulling in changes:';
  119. $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path);
  120. if (!trim($logs)) {
  121. $message = 'Rolling back changes:';
  122. $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path);
  123. }
  124. if (trim($logs)) {
  125. $logs = implode("\n", array_map(function ($line) {
  126. return ' ' . $line;
  127. }, explode("\n", $logs)));
  128. $this->io->writeError(' '.$message);
  129. $this->io->writeError($logs);
  130. }
  131. }
  132. $this->io->writeError('');
  133. }
  134. /**
  135. * {@inheritDoc}
  136. */
  137. public function remove(PackageInterface $package, $path)
  138. {
  139. $this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
  140. $this->cleanChanges($package, $path, false);
  141. if (!$this->filesystem->removeDirectory($path)) {
  142. throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
  143. }
  144. }
  145. /**
  146. * Download progress information is not available for all VCS downloaders.
  147. * {@inheritDoc}
  148. */
  149. public function setOutputProgress($outputProgress)
  150. {
  151. return $this;
  152. }
  153. /**
  154. * Prompt the user to check if changes should be stashed/removed or the operation aborted
  155. *
  156. * @param PackageInterface $package
  157. * @param string $path
  158. * @param bool $update if true (update) the changes can be stashed and reapplied after an update,
  159. * if false (remove) the changes should be assumed to be lost if the operation is not aborted
  160. * @throws \RuntimeException in case the operation must be aborted
  161. */
  162. protected function cleanChanges(PackageInterface $package, $path, $update)
  163. {
  164. // the default implementation just fails if there are any changes, override in child classes to provide stash-ability
  165. if (null !== $this->getLocalChanges($package, $path)) {
  166. throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.');
  167. }
  168. }
  169. /**
  170. * Guarantee that no changes have been made to the local copy
  171. *
  172. * @param string $path
  173. * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly
  174. */
  175. protected function reapplyChanges($path)
  176. {
  177. }
  178. /**
  179. * Downloads specific package into specific folder.
  180. *
  181. * @param PackageInterface $package package instance
  182. * @param string $path download path
  183. * @param string $url package url
  184. */
  185. abstract protected function doDownload(PackageInterface $package, $path, $url);
  186. /**
  187. * Updates specific package in specific folder from initial to target version.
  188. *
  189. * @param PackageInterface $initial initial package
  190. * @param PackageInterface $target updated package
  191. * @param string $path download path
  192. * @param string $url package url
  193. */
  194. abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url);
  195. /**
  196. * Fetches the commit logs between two commits
  197. *
  198. * @param string $fromReference the source reference
  199. * @param string $toReference the target reference
  200. * @param string $path the package path
  201. * @return string
  202. */
  203. abstract protected function getCommitLogs($fromReference, $toReference, $path);
  204. }