Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 4 additions & 9 deletions src/Composer/Repository/Vcs/GitDriver.php
Expand Up @@ -94,15 +94,10 @@ public function getRootIdentifier(): string
if (null === $this->rootIdentifier) {
$this->rootIdentifier = 'master';

if (!(bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
try {
$this->process->execute('git remote show origin', $output, $this->repoDir);
if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $output, $matches)) {
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);
}
$gitUtil = new GitUtil($this->io, $this->config, $this->process, new Filesystem());
$defaultBranch = $gitUtil->getMirrorDefaultBranch($this->url, $this->repoDir, Filesystem::isLocalPath($this->url));
if ($defaultBranch !== null) {
return $this->rootIdentifier = $defaultBranch;
}

// select currently checked out branch if master is not available
Expand Down
50 changes: 42 additions & 8 deletions src/Composer/Util/Git.php
Expand Up @@ -46,10 +46,12 @@ public function __construct(IOInterface $io, Config $config, ProcessExecutor $pr
* @param string $url
* @param string|null $cwd
* @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
*/
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
$this->config->prohibitUrlByConfig($url, $this->io);
Expand Down Expand Up @@ -85,7 +87,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
$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;
}
$messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput());
Expand All @@ -108,7 +110,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,

$auth = null;
$credentials = array();
if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $commandOutput, $cwd)) {
$errorMsg = $this->process->getErrorOutput();
// private github repository without ssh key access, try https with auth
if (Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
Expand All @@ -127,7 +129,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
$auth = $this->io->getAuthentication($match[1]);
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';
$command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
return;
}

Expand Down Expand Up @@ -162,7 +164,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';

$command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
return;
}

Expand All @@ -172,7 +174,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
$sshUrl = 'git@bitbucket.org:' . $match[2] . '.git';
$this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.');
$command = call_user_func($commandCallable, $sshUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
return;
}

Expand Down Expand Up @@ -204,7 +206,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
}

$command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
return;
}

Expand Down Expand Up @@ -241,7 +243,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
$authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3];

$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']);
$authHelper = new AuthHelper($this->io, $this->config);
$authHelper->storeAuth($match[2], $storeAuth);
Expand Down Expand Up @@ -392,6 +394,38 @@ private function isAuthenticationFailure(string $url, array &$match): bool
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
*/
Expand Down
45 changes: 44 additions & 1 deletion tests/Composer/Test/Repository/Vcs/GitDriverTest.php
Expand Up @@ -42,12 +42,43 @@ protected function tearDown(): void
}
}

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
{
$process = $this->getProcessExecutorMock();
$io = $this->getMockBuilder(IOInterface::class)->getMock();

$driver = new GitDriver(['url' => 'https://example.org/acme.git'], $io, $this->config, $this->getHttpDownloaderMock(), $process);
$this->setRepoDir($driver, $this->home);

$stdout = <<<GIT
* remote origin
Expand All @@ -62,7 +93,10 @@ public function testGetRootIdentifierFromRemote(): void

$process
->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,
]]);

Expand All @@ -77,6 +111,7 @@ public function testGetRootIdentifierFromLocalWithNetworkDisabled(): void
$io = $this->getMockBuilder(IOInterface::class)->getMock();

$driver = new GitDriver(['url' => 'https://example.org/acme.git'], $io, $this->config, $this->getHttpDownloaderMock(), $process);
$this->setRepoDir($driver, $this->home);

$stdout = <<<GIT
* main
Expand All @@ -92,4 +127,12 @@ public function testGetRootIdentifierFromLocalWithNetworkDisabled(): void

$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);
}
}