GitDriver: try to fetch default branch form remote using auth (#10701)

main
Stephan 2 years ago committed by GitHub
parent 54063964a7
commit 866d2a49b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -94,15 +94,10 @@ class GitDriver extends VcsDriver
if (null === $this->rootIdentifier) { if (null === $this->rootIdentifier) {
$this->rootIdentifier = 'master'; $this->rootIdentifier = 'master';
if (!(bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { $gitUtil = new GitUtil($this->io, $this->config, $this->process, new Filesystem());
try { $defaultBranch = $gitUtil->getMirrorDefaultBranch($this->url, $this->repoDir, Filesystem::isLocalPath($this->url));
$this->process->execute('git remote show origin', $output, $this->repoDir); if ($defaultBranch !== null) {
if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $output, $matches)) { return $this->rootIdentifier = $defaultBranch;
return $this->rootIdentifier = $matches[1];
}
} catch (\Exception $e) {
$this->io->writeError('<error>Failed to fetch root identifier from remote: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);
}
} }
// select currently checked out branch if master is not available // select currently checked out branch if master is not available

@ -46,10 +46,12 @@ class Git
* @param string $url * @param string $url
* @param string|null $cwd * @param string|null $cwd
* @param bool $initialClone * @param bool $initialClone
* @param mixed $commandOutput the output will be written into this var if passed by ref
* if a callable is passed it will be used as output handler
* *
* @return void * @return void
*/ */
public function runCommand(callable $commandCallable, string $url, ?string $cwd, bool $initialClone = false): void public function runCommand(callable $commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
{ {
// Ensure we are allowed to use this URL by config // Ensure we are allowed to use this URL by config
$this->config->prohibitUrlByConfig($url, $this->io); $this->config->prohibitUrlByConfig($url, $this->io);
@ -85,7 +87,7 @@ class Git
$protoUrl = $protocol . "://" . $match[1] . "/" . $match[2]; $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2];
} }
if (0 === $this->process->execute(call_user_func($commandCallable, $protoUrl), $ignoredOutput, $cwd)) { if (0 === $this->process->execute(call_user_func($commandCallable, $protoUrl), $commandOutput, $cwd)) {
return; return;
} }
$messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput()); $messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput());
@ -108,7 +110,7 @@ class Git
$auth = null; $auth = null;
$credentials = array(); $credentials = array();
if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) { if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $commandOutput, $cwd)) {
$errorMsg = $this->process->getErrorOutput(); $errorMsg = $this->process->getErrorOutput();
// private github repository without ssh key access, try https with auth // private github repository without ssh key access, try https with auth
if (Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match) if (Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
@ -127,7 +129,7 @@ class Git
$auth = $this->io->getAuthentication($match[1]); $auth = $this->io->getAuthentication($match[1]);
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';
$command = call_user_func($commandCallable, $authUrl); $command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
return; return;
} }
@ -162,7 +164,7 @@ class Git
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';
$command = call_user_func($commandCallable, $authUrl); $command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
return; return;
} }
@ -172,7 +174,7 @@ class Git
$sshUrl = 'git@bitbucket.org:' . $match[2] . '.git'; $sshUrl = 'git@bitbucket.org:' . $match[2] . '.git';
$this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.'); $this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.');
$command = call_user_func($commandCallable, $sshUrl); $command = call_user_func($commandCallable, $sshUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
return; return;
} }
@ -204,7 +206,7 @@ class Git
} }
$command = call_user_func($commandCallable, $authUrl); $command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
return; return;
} }
@ -241,7 +243,7 @@ class Git
$authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3]; $authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3];
$command = call_user_func($commandCallable, $authUrl); $command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
$this->io->setAuthentication($match[2], $auth['username'], $auth['password']); $this->io->setAuthentication($match[2], $auth['username'], $auth['password']);
$authHelper = new AuthHelper($this->io, $this->config); $authHelper = new AuthHelper($this->io, $this->config);
$authHelper->storeAuth($match[2], $storeAuth); $authHelper->storeAuth($match[2], $storeAuth);
@ -392,6 +394,38 @@ class Git
return false; return false;
} }
public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPathRepository): ?string
{
if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
return null;
}
try {
if ($isLocalPathRepository) {
$this->process->execute('git remote show origin', $output, $dir);
} else {
$commandCallable = function ($url): string {
$sanitizedUrl = Preg::replace('{://([^@]+?):(.+?)@}', '://', $url);
return sprintf('git remote set-url origin -- %s && git remote show origin && git remote set-url origin -- %s', ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl));
};
$this->runCommand($commandCallable, $url, $dir, false, $output);
}
$lines = $this->process->splitLines($output);
foreach ($lines as $line) {
if (Preg::match('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches) > 0) {
return $matches[1];
}
}
} catch (\Exception $e) {
$this->io->writeError('<error>Failed to fetch root identifier from remote: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);
}
return null;
}
/** /**
* @return void * @return void
*/ */

@ -42,12 +42,43 @@ class GitDriverTest extends TestCase
} }
} }
public function testGetRootIdentifierFromRemoteLocalRepository(): void
{
$process = $this->getProcessExecutorMock();
$io = $this->getMockBuilder(IOInterface::class)->getMock();
$driver = new GitDriver(['url' => $this->home], $io, $this->config, $this->getHttpDownloaderMock(), $process);
$this->setRepoDir($driver, $this->home);
$stdoutFailure = <<<GITFAILURE
fatal: could not read Username for 'https://example.org/acme.git': terminal prompts disabled
GITFAILURE;
$stdout = <<<GIT
* main
2.2
1.10
GIT;
$process
->expects([[
'cmd' => 'git remote show origin',
'stdout' => $stdoutFailure,
], [
'cmd' => 'git branch --no-color',
'stdout' => $stdout,
]]);
$this->assertSame('main', $driver->getRootIdentifier());
}
public function testGetRootIdentifierFromRemote(): void public function testGetRootIdentifierFromRemote(): void
{ {
$process = $this->getProcessExecutorMock(); $process = $this->getProcessExecutorMock();
$io = $this->getMockBuilder(IOInterface::class)->getMock(); $io = $this->getMockBuilder(IOInterface::class)->getMock();
$driver = new GitDriver(['url' => 'https://example.org/acme.git'], $io, $this->config, $this->getHttpDownloaderMock(), $process); $driver = new GitDriver(['url' => 'https://example.org/acme.git'], $io, $this->config, $this->getHttpDownloaderMock(), $process);
$this->setRepoDir($driver, $this->home);
$stdout = <<<GIT $stdout = <<<GIT
* remote origin * remote origin
@ -62,7 +93,10 @@ GIT;
$process $process
->expects([[ ->expects([[
'cmd' => 'git remote show origin', 'cmd' => 'git remote -v',
'stdout' => '',
],[
'cmd' => "git remote set-url origin -- 'https://example.org/acme.git' && git remote show origin && git remote set-url origin -- 'https://example.org/acme.git'",
'stdout' => $stdout, 'stdout' => $stdout,
]]); ]]);
@ -77,6 +111,7 @@ GIT;
$io = $this->getMockBuilder(IOInterface::class)->getMock(); $io = $this->getMockBuilder(IOInterface::class)->getMock();
$driver = new GitDriver(['url' => 'https://example.org/acme.git'], $io, $this->config, $this->getHttpDownloaderMock(), $process); $driver = new GitDriver(['url' => 'https://example.org/acme.git'], $io, $this->config, $this->getHttpDownloaderMock(), $process);
$this->setRepoDir($driver, $this->home);
$stdout = <<<GIT $stdout = <<<GIT
* main * main
@ -92,4 +127,12 @@ GIT;
$this->assertSame('main', $driver->getRootIdentifier()); $this->assertSame('main', $driver->getRootIdentifier());
} }
private function setRepoDir(GitDriver $driver, string $path): void
{
$reflectionClass = new \ReflectionClass($driver);
$reflectionProperty = $reflectionClass->getProperty('repoDir');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($driver, $path);
}
} }

Loading…
Cancel
Save