diff --git a/Repository/Vcs/GitDriver.php b/Repository/Vcs/GitDriver.php index 6a695a56..0ba185c6 100644 --- a/Repository/Vcs/GitDriver.php +++ b/Repository/Vcs/GitDriver.php @@ -12,7 +12,10 @@ namespace Fxp\Composer\AssetPlugin\Repository\Vcs; use Composer\Cache; +use Composer\IO\IOInterface; use Composer\Repository\Vcs\GitDriver as BaseGitDriver; +use Composer\Util\Filesystem; +use Composer\Util\Git as GitUtil; /** * Git vcs driver. @@ -35,4 +38,58 @@ public function getComposerInformation($identifier) return ProcessUtil::getComposerInformation($this->cache, $this->infoCache, $this->repoConfig['asset-type'], $this->process, $identifier, $resource, sprintf('git show %s', $resource), sprintf('git log -1 --format=%%at %s', escapeshellarg($identifier)), $this->repoDir, '@'); } + + /** + * {@inheritDoc} + */ + public function initialize() + { + /* @var AssetRepositoryManager $arm */ + $arm = $this->repoConfig['asset-repository-manager']; + + $skipSync = false; + if (null !== ($skip = $arm->getConfig()->get('git-skip-update'))) { + $localUrl = $this->config->get('cache-vcs-dir') . '/' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/'; + // check if local copy exists and if it is a git repository and that modification time is within threshold + if (is_dir($localUrl) && is_file($localUrl.'/config') && filemtime($localUrl) > strtotime('-'.$skip)) { + $skipSync = true; + $this->io->write('(skip update) ', false, IOInterface::VERBOSE); + } + } + + // copied from parent::initialize() + if (Filesystem::isLocalPath($this->url)) { + $this->url = preg_replace('{[\\/]\.git/?$}', '', $this->url); + $this->repoDir = $this->url; + $cacheUrl = realpath($this->url); + } else { + $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/'; + + GitUtil::cleanEnv(); + + $fs = new Filesystem(); + $fs->ensureDirectoryExists(dirname($this->repoDir)); + + if (!is_writable(dirname($this->repoDir))) { + throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.dirname($this->repoDir).'" directory is not writable by the current user.'); + } + + if (preg_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) { + throw new \InvalidArgumentException('The source URL '.$this->url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); + } + + $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs); + // patched line, sync from local dir without modifying url + if (!$skipSync && !$gitUtil->syncMirror($this->url, $this->repoDir)) { + $this->io->writeError('Failed to update '.$this->url.', package information from this repository may be outdated'); + } + + $cacheUrl = $this->url; + } + + $this->getTags(); + $this->getBranches(); + + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $cacheUrl)); + } } diff --git a/Resources/doc/schema.md b/Resources/doc/schema.md index 3bbbd720..eb36c26e 100644 --- a/Resources/doc/schema.md +++ b/Resources/doc/schema.md @@ -49,6 +49,16 @@ The plugin can override the main file definitions of the Bower packages. To over definitions specify the packages and their main file array as name/value pairs. For an example see the [usage informations](index.md#override-the-main-files-for-bower). +##### config.fxp-asset.git-skip-update (root-only) + +The plugin can skip updating meta-data in git repositories for given amount of time, i.e. `6 hours`, `3 days` or `1 week`. + + "config": { + "fxp-asset": { + "git-skip-update": "2 days" + } + } + ### Mapping asset file to composer package ##### NPM mapping diff --git a/Tests/Repository/Vcs/GitDriverTest.php b/Tests/Repository/Vcs/GitDriverTest.php index 9ebda6aa..fa0f7637 100644 --- a/Tests/Repository/Vcs/GitDriverTest.php +++ b/Tests/Repository/Vcs/GitDriverTest.php @@ -15,6 +15,7 @@ use Composer\IO\IOInterface; use Composer\Util\Filesystem; use Composer\Util\ProcessExecutor; +use Fxp\Composer\AssetPlugin\Repository\AssetRepositoryManager; use Fxp\Composer\AssetPlugin\Repository\Vcs\GitDriver; /** @@ -29,15 +30,34 @@ class GitDriverTest extends \PHPUnit_Framework_TestCase */ private $config; + /** + * @var AssetRepositoryManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $assetRepositoryManager; + public function setUp() { + $assetConfig = new \Fxp\Composer\AssetPlugin\Config\Config(array('git-skip-update' => '1 hour')); + + /* @var AssetRepositoryManager|\PHPUnit_Framework_MockObject_MockObject $arm */ + $this->assetRepositoryManager = $this->getMockBuilder(AssetRepositoryManager::class)->disableOriginalConstructor()->getMock(); + $this->assetRepositoryManager->expects($this->any()) + ->method('getConfig') + ->willReturn($assetConfig); + $this->config = new Config(); $this->config->merge(array( 'config' => array( 'home' => sys_get_temp_dir().'/composer-test', 'cache-repo-dir' => sys_get_temp_dir().'/composer-test-cache', + 'cache-vcs-dir' => sys_get_temp_dir().'/composer-test-cache', ), )); + + // Mock for skip asset + $fs = new Filesystem(); + $fs->ensureDirectoryExists(sys_get_temp_dir().'/composer-test-cache/https---github.com-fxpio-composer-asset-plugin.git'); + file_put_contents(sys_get_temp_dir().'/composer-test-cache/https---github.com-fxpio-composer-asset-plugin.git/config', ''); } public function tearDown() @@ -71,6 +91,7 @@ public function testPublicRepositoryWithEmptyComposer($type, $filename) 'url' => $repoUrl, 'asset-type' => $type, 'filename' => $filename, + 'asset-repository-manager' => $this->assetRepositoryManager, ); $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); @@ -96,6 +117,65 @@ public function testPublicRepositoryWithEmptyComposer($type, $filename) $this->assertSame($validEmpty, $gitDriver->getComposerInformation($identifier)); } + /** + * @dataProvider getAssetTypes + * + * @param string $type + * @param string $filename + */ + public function testPublicRepositoryWithSkipUpdate($type, $filename) + { + $repoUrl = 'https://github.com/fxpio/composer-asset-plugin.git'; + $identifier = '92bebbfdcde75ef2368317830e54b605bc938123'; + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + + $repoConfig = array( + 'url' => $repoUrl, + 'asset-type' => $type, + 'filename' => $filename, + 'asset-repository-manager' => $this->assetRepositoryManager, + ); + + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); + $process->expects($this->any()) + ->method('splitLines') + ->will($this->returnValue(array())); + $process->expects($this->any()) + ->method('execute') + ->will($this->returnCallback(function ($command, &$output = null) use ($identifier, $repoConfig) { + if ($command === sprintf('git show %s', sprintf('%s:%s', escapeshellarg($identifier), $repoConfig['filename']))) { + $output = '{"name": "foo"}'; + } elseif (false !== strpos($command, 'git log')) { + $date = new \DateTime(null, new \DateTimeZone('UTC')); + $output = $date->getTimestamp(); + } + + return 0; + })); + + /* @var IOInterface $io */ + /* @var ProcessExecutor $process */ + + $gitDriver1 = new GitDriver($repoConfig, $io, $this->config, $process, null); + $gitDriver1->initialize(); + + $gitDriver2 = new GitDriver($repoConfig, $io, $this->config, $process, null); + $gitDriver2->initialize(); + + $validEmpty = array( + '_nonexistent_package' => true, + ); + + $composer1 = $gitDriver1->getComposerInformation($identifier); + $composer2 = $gitDriver2->getComposerInformation($identifier); + + $this->assertNotNull($composer1); + $this->assertNotNull($composer2); + $this->assertSame($composer1, $composer2); + $this->assertNotSame($validEmpty, $composer1); + $this->assertNotSame($validEmpty, $composer2); + } + /** * @dataProvider getAssetTypes * @@ -110,6 +190,7 @@ public function testPublicRepositoryWithCodeCache($type, $filename) 'url' => $repoUrl, 'asset-type' => $type, 'filename' => $filename, + 'asset-repository-manager' => $this->assetRepositoryManager, ); $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); @@ -156,6 +237,7 @@ public function testPublicRepositoryWithFilesystemCache($type, $filename) 'url' => $repoUrl, 'asset-type' => $type, 'filename' => $filename, + 'asset-repository-manager' => $this->assetRepositoryManager, ); $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); diff --git a/Tests/Repository/Vcs/GitHubDriverTest.php b/Tests/Repository/Vcs/GitHubDriverTest.php index 4c007ff7..32836f79 100644 --- a/Tests/Repository/Vcs/GitHubDriverTest.php +++ b/Tests/Repository/Vcs/GitHubDriverTest.php @@ -49,8 +49,13 @@ public function setUp() ), )); + $assetConfig = new \Fxp\Composer\AssetPlugin\Config\Config(array()); + $this->assetRepositoryManager = $this->getMockBuilder(AssetRepositoryManager::class) ->disableOriginalConstructor()->getMock(); + $this->assetRepositoryManager->expects($this->any()) + ->method('getConfig') + ->willReturn($assetConfig); } public function tearDown()