GitBitbucketDriver.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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\Repository\Vcs;
  12. use Composer\Cache;
  13. use Composer\Config;
  14. use Composer\Downloader\TransportException;
  15. use Composer\Json\JsonFile;
  16. use Composer\IO\IOInterface;
  17. use Composer\Util\Bitbucket;
  18. /**
  19. * @author Per Bernhardt <plb@webfactory.de>
  20. */
  21. class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
  22. {
  23. /**
  24. * @var Cache
  25. */
  26. protected $cache;
  27. protected $owner;
  28. protected $repository;
  29. protected $tags;
  30. protected $branches;
  31. protected $rootIdentifier;
  32. protected $infoCache = array();
  33. /**
  34. * @var GitDriver
  35. */
  36. private $gitDriver;
  37. /**
  38. * {@inheritDoc}
  39. */
  40. public function initialize()
  41. {
  42. preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match);
  43. $this->owner = $match[1];
  44. $this->repository = $match[2];
  45. $this->originUrl = 'bitbucket.org';
  46. $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);
  47. }
  48. /**
  49. * {@inheritDoc}
  50. */
  51. public function getRootIdentifier()
  52. {
  53. if ($this->gitDriver) {
  54. return $this->gitDriver->getRootIdentifier();
  55. }
  56. if (null === $this->rootIdentifier) {
  57. $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository;
  58. $repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource);
  59. $this->rootIdentifier = !empty($repoData['main_branch']) ? $repoData['main_branch'] : 'master';
  60. }
  61. return $this->rootIdentifier;
  62. }
  63. /**
  64. * {@inheritDoc}
  65. */
  66. public function getUrl()
  67. {
  68. if ($this->gitDriver) {
  69. return $this->gitDriver->getUrl();
  70. }
  71. return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
  72. }
  73. /**
  74. * {@inheritDoc}
  75. */
  76. public function getSource($identifier)
  77. {
  78. if ($this->gitDriver) {
  79. return $this->gitDriver->getSource($identifier);
  80. }
  81. return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier);
  82. }
  83. /**
  84. * {@inheritDoc}
  85. */
  86. public function getDist($identifier)
  87. {
  88. $url = $this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/get/'.$identifier.'.zip';
  89. return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '');
  90. }
  91. /**
  92. * {@inheritDoc}
  93. */
  94. public function getComposerInformation($identifier)
  95. {
  96. if ($this->gitDriver) {
  97. return $this->gitDriver->getComposerInformation($identifier);
  98. }
  99. if (preg_match('{[a-f0-9]{40}}i', $identifier) && $res = $this->cache->read($identifier)) {
  100. $this->infoCache[$identifier] = JsonFile::parseJson($res);
  101. }
  102. if (!isset($this->infoCache[$identifier])) {
  103. $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/src/'.$identifier.'/composer.json';
  104. $file = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
  105. if (!is_array($file) || ! array_key_exists('data', $file)) {
  106. return array();
  107. }
  108. $composer = JsonFile::parseJson($file['data'], $resource);
  109. if (empty($composer['time'])) {
  110. $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier;
  111. $changeset = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
  112. $composer['time'] = $changeset['timestamp'];
  113. }
  114. if (preg_match('{[a-f0-9]{40}}i', $identifier)) {
  115. $this->cache->write($identifier, json_encode($composer));
  116. }
  117. $this->infoCache[$identifier] = $composer;
  118. }
  119. return $this->infoCache[$identifier];
  120. }
  121. /**
  122. * {@inheritDoc}
  123. */
  124. public function getTags()
  125. {
  126. if ($this->gitDriver) {
  127. return $this->gitDriver->getTags();
  128. }
  129. if (null === $this->tags) {
  130. $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags';
  131. $tagsData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
  132. $this->tags = array();
  133. foreach ($tagsData as $tag => $data) {
  134. $this->tags[$tag] = $data['raw_node'];
  135. }
  136. }
  137. return $this->tags;
  138. }
  139. /**
  140. * {@inheritDoc}
  141. */
  142. public function getBranches()
  143. {
  144. if ($this->gitDriver) {
  145. return $this->gitDriver->getBranches();
  146. }
  147. if (null === $this->branches) {
  148. $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches';
  149. $branchData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
  150. $this->branches = array();
  151. foreach ($branchData as $branch => $data) {
  152. $this->branches[$branch] = $data['raw_node'];
  153. }
  154. }
  155. return $this->branches;
  156. }
  157. /**
  158. * {@inheritDoc}
  159. */
  160. public static function supports(IOInterface $io, Config $config, $url, $deep = false)
  161. {
  162. if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
  163. return false;
  164. }
  165. if (!extension_loaded('openssl')) {
  166. $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);
  167. return false;
  168. }
  169. return true;
  170. }
  171. protected function attemptCloneFallback()
  172. {
  173. try {
  174. $this->setupGitDriver($this->generateSshUrl());
  175. return;
  176. } catch (\RuntimeException $e) {
  177. $this->gitDriver = null;
  178. $this->io->writeError('<error>Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your Bitbucket OAuth consumer credentials</error>');
  179. throw $e;
  180. }
  181. }
  182. /**
  183. * Generate an SSH URL
  184. *
  185. * @return string
  186. */
  187. private function generateSshUrl()
  188. {
  189. return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git';
  190. }
  191. /**
  192. * Get the remote content.
  193. *
  194. * @param string $url The URL of content
  195. * @param bool $fetchingRepoData
  196. *
  197. * @return mixed The result
  198. */
  199. protected function getContentsWithOAuthCredentials($url, $fetchingRepoData = false)
  200. {
  201. try {
  202. return parent::getContents($url);
  203. } catch (TransportException $e) {
  204. $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->remoteFilesystem);
  205. switch ($e->getCode()) {
  206. case 403:
  207. if (!$this->io->hasAuthentication($this->originUrl) && $bitbucketUtil->authorizeOAuth($this->originUrl)) {
  208. return parent::getContents($url);
  209. }
  210. if (!$this->io->isInteractive() && $fetchingRepoData) {
  211. return $this->attemptCloneFallback();
  212. }
  213. throw $e;
  214. default:
  215. throw $e;
  216. }
  217. }
  218. }
  219. /**
  220. * @param string $url
  221. */
  222. private function setupGitDriver($url)
  223. {
  224. $this->gitDriver = new GitDriver(
  225. array('url' => $url),
  226. $this->io,
  227. $this->config,
  228. $this->process,
  229. $this->remoteFilesystem
  230. );
  231. $this->gitDriver->initialize();
  232. }
  233. }