* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Fxp\Composer\AssetPlugin\Repository\Vcs; use Composer\Cache; use Composer\Downloader\TransportException; use Composer\Json\JsonFile; use Composer\Repository\Vcs\GitHubDriver as BaseGitHubDriver; use Fxp\Composer\AssetPlugin\Repository\Util as RepoUtil; /** * Abstract class for GitHub vcs driver. * * @author François Pluchino */ abstract class AbstractGitHubDriver extends BaseGitHubDriver { /** * @var Cache */ protected $cache; /** * @var string|null|false */ protected $redirectApi; public function initialize() { if (!isset($this->repoConfig['no-api'])) { $this->repoConfig['no-api'] = $this->getNoApiOption(); } parent::initialize(); } /** * Get the no-api repository option. * * @return bool */ protected function getNoApiOption() { $packageName = $this->repoConfig['package-name']; $opts = RepoUtil::getArrayValue($this->repoConfig, 'vcs-driver-options', array()); $noApiOpt = RepoUtil::getArrayValue($opts, 'github-no-api', array()); $defaultValue = false; if (is_bool($noApiOpt)) { $defaultValue = $noApiOpt; $noApiOpt = array(); } $noApiOpt['default'] = (bool) RepoUtil::getArrayValue($noApiOpt, 'default', $defaultValue); $noApiOpt['packages'] = (array) RepoUtil::getArrayValue($noApiOpt, 'packages', array()); return (bool) RepoUtil::getArrayValue($noApiOpt['packages'], $packageName, $defaultValue); } /** * Get the remote content. * * @param string $url The URL of content * @param bool $fetchingRepoData Fetching the repo data or not * * @return mixed The result */ protected function getContents($url, $fetchingRepoData = false) { $url = $this->getValidContentUrl($url); if (null !== $this->redirectApi) { return parent::getContents($url, $fetchingRepoData); } try { $contents = $this->getRemoteContents($url); $this->redirectApi = false; return $contents; } catch (TransportException $e) { if ($this->hasRedirectUrl($url)) { $url = $this->getValidContentUrl($url); } return parent::getContents($url, $fetchingRepoData); } } /** * @param string $url The url * * @return string The url redirected */ protected function getValidContentUrl($url) { if (null === $this->redirectApi && false !== $redirectApi = $this->cache->read('redirect-api')) { $this->redirectApi = $redirectApi; } if (is_string($this->redirectApi) && 0 === strpos($url, $this->getRepositoryApiUrl())) { $url = $this->redirectApi.substr($url, strlen($this->getRepositoryApiUrl())); } return $url; } /** * Check if the driver must find the new url. * * @param string $url The url * * @return bool */ protected function hasRedirectUrl($url) { if (null === $this->redirectApi && 0 === strpos($url, $this->getRepositoryApiUrl())) { $this->redirectApi = $this->getNewRepositoryUrl(); if (is_string($this->redirectApi)) { $this->cache->write('redirect-api', $this->redirectApi); } } return is_string($this->redirectApi); } /** * Get the new url of repository. * * @return string|false The new url or false if there is not a new url */ protected function getNewRepositoryUrl() { try { $this->getRemoteContents($this->getRepositoryUrl()); $headers = $this->remoteFilesystem->getLastHeaders(); if (!empty($headers[0]) && preg_match('{^HTTP/\S+ (30[1278])}i', $headers[0], $match)) { array_shift($headers); return $this->findNewLocationInHeader($headers); } return false; } catch (\Exception $ex) { return false; } } /** * Find the new url api in the header. * * @param array $headers The http header * * @return string|false */ protected function findNewLocationInHeader(array $headers) { $url = false; foreach ($headers as $header) { if (0 === strpos($header, 'Location:')) { $newUrl = trim(substr($header, 9)); preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $newUrl, $match); $owner = $match[3]; $repository = $match[4]; $paramPos = strpos($repository, '?'); $repository = is_int($paramPos) ? substr($match[4], 0, $paramPos) : $repository; $url = $this->getRepositoryApiUrl($owner, $repository); break; } } return $url; } /** * Get the url API of the repository. * * @param string $owner * @param string $repository * * @return string */ protected function getRepositoryApiUrl($owner = null, $repository = null) { $owner = null !== $owner ? $owner : $this->owner; $repository = null !== $repository ? $repository : $this->repository; return $this->getApiUrl().'/repos/'.$owner.'/'.$repository; } /** * Get the remote content. * * @param string $url * * @return bool|string */ protected function getRemoteContents($url) { return $this->remoteFilesystem->getContents($this->originUrl, $url, false); } /** * {@inheritdoc} */ public function getBranches() { if ($this->gitDriver) { return $this->gitDriver->getBranches(); } if (null === $this->branches) { $this->branches = array(); $resource = $this->getApiUrl().'/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100'; $branchBlacklist = 'gh-pages' === $this->getRootIdentifier() ? array() : array('gh-pages'); $this->doAddBranches($resource, $branchBlacklist); } return $this->branches; } /** * Push the list of all branch. * * @param string $resource * @param array $branchBlacklist */ protected function doAddBranches($resource, array $branchBlacklist) { do { $branchData = JsonFile::parseJson((string) $this->getContents($resource), $resource); foreach ($branchData as $branch) { $name = substr($branch['ref'], 11); if (!in_array($name, $branchBlacklist)) { $this->branches[$name] = $branch['object']['sha']; } } $resource = $this->getNextPage(); } while ($resource); } }