diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 4a14974fb..cc6e3edb4 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -143,7 +143,7 @@ class GitDriver extends VcsDriver public function getChangeDate($identifier) { $this->process->execute(sprintf( - 'git log -1 --format=%%at %s', + 'git -c log.showSignature=false log -1 --format=%%at %s', ProcessExecutor::escape($identifier) ), $output, $this->repoDir); diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 15c46d080..81c685011 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -85,7 +85,9 @@ class Git } // failed to checkout, first check git accessibility - $this->throwException('Failed to clone ' . $url . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url); + if (!$this->io->hasAuthentication($match[1]) && !$this->io->isInteractive()) { + $this->throwException('Failed to clone ' . $url . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url); + } } // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https @@ -97,7 +99,7 @@ class Git if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) { // private github repository without ssh key access, try https with auth if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match) - || preg_match('{^(https?)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match) + || preg_match('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match) ) { if (!$this->io->hasAuthentication($match[1])) { $gitHubUtil = new GitHub($this->io, $this->config, $this->process); diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index d72a02981..00b2e7547 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -155,7 +155,7 @@ class ProcessExecutor //@see https://bugs.php.net/bug.php?id=43784 //@see https://bugs.php.net/bug.php?id=49446 if ('\\' === DIRECTORY_SEPARATOR) { - if ('' === $argument) { + if ((string) $argument === '') { return escapeshellarg($argument); } diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index cd3375464..9ff169925 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -734,7 +734,7 @@ composer https://github.com/old/url (push) $cmd = str_replace('cd ', 'cd /D ', $cmd); $cmd = str_replace('composerPath', getcwd().'/composerPath', $cmd); - return str_replace('""', '', strtr($cmd, "'", '"')); + return strtr($cmd, "'", '"'); } return $cmd; diff --git a/tests/Composer/Test/Util/GitTest.php b/tests/Composer/Test/Util/GitTest.php new file mode 100644 index 000000000..7aa66be12 --- /dev/null +++ b/tests/Composer/Test/Util/GitTest.php @@ -0,0 +1,145 @@ +io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $this->config = $this->getMockBuilder('Composer\Config')->disableOriginalConstructor()->getMock(); + $this->process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->disableOriginalConstructor()->getMock(); + $this->fs = $this->getMockBuilder('Composer\Util\Filesystem')->disableOriginalConstructor()->getMock(); + $this->git = new Git($this->io, $this->config, $this->process, $this->fs); + } + + /** + * @dataProvider publicGithubNoCredentialsProvider + */ + public function testRunCommandPublicGitHubRepositoryNotInitialClone($protocol, $expectedUrl) + { + $that = $this; + $commandCallable = function ($url) use ($that, $expectedUrl) { + $that->assertSame($expectedUrl, $url); + + return 'git command'; + }; + + $this->mockConfig($protocol); + + $this->process + ->expects($this->once()) + ->method('execute') + ->with($this->equalTo('git command')) + ->willReturn(0); + + $this->git->runCommand($commandCallable, 'https://github.com/acme/repo', null, true); + } + + public function publicGithubNoCredentialsProvider() + { + return array( + array('ssh', 'git@github.com:acme/repo'), + array('https', 'https://github.com/acme/repo'), + ); + } + + /** + * @expectedException \RuntimeException + */ + public function testRunCommandPrivateGitHubRepositoryNotInitialCloneNotInteractiveWithoutAuthentication() + { + $that = $this; + $commandCallable = function ($url) use ($that) { + $that->assertSame('https://github.com/acme/repo', $url); + + return 'git command'; + }; + + $this->mockConfig('https'); + + $this->process + ->method('execute') + ->willReturnMap(array( + array('git command', null, null, 1), + array('git --version', null, null, 0), + )); + + $this->git->runCommand($commandCallable, 'https://github.com/acme/repo', null, true); + } + + /** + * @dataProvider privateGithubWithCredentialsProvider + */ + public function testRunCommandPrivateGitHubRepositoryNotInitialCloneNotInteractiveWithAuthentication($gitUrl, $protocol, $gitHubToken, $expectedUrl) + { + $commandCallable = function ($url) use ($expectedUrl) { + if ($url !== $expectedUrl) { + return 'git command failing'; + } + + return 'git command ok'; + }; + + $this->mockConfig($protocol); + + $this->process + ->method('execute') + ->willReturnMap(array( + array('git command failing', null, null, 1), + array('git command ok', null, null, 0), + )); + + $this->io + ->method('isInteractive') + ->willReturn(false); + + $this->io + ->method('hasAuthentication') + ->with($this->equalTo('github.com')) + ->willReturn(true); + + $this->io + ->method('getAuthentication') + ->with($this->equalTo('github.com')) + ->willReturn(array('username' => 'token', 'password' => $gitHubToken)); + + $this->git->runCommand($commandCallable, $gitUrl, null, true); + } + + public function privateGithubWithCredentialsProvider() + { + return array( + array('git@github.com:acme/repo.git', 'ssh', 'MY_GITHUB_TOKEN', 'https://token:MY_GITHUB_TOKEN@github.com/acme/repo.git'), + array('https://github.com/acme/repo', 'https', 'MY_GITHUB_TOKEN', 'https://token:MY_GITHUB_TOKEN@github.com/acme/repo.git'), + ); + } + + private function mockConfig($protocol) + { + $this->config + ->method('get') + ->willReturnMap(array( + array('github-domains', 0, array('github.com')), + array('github-protocols', 0, array($protocol)), + )); + } +}