GitHubDriver.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <?php
  2. namespace Composer\Repository\Vcs;
  3. use Composer\Downloader\TransportException;
  4. use Composer\Json\JsonFile;
  5. use Composer\IO\IOInterface;
  6. use Composer\Util\ProcessExecutor;
  7. /**
  8. * @author Jordi Boggiano <j.boggiano@seld.be>
  9. */
  10. class GitHubDriver extends VcsDriver
  11. {
  12. protected $owner;
  13. protected $repository;
  14. protected $tags;
  15. protected $branches;
  16. protected $rootIdentifier;
  17. protected $infoCache = array();
  18. protected $isPrivate = false;
  19. /**
  20. * Git Driver
  21. *
  22. * @var GitDriver
  23. */
  24. protected $gitDriver;
  25. /**
  26. * Constructor
  27. *
  28. * @param string $url
  29. * @param IOInterface $io
  30. * @param ProcessExecutor $process
  31. * @param callable $remoteFilesystemGenerator
  32. */
  33. public function __construct($url, IOInterface $io, ProcessExecutor $process = null, $remoteFilesystemGenerator = null)
  34. {
  35. preg_match('#^(?:https?|git)://github\.com/([^/]+)/(.+?)(?:\.git)?$#', $url, $match);
  36. $this->owner = $match[1];
  37. $this->repository = $match[2];
  38. parent::__construct($url, $io, $process, $remoteFilesystemGenerator);
  39. }
  40. /**
  41. * {@inheritDoc}
  42. */
  43. public function initialize()
  44. {
  45. $this->fetchRootIdentifier();
  46. }
  47. /**
  48. * {@inheritDoc}
  49. */
  50. public function getRootIdentifier()
  51. {
  52. if ($this->gitDriver) {
  53. return $this->gitDriver->getRootIdentifier();
  54. }
  55. return $this->rootIdentifier;
  56. }
  57. /**
  58. * {@inheritDoc}
  59. */
  60. public function getUrl()
  61. {
  62. if ($this->gitDriver) {
  63. return $this->gitDriver->getUrl();
  64. }
  65. return $this->url;
  66. }
  67. /**
  68. * {@inheritDoc}
  69. */
  70. public function getSource($identifier)
  71. {
  72. if ($this->gitDriver) {
  73. return $this->gitDriver->getSource($identifier);
  74. }
  75. $label = array_search($identifier, $this->getTags()) ?: $identifier;
  76. if ($this->isPrivate) {
  77. // Private GitHub repositories should be accessed using the
  78. // SSH version of the URL.
  79. $url = $this->generateSshUrl();
  80. } else {
  81. $url = $this->getUrl();
  82. }
  83. return array('type' => 'git', 'url' => $url, 'reference' => $label);
  84. }
  85. /**
  86. * {@inheritDoc}
  87. */
  88. public function getDist($identifier)
  89. {
  90. if ($this->gitDriver) {
  91. return $this->gitDriver->getDist($identifier);
  92. }
  93. $label = array_search($identifier, $this->getTags()) ?: $identifier;
  94. $url = $this->getScheme() . '://github.com/'.$this->owner.'/'.$this->repository.'/zipball/'.$label;
  95. return array('type' => 'zip', 'url' => $url, 'reference' => $label, 'shasum' => '');
  96. }
  97. /**
  98. * {@inheritDoc}
  99. */
  100. public function getComposerInformation($identifier)
  101. {
  102. if ($this->gitDriver) {
  103. return $this->gitDriver->getComposerInformation($identifier);
  104. }
  105. if (!isset($this->infoCache[$identifier])) {
  106. $composer = $this->getContents($this->getScheme() . '://raw.github.com/'.$this->owner.'/'.$this->repository.'/'.$identifier.'/composer.json');
  107. if (!$composer) {
  108. return;
  109. }
  110. $composer = JsonFile::parseJson($composer);
  111. if (!isset($composer['time'])) {
  112. $commit = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier));
  113. $composer['time'] = $commit['commit']['committer']['date'];
  114. }
  115. $this->infoCache[$identifier] = $composer;
  116. }
  117. return $this->infoCache[$identifier];
  118. }
  119. /**
  120. * {@inheritDoc}
  121. */
  122. public function getTags()
  123. {
  124. if ($this->gitDriver) {
  125. return $this->gitDriver->getTags();
  126. }
  127. if (null === $this->tags) {
  128. $tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags'));
  129. $this->tags = array();
  130. foreach ($tagsData as $tag) {
  131. $this->tags[$tag['name']] = $tag['commit']['sha'];
  132. }
  133. }
  134. return $this->tags;
  135. }
  136. /**
  137. * {@inheritDoc}
  138. */
  139. public function getBranches()
  140. {
  141. if ($this->gitDriver) {
  142. return $this->gitDriver->getBranches();
  143. }
  144. if (null === $this->branches) {
  145. $branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches'));
  146. $this->branches = array();
  147. foreach ($branchData as $branch) {
  148. $this->branches[$branch['name']] = $branch['commit']['sha'];
  149. }
  150. }
  151. return $this->branches;
  152. }
  153. /**
  154. * {@inheritDoc}
  155. */
  156. public static function supports($url, $deep = false)
  157. {
  158. return extension_loaded('openssl') && preg_match('#^(?:https?|git)://github\.com/([^/]+)/(.+?)(?:\.git)?$#', $url);
  159. }
  160. /**
  161. * Generate an SSH URL
  162. *
  163. * @return string
  164. */
  165. protected function generateSshUrl()
  166. {
  167. return 'git@github.com:'.$this->owner.'/'.$this->repository.'.git';
  168. }
  169. /**
  170. * Fetch root identifier from GitHub
  171. *
  172. * @throws TransportException
  173. */
  174. protected function fetchRootIdentifier()
  175. {
  176. $repoDataUrl = $this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository;
  177. $attemptCounter = 0;
  178. while (null === $this->rootIdentifier) {
  179. if (5 == $attemptCounter++) {
  180. throw new \RuntimeException("Either you have entered invalid credentials or this GitHub repository does not exists (404)");
  181. }
  182. try {
  183. $repoData = JsonFile::parseJson($this->getContents($repoDataUrl));
  184. $this->rootIdentifier = $repoData['master_branch'] ?: 'master';
  185. } catch (TransportException $e) {
  186. switch($e->getCode()) {
  187. case 401:
  188. case 404:
  189. $this->isPrivate = true;
  190. if (!$this->io->isInteractive()) {
  191. $this->gitDriver = new GitDriver(
  192. $this->generateSshUrl(),
  193. $this->io,
  194. $this->process,
  195. $this->remoteFilesystemGenerator
  196. );
  197. $this->gitDriver->initialize();
  198. return;
  199. }
  200. $this->io->write('Authentication required (<info>'.$this->url.'</info>):');
  201. $username = $this->io->ask('Username: ');
  202. $password = $this->io->askAndHideAnswer('Password: ');
  203. $this->io->setAuthorization($this->url, $username, $password);
  204. break;
  205. default:
  206. throw $e;
  207. break;
  208. }
  209. }
  210. }
  211. }
  212. }