PageRenderTime 27ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/core/vendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php

https://github.com/modxcms/evolution
PHP | 302 lines | 202 code | 50 blank | 50 comment | 49 complexity | 05df866797a44c56169b855c4369d76e MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, MIT, BSD-2-Clause, Apache-2.0, BSD-3-Clause
  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\Package\Version;
  12. use Composer\Config;
  13. use Composer\Repository\Vcs\HgDriver;
  14. use Composer\IO\NullIO;
  15. use Composer\Semver\VersionParser as SemverVersionParser;
  16. use Composer\Util\Git as GitUtil;
  17. use Composer\Util\ProcessExecutor;
  18. use Composer\Util\Svn as SvnUtil;
  19. /**
  20. * Try to guess the current version number based on different VCS configuration.
  21. *
  22. * @author Jordi Boggiano <j.boggiano@seld.be>
  23. * @author Samuel Roze <samuel.roze@gmail.com>
  24. */
  25. class VersionGuesser
  26. {
  27. /**
  28. * @var Config
  29. */
  30. private $config;
  31. /**
  32. * @var ProcessExecutor
  33. */
  34. private $process;
  35. /**
  36. * @var SemverVersionParser
  37. */
  38. private $versionParser;
  39. /**
  40. * @param Config $config
  41. * @param ProcessExecutor $process
  42. * @param SemverVersionParser $versionParser
  43. */
  44. public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser)
  45. {
  46. $this->config = $config;
  47. $this->process = $process;
  48. $this->versionParser = $versionParser;
  49. }
  50. /**
  51. * @param array $packageConfig
  52. * @param string $path Path to guess into
  53. *
  54. * @return null|array versionData, 'version', 'pretty_version' and 'commit' keys
  55. */
  56. public function guessVersion(array $packageConfig, $path)
  57. {
  58. if (function_exists('proc_open')) {
  59. $versionData = $this->guessGitVersion($packageConfig, $path);
  60. if (null !== $versionData && null !== $versionData['version']) {
  61. return $this->postprocess($versionData);
  62. }
  63. $versionData = $this->guessHgVersion($packageConfig, $path);
  64. if (null !== $versionData && null !== $versionData['version']) {
  65. return $this->postprocess($versionData);
  66. }
  67. $versionData = $this->guessFossilVersion($packageConfig, $path);
  68. if (null !== $versionData && null !== $versionData['version']) {
  69. return $this->postprocess($versionData);
  70. }
  71. $versionData = $this->guessSvnVersion($packageConfig, $path);
  72. if (null !== $versionData && null !== $versionData['version']) {
  73. return $this->postprocess($versionData);
  74. }
  75. }
  76. }
  77. private function postprocess(array $versionData)
  78. {
  79. if ('-dev' === substr($versionData['version'], -4) && preg_match('{\.9{7}}', $versionData['version'])) {
  80. $versionData['pretty_version'] = preg_replace('{(\.9{7})+}', '.x', $versionData['version']);
  81. }
  82. return $versionData;
  83. }
  84. private function guessGitVersion(array $packageConfig, $path)
  85. {
  86. GitUtil::cleanEnv();
  87. $commit = null;
  88. $version = null;
  89. $prettyVersion = null;
  90. $isDetached = false;
  91. // try to fetch current version from git branch
  92. if (0 === $this->process->execute('git branch --no-color --no-abbrev -v', $output, $path)) {
  93. $branches = array();
  94. $isFeatureBranch = false;
  95. // find current branch and collect all branch names
  96. foreach ($this->process->splitLines($output) as $branch) {
  97. if ($branch && preg_match('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\(HEAD detached at \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
  98. if ($match[1] === '(no branch)' || substr($match[1], 0, 10) === '(detached ' || substr($match[1], 0, 17) === '(HEAD detached at') {
  99. $version = 'dev-' . $match[2];
  100. $prettyVersion = $version;
  101. $isFeatureBranch = true;
  102. $isDetached = true;
  103. } else {
  104. $version = $this->versionParser->normalizeBranch($match[1]);
  105. $prettyVersion = 'dev-' . $match[1];
  106. $isFeatureBranch = 0 === strpos($version, 'dev-');
  107. }
  108. if ($match[2]) {
  109. $commit = $match[2];
  110. }
  111. }
  112. if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
  113. if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
  114. $branches[] = $match[1];
  115. }
  116. }
  117. }
  118. if ($isFeatureBranch) {
  119. // try to find the best (nearest) version branch to assume this feature's version
  120. $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'git rev-list %candidate%..%branch%', $path);
  121. $version = $result['version'];
  122. $prettyVersion = $result['pretty_version'];
  123. }
  124. }
  125. if (!$version || $isDetached) {
  126. $result = $this->versionFromGitTags($path);
  127. if ($result) {
  128. $version = $result['version'];
  129. $prettyVersion = $result['pretty_version'];
  130. }
  131. }
  132. if (!$commit) {
  133. $command = 'git log --pretty="%H" -n1 HEAD';
  134. if (0 === $this->process->execute($command, $output, $path)) {
  135. $commit = trim($output) ?: null;
  136. }
  137. }
  138. return array('version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion);
  139. }
  140. private function versionFromGitTags($path)
  141. {
  142. // try to fetch current version from git tags
  143. if (0 === $this->process->execute('git describe --exact-match --tags', $output, $path)) {
  144. try {
  145. $version = $this->versionParser->normalize(trim($output));
  146. return array('version' => $version, 'pretty_version' => trim($output));
  147. } catch (\Exception $e) {
  148. }
  149. }
  150. return null;
  151. }
  152. private function guessHgVersion(array $packageConfig, $path)
  153. {
  154. // try to fetch current version from hg branch
  155. if (0 === $this->process->execute('hg branch', $output, $path)) {
  156. $branch = trim($output);
  157. $version = $this->versionParser->normalizeBranch($branch);
  158. $isFeatureBranch = 0 === strpos($version, 'dev-');
  159. if ('9999999-dev' === $version) {
  160. return array('version' => $version, 'commit' => null, 'pretty_version' => 'dev-'.$branch);
  161. }
  162. if (!$isFeatureBranch) {
  163. return array('version' => $version, 'commit' => null, 'pretty_version' => $version);
  164. }
  165. // re-use the HgDriver to fetch branches (this properly includes bookmarks)
  166. $driver = new HgDriver(array('url' => $path), new NullIO(), $this->config, $this->process);
  167. $branches = array_keys($driver->getBranches());
  168. // try to find the best (nearest) version branch to assume this feature's version
  169. $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"', $path);
  170. $result['commit'] = '';
  171. return $result;
  172. }
  173. }
  174. private function guessFeatureVersion(array $packageConfig, $version, array $branches, $scmCmdline, $path)
  175. {
  176. $prettyVersion = $version;
  177. // ignore feature branches if they have no branch-alias or self.version is used
  178. // and find the branch they came from to use as a version instead
  179. if ((isset($packageConfig['extra']['branch-alias']) && !isset($packageConfig['extra']['branch-alias'][$version]))
  180. || strpos(json_encode($packageConfig), '"self.version"')
  181. ) {
  182. $branch = preg_replace('{^dev-}', '', $version);
  183. $length = PHP_INT_MAX;
  184. $nonFeatureBranches = '';
  185. if (!empty($packageConfig['non-feature-branches'])) {
  186. $nonFeatureBranches = implode('|', $packageConfig['non-feature-branches']);
  187. }
  188. foreach ($branches as $candidate) {
  189. // return directly, if branch is configured to be non-feature branch
  190. if ($candidate === $branch && preg_match('{^(' . $nonFeatureBranches . ')$}', $candidate)) {
  191. break;
  192. }
  193. // do not compare against itself or other feature branches
  194. if ($candidate === $branch || !preg_match('{^(' . $nonFeatureBranches . '|master|trunk|default|develop|\d+\..+)$}', $candidate, $match)) {
  195. continue;
  196. }
  197. $cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline);
  198. if (0 !== $this->process->execute($cmdLine, $output, $path)) {
  199. continue;
  200. }
  201. if (strlen($output) < $length) {
  202. $length = strlen($output);
  203. $version = $this->versionParser->normalizeBranch($candidate);
  204. $prettyVersion = 'dev-' . $match[1];
  205. }
  206. }
  207. }
  208. return array('version' => $version, 'pretty_version' => $prettyVersion);
  209. }
  210. private function guessFossilVersion(array $packageConfig, $path)
  211. {
  212. $version = null;
  213. $prettyVersion = null;
  214. // try to fetch current version from fossil
  215. if (0 === $this->process->execute('fossil branch list', $output, $path)) {
  216. $branch = trim($output);
  217. $version = $this->versionParser->normalizeBranch($branch);
  218. $prettyVersion = 'dev-' . $branch;
  219. }
  220. // try to fetch current version from fossil tags
  221. if (0 === $this->process->execute('fossil tag list', $output, $path)) {
  222. try {
  223. $version = $this->versionParser->normalize(trim($output));
  224. $prettyVersion = trim($output);
  225. } catch (\Exception $e) {
  226. }
  227. }
  228. return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion);
  229. }
  230. private function guessSvnVersion(array $packageConfig, $path)
  231. {
  232. SvnUtil::cleanEnv();
  233. // try to fetch current version from svn
  234. if (0 === $this->process->execute('svn info --xml', $output, $path)) {
  235. $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk';
  236. $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches';
  237. $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags';
  238. $urlPattern = '#<url>.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))</url>#';
  239. if (preg_match($urlPattern, $output, $matches)) {
  240. if (isset($matches[2]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) {
  241. // we are in a branches path
  242. $version = $this->versionParser->normalizeBranch($matches[3]);
  243. $prettyVersion = 'dev-' . $matches[3];
  244. return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion);
  245. }
  246. $prettyVersion = trim($matches[1]);
  247. $version = $this->versionParser->normalize($prettyVersion);
  248. return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion);
  249. }
  250. }
  251. }
  252. }