8000 Ensure Git::runCommand runs all commands at once with a given URL and… · composer/composer@7578df9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7578df9

Browse files
committed
Ensure Git::runCommand runs all commands at once with a given URL and does not reset URL between commands
1 parent 3d1fb9d commit 7578df9

File tree

4 files changed

+102
-96
lines changed

4 files changed

+102
-96
lines changed

src/Composer/Downloader/GitDownloader.php

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -94,28 +94,28 @@ protected function doInstall(PackageInterface $package, string $path, string $ur
9494
$path = $this->normalizePath($path);
9595
$cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/';
9696
$ref = $package->getSourceReference();
97-
$flag = Platform::isWindows() ? ['/D '] : [];
97+
$flag = Platform::isWindows() ? ['/D'] : [];
9898

9999
if (!empty($this->cachedPackages[$package->getId()][$ref])) {
100100
$msg = "Cloning ".$this->getShortHash($ref).' from cache';
101101

102-
$cloneFlags = ['--dissociate', '--reference', '%cachePath%'];
102+
$cloneFlags = ['--dissociate', '--reference', $cachePath];
103103
$transportOptions = $package->getTransportOptions();
104104
if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) {
105105
$cloneFlags = [];
106106
}
107107

108108
$commands = [
109-
array_merge(['git', 'clone', '--no-checkout', '%cachePath%', '%path%'], $cloneFlags),
110-
array_merge(['cd'], $flag, ['%path%']),
109+
array_merge(['git', 'clone', '--no-checkout', $cachePath, $path], $cloneFlags),
110+
array_merge(['cd'], $flag, [$path]),
111111
['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
112112
['git', 'remote', 'add', 'composer', '--', '%sanitizedUrl%'],
113113
];
114114
} else {
115115
$msg = "Cloning ".$this->getShortHash($ref);
116116
$commands = [
117-
array_merge(['git', 'clone', '--no-checkout', '--', '%url%', '%path%']),
118-
array_merge(['cd'], $flag, ['%path%']),
117+
array_merge(['git', 'clone', '--no-checkout', '--', '%url%', $path]),
118+
array_merge(['cd'], $flag, [$path]),
119119
['git', 'remote', 'add', 'composer', '--', '%url%'],
120120
['git', 'fetch', 'composer'],
121121
['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
@@ -128,23 +128,7 @@ protected function doInstall(PackageInterface $package, string $path, string $ur
128128

129129
$this->io->writeError($msg);
130130

131-
$command = [];
132-
$commandCallable = static function (string $url) use (&$command, $path, $cachePath): array {
133-
$map = [
134-
'%url%' => $url,
135-
'%path%' => $path,
136-
'%cachePath%' => $cachePath,
137-
'%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url),
138-
];
139-
140-
return array_map(static function($value) use ($map): string {
141-
return $map[$value] ?? $value;
142 F438 -
}, $command);
143-
};
144-
145-
foreach ($commands as $command) {
146-
$this->gitUtil->runCommand($commandCallable, $url, $path, true);
147-
}
131+
$this->gitUtil->runCommands($commands, $url, $path, true);
148132

149133
$sourceUrl = $package->getSourceUrl();
150134
if ($url !== $sourceUrl && $sourceUrl !== null) {
@@ -191,28 +175,17 @@ protected function doUpdate(PackageInterface $initial, PackageInterface $target,
191175
$this->io->writeError($msg);
192176

193177
if (0 !== $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $output, $path)) {
194-
$command = [];
195-
$commandCallable = static function (string $url) use (&$command): array {
196-
return array_map(static function($value) use ($url): string {
197-
return $value === '%url%' ? $url : $value;
198-
}, $command);
199-
};
200-
201178
$commands = [
202179
['git', 'remote', 'set-url', 'composer', '--', $remoteUrl],
203180
['git', 'fetch', 'composer'],
204181
['git', 'fetch', '--tags', 'composer'],
205182
];
206183

207-
foreach ($commands as $command) {
208-
$this->gitUtil->runCommand($commandCallable, $url, $path, true);
209-
}
184+
$this->gitUtil->runCommands($commands, $url, $path, true);
210185
}
211186

212-
$commandCallable = static function (string $url): array {
213-
return ['git', 'remote', 'set-url', 'composer', '--', Preg::replace('{://([^@]+?):(.+?)@}', '://', $url)];
214-
};
215-
$this->gitUtil->runCommand($commandCallable, $url, $path);
187+
$command = ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'];
188+
$this->gitUtil->runCommands([$command], $url, $path);
216189

217190
if ($newRef FAB3 = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) {
218191
if ($target->getDistReference() === $target->getSourceReference()) {
@@ -474,7 +447,12 @@ protected function updateToCommit(PackageInterface $package, string $path, strin
474447

475448
$branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion);
476449

450+
/**
451+
* @var \Closure(non-empty-list<string>): bool $execute
452+
* @phpstan-ignore varTag.nativeType
453+
*/
477454
$execute = function (array $command) use (&$output, $path) {
455+
/** @var non-empty-list<string> $command */
478456
$output = '';
479457

480458
return 0 === $this->process->execute($command, $output, $path);

src/Composer/Repository/Vcs/GitDriver.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,7 @@ public static function supports(IOInterface $io, Config $config, string $url, bo
242242
GitUtil::cleanEnv();
243243

244244
try {
245-
$gitUtil->runCommand(static function ($url): array {
246-
return ['git', 'ls-remote', '--heads', '--', $url];
247-
}, $url, sys_get_temp_dir());
245+
$gitUtil->runCommands([['git', 'ls-remote', '--heads', '--', '%url%']], $url, sys_get_temp_dir());
248246
} catch (\RuntimeException $e) {
249247
return false;
250248
}

src/Composer/Util/Git.php

Lines changed: 81 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,74 @@ public function setHttpDownloader(HttpDownloader $httpDownloader): void
4949
}
5050

5151
/**
52+
* Runs a set of commands using the $url or a variation of it (with auth, ssh, ..)
53+
*
54+
* Commands should use %url% placeholders for the URL instead of inlining it to allow this function to do its job
55+
* %sanitizedUrl% is also automatically replaced by the url without user/pass
56+
*
57+
* As soon as a single command fails it will halt, so assume the commands are run as && in bash
58+
*
59+
* @param non-empty-array<non-empty-list<string>> $commands
60+
* @param mixed $commandOutput the output will be written into this var if passed by ref
61+
* if a callable is passed it will be used as output handler
62+
*/
63+
public function runCommands(array $commands, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
64+
{
65+
$callables = [];
66+
foreach ($commands as $cmd) {
67+
$callables[] = static function (string $url) use ($cmd): array {
68+
$map = [
69+
'%url%' => $url,
70+
'%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url),
71+
];
72+
73+
return array_map(static function ($value) use ($map): string {
74+
return $map[$value] ?? $value;
75+
}, $cmd);
76+
};
77+
}
78+
79+
// @phpstan-ignore method.deprecated
80+
$this->runCommand($callables, $url, $cwd, $initialClone, $commandOutput);
81+
}
82+
83+
/**
84+
* @param callable|array<callable> $commandCallable
5285
* @param mixed $commandOutput the output will be written into this var if passed by ref
5386
* if a callable is passed it will be used as output handler
87+
* @deprecated Use runCommands with placeholders instead of callbacks for simplicity
5488
*/
55-
public function runCommand(callable $commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
89+
public function runCommand($commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
5690
{
91+
$commandCallables = is_callable($commandCallable) ? [$commandCallable] : $commandCallable;
92+
$lastCommand = '';
93+
94+
$runCommands = function ($url) use ($commandCallables, $cwd, &$commandOutput, &$lastCommand) {
95+
$collectOutputs = !is_callable($commandOutput);
96+
$outputs = [];
97+
98+
$status = 0;
99+
foreach ($commandCallables as $callable) {
100+
$lastCommand = $callable($url);
101+
if ($collectOutputs) {
102+
$outputs[] = '';
103+
$output = &$outputs[count($outputs) - 1];
104+
} else {
105+
$output = &$commandOutput;
106+
}
107+
$status = $this->process->execute($lastCommand, $output, $cwd);
108+
if ($status !== 0) {
109+
break;
110+
}
111+
}
112+
113+
if ($collectOutputs) {
114+
$commandOutput = implode('', $outputs);
115+
}
116+
117+
return $status;
118+
};
119+
57120
// Ensure we are allowed to use this URL by config
58121
$this->config->prohibitUrlByConfig($url, $this->io);
59122

@@ -86,7 +149,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
86149
$protoUrl = $protocol . "://" . $match[1] . "/" . $match[2];
87150
}
88151

89-
if (0 === $this->process->execute($commandCallable($protoUrl), $commandOutput, $cwd)) {
152+
if (0 === $runCommands($protoUrl)) {
90153
return;
91154
}
92155
$messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput());
@@ -105,11 +168,9 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
105168
// if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https
106169
$bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true);
107170

108-
$command = $commandCallable($url);
109-
110171
$auth = null;
111172
$credentials = [];
112-
if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $commandOutput, $cwd)) {
173+
if ($bypassSshForGitHub || 0 !== $runCommands($url)) {
113174
$errorMsg = $this->process->getErrorOutput();
114175
// private github repository without ssh key access, try https with auth
115176
// @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
@@ -129,8 +190,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
129190
if ($this->io->hasAuthentication($match[1])) {
130191
$auth = $this->io->getAuthentication($match[1]);
131192
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';
132-
$command = $commandCallable($authUrl);
133-
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
193+
if (0 === $runCommands($authUrl)) {
134194
return;
135195
}
136196

@@ -166,8 +226,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
166226
$auth = $this->io->getAuthentication($domain);
167227
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;
168228

169-
$command = $commandCallable($authUrl);
170-
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
229+
if (0 === $runCommands($authUrl)) {
171230
// Well if that succeeded on our first try, let's just
172231
// take the win.
173232
return;
@@ -185,8 +244,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
185244
if ($this->io->hasAuthentication($domain)) {
186245
$auth = $this->io->getAuthentication($domain);
187246
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;
188-
$command = $commandCallable($authUrl);
189-
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
247+
if (0 === $runCommands($authUrl)) {
190248
return;
191249
}
192250

@@ -195,8 +253,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
195253
//Falling back to ssh
196254
$sshUrl = 'git@bitbucket.org:' . $repo_with_git_part;
197255
$this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.');
198-
$command = $commandCallable($sshUrl);
199-
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
256+
if (0 === $runCommands($sshUrl)) {
200257
return;
201258
}
202259

@@ -228,8 +285,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
228285
$authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3];
229286
}
230287

231-
$command = $commandCallable($authUrl);
232-
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
288+
if (0 === $runCommands($authUrl)) {
233289
return;
234290
}
235291

@@ -266,8 +322,7 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
266322
if (null !== $auth) {
267323
$authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3];
268324

269-
$command = $commandCallable($authUrl);
270-
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
325+
if (0 === $runCommands($authUrl)) {
271326
$this->io->setAuthentication($match[2], $auth['username'], $auth['password']);
272327
$authHelper = new AuthHelper($this->io, $this->config);
273328
$authHelper->storeAuth($match[2], $storeAuth);
@@ -285,10 +340,10 @@ public function runCommand(callable $commandCallable, string $url, ?string $cwd,
285340
}
286341

287342
if (count($credentials) > 0) {
288-
$command = $this->maskCredentials($command, $credentials);
343+
$lastCommand = $this->maskCredentials($lastCommand, $credentials);
289344
$errorMsg = $this->maskCredentials($errorMsg, $credentials);
290345
}
291-
$this->throwException('Failed to execute ' . $command . "\n\n" . $errorMsg, $url);
346+
$this->throwException('Failed to execute ' . $lastCommand . "\n\n" . $errorMsg, $url);
292347
}
293348
}
294349

@@ -310,21 +365,7 @@ public function syncMirror(string $url, string $dir): bool
310365
['git', 'gc', '--auto'],
311366
];
312367

313-
$command = [];
314-
$commandCallable = static function (string $url) use (&$command): array {
315-
$map = [
316-
'%url%' => $url,
317-
'%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url),
318-
];
319-
320-
return array_map(static function($value) use ($map): string {
321-
return $map[$value] ?? $value;
322-
}, $command);
323-
};
324-
325-
foreach ($commands as $command) {
326-
$this->runCommand($commandCallable, $url, $dir);
327-
}
368+
$this->runCommands($commands, $url, $dir);
328369
} catch (\Exception $e) {
329370
$this->io->writeError('<error>Sync mirror failed: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);
330371

@@ -337,11 +378,7 @@ public function syncMirror(string $url, string $dir): bool
337378
// clean up directory and do a fresh clone into it
338379
$this->filesystem->removeDirectory($dir);
339380

340-
$commandCallable = static function ($url) use ($dir): array {
341-
return ['git', 'clone', '--mirror', '--', $url, $dir];
342-
};
343-
344-
$this->runCommand($commandCallable, $url, $dir, true);
381+
$this->runCommands([['git', 'clone', '--mirror', '--', '%url%', $dir]], $url, $dir, true);
345382

346383
return true;
347384
}
@@ -391,6 +428,9 @@ public static function getNoShowSignatureFlag(ProcessExecutor $process): string
391428
return '';
392429
}
393430

431+
/**
432+
* @return list<string>
433+
*/
394434
public static function getNoShowSignatureFlags(ProcessExecutor $process): array
395435
{
396436
return explode(' ', substr(static::getNoShowSignatureFlag($process), 1));
@@ -451,25 +491,10 @@ public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPa
451491
['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
452492
];
453493

454-
$output = [];
455-
$command = [];
456-
$commandCallable = static function (string $url) use (&$command): array {
457-
$map = [
458-
'%url%' => $url,
459-
'%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url),
460-
];
461-
462-
return array_map(static function($value) use ($map): string {
463-
return $map[$value] ?? $value;
464-
}, $command);
465-
};
466-
467-
foreach ($commands as $command) {
468-
$this->runCommand($commandCallable, $url, $dir, false, $output[]);
469-
}
494+
$this->runCommands($commands, $url, $dir, false, $output);
470495
}
471496

472-
$lines = $this->process->splitLines(implode('', $output));
497+
$lines = $this->process->splitLines($output);
473498
foreach ($lines as $line) {
474499
if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches)) {
475500
return $matches[1];

0 commit comments

Comments
 (0)
0