PageRenderTime 27ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/composer/composer/src/Composer/Downloader/GitDownloader.php

https://gitlab.com/yousafsyed/easternglamor
PHP | 345 lines | 243 code | 48 blank | 54 comment | 42 complexity | e47b6aa3432b4f673590f4fb5ee2ee7f MD5 | raw file
  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\GitHub;
  14. use Composer\Util\Git as GitUtil;
  15. use Composer\Util\ProcessExecutor;
  16. use Composer\IO\IOInterface;
  17. use Composer\Util\Filesystem;
  18. use Composer\Config;
  19. /**
  20. * @author Jordi Boggiano <j.boggiano@seld.be>
  21. */
  22. class GitDownloader extends VcsDownloader
  23. {
  24. private $hasStashedChanges = false;
  25. private $gitUtil;
  26. public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
  27. {
  28. parent::__construct($io, $config, $process, $fs);
  29. $this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem);
  30. }
  31. /**
  32. * {@inheritDoc}
  33. */
  34. public function doDownload(PackageInterface $package, $path, $url)
  35. {
  36. GitUtil::cleanEnv();
  37. $path = $this->normalizePath($path);
  38. $ref = $package->getSourceReference();
  39. $flag = defined('PHP_WINDOWS_VERSION_MAJOR') ? '/D ' : '';
  40. $command = 'git clone --no-checkout %s %s && cd '.$flag.'%2$s && git remote add composer %1$s && git fetch composer';
  41. $this->io->writeError(" Cloning ".$ref);
  42. $commandCallable = function ($url) use ($ref, $path, $command) {
  43. return sprintf($command, ProcessExecutor::escape($url), ProcessExecutor::escape($path), ProcessExecutor::escape($ref));
  44. };
  45. $this->gitUtil->runCommand($commandCallable, $url, $path, true);
  46. if ($url !== $package->getSourceUrl()) {
  47. $url = $package->getSourceUrl();
  48. $this->process->execute(sprintf('git remote set-url origin %s', ProcessExecutor::escape($url)), $output, $path);
  49. }
  50. $this->setPushUrl($path, $url);
  51. if ($newRef = $this->updateToCommit($path, $ref, $package->getPrettyVersion(), $package->getReleaseDate())) {
  52. if ($package->getDistReference() === $package->getSourceReference()) {
  53. $package->setDistReference($newRef);
  54. }
  55. $package->setSourceReference($newRef);
  56. }
  57. }
  58. /**
  59. * {@inheritDoc}
  60. */
  61. public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
  62. {
  63. GitUtil::cleanEnv();
  64. $path = $this->normalizePath($path);
  65. if (!is_dir($path.'/.git')) {
  66. throw new \RuntimeException('The .git directory is missing from '.$path.', see http://getcomposer.org/commit-deps for more information');
  67. }
  68. $ref = $target->getSourceReference();
  69. $this->io->writeError(" Checking out ".$ref);
  70. $command = 'git remote set-url composer %s && git fetch composer && git fetch --tags composer';
  71. $commandCallable = function ($url) use ($command) {
  72. return sprintf($command, ProcessExecutor::escape ($url));
  73. };
  74. $this->gitUtil->runCommand($commandCallable, $url, $path);
  75. if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) {
  76. if ($target->getDistReference() === $target->getSourceReference()) {
  77. $target->setDistReference($newRef);
  78. }
  79. $target->setSourceReference($newRef);
  80. }
  81. }
  82. /**
  83. * {@inheritDoc}
  84. */
  85. public function getLocalChanges(PackageInterface $package, $path)
  86. {
  87. GitUtil::cleanEnv();
  88. $path = $this->normalizePath($path);
  89. if (!is_dir($path.'/.git')) {
  90. return;
  91. }
  92. $command = 'git status --porcelain --untracked-files=no';
  93. if (0 !== $this->process->execute($command, $output, $path)) {
  94. throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
  95. }
  96. return trim($output) ?: null;
  97. }
  98. /**
  99. * {@inheritDoc}
  100. */
  101. protected function cleanChanges(PackageInterface $package, $path, $update)
  102. {
  103. GitUtil::cleanEnv();
  104. $path = $this->normalizePath($path);
  105. if (!$changes = $this->getLocalChanges($package, $path)) {
  106. return;
  107. }
  108. if (!$this->io->isInteractive()) {
  109. $discardChanges = $this->config->get('discard-changes');
  110. if (true === $discardChanges) {
  111. return $this->discardChanges($path);
  112. }
  113. if ('stash' === $discardChanges) {
  114. if (!$update) {
  115. return parent::cleanChanges($package, $path, $update);
  116. }
  117. return $this->stashChanges($path);
  118. }
  119. return parent::cleanChanges($package, $path, $update);
  120. }
  121. $changes = array_map(function ($elem) {
  122. return ' '.$elem;
  123. }, preg_split('{\s*\r?\n\s*}', $changes));
  124. $this->io->writeError(' <error>The package has modified files:</error>');
  125. $this->io->writeError(array_slice($changes, 0, 10));
  126. if (count($changes) > 10) {
  127. $this->io->writeError(' <info>'.count($changes) - 10 . ' more files modified, choose "v" to view the full list</info>');
  128. }
  129. while (true) {
  130. switch ($this->io->ask(' <info>Discard changes [y,n,v,'.($update ? 's,' : '').'?]?</info> ', '?')) {
  131. case 'y':
  132. $this->discardChanges($path);
  133. break 2;
  134. case 's':
  135. if (!$update) {
  136. goto help;
  137. }
  138. $this->stashChanges($path);
  139. break 2;
  140. case 'n':
  141. throw new \RuntimeException('Update aborted');
  142. case 'v':
  143. $this->io->writeError($changes);
  144. break;
  145. case '?':
  146. default:
  147. help:
  148. $this->io->writeError(array(
  149. ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'),
  150. ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up',
  151. ' v - view modified files',
  152. ));
  153. if ($update) {
  154. $this->io->writeError(' s - stash changes and try to reapply them after the update');
  155. }
  156. $this->io->writeError(' ? - print help');
  157. break;
  158. }
  159. }
  160. }
  161. /**
  162. * {@inheritDoc}
  163. */
  164. protected function reapplyChanges($path)
  165. {
  166. $path = $this->normalizePath($path);
  167. if ($this->hasStashedChanges) {
  168. $this->hasStashedChanges = false;
  169. $this->io->writeError(' <info>Re-applying stashed changes</info>');
  170. if (0 !== $this->process->execute('git stash pop', $output, $path)) {
  171. throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput());
  172. }
  173. }
  174. }
  175. /**
  176. * Updates the given path to the given commit ref
  177. *
  178. * @param string $path
  179. * @param string $reference
  180. * @param string $branch
  181. * @param \DateTime $date
  182. * @return null|string if a string is returned, it is the commit reference that was checked out if the original could not be found
  183. *
  184. * @throws \RuntimeException
  185. */
  186. protected function updateToCommit($path, $reference, $branch, $date)
  187. {
  188. $template = 'git checkout %s && git reset --hard %1$s';
  189. $branch = preg_replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $branch);
  190. $branches = null;
  191. if (0 === $this->process->execute('git branch -r', $output, $path)) {
  192. $branches = $output;
  193. }
  194. // check whether non-commitish are branches or tags, and fetch branches with the remote name
  195. $gitRef = $reference;
  196. if (!preg_match('{^[a-f0-9]{40}$}', $reference)
  197. && $branches
  198. && preg_match('{^\s+composer/'.preg_quote($reference).'$}m', $branches)
  199. ) {
  200. $command = sprintf('git checkout -B %s %s && git reset --hard %2$s', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference));
  201. if (0 === $this->process->execute($command, $output, $path)) {
  202. return;
  203. }
  204. }
  205. // try to checkout branch by name and then reset it so it's on the proper branch name
  206. if (preg_match('{^[a-f0-9]{40}$}', $reference)) {
  207. // add 'v' in front of the branch if it was stripped when generating the pretty name
  208. if (!preg_match('{^\s+composer/'.preg_quote($branch).'$}m', $branches) && preg_match('{^\s+composer/v'.preg_quote($branch).'$}m', $branches)) {
  209. $branch = 'v' . $branch;
  210. }
  211. $command = sprintf('git checkout %s', ProcessExecutor::escape($branch));
  212. $fallbackCommand = sprintf('git checkout -B %s %s', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch));
  213. if (0 === $this->process->execute($command, $output, $path)
  214. || 0 === $this->process->execute($fallbackCommand, $output, $path)
  215. ) {
  216. $command = sprintf('git reset --hard %s', ProcessExecutor::escape($reference));
  217. if (0 === $this->process->execute($command, $output, $path)) {
  218. return;
  219. }
  220. }
  221. }
  222. $command = sprintf($template, ProcessExecutor::escape($gitRef));
  223. if (0 === $this->process->execute($command, $output, $path)) {
  224. return;
  225. }
  226. // reference was not found (prints "fatal: reference is not a tree: $ref")
  227. if (false !== strpos($this->process->getErrorOutput(), $reference)) {
  228. $this->io->writeError(' <warning>'.$reference.' is gone (history was rewritten?)</warning>');
  229. }
  230. throw new \RuntimeException('Failed to execute ' . GitUtil::sanitizeUrl($command) . "\n\n" . $this->process->getErrorOutput());
  231. }
  232. protected function setPushUrl($path, $url)
  233. {
  234. // set push url for github projects
  235. if (preg_match('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) {
  236. $protocols = $this->config->get('github-protocols');
  237. $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git';
  238. if ($protocols[0] !== 'git') {
  239. $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
  240. }
  241. $cmd = sprintf('git remote set-url --push origin %s', ProcessExecutor::escape($pushUrl));
  242. $this->process->execute($cmd, $ignoredOutput, $path);
  243. }
  244. }
  245. /**
  246. * {@inheritDoc}
  247. */
  248. protected function getCommitLogs($fromReference, $toReference, $path)
  249. {
  250. $path = $this->normalizePath($path);
  251. $command = sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"', $fromReference, $toReference);
  252. if (0 !== $this->process->execute($command, $output, $path)) {
  253. throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
  254. }
  255. return $output;
  256. }
  257. /**
  258. * @param $path
  259. * @throws \RuntimeException
  260. */
  261. protected function discardChanges($path)
  262. {
  263. $path = $this->normalizePath($path);
  264. if (0 !== $this->process->execute('git reset --hard', $output, $path)) {
  265. throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
  266. }
  267. }
  268. /**
  269. * @param $path
  270. * @throws \RuntimeException
  271. */
  272. protected function stashChanges($path)
  273. {
  274. $path = $this->normalizePath($path);
  275. if (0 !== $this->process->execute('git stash', $output, $path)) {
  276. throw new \RuntimeException("Could not stash changes\n\n:".$this->process->getErrorOutput());
  277. }
  278. $this->hasStashedChanges = true;
  279. }
  280. protected function normalizePath($path)
  281. {
  282. if (defined('PHP_WINDOWS_VERSION_MAJOR') && strlen($path) > 0) {
  283. $basePath = $path;
  284. $removed = array();
  285. while (!is_dir($basePath) && $basePath !== '\\') {
  286. array_unshift($removed, basename($basePath));
  287. $basePath = dirname($basePath);
  288. }
  289. if ($basePath === '\\') {
  290. return $path;
  291. }
  292. $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/');
  293. }
  294. return $path;
  295. }
  296. }