Stop relying on OS to find executables on Windows, and migrate most Process calls to array syntax (#12180)
Co-authored-by: Jordi Boggiano <j.boggiano@seld.be>pull/12186/head
parent
5a75d32414
commit
3dc279cf66
|
@ -8,16 +8,16 @@
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "composer/ca-bundle",
|
"name": "composer/ca-bundle",
|
||||||
"version": "1.5.2",
|
"version": "1.5.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/ca-bundle.git",
|
"url": "https://github.com/composer/ca-bundle.git",
|
||||||
"reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137"
|
"reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/48a792895a2b7a6ee65dd5442c299d7b835b6137",
|
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/3b1fc3f0be055baa7c6258b1467849c3e8204eb2",
|
||||||
"reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137",
|
"reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
"support": {
|
"support": {
|
||||||
"irc": "irc://irc.freenode.org/composer",
|
"irc": "irc://irc.freenode.org/composer",
|
||||||
"issues": "https://github.com/composer/ca-bundle/issues",
|
"issues": "https://github.com/composer/ca-bundle/issues",
|
||||||
"source": "https://github.com/composer/ca-bundle/tree/1.5.2"
|
"source": "https://github.com/composer/ca-bundle/tree/1.5.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-09-25T07:49:53+00:00"
|
"time": "2024-11-04T10:15:26+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/class-map-generator",
|
"name": "composer/class-map-generator",
|
||||||
|
@ -941,16 +941,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/console",
|
"name": "symfony/console",
|
||||||
"version": "v5.4.45",
|
"version": "v5.4.46",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/console.git",
|
"url": "https://github.com/symfony/console.git",
|
||||||
"reference": "108d436c2af470858bdaba3257baab3a74172017"
|
"reference": "fb0d4760e7147d81ab4d9e2d57d56268261b4e4e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/console/zipball/108d436c2af470858bdaba3257baab3a74172017",
|
"url": "https://api.github.com/repos/symfony/console/zipball/fb0d4760e7147d81ab4d9e2d57d56268261b4e4e",
|
||||||
"reference": "108d436c2af470858bdaba3257baab3a74172017",
|
"reference": "fb0d4760e7147d81ab4d9e2d57d56268261b4e4e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1020,7 +1020,7 @@
|
||||||
"terminal"
|
"terminal"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/console/tree/v5.4.45"
|
"source": "https://github.com/symfony/console/tree/v5.4.46"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -1036,7 +1036,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-10-08T07:27:17+00:00"
|
"time": "2024-11-05T14:17:06+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/deprecation-contracts",
|
"name": "symfony/deprecation-contracts",
|
||||||
|
@ -1787,16 +1787,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/process",
|
"name": "symfony/process",
|
||||||
"version": "v5.4.45",
|
"version": "v5.4.46",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/process.git",
|
"url": "https://github.com/symfony/process.git",
|
||||||
"reference": "95f3f19d0f8f06e4253c66a0828ddb69f8b8ede4"
|
"reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/process/zipball/95f3f19d0f8f06e4253c66a0828ddb69f8b8ede4",
|
"url": "https://api.github.com/repos/symfony/process/zipball/01906871cb9b5e3cf872863b91aba4ec9767daf4",
|
||||||
"reference": "95f3f19d0f8f06e4253c66a0828ddb69f8b8ede4",
|
"reference": "01906871cb9b5e3cf872863b91aba4ec9767daf4",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1829,7 +1829,7 @@
|
||||||
"description": "Executes commands in sub-processes",
|
"description": "Executes commands in sub-processes",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/process/tree/v5.4.45"
|
"source": "https://github.com/symfony/process/tree/v5.4.46"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -1845,7 +1845,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-09-25T14:11:13+00:00"
|
"time": "2024-11-06T09:18:28+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/service-contracts",
|
"name": "symfony/service-contracts",
|
||||||
|
@ -2226,16 +2226,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-symfony",
|
"name": "phpstan/phpstan-symfony",
|
||||||
"version": "1.4.10",
|
"version": "1.4.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
||||||
"reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1"
|
"reference": "270c2ee1478d1f8dc5121f539e890017bd64b04c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/f7d5782044bedf93aeb3f38e09c91148ee90e5a1",
|
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/270c2ee1478d1f8dc5121f539e890017bd64b04c",
|
||||||
"reference": "f7d5782044bedf93aeb3f38e09c91148ee90e5a1",
|
"reference": "270c2ee1478d1f8dc5121f539e890017bd64b04c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2292,9 +2292,9 @@
|
||||||
"description": "Symfony Framework extensions and rules for PHPStan",
|
"description": "Symfony Framework extensions and rules for PHPStan",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.10"
|
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.11"
|
||||||
},
|
},
|
||||||
"time": "2024-09-26T18:14:50+00:00"
|
"time": "2024-10-30T12:07:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/phpunit-bridge",
|
"name": "symfony/phpunit-bridge",
|
||||||
|
|
|
@ -300,11 +300,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Command/CreateProjectCommand.php
|
path: ../src/Composer/Command/CreateProjectCommand.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#3 \\$existingRepos of static method Composer\\\\Repository\\\\RepositoryFactory\\:\\:generateRepositoryName\\(\\) expects array\\<string, mixed\\>, array\\<int\\|string, mixed\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Command/CreateProjectCommand.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Variable method call on Composer\\\\Package\\\\RootPackageInterface\\.$#"
|
message: "#^Variable method call on Composer\\\\Package\\\\RootPackageInterface\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
@ -495,11 +490,6 @@ parameters:
|
||||||
count: 2
|
count: 2
|
||||||
path: ../src/Composer/Command/LicensesCommand.php
|
path: ../src/Composer/Command/LicensesCommand.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Only booleans are allowed in a negated boolean, array\\<int, Composer\\\\Package\\\\PackageInterface\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Command/ReinstallCommand.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Foreach overwrites \\$type with its key variable\\.$#"
|
message: "#^Foreach overwrites \\$type with its key variable\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
@ -955,11 +945,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Config.php
|
path: ../src/Composer/Config.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Only booleans are allowed in a ternary operator condition, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Config.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
|
message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
@ -1160,11 +1145,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Downloader/FileDownloader.php
|
path: ../src/Composer/Downloader/FileDownloader.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#3 \\$cwd of method Composer\\\\Util\\\\ProcessExecutor\\:\\:execute\\(\\) expects string\\|null, string\\|false given\\.$#"
|
|
||||||
count: 5
|
|
||||||
path: ../src/Composer/Downloader/FossilDownloader.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
|
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
|
||||||
count: 5
|
count: 5
|
||||||
|
@ -1367,7 +1347,7 @@ parameters:
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Only booleans are allowed in a negated boolean, array\\<int, array\\<int, string\\>\\> given\\.$#"
|
message: "#^Only booleans are allowed in a negated boolean, array\\<int, array\\<int, string\\>\\> given\\.$#"
|
||||||
count: 3
|
count: 2
|
||||||
path: ../src/Composer/Downloader/ZipDownloader.php
|
path: ../src/Composer/Downloader/ZipDownloader.php
|
||||||
|
|
||||||
-
|
-
|
||||||
|
@ -2330,11 +2310,6 @@ parameters:
|
||||||
count: 2
|
count: 2
|
||||||
path: ../src/Composer/Question/StrictConfirmationQuestion.php
|
path: ../src/Composer/Question/StrictConfirmationQuestion.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method Composer\\\\Repository\\\\ArrayRepository\\:\\:getProviders\\(\\) should return array\\<string, array\\{name\\: string, description\\: string, type\\: string\\}\\> but returns array\\<string, array\\{name\\: string, description\\: string\\|null, type\\: string\\}\\>\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Repository/ArrayRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Only booleans are allowed in a negated boolean, Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\|null given\\.$#"
|
message: "#^Only booleans are allowed in a negated boolean, Composer\\\\Semver\\\\Constraint\\\\ConstraintInterface\\|null given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
@ -2381,7 +2356,7 @@ parameters:
|
||||||
path: ../src/Composer/Repository/ComposerRepository.php
|
path: ../src/Composer/Repository/ComposerRepository.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method Composer\\\\Repository\\\\ComposerRepository\\:\\:getProviders\\(\\) should return array\\<string, array\\{name\\: string, description\\: string|null, type\\: string\\}\\> but returns array\\<int\\|string, array\\{name\\: mixed, description\\: mixed, type\\: mixed\\}\\>\\.$#"
|
message: "#^Method Composer\\\\Repository\\\\ComposerRepository\\:\\:getProviders\\(\\) should return array\\<string, array\\{name\\: string, description\\: string\\|null, type\\: string\\}\\> but returns array\\<int\\|string, array\\{name\\: mixed, description\\: mixed, type\\: mixed\\}\\>\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Repository/ComposerRepository.php
|
path: ../src/Composer/Repository/ComposerRepository.php
|
||||||
|
|
||||||
|
@ -2496,7 +2471,7 @@ parameters:
|
||||||
path: ../src/Composer/Repository/CompositeRepository.php
|
path: ../src/Composer/Repository/CompositeRepository.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Only booleans are allowed in a ternary operator condition, array\\<int, array\\<string, array\\<string, string|null\\>\\>\\> given\\.$#"
|
message: "#^Only booleans are allowed in a ternary operator condition, array\\<int, array\\<string, array\\<string, string\\|null\\>\\>\\> given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Repository/CompositeRepository.php
|
path: ../src/Composer/Repository/CompositeRepository.php
|
||||||
|
|
||||||
|
@ -2714,7 +2689,7 @@ parameters:
|
||||||
path: ../src/Composer/Repository/RepositorySet.php
|
path: ../src/Composer/Repository/RepositorySet.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Only booleans are allowed in an if condition, array\\<string, array\\<string, string|null\\>\\> given\\.$#"
|
message: "#^Only booleans are allowed in an if condition, array\\<string, array\\<string, string\\|null\\>\\> given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Repository/RepositorySet.php
|
path: ../src/Composer/Repository/RepositorySet.php
|
||||||
|
|
||||||
|
@ -2723,21 +2698,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Repository/RepositorySet.php
|
path: ../src/Composer/Repository/RepositorySet.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Only booleans are allowed in a negated boolean, string given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Repository/Vcs/FossilDriver.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$file of method Composer\\\\Util\\\\Filesystem\\:\\:remove\\(\\) expects string, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Repository/Vcs/FossilDriver.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$filename of function is_file expects string, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Repository/Vcs/FossilDriver.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Call to function array_search\\(\\) requires parameter \\#3 to be set\\.$#"
|
message: "#^Call to function array_search\\(\\) requires parameter \\#3 to be set\\.$#"
|
||||||
count: 2
|
count: 2
|
||||||
|
@ -3968,11 +3928,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Util/Svn.php
|
path: ../src/Composer/Util/Svn.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Util/Svn.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
|
message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
@ -4341,6 +4296,11 @@ parameters:
|
||||||
count: 2
|
count: 2
|
||||||
path: ../tests/Composer/Test/Mock/ProcessExecutorMock.php
|
path: ../tests/Composer/Test/Mock/ProcessExecutorMock.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: "#^Property Composer\\\\Test\\\\Mock\\\\ProcessExecutorMock\\:\\:\\$expectations \\(array\\<array\\{cmd\\: list\\<string\\>\\|string, return\\: int, stdout\\: string, stderr\\: string, callback\\: \\(callable\\(\\)\\: mixed\\)\\|null\\}\\>\\|null\\) does not accept array\\<non\\-empty\\-array\\<'callback'\\|'cmd'\\|'return'\\|'stderr'\\|'stdout'\\|int\\<0, max\\>, non\\-empty\\-list\\<non\\-empty\\-list\\<string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|int\\|string\\>\\|\\(callable\\(\\)\\: mixed\\)\\|int\\|string\\|null\\>\\>\\.$#"
|
||||||
|
count: 1
|
||||||
|
path: ../tests/Composer/Test/Mock/ProcessExecutorMock.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Composer\\\\Test\\\\Mock\\\\VersionGuesserMock\\:\\:__construct\\(\\) does not call parent constructor from Composer\\\\Package\\\\Version\\\\VersionGuesser\\.$#"
|
message: "#^Composer\\\\Test\\\\Mock\\\\VersionGuesserMock\\:\\:__construct\\(\\) does not call parent constructor from Composer\\\\Package\\\\Version\\\\VersionGuesser\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
@ -4486,9 +4446,14 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../tests/Composer/Test/TestCase.php
|
path: ../tests/Composer/Test/TestCase.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: "#^Cannot access an offset on array\\<int, array\\<string, array\\<int, string\\>\\|int\\|string\\>\\>\\|false\\.$#"
|
||||||
|
count: 2
|
||||||
|
path: ../tests/Composer/Test/Util/GitTest.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot access an offset on array\\<int, array\\<string, int\\|string\\>\\>\\|false\\.$#"
|
message: "#^Cannot access an offset on array\\<int, array\\<string, int\\|string\\>\\>\\|false\\.$#"
|
||||||
count: 3
|
count: 1
|
||||||
path: ../tests/Composer/Test/Util/GitTest.php
|
path: ../tests/Composer/Test/Util/GitTest.php
|
||||||
|
|
||||||
-
|
-
|
||||||
|
|
|
@ -302,7 +302,7 @@ EOT
|
||||||
return '<comment>proc_open is not available, git cannot be used</comment>';
|
return '<comment>proc_open is not available, git cannot be used</comment>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->process->execute('git config color.ui', $output);
|
$this->process->execute(['git', 'config', 'color.ui'], $output);
|
||||||
if (strtolower(trim($output)) === 'always') {
|
if (strtolower(trim($output)) === 'always') {
|
||||||
return '<comment>Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.</comment>';
|
return '<comment>Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.</comment>';
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,22 +122,20 @@ EOT
|
||||||
*/
|
*/
|
||||||
private function openBrowser(string $url): void
|
private function openBrowser(string $url): void
|
||||||
{
|
{
|
||||||
$url = ProcessExecutor::escape($url);
|
|
||||||
|
|
||||||
$process = new ProcessExecutor($this->getIO());
|
$process = new ProcessExecutor($this->getIO());
|
||||||
if (Platform::isWindows()) {
|
if (Platform::isWindows()) {
|
||||||
$process->execute('start "web" explorer ' . $url, $output);
|
$process->execute(['start', '"web"', 'explorer', $url], $output);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$linux = $process->execute('which xdg-open', $output);
|
$linux = $process->execute(['which', 'xdg-open'], $output);
|
||||||
$osx = $process->execute('which open', $output);
|
$osx = $process->execute(['which', 'open'], $output);
|
||||||
|
|
||||||
if (0 === $linux) {
|
if (0 === $linux) {
|
||||||
$process->execute('xdg-open ' . $url, $output);
|
$process->execute(['xdg-open', $url], $output);
|
||||||
} elseif (0 === $osx) {
|
} elseif (0 === $osx) {
|
||||||
$process->execute('open ' . $url, $output);
|
$process->execute(['open', $url], $output);
|
||||||
} else {
|
} else {
|
||||||
$this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url);
|
$this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ use Composer\Util\Silencer;
|
||||||
use Symfony\Component\Console\Input\ArrayInput;
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Composer\Console\Input\InputOption;
|
use Composer\Console\Input\InputOption;
|
||||||
|
use Composer\Util\ProcessExecutor;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Process\ExecutableFinder;
|
use Symfony\Component\Process\ExecutableFinder;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
|
@ -535,15 +536,11 @@ EOT
|
||||||
return $this->gitConfig;
|
return $this->gitConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
$finder = new ExecutableFinder();
|
$process = new ProcessExecutor($this->getIO());
|
||||||
$gitBin = $finder->find('git');
|
|
||||||
|
|
||||||
$cmd = new Process([$gitBin, 'config', '-l']);
|
if (0 === $process->execute(['git', 'config', '-l'], $output)) {
|
||||||
$cmd->run();
|
|
||||||
|
|
||||||
if ($cmd->isSuccessful()) {
|
|
||||||
$this->gitConfig = [];
|
$this->gitConfig = [];
|
||||||
Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $cmd->getOutput(), $matches);
|
Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $output, $matches);
|
||||||
foreach ($matches[1] as $key => $match) {
|
foreach ($matches[1] as $key => $match) {
|
||||||
$this->gitConfig[$match] = $matches[2][$key];
|
$this->gitConfig[$match] = $matches[2][$key];
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace Composer;
|
||||||
use Composer\Json\JsonFile;
|
use Composer\Json\JsonFile;
|
||||||
use Composer\CaBundle\CaBundle;
|
use Composer\CaBundle\CaBundle;
|
||||||
use Composer\Pcre\Preg;
|
use Composer\Pcre\Preg;
|
||||||
|
use Composer\Util\ProcessExecutor;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
use Seld\PharUtils\Timestamps;
|
use Seld\PharUtils\Timestamps;
|
||||||
|
@ -48,23 +49,22 @@ class Compiler
|
||||||
unlink($pharFile);
|
unlink($pharFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
$process = new Process(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], __DIR__);
|
$process = new ProcessExecutor();
|
||||||
if ($process->run() !== 0) {
|
|
||||||
|
if (0 !== $process->execute(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], $output, __DIR__)) {
|
||||||
throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
|
throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
|
||||||
}
|
}
|
||||||
$this->version = trim($process->getOutput());
|
$this->version = trim($output);
|
||||||
|
|
||||||
$process = new Process(['git', 'log', '-n1', '--pretty=%ci', 'HEAD'], __DIR__);
|
if (0 !== $process->execute(['git', 'log', '-n1', '--pretty=%ci', 'HEAD'], $output, __DIR__)) {
|
||||||
if ($process->run() !== 0) {
|
|
||||||
throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
|
throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->versionDate = new \DateTime(trim($process->getOutput()));
|
$this->versionDate = new \DateTime(trim($output));
|
||||||
$this->versionDate->setTimezone(new \DateTimeZone('UTC'));
|
$this->versionDate->setTimezone(new \DateTimeZone('UTC'));
|
||||||
|
|
||||||
$process = new Process(['git', 'describe', '--tags', '--exact-match', 'HEAD'], __DIR__);
|
if (0 === $process->execute(['git', 'describe', '--tags', '--exact-match', 'HEAD'], $output, __DIR__)) {
|
||||||
if ($process->run() === 0) {
|
$this->version = trim($output);
|
||||||
$this->version = trim($process->getOutput());
|
|
||||||
} else {
|
} else {
|
||||||
// get branch-alias defined in composer.json for dev-main (if any)
|
// get branch-alias defined in composer.json for dev-main (if any)
|
||||||
$localConfig = __DIR__.'/../../composer.json';
|
$localConfig = __DIR__.'/../../composer.json';
|
||||||
|
@ -75,6 +75,10 @@ class Compiler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('' === $this->version) {
|
||||||
|
throw new \UnexpectedValueException('Version detection failed');
|
||||||
|
}
|
||||||
|
|
||||||
$phar = new \Phar($pharFile, 0, 'composer.phar');
|
$phar = new \Phar($pharFile, 0, 'composer.phar');
|
||||||
$phar->setSignatureAlgorithm(\Phar::SHA512);
|
$phar->setSignatureAlgorithm(\Phar::SHA512);
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,12 @@
|
||||||
|
|
||||||
namespace Composer\Downloader;
|
namespace Composer\Downloader;
|
||||||
|
|
||||||
|
use Composer\Util\Platform;
|
||||||
use React\Promise\PromiseInterface;
|
use React\Promise\PromiseInterface;
|
||||||
use Composer\Package\PackageInterface;
|
use Composer\Package\PackageInterface;
|
||||||
use Composer\Pcre\Preg;
|
use Composer\Pcre\Preg;
|
||||||
use Composer\Util\ProcessExecutor;
|
use Composer\Util\ProcessExecutor;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BohwaZ <http://bohwaz.net/>
|
* @author BohwaZ <http://bohwaz.net/>
|
||||||
|
@ -38,22 +40,13 @@ class FossilDownloader extends VcsDownloader
|
||||||
// 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);
|
||||||
|
|
||||||
$url = ProcessExecutor::escape($url);
|
|
||||||
$ref = ProcessExecutor::escape($package->getSourceReference());
|
|
||||||
$repoFile = $path . '.fossil';
|
$repoFile = $path . '.fossil';
|
||||||
|
$realPath = Platform::realpath($path);
|
||||||
|
|
||||||
$this->io->writeError("Cloning ".$package->getSourceReference());
|
$this->io->writeError("Cloning ".$package->getSourceReference());
|
||||||
$command = sprintf('fossil clone -- %s %s', $url, ProcessExecutor::escape($repoFile));
|
$this->execute(['fossil', 'clone', '--', $url, $repoFile]);
|
||||||
if (0 !== $this->process->execute($command, $ignoredOutput)) {
|
$this->execute(['fossil', 'open', '--nested', '--', $repoFile], $realPath);
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
$this->execute(['fossil', 'update', '--', (string) $package->getSourceReference()], $realPath);
|
||||||
}
|
|
||||||
$command = sprintf('fossil open --nested -- %s', ProcessExecutor::escape($repoFile));
|
|
||||||
if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
|
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
|
||||||
}
|
|
||||||
$command = sprintf('fossil update -- %s', $ref);
|
|
||||||
if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
|
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
|
||||||
}
|
|
||||||
|
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
}
|
}
|
||||||
|
@ -66,17 +59,15 @@ class FossilDownloader extends VcsDownloader
|
||||||
// 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);
|
||||||
|
|
||||||
$ref = ProcessExecutor::escape($target->getSourceReference());
|
|
||||||
$this->io->writeError(" Updating to ".$target->getSourceReference());
|
$this->io->writeError(" Updating to ".$target->getSourceReference());
|
||||||
|
|
||||||
if (!$this->hasMetadataRepository($path)) {
|
if (!$this->hasMetadataRepository($path)) {
|
||||||
throw new \RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
|
throw new \RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = sprintf('fossil pull && fossil up %s', $ref);
|
$realPath = Platform::realpath($path);
|
||||||
if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
|
$this->execute(['fossil', 'pull'], $realPath);
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
$this->execute(['fossil', 'up', (string) $target->getSourceReference()], $realPath);
|
||||||
}
|
|
||||||
|
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
}
|
}
|
||||||
|
@ -90,7 +81,7 @@ class FossilDownloader extends VcsDownloader
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->process->execute('fossil changes', $output, realpath($path));
|
$this->process->execute(['fossil', 'changes'], $output, Platform::realpath($path));
|
||||||
|
|
||||||
$output = trim($output);
|
$output = trim($output);
|
||||||
|
|
||||||
|
@ -102,11 +93,7 @@ class FossilDownloader extends VcsDownloader
|
||||||
*/
|
*/
|
||||||
protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
|
protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
|
||||||
{
|
{
|
||||||
$command = sprintf('fossil timeline -t ci -W 0 -n 0 before %s', ProcessExecutor::escape($toReference));
|
$this->execute(['fossil', 'timeline', '-t', 'ci', '-W', '0', '-n', '0', 'before', $toReference], Platform::realpath($path), $output);
|
||||||
|
|
||||||
if (0 !== $this->process->execute($command, $output, realpath($path))) {
|
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
|
||||||
}
|
|
||||||
|
|
||||||
$log = '';
|
$log = '';
|
||||||
$match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/';
|
$match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/';
|
||||||
|
@ -121,6 +108,17 @@ class FossilDownloader extends VcsDownloader
|
||||||
return $log;
|
return $log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param non-empty-list<string> $command
|
||||||
|
* @throws \RuntimeException
|
||||||
|
*/
|
||||||
|
private function execute(array $command, ?string $cwd = null, ?string &$output = null): void
|
||||||
|
{
|
||||||
|
if (0 !== $this->process->execute($command, $output, $cwd)) {
|
||||||
|
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -73,7 +73,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
// --dissociate option is only available since git 2.3.0-rc0
|
// --dissociate option is only available since git 2.3.0-rc0
|
||||||
if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
|
if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
|
||||||
$this->io->writeError(" - Syncing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) into cache");
|
$this->io->writeError(" - Syncing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) into cache");
|
||||||
$this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG);
|
$this->io->writeError(sprintf(' Cloning to cache at %s', $cachePath), true, IOInterface::DEBUG);
|
||||||
$ref = $package->getSourceReference();
|
$ref = $package->getSourceReference();
|
||||||
if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref, $package->getPrettyVersion()) && is_dir($cachePath)) {
|
if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref, $package->getPrettyVersion()) && is_dir($cachePath)) {
|
||||||
$this->cachedPackages[$package->getId()][$ref] = true;
|
$this->cachedPackages[$package->getId()][$ref] = true;
|
||||||
|
@ -94,24 +94,30 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
$cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/';
|
$cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/';
|
||||||
$ref = $package->getSourceReference();
|
$ref = $package->getSourceReference();
|
||||||
$flag = Platform::isWindows() ? '/D ' : '';
|
|
||||||
|
|
||||||
if (!empty($this->cachedPackages[$package->getId()][$ref])) {
|
if (!empty($this->cachedPackages[$package->getId()][$ref])) {
|
||||||
$msg = "Cloning ".$this->getShortHash($ref).' from cache';
|
$msg = "Cloning ".$this->getShortHash($ref).' from cache';
|
||||||
|
|
||||||
$cloneFlags = '--dissociate --reference %cachePath% ';
|
$cloneFlags = ['--dissociate', '--reference', $cachePath];
|
||||||
$transportOptions = $package->getTransportOptions();
|
$transportOptions = $package->getTransportOptions();
|
||||||
if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) {
|
if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) {
|
||||||
$cloneFlags = '';
|
$cloneFlags = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$command =
|
$commands = [
|
||||||
'git clone --no-checkout %cachePath% %path% ' . $cloneFlags
|
array_merge(['git', 'clone', '--no-checkout', $cachePath, $path], $cloneFlags),
|
||||||
. '&& cd '.$flag.'%path% '
|
['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
|
||||||
. '&& git remote set-url origin -- %sanitizedUrl% && git remote add composer -- %sanitizedUrl%';
|
['git', 'remote', 'add', 'composer', '--', '%sanitizedUrl%'],
|
||||||
|
];
|
||||||
} else {
|
} else {
|
||||||
$msg = "Cloning ".$this->getShortHash($ref);
|
$msg = "Cloning ".$this->getShortHash($ref);
|
||||||
$command = 'git clone --no-checkout -- %url% %path% && cd '.$flag.'%path% && git remote add composer -- %url% && git fetch composer && git remote set-url origin -- %sanitizedUrl% && git remote set-url composer -- %sanitizedUrl%';
|
$commands = [
|
||||||
|
array_merge(['git', 'clone', '--no-checkout', '--', '%url%', $path]),
|
||||||
|
['git', 'remote', 'add', 'composer', '--', '%url%'],
|
||||||
|
['git', 'fetch', 'composer'],
|
||||||
|
['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
|
||||||
|
['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'],
|
||||||
|
];
|
||||||
if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
|
if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
|
||||||
throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
|
throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
|
||||||
}
|
}
|
||||||
|
@ -119,20 +125,8 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
|
|
||||||
$this->io->writeError($msg);
|
$this->io->writeError($msg);
|
||||||
|
|
||||||
$commandCallable = static function (string $url) use ($path, $command, $cachePath): string {
|
$this->gitUtil->runCommands($commands, $url, $path, true);
|
||||||
return str_replace(
|
|
||||||
['%url%', '%path%', '%cachePath%', '%sanitizedUrl%'],
|
|
||||||
[
|
|
||||||
ProcessExecutor::escape($url),
|
|
||||||
ProcessExecutor::escape($path),
|
|
||||||
ProcessExecutor::escape($cachePath),
|
|
||||||
ProcessExecutor::escape(Preg::replace('{://([^@]+?):(.+?)@}', '://', $url)),
|
|
||||||
],
|
|
||||||
$command
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
$this->gitUtil->runCommand($commandCallable, $url, $path, true);
|
|
||||||
$sourceUrl = $package->getSourceUrl();
|
$sourceUrl = $package->getSourceUrl();
|
||||||
if ($url !== $sourceUrl && $sourceUrl !== null) {
|
if ($url !== $sourceUrl && $sourceUrl !== null) {
|
||||||
$this->updateOriginUrl($path, $sourceUrl);
|
$this->updateOriginUrl($path, $sourceUrl);
|
||||||
|
@ -166,10 +160,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
|
|
||||||
if (!empty($this->cachedPackages[$target->getId()][$ref])) {
|
if (!empty($this->cachedPackages[$target->getId()][$ref])) {
|
||||||
$msg = "Checking out ".$this->getShortHash($ref).' from cache';
|
$msg = "Checking out ".$this->getShortHash($ref).' from cache';
|
||||||
$command = '(git rev-parse --quiet --verify %ref% || (git remote set-url composer -- %cachePath% && git fetch composer && git fetch --tags composer)) && git remote set-url composer -- %sanitizedUrl%';
|
$remoteUrl = $cachePath;
|
||||||
} else {
|
} else {
|
||||||
$msg = "Checking out ".$this->getShortHash($ref);
|
$msg = "Checking out ".$this->getShortHash($ref);
|
||||||
$command = '(git remote set-url composer -- %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- %sanitizedUrl%';
|
$remoteUrl = '%url%';
|
||||||
if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
|
if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
|
||||||
throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
|
throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
|
||||||
}
|
}
|
||||||
|
@ -177,20 +171,19 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
|
|
||||||
$this->io->writeError($msg);
|
$this->io->writeError($msg);
|
||||||
|
|
||||||
$commandCallable = static function ($url) use ($ref, $command, $cachePath): string {
|
if (0 !== $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $output, $path)) {
|
||||||
return str_replace(
|
$commands = [
|
||||||
['%url%', '%ref%', '%cachePath%', '%sanitizedUrl%'],
|
['git', 'remote', 'set-url', 'composer', '--', $remoteUrl],
|
||||||
[
|
['git', 'fetch', 'composer'],
|
||||||
ProcessExecutor::escape($url),
|
['git', 'fetch', '--tags', 'composer'],
|
||||||
ProcessExecutor::escape($ref.'^{commit}'),
|
];
|
||||||
ProcessExecutor::escape($cachePath),
|
|
||||||
ProcessExecutor::escape(Preg::replace('{://([^@]+?):(.+?)@}', '://', $url)),
|
$this->gitUtil->runCommands($commands, $url, $path);
|
||||||
],
|
}
|
||||||
$command
|
|
||||||
);
|
$command = ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'];
|
||||||
};
|
$this->gitUtil->runCommands([$command], $url, $path);
|
||||||
|
|
||||||
$this->gitUtil->runCommand($commandCallable, $url, $path);
|
|
||||||
if ($newRef = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) {
|
if ($newRef = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) {
|
||||||
if ($target->getDistReference() === $target->getSourceReference()) {
|
if ($target->getDistReference() === $target->getSourceReference()) {
|
||||||
$target->setDistReference($newRef);
|
$target->setDistReference($newRef);
|
||||||
|
@ -200,7 +193,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
|
|
||||||
$updateOriginUrl = false;
|
$updateOriginUrl = false;
|
||||||
if (
|
if (
|
||||||
0 === $this->process->execute('git remote -v', $output, $path)
|
0 === $this->process->execute(['git', 'remote', '-v'], $output, $path)
|
||||||
&& Preg::isMatch('{^origin\s+(?P<url>\S+)}m', $output, $originMatch)
|
&& Preg::isMatch('{^origin\s+(?P<url>\S+)}m', $output, $originMatch)
|
||||||
&& Preg::isMatch('{^composer\s+(?P<url>\S+)}m', $output, $composerMatch)
|
&& Preg::isMatch('{^composer\s+(?P<url>\S+)}m', $output, $composerMatch)
|
||||||
) {
|
) {
|
||||||
|
@ -225,9 +218,9 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = 'git status --porcelain --untracked-files=no';
|
$command = ['git', 'status', '--porcelain', '--untracked-files=no'];
|
||||||
if (0 !== $this->process->execute($command, $output, $path)) {
|
if (0 !== $this->process->execute($command, $output, $path)) {
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = trim($output);
|
$output = trim($output);
|
||||||
|
@ -243,9 +236,9 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = 'git show-ref --head -d';
|
$command = ['git', 'show-ref', '--head', '-d'];
|
||||||
if (0 !== $this->process->execute($command, $output, $path)) {
|
if (0 !== $this->process->execute($command, $output, $path)) {
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
$refs = trim($output);
|
$refs = trim($output);
|
||||||
|
@ -310,12 +303,12 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
// first pass and we found unpushed changes, fetch from all remotes to make sure we have up to date
|
// first pass and we found unpushed changes, fetch from all remotes to make sure we have up to date
|
||||||
// remotes and then try again as outdated remotes can sometimes cause false-positives
|
// remotes and then try again as outdated remotes can sometimes cause false-positives
|
||||||
if ($unpushedChanges && $i === 0) {
|
if ($unpushedChanges && $i === 0) {
|
||||||
$this->process->execute('git fetch --all', $output, $path);
|
$this->process->execute(['git', 'fetch', '--all'], $output, $path);
|
||||||
|
|
||||||
// update list of refs after fetching
|
// update list of refs after fetching
|
||||||
$command = 'git show-ref --head -d';
|
$command = ['git', 'show-ref', '--head', '-d'];
|
||||||
if (0 !== $this->process->execute($command, $output, $path)) {
|
if (0 !== $this->process->execute($command, $output, $path)) {
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
|
||||||
}
|
}
|
||||||
$refs = trim($output);
|
$refs = trim($output);
|
||||||
}
|
}
|
||||||
|
@ -425,7 +418,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
if (!empty($this->hasStashedChanges[$path])) {
|
if (!empty($this->hasStashedChanges[$path])) {
|
||||||
unset($this->hasStashedChanges[$path]);
|
unset($this->hasStashedChanges[$path]);
|
||||||
$this->io->writeError(' <info>Re-applying stashed changes</info>');
|
$this->io->writeError(' <info>Re-applying stashed changes</info>');
|
||||||
if (0 !== $this->process->execute('git stash pop', $output, $path)) {
|
if (0 !== $this->process->execute(['git', 'stash', 'pop'], $output, $path)) {
|
||||||
throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput());
|
throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -441,18 +434,29 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
*/
|
*/
|
||||||
protected function updateToCommit(PackageInterface $package, string $path, string $reference, string $prettyVersion): ?string
|
protected function updateToCommit(PackageInterface $package, string $path, string $reference, string $prettyVersion): ?string
|
||||||
{
|
{
|
||||||
$force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? '-f ' : '';
|
$force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? ['-f'] : [];
|
||||||
|
|
||||||
// This uses the "--" sequence to separate branch from file parameters.
|
// This uses the "--" sequence to separate branch from file parameters.
|
||||||
//
|
//
|
||||||
// Otherwise git tries the branch name as well as file name.
|
// Otherwise git tries the branch name as well as file name.
|
||||||
// If the non-existent branch is actually the name of a file, the file
|
// If the non-existent branch is actually the name of a file, the file
|
||||||
// is checked out.
|
// is checked out.
|
||||||
$template = 'git checkout '.$force.'%s -- && git reset --hard %1$s --';
|
|
||||||
$branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion);
|
$branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Closure(non-empty-list<string>): bool $execute
|
||||||
|
* @phpstan-ignore varTag.nativeType
|
||||||
|
*/
|
||||||
|
$execute = function (array $command) use (&$output, $path) {
|
||||||
|
/** @var non-empty-list<string> $command */
|
||||||
|
$output = '';
|
||||||
|
|
||||||
|
return 0 === $this->process->execute($command, $output, $path);
|
||||||
|
};
|
||||||
|
|
||||||
$branches = null;
|
$branches = null;
|
||||||
if (0 === $this->process->execute('git branch -r', $output, $path)) {
|
if ($execute(['git', 'branch', '-r'])) {
|
||||||
$branches = $output;
|
$branches = $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,8 +466,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
&& null !== $branches
|
&& null !== $branches
|
||||||
&& Preg::isMatch('{^\s+composer/'.preg_quote($reference).'$}m', $branches)
|
&& Preg::isMatch('{^\s+composer/'.preg_quote($reference).'$}m', $branches)
|
||||||
) {
|
) {
|
||||||
$command = sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference));
|
$command1 = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$reference, '--']);
|
||||||
if (0 === $this->process->execute($command, $output, $path)) {
|
$command2 = ['git', 'reset', '--hard', 'composer/'.$reference, '--'];
|
||||||
|
|
||||||
|
if ($execute($command1) && $execute($command2)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -475,17 +481,18 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
$branch = 'v' . $branch;
|
$branch = 'v' . $branch;
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = sprintf('git checkout %s --', ProcessExecutor::escape($branch));
|
$command = ['git', 'checkout', $branch, '--'];
|
||||||
$fallbackCommand = sprintf('git checkout '.$force.'-B %s %s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch));
|
$fallbackCommand = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$branch, '--']);
|
||||||
$resetCommand = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference));
|
$resetCommand = ['git', 'reset', '--hard', $reference, '--'];
|
||||||
|
|
||||||
if (0 === $this->process->execute("($command || $fallbackCommand) && $resetCommand", $output, $path)) {
|
if (($execute($command) || $execute($fallbackCommand)) && $execute($resetCommand)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = sprintf($template, ProcessExecutor::escape($gitRef));
|
$command1 = array_merge(['git', 'checkout'], $force, [$gitRef, '--']);
|
||||||
if (0 === $this->process->execute($command, $output, $path)) {
|
$command2 = ['git', 'reset', '--hard', $gitRef, '--'];
|
||||||
|
if ($execute($command1) && $execute($command2)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -497,12 +504,14 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
$exceptionExtra = "\nIt looks like the commit hash is not available in the repository, maybe ".($package->isDev() ? 'the commit was removed from the branch' : 'the tag was recreated').'? Run "composer update '.$package->getPrettyName().'" to resolve this.';
|
$exceptionExtra = "\nIt looks like the commit hash is not available in the repository, maybe ".($package->isDev() ? 'the commit was removed from the branch' : 'the tag was recreated').'? Run "composer update '.$package->getPrettyName().'" to resolve this.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$command = implode(' ', $command1). ' && '.implode(' ', $command2);
|
||||||
|
|
||||||
throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() . $exceptionExtra));
|
throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() . $exceptionExtra));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function updateOriginUrl(string $path, string $url): void
|
protected function updateOriginUrl(string $path, string $url): void
|
||||||
{
|
{
|
||||||
$this->process->execute(sprintf('git remote set-url origin -- %s', ProcessExecutor::escape($url)), $output, $path);
|
$this->process->execute(['git', 'remote', 'set-url', 'origin', '--', $url], $output, $path);
|
||||||
$this->setPushUrl($path, $url);
|
$this->setPushUrl($path, $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,7 +524,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
if (!in_array('ssh', $protocols, true)) {
|
if (!in_array('ssh', $protocols, true)) {
|
||||||
$pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
|
$pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
|
||||||
}
|
}
|
||||||
$cmd = sprintf('git remote set-url --push origin -- %s', ProcessExecutor::escape($pushUrl));
|
$cmd = ['git', 'remote', 'set-url', '--push', 'origin', '--', $pushUrl];
|
||||||
$this->process->execute($cmd, $ignoredOutput, $path);
|
$this->process->execute($cmd, $ignoredOutput, $path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -526,10 +535,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
|
protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
|
||||||
{
|
{
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
$command = sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"'.GitUtil::getNoShowSignatureFlag($this->process), ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference));
|
$command = array_merge(['git', 'log', $fromReference.'..'.$toReference, '--pretty=format:%h - %an: %s'], GitUtil::getNoShowSignatureFlags($this->process));
|
||||||
|
|
||||||
if (0 !== $this->process->execute($command, $output, $path)) {
|
if (0 !== $this->process->execute($command, $output, $path)) {
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
|
@ -542,7 +551,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
protected function discardChanges(string $path): PromiseInterface
|
protected function discardChanges(string $path): PromiseInterface
|
||||||
{
|
{
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
if (0 !== $this->process->execute('git clean -df && git reset --hard', $output, $path)) {
|
if (0 !== $this->process->execute(['git', 'clean', '-df'], $output, $path)) {
|
||||||
|
throw new \RuntimeException("Could not reset changes\n\n:".$output);
|
||||||
|
}
|
||||||
|
if (0 !== $this->process->execute(['git', 'reset', '--hard'], $output, $path)) {
|
||||||
throw new \RuntimeException("Could not reset changes\n\n:".$output);
|
throw new \RuntimeException("Could not reset changes\n\n:".$output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,7 +570,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
protected function stashChanges(string $path): PromiseInterface
|
protected function stashChanges(string $path): PromiseInterface
|
||||||
{
|
{
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
if (0 !== $this->process->execute('git stash --include-untracked', $output, $path)) {
|
if (0 !== $this->process->execute(['git', 'stash', '--include-untracked'], $output, $path)) {
|
||||||
throw new \RuntimeException("Could not stash changes\n\n:".$output);
|
throw new \RuntimeException("Could not stash changes\n\n:".$output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,7 +585,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
|
||||||
protected function viewDiff(string $path): void
|
protected function viewDiff(string $path): void
|
||||||
{
|
{
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
if (0 !== $this->process->execute('git diff HEAD', $output, $path)) {
|
if (0 !== $this->process->execute(['git', 'diff', 'HEAD'], $output, $path)) {
|
||||||
throw new \RuntimeException("Could not view diff\n\n:".$output);
|
throw new \RuntimeException("Could not view diff\n\n:".$output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ class GzipDownloader extends ArchiveDownloader
|
||||||
|
|
||||||
// Try to use gunzip on *nix
|
// Try to use gunzip on *nix
|
||||||
if (!Platform::isWindows()) {
|
if (!Platform::isWindows()) {
|
||||||
$command = 'gzip -cd -- ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath);
|
$command = ['sh', '-c', 'gzip -cd -- "$0" > "$1"', $file, $targetFilepath];
|
||||||
|
|
||||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
|
@ -44,7 +44,7 @@ class GzipDownloader extends ArchiveDownloader
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
$processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
|
$processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput();
|
||||||
throw new \RuntimeException($processError);
|
throw new \RuntimeException($processError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,16 +41,15 @@ class HgDownloader extends VcsDownloader
|
||||||
{
|
{
|
||||||
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
|
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
|
||||||
|
|
||||||
$cloneCommand = static function (string $url) use ($path): string {
|
$cloneCommand = static function (string $url) use ($path): array {
|
||||||
return sprintf('hg clone -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($path));
|
return ['hg', 'clone', '--', $url, $path];
|
||||||
};
|
};
|
||||||
|
|
||||||
$hgUtils->runCommand($cloneCommand, $url, $path);
|
$hgUtils->runCommand($cloneCommand, $url, $path);
|
||||||
|
|
||||||
$ref = ProcessExecutor::escape($package->getSourceReference());
|
$command = ['hg', 'up', '--', (string) $package->getSourceReference()];
|
||||||
$command = sprintf('hg up -- %s', $ref);
|
|
||||||
if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
|
if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
|
@ -70,10 +69,14 @@ class HgDownloader extends VcsDownloader
|
||||||
throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
|
throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = static function ($url) use ($ref): string {
|
$command = static function ($url): array {
|
||||||
return sprintf('hg pull -- %s && hg up -- %s', ProcessExecutor::escape($url), ProcessExecutor::escape($ref));
|
return ['hg', 'pull', '--', $url];
|
||||||
};
|
};
|
||||||
|
$hgUtils->runCommand($command, $url, $path);
|
||||||
|
|
||||||
|
$command = static function () use ($ref): array {
|
||||||
|
return ['hg', 'up', '--', $ref];
|
||||||
|
};
|
||||||
$hgUtils->runCommand($command, $url, $path);
|
$hgUtils->runCommand($command, $url, $path);
|
||||||
|
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
|
@ -88,7 +91,7 @@ class HgDownloader extends VcsDownloader
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->process->execute('hg st', $output, realpath($path));
|
$this->process->execute(['hg', 'st'], $output, realpath($path));
|
||||||
|
|
||||||
$output = trim($output);
|
$output = trim($output);
|
||||||
|
|
||||||
|
@ -100,10 +103,10 @@ class HgDownloader extends VcsDownloader
|
||||||
*/
|
*/
|
||||||
protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
|
protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
|
||||||
{
|
{
|
||||||
$command = sprintf('hg log -r %s:%s --style compact', ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference));
|
$command = ['hg', 'log', '-r', $fromReference.':'.$toReference, '--style', 'compact'];
|
||||||
|
|
||||||
if (0 !== $this->process->execute($command, $output, realpath($path))) {
|
if (0 !== $this->process->execute($command, $output, realpath($path))) {
|
||||||
throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
|
throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
|
|
|
@ -34,13 +34,13 @@ class RarDownloader extends ArchiveDownloader
|
||||||
|
|
||||||
// Try to use unrar on *nix
|
// Try to use unrar on *nix
|
||||||
if (!Platform::isWindows()) {
|
if (!Platform::isWindows()) {
|
||||||
$command = 'unrar x -- ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' >/dev/null && chmod -R u+w ' . ProcessExecutor::escape($path);
|
$command = ['sh', '-c', 'unrar x -- "$0" "$1" >/dev/null && chmod -R u+w "$1"', $file, $path];
|
||||||
|
|
||||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
$processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
|
$processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!class_exists('RarArchive')) {
|
if (!class_exists('RarArchive')) {
|
||||||
|
|
|
@ -59,7 +59,7 @@ class SvnDownloader extends VcsDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError(" Checking out ".$package->getSourceReference());
|
$this->io->writeError(" Checking out ".$package->getSourceReference());
|
||||||
$this->execute($package, $url, "svn co", sprintf("%s/%s", $url, $ref), null, $path);
|
$this->execute($package, $url, ['svn', 'co'], sprintf("%s/%s", $url, $ref), null, $path);
|
||||||
|
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
}
|
}
|
||||||
|
@ -77,13 +77,13 @@ class SvnDownloader extends VcsDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
$util = new SvnUtil($url, $this->io, $this->config, $this->process);
|
$util = new SvnUtil($url, $this->io, $this->config, $this->process);
|
||||||
$flags = "";
|
$flags = [];
|
||||||
if (version_compare($util->binaryVersion(), '1.7.0', '>=')) {
|
if (version_compare($util->binaryVersion(), '1.7.0', '>=')) {
|
||||||
$flags .= ' --ignore-ancestry';
|
$flags[] = '--ignore-ancestry';
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->io->writeError(" Checking out " . $ref);
|
$this->io->writeError(" Checking out " . $ref);
|
||||||
$this->execute($target, $url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path);
|
$this->execute($target, $url, array_merge(['svn', 'switch'], $flags), sprintf("%s/%s", $url, $ref), $path);
|
||||||
|
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ class SvnDownloader extends VcsDownloader
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->process->execute('svn status --ignore-externals', $output, $path);
|
$this->process->execute(['svn', 'status', '--ignore-externals'], $output, $path);
|
||||||
|
|
||||||
return Preg::isMatch('{^ *[^X ] +}m', $output) ? $output : null;
|
return Preg::isMatch('{^ *[^X ] +}m', $output) ? $output : null;
|
||||||
}
|
}
|
||||||
|
@ -107,13 +107,13 @@ class SvnDownloader extends VcsDownloader
|
||||||
* if necessary.
|
* if necessary.
|
||||||
*
|
*
|
||||||
* @param string $baseUrl Base URL of the repository
|
* @param string $baseUrl Base URL of the repository
|
||||||
* @param string $command SVN command to run
|
* @param non-empty-list<string> $command SVN command to run
|
||||||
* @param string $url SVN url
|
* @param string $url SVN url
|
||||||
* @param string $cwd Working directory
|
* @param string $cwd Working directory
|
||||||
* @param string $path Target for a checkout
|
* @param string $path Target for a checkout
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
*/
|
*/
|
||||||
protected function execute(PackageInterface $package, string $baseUrl, string $command, string $url, ?string $cwd = null, ?string $path = null): string
|
protected function execute(PackageInterface $package, string $baseUrl, array $command, string $url, ?string $cwd = null, ?string $path = null): string
|
||||||
{
|
{
|
||||||
$util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process);
|
$util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process);
|
||||||
$util->setCacheCredentials($this->cacheCredentials);
|
$util->setCacheCredentials($this->cacheCredentials);
|
||||||
|
@ -194,10 +194,10 @@ class SvnDownloader extends VcsDownloader
|
||||||
{
|
{
|
||||||
if (Preg::isMatch('{@(\d+)$}', $fromReference) && Preg::isMatch('{@(\d+)$}', $toReference)) {
|
if (Preg::isMatch('{@(\d+)$}', $fromReference) && Preg::isMatch('{@(\d+)$}', $toReference)) {
|
||||||
// retrieve the svn base url from the checkout folder
|
// retrieve the svn base url from the checkout folder
|
||||||
$command = sprintf('svn info --non-interactive --xml -- %s', ProcessExecutor::escape($path));
|
$command = ['svn', 'info', '--non-interactive', '--xml', '--', $path];
|
||||||
if (0 !== $this->process->execute($command, $output, $path)) {
|
if (0 !== $this->process->execute($command, $output, $path)) {
|
||||||
throw new \RuntimeException(
|
throw new \RuntimeException(
|
||||||
'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()
|
'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@ class SvnDownloader extends VcsDownloader
|
||||||
$fromRevision = Preg::replace('{.*@(\d+)$}', '$1', $fromReference);
|
$fromRevision = Preg::replace('{.*@(\d+)$}', '$1', $fromReference);
|
||||||
$toRevision = Preg::replace('{.*@(\d+)$}', '$1', $toReference);
|
$toRevision = Preg::replace('{.*@(\d+)$}', '$1', $toReference);
|
||||||
|
|
||||||
$command = sprintf('svn log -r%s:%s --incremental', ProcessExecutor::escape($fromRevision), ProcessExecutor::escape($toRevision));
|
$command = ['svn', 'log', '-r', $fromRevision.':'.$toRevision, '--incremental'];
|
||||||
|
|
||||||
$util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process);
|
$util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process);
|
||||||
$util->setCacheCredentials($this->cacheCredentials);
|
$util->setCacheCredentials($this->cacheCredentials);
|
||||||
|
@ -222,7 +222,7 @@ class SvnDownloader extends VcsDownloader
|
||||||
return $util->executeLocal($command, $path, null, $this->io->isVerbose());
|
return $util->executeLocal($command, $path, null, $this->io->isVerbose());
|
||||||
} catch (\RuntimeException $e) {
|
} catch (\RuntimeException $e) {
|
||||||
throw new \RuntimeException(
|
throw new \RuntimeException(
|
||||||
'Failed to execute ' . $command . "\n\n".$e->getMessage()
|
'Failed to execute ' . implode(' ', $command) . "\n\n".$e->getMessage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,7 +235,7 @@ class SvnDownloader extends VcsDownloader
|
||||||
*/
|
*/
|
||||||
protected function discardChanges(string $path): PromiseInterface
|
protected function discardChanges(string $path): PromiseInterface
|
||||||
{
|
{
|
||||||
if (0 !== $this->process->execute('svn revert -R .', $output, $path)) {
|
if (0 !== $this->process->execute(['svn', 'revert', '-R', '.'], $output, $path)) {
|
||||||
throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
|
throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,13 +26,13 @@ class XzDownloader extends ArchiveDownloader
|
||||||
{
|
{
|
||||||
protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
|
protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
|
||||||
{
|
{
|
||||||
$command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path);
|
$command = ['tar', '-xJf', $file, '-C', $path];
|
||||||
|
|
||||||
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
if (0 === $this->process->execute($command, $ignoredOutput)) {
|
||||||
return \React\Promise\resolve(null);
|
return \React\Promise\resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
$processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();
|
$processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput();
|
||||||
|
|
||||||
throw new \RuntimeException($processError);
|
throw new \RuntimeException($processError);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ use ZipArchive;
|
||||||
*/
|
*/
|
||||||
class ZipDownloader extends ArchiveDownloader
|
class ZipDownloader extends ArchiveDownloader
|
||||||
{
|
{
|
||||||
/** @var array<int, array{0: string, 1: string}> */
|
/** @var array<int, non-empty-list<string>> */
|
||||||
private static $unzipCommands;
|
private static $unzipCommands;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
private static $hasZipArchive;
|
private static $hasZipArchive;
|
||||||
|
@ -46,16 +46,16 @@ class ZipDownloader extends ArchiveDownloader
|
||||||
self::$unzipCommands = [];
|
self::$unzipCommands = [];
|
||||||
$finder = new ExecutableFinder;
|
$finder = new ExecutableFinder;
|
||||||
if (Platform::isWindows() && ($cmd = $finder->find('7z', null, ['C:\Program Files\7-Zip']))) {
|
if (Platform::isWindows() && ($cmd = $finder->find('7z', null, ['C:\Program Files\7-Zip']))) {
|
||||||
self::$unzipCommands[] = ['7z', ProcessExecutor::escape($cmd).' x -bb0 -y %s -o%s'];
|
self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
|
||||||
}
|
}
|
||||||
if ($cmd = $finder->find('unzip')) {
|
if ($cmd = $finder->find('unzip')) {
|
||||||
self::$unzipCommands[] = ['unzip', ProcessExecutor::escape($cmd).' -qq %s -d %s'];
|
self::$unzipCommands[] = ['unzip', $cmd, '-qq', '%file%', '-d', '%path%'];
|
||||||
}
|
}
|
||||||
if (!Platform::isWindows() && ($cmd = $finder->find('7z'))) { // 7z linux/macOS support is only used if unzip is not present
|
if (!Platform::isWindows() && ($cmd = $finder->find('7z'))) { // 7z linux/macOS support is only used if unzip is not present
|
||||||
self::$unzipCommands[] = ['7z', ProcessExecutor::escape($cmd).' x -bb0 -y %s -o%s'];
|
self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
|
||||||
}
|
}
|
||||||
if (!Platform::isWindows() && ($cmd = $finder->find('7zz'))) { // 7zz linux/macOS support is only used if unzip is not present
|
if (!Platform::isWindows() && ($cmd = $finder->find('7zz'))) { // 7zz linux/macOS support is only used if unzip is not present
|
||||||
self::$unzipCommands[] = ['7zz', ProcessExecutor::escape($cmd).' x -bb0 -y %s -o%s'];
|
self::$unzipCommands[] = ['7zz', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,24 +114,28 @@ class ZipDownloader extends ArchiveDownloader
|
||||||
// Force Exception throwing if the other alternative extraction method is not available
|
// Force Exception throwing if the other alternative extraction method is not available
|
||||||
$isLastChance = !self::$hasZipArchive;
|
$isLastChance = !self::$hasZipArchive;
|
||||||
|
|
||||||
if (!self::$unzipCommands) {
|
if (0 === \count(self::$unzipCommands)) {
|
||||||
// This was call as the favorite extract way, but is not available
|
// This was call as the favorite extract way, but is not available
|
||||||
// We switch to the alternative
|
// We switch to the alternative
|
||||||
return $this->extractWithZipArchive($package, $file, $path);
|
return $this->extractWithZipArchive($package, $file, $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
$commandSpec = reset(self::$unzipCommands);
|
$commandSpec = reset(self::$unzipCommands);
|
||||||
$command = sprintf($commandSpec[1], ProcessExecutor::escape($file), ProcessExecutor::escape($path));
|
|
||||||
// normalize separators to backslashes to avoid problems with 7-zip on windows
|
|
||||||
// see https://github.com/composer/composer/issues/10058
|
|
||||||
if (Platform::isWindows()) {
|
|
||||||
$command = sprintf($commandSpec[1], ProcessExecutor::escape(strtr($file, '/', '\\')), ProcessExecutor::escape(strtr($path, '/', '\\')));
|
|
||||||
}
|
|
||||||
|
|
||||||
$executable = $commandSpec[0];
|
$executable = $commandSpec[0];
|
||||||
|
$command = array_slice($commandSpec, 1);
|
||||||
|
$map = [
|
||||||
|
// normalize separators to backslashes to avoid problems with 7-zip on windows
|
||||||
|
// see https://github.com/composer/composer/issues/10058
|
||||||
|
'%file%' => strtr($file, '/', DIRECTORY_SEPARATOR),
|
||||||
|
'%path%' => strtr($path, '/', DIRECTORY_SEPARATOR),
|
||||||
|
];
|
||||||
|
$command = array_map(static function ($value) use ($map) {
|
||||||
|
return strtr($value, $map);
|
||||||
|
}, $command);
|
||||||
|
|
||||||
if (!$warned7ZipLinux && !Platform::isWindows() && in_array($executable, ['7z', '7zz'], true)) {
|
if (!$warned7ZipLinux && !Platform::isWindows() && in_array($executable, ['7z', '7zz'], true)) {
|
||||||
$warned7ZipLinux = true;
|
$warned7ZipLinux = true;
|
||||||
if (0 === $this->process->execute($executable, $output)) {
|
if (0 === $this->process->execute([$commandSpec[1]], $output)) {
|
||||||
if (Preg::isMatchStrictGroups('{^\s*7-Zip(?: \[64\])? ([0-9.]+)}', $output, $match) && version_compare($match[1], '21.01', '<')) {
|
if (Preg::isMatchStrictGroups('{^\s*7-Zip(?: \[64\])? ([0-9.]+)}', $output, $match) && version_compare($match[1], '21.01', '<')) {
|
||||||
$this->io->writeError(' <warning>Unzipping using '.$executable.' '.$match[1].' may result in incorrect file permissions. Install '.$executable.' 21.01+ or unzip to ensure you get correct permissions.</warning>');
|
$this->io->writeError(' <warning>Unzipping using '.$executable.' '.$match[1].' may result in incorrect file permissions. Install '.$executable.' 21.01+ or unzip to ensure you get correct permissions.</warning>');
|
||||||
}
|
}
|
||||||
|
@ -186,7 +190,7 @@ class ZipDownloader extends ArchiveDownloader
|
||||||
$output = $process->getErrorOutput();
|
$output = $process->getErrorOutput();
|
||||||
$output = str_replace(', '.$file.'.zip or '.$file.'.ZIP', '', $output);
|
$output = str_replace(', '.$file.'.zip or '.$file.'.ZIP', '', $output);
|
||||||
|
|
||||||
return $tryFallback(new \RuntimeException('Failed to extract '.$package->getName().': ('.$process->getExitCode().') '.$command."\n\n".$output));
|
return $tryFallback(new \RuntimeException('Failed to extract '.$package->getName().': ('.$process->getExitCode().') '.implode(' ', $command)."\n\n".$output));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|
|
@ -554,13 +554,14 @@ class Locker
|
||||||
case 'git':
|
case 'git':
|
||||||
GitUtil::cleanEnv();
|
GitUtil::cleanEnv();
|
||||||
|
|
||||||
if (0 === $this->process->execute('git log -n1 --pretty=%ct '.ProcessExecutor::escape($sourceRef).GitUtil::getNoShowSignatureFlag($this->process), $output, $path) && Preg::isMatch('{^\s*\d+\s*$}', $output)) {
|
$command = array_merge(['git', 'log', '-n1', '--pretty=%ct', (string) $sourceRef], GitUtil::getNoShowSignatureFlags($this->process));
|
||||||
|
if (0 === $this->process->execute($command, $output, $path) && Preg::isMatch('{^\s*\d+\s*$}', $output)) {
|
||||||
$datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
|
$datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'hg':
|
case 'hg':
|
||||||
if (0 === $this->process->execute('hg log --template "{date|hgdate}" -r '.ProcessExecutor::escape($sourceRef), $output, $path) && Preg::isMatch('{^\s*(\d+)\s*}', $output, $match)) {
|
if (0 === $this->process->execute(['hg', 'log', '--template', '{date|hgdate}', '-r', (string) $sourceRef], $output, $path) && Preg::isMatch('{^\s*(\d+)\s*}', $output, $match)) {
|
||||||
$datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC'));
|
$datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC'));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -198,7 +198,7 @@ class VersionGuesser
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $commit) {
|
if (null === $commit) {
|
||||||
$command = 'git log --pretty="%H" -n1 HEAD'.GitUtil::getNoShowSignatureFlag($this->process);
|
$command = array_merge(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process));
|
||||||
if (0 === $this->process->execute($command, $output, $path)) {
|
if (0 === $this->process->execute($command, $output, $path)) {
|
||||||
$commit = trim($output) ?: null;
|
$commit = trim($output) ?: null;
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ class VersionGuesser
|
||||||
private function versionFromGitTags(string $path): ?array
|
private function versionFromGitTags(string $path): ?array
|
||||||
{
|
{
|
||||||
// try to fetch current version from git tags
|
// try to fetch current version from git tags
|
||||||
if (0 === $this->process->execute('git describe --exact-match --tags', $output, $path)) {
|
if (0 === $this->process->execute(['git', 'describe', '--exact-match', '--tags'], $output, $path)) {
|
||||||
try {
|
try {
|
||||||
$version = $this->versionParser->normalize(trim($output));
|
$version = $this->versionParser->normalize(trim($output));
|
||||||
|
|
||||||
|
@ -237,7 +237,7 @@ class VersionGuesser
|
||||||
private function guessHgVersion(array $packageConfig, string $path): ?array
|
private function guessHgVersion(array $packageConfig, string $path): ?array
|
||||||
{
|
{
|
||||||
// try to fetch current version from hg branch
|
// try to fetch current version from hg branch
|
||||||
if (0 === $this->process->execute('hg branch', $output, $path)) {
|
if (0 === $this->process->execute(['hg', 'branch'], $output, $path)) {
|
||||||
$branch = trim($output);
|
$branch = trim($output);
|
||||||
$version = $this->versionParser->normalizeBranch($branch);
|
$version = $this->versionParser->normalizeBranch($branch);
|
||||||
$isFeatureBranch = 0 === strpos($version, 'dev-');
|
$isFeatureBranch = 0 === strpos($version, 'dev-');
|
||||||
|
@ -375,14 +375,14 @@ class VersionGuesser
|
||||||
$prettyVersion = null;
|
$prettyVersion = null;
|
||||||
|
|
||||||
// try to fetch current version from fossil
|
// try to fetch current version from fossil
|
||||||
if (0 === $this->process->execute('fossil branch list', $output, $path)) {
|
if (0 === $this->process->execute(['fossil', 'branch', 'list'], $output, $path)) {
|
||||||
$branch = trim($output);
|
$branch = trim($output);
|
||||||
$version = $this->versionParser->normalizeBranch($branch);
|
$version = $this->versionParser->normalizeBranch($branch);
|
||||||
$prettyVersion = 'dev-' . $branch;
|
$prettyVersion = 'dev-' . $branch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to fetch current version from fossil tags
|
// try to fetch current version from fossil tags
|
||||||
if (0 === $this->process->execute('fossil tag list', $output, $path)) {
|
if (0 === $this->process->execute(['fossil', 'tag', 'list'], $output, $path)) {
|
||||||
try {
|
try {
|
||||||
$version = $this->versionParser->normalize(trim($output));
|
$version = $this->versionParser->normalize(trim($output));
|
||||||
$prettyVersion = trim($output);
|
$prettyVersion = trim($output);
|
||||||
|
@ -403,7 +403,7 @@ class VersionGuesser
|
||||||
SvnUtil::cleanEnv();
|
SvnUtil::cleanEnv();
|
||||||
|
|
||||||
// try to fetch current version from svn
|
// try to fetch current version from svn
|
||||||
if (0 === $this->process->execute('svn info --xml', $output, $path)) {
|
if (0 === $this->process->execute(['svn', 'info', '--xml'], $output, $path)) {
|
||||||
$trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk';
|
$trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk';
|
||||||
$branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches';
|
$branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches';
|
||||||
$tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags';
|
$tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags';
|
||||||
|
|
|
@ -49,11 +49,7 @@ class HhvmDetector
|
||||||
$hhvmPath = $this->executableFinder->find('hhvm');
|
$hhvmPath = $this->executableFinder->find('hhvm');
|
||||||
if ($hhvmPath !== null) {
|
if ($hhvmPath !== null) {
|
||||||
$this->processExecutor = $this->processExecutor ?? new ProcessExecutor();
|
$this->processExecutor = $this->processExecutor ?? new ProcessExecutor();
|
||||||
$exitCode = $this->processExecutor->execute(
|
$exitCode = $this->processExecutor->execute([$hhvmPath, '--php', '-d', 'hhvm.jit=0', '-r', 'echo HHVM_VERSION;'], self::$hhvmVersion);
|
||||||
ProcessExecutor::escape($hhvmPath).
|
|
||||||
' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null',
|
|
||||||
self::$hhvmVersion
|
|
||||||
);
|
|
||||||
if ($exitCode !== 0) {
|
if ($exitCode !== 0) {
|
||||||
self::$hhvmVersion = false;
|
self::$hhvmVersion = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,8 +194,8 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
||||||
// carry over the root package version if this path repo is in the same git repository as root package
|
// carry over the root package version if this path repo is in the same git repository as root package
|
||||||
if (!isset($package['version']) && ($rootVersion = Platform::getEnv('COMPOSER_ROOT_VERSION'))) {
|
if (!isset($package['version']) && ($rootVersion = Platform::getEnv('COMPOSER_ROOT_VERSION'))) {
|
||||||
if (
|
if (
|
||||||
0 === $this->process->execute('git rev-parse HEAD', $ref1, $path)
|
0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref1, $path)
|
||||||
&& 0 === $this->process->execute('git rev-parse HEAD', $ref2)
|
&& 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref2)
|
||||||
&& $ref1 === $ref2
|
&& $ref1 === $ref2
|
||||||
) {
|
) {
|
||||||
$package['version'] = $this->versionGuesser->getRootVersionFromEnv();
|
$package['version'] = $this->versionGuesser->getRootVersionFromEnv();
|
||||||
|
@ -203,7 +203,7 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = '';
|
$output = '';
|
||||||
if ('auto' === $reference && is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute('git log -n1 --pretty=%H'.GitUtil::getNoShowSignatureFlag($this->process), $output, $path)) {
|
if ('auto' === $reference && is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute(array_merge(['git', 'log', '-n1', '--pretty=%H'], GitUtil::getNoShowSignatureFlags($this->process)), $output, $path)) {
|
||||||
$package['dist']['reference'] = trim($output);
|
$package['dist']['reference'] = trim($output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ class FossilDriver extends VcsDriver
|
||||||
*/
|
*/
|
||||||
protected function checkFossil(): void
|
protected function checkFossil(): void
|
||||||
{
|
{
|
||||||
if (0 !== $this->process->execute('fossil version', $ignoredOutput)) {
|
if (0 !== $this->process->execute(['fossil', 'version'], $ignoredOutput)) {
|
||||||
throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput());
|
throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,8 @@ class FossilDriver extends VcsDriver
|
||||||
*/
|
*/
|
||||||
protected function updateLocalRepo(): void
|
protected function updateLocalRepo(): void
|
||||||
{
|
{
|
||||||
|
assert($this->repoFile !== null);
|
||||||
|
|
||||||
$fs = new Filesystem();
|
$fs = new Filesystem();
|
||||||
$fs->ensureDirectoryExists($this->checkoutDir);
|
$fs->ensureDirectoryExists($this->checkoutDir);
|
||||||
|
|
||||||
|
@ -89,8 +91,8 @@ class FossilDriver extends VcsDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the repo if it is a valid fossil repository
|
// update the repo if it is a valid fossil repository
|
||||||
if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute('fossil info', $output, $this->checkoutDir)) {
|
if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute(['fossil', 'info'], $output, $this->checkoutDir)) {
|
||||||
if (0 !== $this->process->execute('fossil pull', $output, $this->checkoutDir)) {
|
if (0 !== $this->process->execute(['fossil', 'pull'], $output, $this->checkoutDir)) {
|
||||||
$this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
|
$this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,13 +102,13 @@ class FossilDriver extends VcsDriver
|
||||||
|
|
||||||
$fs->ensureDirectoryExists($this->checkoutDir);
|
$fs->ensureDirectoryExists($this->checkoutDir);
|
||||||
|
|
||||||
if (0 !== $this->process->execute(sprintf('fossil clone -- %s %s', ProcessExecutor::escape($this->url), ProcessExecutor::escape($this->repoFile)), $output)) {
|
if (0 !== $this->process->execute(['fossil', 'clone', '--', $this->url, $this->repoFile], $output)) {
|
||||||
$output = $this->process->getErrorOutput();
|
$output = $this->process->getErrorOutput();
|
||||||
|
|
||||||
throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output);
|
throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 !== $this->process->execute(sprintf('fossil open --nested -- %s', ProcessExecutor::escape($this->repoFile)), $output, $this->checkoutDir)) {
|
if (0 !== $this->process->execute(['fossil', 'open', '--nested', '--', $this->repoFile], $output, $this->checkoutDir)) {
|
||||||
$output = $this->process->getErrorOutput();
|
$output = $this->process->getErrorOutput();
|
||||||
|
|
||||||
throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output);
|
throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output);
|
||||||
|
@ -155,10 +157,9 @@ class FossilDriver extends VcsDriver
|
||||||
*/
|
*/
|
||||||
public function getFileContent(string $file, string $identifier): ?string
|
public function getFileContent(string $file, string $identifier): ?string
|
||||||
{
|
{
|
||||||
$command = sprintf('fossil cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));
|
$this->process->execute(['fossil', 'cat', '-r', $identifier, '--', $file], $content, $this->checkoutDir);
|
||||||
$this->process->execute($command, $content, $this->checkoutDir);
|
|
||||||
|
|
||||||
if (!trim($content)) {
|
if ('' === trim($content)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +171,7 @@ class FossilDriver extends VcsDriver
|
||||||
*/
|
*/
|
||||||
public function getChangeDate(string $identifier): ?\DateTimeImmutable
|
public function getChangeDate(string $identifier): ?\DateTimeImmutable
|
||||||
{
|
{
|
||||||
$this->process->execute('fossil finfo -b -n 1 composer.json', $output, $this->checkoutDir);
|
$this->process->execute(['fossil', 'finfo', '-b', '-n', '1', 'composer.json'], $output, $this->checkoutDir);
|
||||||
[, $date] = explode(' ', trim($output), 3);
|
[, $date] = explode(' ', trim($output), 3);
|
||||||
|
|
||||||
return new \DateTimeImmutable($date, new \DateTimeZone('UTC'));
|
return new \DateTimeImmutable($date, new \DateTimeZone('UTC'));
|
||||||
|
@ -184,7 +185,7 @@ class FossilDriver extends VcsDriver
|
||||||
if (null === $this->tags) {
|
if (null === $this->tags) {
|
||||||
$tags = [];
|
$tags = [];
|
||||||
|
|
||||||
$this->process->execute('fossil tag list', $output, $this->checkoutDir);
|
$this->process->execute(['fossil', 'tag', 'list'], $output, $this->checkoutDir);
|
||||||
foreach ($this->process->splitLines($output) as $tag) {
|
foreach ($this->process->splitLines($output) as $tag) {
|
||||||
$tags[$tag] = $tag;
|
$tags[$tag] = $tag;
|
||||||
}
|
}
|
||||||
|
@ -203,7 +204,7 @@ class FossilDriver extends VcsDriver
|
||||||
if (null === $this->branches) {
|
if (null === $this->branches) {
|
||||||
$branches = [];
|
$branches = [];
|
||||||
|
|
||||||
$this->process->execute('fossil branch list', $output, $this->checkoutDir);
|
$this->process->execute(['fossil', 'branch', 'list'], $output, $this->checkoutDir);
|
||||||
foreach ($this->process->splitLines($output) as $branch) {
|
foreach ($this->process->splitLines($output) as $branch) {
|
||||||
$branch = trim(Preg::replace('/^\*/', '', trim($branch)));
|
$branch = trim(Preg::replace('/^\*/', '', trim($branch)));
|
||||||
$branches[$branch] = $branch;
|
$branches[$branch] = $branch;
|
||||||
|
@ -237,7 +238,7 @@ class FossilDriver extends VcsDriver
|
||||||
|
|
||||||
$process = new ProcessExecutor($io);
|
$process = new ProcessExecutor($io);
|
||||||
// check whether there is a fossil repo in that path
|
// check whether there is a fossil repo in that path
|
||||||
if ($process->execute('fossil info', $output, $url) === 0) {
|
if ($process->execute(['fossil', 'info'], $output, $url) === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ class GitDriver extends VcsDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
// select currently checked out branch as default branch
|
// select currently checked out branch as default branch
|
||||||
$this->process->execute('git branch --no-color', $output, $this->repoDir);
|
$this->process->execute(['git', 'branch', '--no-color'], $output, $this->repoDir);
|
||||||
$branches = $this->process->splitLines($output);
|
$branches = $this->process->splitLines($output);
|
||||||
if (!in_array('* master', $branches)) {
|
if (!in_array('* master', $branches)) {
|
||||||
foreach ($branches as $branch) {
|
foreach ($branches as $branch) {
|
||||||
|
@ -150,8 +150,7 @@ class GitDriver extends VcsDriver
|
||||||
throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier);
|
throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
$resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));
|
$this->process->execute(['git', 'show', $identifier.':'.$file], $content, $this->repoDir);
|
||||||
$this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir);
|
|
||||||
|
|
||||||
if (trim($content) === '') {
|
if (trim($content) === '') {
|
||||||
return null;
|
return null;
|
||||||
|
@ -165,10 +164,7 @@ class GitDriver extends VcsDriver
|
||||||
*/
|
*/
|
||||||
public function getChangeDate(string $identifier): ?\DateTimeImmutable
|
public function getChangeDate(string $identifier): ?\DateTimeImmutable
|
||||||
{
|
{
|
||||||
$this->process->execute(sprintf(
|
$this->process->execute(['git', '-c', 'log.showSignature=false', 'log', '-1', '--format=%at', $identifier], $output, $this->repoDir);
|
||||||
'git -c log.showSignature=false log -1 --format=%%at %s',
|
|
||||||
ProcessExecutor::escape($identifier)
|
|
||||||
), $output, $this->repoDir);
|
|
||||||
|
|
||||||
return new \DateTimeImmutable('@'.trim($output), new \DateTimeZone('UTC'));
|
return new \DateTimeImmutable('@'.trim($output), new \DateTimeZone('UTC'));
|
||||||
}
|
}
|
||||||
|
@ -181,7 +177,7 @@ class GitDriver extends VcsDriver
|
||||||
if (null === $this->tags) {
|
if (null === $this->tags) {
|
||||||
$this->tags = [];
|
$this->tags = [];
|
||||||
|
|
||||||
$this->process->execute('git show-ref --tags --dereference', $output, $this->repoDir);
|
$this->process->execute(['git', 'show-ref', '--tags', '--dereference'], $output, $this->repoDir);
|
||||||
foreach ($this->process->splitLines($output) as $tag) {
|
foreach ($this->process->splitLines($output) as $tag) {
|
||||||
if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) {
|
if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) {
|
||||||
$this->tags[$match[2]] = $match[1];
|
$this->tags[$match[2]] = $match[1];
|
||||||
|
@ -200,7 +196,7 @@ class GitDriver extends VcsDriver
|
||||||
if (null === $this->branches) {
|
if (null === $this->branches) {
|
||||||
$branches = [];
|
$branches = [];
|
||||||
|
|
||||||
$this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir);
|
$this->process->execute(['git', 'branch', '--no-color', '--no-abbrev', '-v'], $output, $this->repoDir);
|
||||||
foreach ($this->process->splitLines($output) as $branch) {
|
foreach ($this->process->splitLines($output) as $branch) {
|
||||||
if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) {
|
if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) {
|
||||||
if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') {
|
if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') {
|
||||||
|
@ -233,7 +229,7 @@ class GitDriver extends VcsDriver
|
||||||
|
|
||||||
$process = new ProcessExecutor($io);
|
$process = new ProcessExecutor($io);
|
||||||
// check whether there is a git repo in that path
|
// check whether there is a git repo in that path
|
||||||
if ($process->execute('git tag', $output, $url) === 0) {
|
if ($process->execute(['git', 'tag'], $output, $url) === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
GitUtil::checkForRepoOwnershipError($process->getErrorOutput(), $url);
|
GitUtil::checkForRepoOwnershipError($process->getErrorOutput(), $url);
|
||||||
|
@ -247,9 +243,7 @@ class GitDriver extends VcsDriver
|
||||||
GitUtil::cleanEnv();
|
GitUtil::cleanEnv();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$gitUtil->runCommand(static function ($url): string {
|
$gitUtil->runCommands([['git', 'ls-remote', '--heads', '--', '%url%']], $url, sys_get_temp_dir());
|
||||||
return 'git ls-remote --heads -- ' . ProcessExecutor::escape($url);
|
|
||||||
}, $url, sys_get_temp_dir());
|
|
||||||
} catch (\RuntimeException $e) {
|
} catch (\RuntimeException $e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,8 @@ class HgDriver extends VcsDriver
|
||||||
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
|
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
|
||||||
|
|
||||||
// update the repo if it is a valid hg repository
|
// update the repo if it is a valid hg repository
|
||||||
if (is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) {
|
if (is_dir($this->repoDir) && 0 === $this->process->execute(['hg', 'summary'], $output, $this->repoDir)) {
|
||||||
if (0 !== $this->process->execute('hg pull', $output, $this->repoDir)) {
|
if (0 !== $this->process->execute(['hg', 'pull'], $output, $this->repoDir)) {
|
||||||
$this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
|
$this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,8 +72,8 @@ class HgDriver extends VcsDriver
|
||||||
$fs->removeDirectory($this->repoDir);
|
$fs->removeDirectory($this->repoDir);
|
||||||
|
|
||||||
$repoDir = $this->repoDir;
|
$repoDir = $this->repoDir;
|
||||||
$command = static function ($url) use ($repoDir): string {
|
$command = static function ($url) use ($repoDir): array {
|
||||||
return sprintf('hg clone --noupdate -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir));
|
return ['hg', 'clone', '--noupdate', '--', $url, $repoDir];
|
||||||
};
|
};
|
||||||
|
|
||||||
$hgUtils->runCommand($command, $this->url, null);
|
$hgUtils->runCommand($command, $this->url, null);
|
||||||
|
@ -90,7 +90,7 @@ class HgDriver extends VcsDriver
|
||||||
public function getRootIdentifier(): string
|
public function getRootIdentifier(): string
|
||||||
{
|
{
|
||||||
if (null === $this->rootIdentifier) {
|
if (null === $this->rootIdentifier) {
|
||||||
$this->process->execute('hg tip --template "{node}"', $output, $this->repoDir);
|
$this->process->execute(['hg', 'tip', '--template', '{node}'], $output, $this->repoDir);
|
||||||
$output = $this->process->splitLines($output);
|
$output = $this->process->splitLines($output);
|
||||||
$this->rootIdentifier = $output[0];
|
$this->rootIdentifier = $output[0];
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ class HgDriver extends VcsDriver
|
||||||
throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier);
|
throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
$resource = sprintf('hg cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));
|
$resource = ['hg', 'cat', '-r', $identifier, '--', $file];
|
||||||
$this->process->execute($resource, $content, $this->repoDir);
|
$this->process->execute($resource, $content, $this->repoDir);
|
||||||
|
|
||||||
if (!trim($content)) {
|
if (!trim($content)) {
|
||||||
|
@ -147,10 +147,7 @@ class HgDriver extends VcsDriver
|
||||||
public function getChangeDate(string $identifier): ?\DateTimeImmutable
|
public function getChangeDate(string $identifier): ?\DateTimeImmutable
|
||||||
{
|
{
|
||||||
$this->process->execute(
|
$this->process->execute(
|
||||||
sprintf(
|
['hg', 'log', '--template', '{date|rfc3339date}', '-r', $identifier],
|
||||||
'hg log --template "{date|rfc3339date}" -r %s',
|
|
||||||
ProcessExecutor::escape($identifier)
|
|
||||||
),
|
|
||||||
$output,
|
$output,
|
||||||
$this->repoDir
|
$this->repoDir
|
||||||
);
|
);
|
||||||
|
@ -166,7 +163,7 @@ class HgDriver extends VcsDriver
|
||||||
if (null === $this->tags) {
|
if (null === $this->tags) {
|
||||||
$tags = [];
|
$tags = [];
|
||||||
|
|
||||||
$this->process->execute('hg tags', $output, $this->repoDir);
|
$this->process->execute(['hg', 'tags'], $output, $this->repoDir);
|
||||||
foreach ($this->process->splitLines($output) as $tag) {
|
foreach ($this->process->splitLines($output) as $tag) {
|
||||||
if ($tag && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) {
|
if ($tag && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) {
|
||||||
$tags[$match[1]] = $match[2];
|
$tags[$match[1]] = $match[2];
|
||||||
|
@ -189,14 +186,14 @@ class HgDriver extends VcsDriver
|
||||||
$branches = [];
|
$branches = [];
|
||||||
$bookmarks = [];
|
$bookmarks = [];
|
||||||
|
|
||||||
$this->process->execute('hg branches', $output, $this->repoDir);
|
$this->process->execute(['hg', 'branches'], $output, $this->repoDir);
|
||||||
foreach ($this->process->splitLines($output) as $branch) {
|
foreach ($this->process->splitLines($output) as $branch) {
|
||||||
if ($branch && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') {
|
if ($branch && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') {
|
||||||
$branches[$match[1]] = $match[2];
|
$branches[$match[1]] = $match[2];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->process->execute('hg bookmarks', $output, $this->repoDir);
|
$this->process->execute(['hg', 'bookmarks'], $output, $this->repoDir);
|
||||||
foreach ($this->process->splitLines($output) as $branch) {
|
foreach ($this->process->splitLines($output) as $branch) {
|
||||||
if ($branch && Preg::isMatchStrictGroups('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') {
|
if ($branch && Preg::isMatchStrictGroups('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') {
|
||||||
$bookmarks[$match[1]] = $match[2];
|
$bookmarks[$match[1]] = $match[2];
|
||||||
|
@ -228,7 +225,7 @@ class HgDriver extends VcsDriver
|
||||||
|
|
||||||
$process = new ProcessExecutor($io);
|
$process = new ProcessExecutor($io);
|
||||||
// check whether there is a hg repo in that path
|
// check whether there is a hg repo in that path
|
||||||
if ($process->execute('hg summary', $output, $url) === 0) {
|
if ($process->execute(['hg', 'summary'], $output, $url) === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,7 +235,7 @@ class HgDriver extends VcsDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
$process = new ProcessExecutor($io);
|
$process = new ProcessExecutor($io);
|
||||||
$exit = $process->execute(sprintf('hg identify -- %s', ProcessExecutor::escape($url)), $ignored);
|
$exit = $process->execute(['hg', 'identify', '--', $url], $ignored);
|
||||||
|
|
||||||
return $exit === 0;
|
return $exit === 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@ class SvnDriver extends VcsDriver
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$resource = $path.$file;
|
$resource = $path.$file;
|
||||||
$output = $this->execute('svn cat', $this->baseUrl . $resource . $rev);
|
$output = $this->execute(['svn', 'cat'], $this->baseUrl . $resource . $rev);
|
||||||
if ('' === trim($output)) {
|
if ('' === trim($output)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -213,7 +213,7 @@ class SvnDriver extends VcsDriver
|
||||||
$rev = '';
|
$rev = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = $this->execute('svn info', $this->baseUrl . $path . $rev);
|
$output = $this->execute(['svn', 'info'], $this->baseUrl . $path . $rev);
|
||||||
foreach ($this->process->splitLines($output) as $line) {
|
foreach ($this->process->splitLines($output) as $line) {
|
||||||
if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) {
|
if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) {
|
||||||
return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC'));
|
return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC'));
|
||||||
|
@ -232,7 +232,7 @@ class SvnDriver extends VcsDriver
|
||||||
$tags = [];
|
$tags = [];
|
||||||
|
|
||||||
if ($this->tagsPath !== false) {
|
if ($this->tagsPath !== false) {
|
||||||
$output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->tagsPath);
|
$output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->tagsPath);
|
||||||
if ($output !== '') {
|
if ($output !== '') {
|
||||||
$lastRev = 0;
|
$lastRev = 0;
|
||||||
foreach ($this->process->splitLines($output) as $line) {
|
foreach ($this->process->splitLines($output) as $line) {
|
||||||
|
@ -271,7 +271,7 @@ class SvnDriver extends VcsDriver
|
||||||
$trunkParent = $this->baseUrl . '/' . $this->trunkPath;
|
$trunkParent = $this->baseUrl . '/' . $this->trunkPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = $this->execute('svn ls --verbose', $trunkParent);
|
$output = $this->execute(['svn', 'ls', '--verbose'], $trunkParent);
|
||||||
if ($output !== '') {
|
if ($output !== '') {
|
||||||
foreach ($this->process->splitLines($output) as $line) {
|
foreach ($this->process->splitLines($output) as $line) {
|
||||||
$line = trim($line);
|
$line = trim($line);
|
||||||
|
@ -290,7 +290,7 @@ class SvnDriver extends VcsDriver
|
||||||
unset($output);
|
unset($output);
|
||||||
|
|
||||||
if ($this->branchesPath !== false) {
|
if ($this->branchesPath !== false) {
|
||||||
$output = $this->execute('svn ls --verbose', $this->baseUrl . '/' . $this->branchesPath);
|
$output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->branchesPath);
|
||||||
if ($output !== '') {
|
if ($output !== '') {
|
||||||
$lastRev = 0;
|
$lastRev = 0;
|
||||||
foreach ($this->process->splitLines(trim($output)) as $line) {
|
foreach ($this->process->splitLines(trim($output)) as $line) {
|
||||||
|
@ -331,10 +331,7 @@ class SvnDriver extends VcsDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
$process = new ProcessExecutor($io);
|
$process = new ProcessExecutor($io);
|
||||||
$exit = $process->execute(
|
$exit = $process->execute(['svn', 'info', '--non-interactive', '--', $url], $ignoredOutput);
|
||||||
"svn info --non-interactive -- ".ProcessExecutor::escape($url),
|
|
||||||
$ignoredOutput
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($exit === 0) {
|
if ($exit === 0) {
|
||||||
// This is definitely a Subversion repository.
|
// This is definitely a Subversion repository.
|
||||||
|
@ -375,11 +372,11 @@ class SvnDriver extends VcsDriver
|
||||||
* Execute an SVN command and try to fix up the process with credentials
|
* Execute an SVN command and try to fix up the process with credentials
|
||||||
* if necessary.
|
* if necessary.
|
||||||
*
|
*
|
||||||
* @param string $command The svn command to run.
|
* @param non-empty-list<string> $command The svn command to run.
|
||||||
* @param string $url The SVN URL.
|
* @param string $url The SVN URL.
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
*/
|
*/
|
||||||
protected function execute(string $command, string $url): string
|
protected function execute(array $command, string $url): string
|
||||||
{
|
{
|
||||||
if (null === $this->util) {
|
if (null === $this->util) {
|
||||||
$this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process);
|
$this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process);
|
||||||
|
|
|
@ -77,7 +77,7 @@ class Bitbucket
|
||||||
}
|
}
|
||||||
|
|
||||||
// if available use token from git config
|
// if available use token from git config
|
||||||
if (0 === $this->process->execute('git config bitbucket.accesstoken', $output)) {
|
if (0 === $this->process->execute(['git', 'config', 'bitbucket.accesstoken'], $output)) {
|
||||||
$this->io->setAuthentication($originUrl, 'x-token-auth', trim($output));
|
$this->io->setAuthentication($originUrl, 'x-token-auth', trim($output));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -109,9 +109,9 @@ class Filesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform::isWindows()) {
|
if (Platform::isWindows()) {
|
||||||
$cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape(realpath($directory)));
|
$cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)];
|
||||||
} else {
|
} else {
|
||||||
$cmd = sprintf('rm -rf %s', ProcessExecutor::escape($directory));
|
$cmd = ['rm', '-rf', $directory];
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->getProcess()->execute($cmd, $output) === 0;
|
$result = $this->getProcess()->execute($cmd, $output) === 0;
|
||||||
|
@ -144,9 +144,9 @@ class Filesystem
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform::isWindows()) {
|
if (Platform::isWindows()) {
|
||||||
$cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape(realpath($directory)));
|
$cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)];
|
||||||
} else {
|
} else {
|
||||||
$cmd = sprintf('rm -rf %s', ProcessExecutor::escape($directory));
|
$cmd = ['rm', '-rf', $directory];
|
||||||
}
|
}
|
||||||
|
|
||||||
$promise = $this->getProcess()->executeAsync($cmd);
|
$promise = $this->getProcess()->executeAsync($cmd);
|
||||||
|
@ -427,8 +427,7 @@ class Filesystem
|
||||||
|
|
||||||
if (Platform::isWindows()) {
|
if (Platform::isWindows()) {
|
||||||
// Try to copy & delete - this is a workaround for random "Access denied" errors.
|
// Try to copy & delete - this is a workaround for random "Access denied" errors.
|
||||||
$command = sprintf('xcopy %s %s /E /I /Q /Y', ProcessExecutor::escape($source), ProcessExecutor::escape($target));
|
$result = $this->getProcess()->execute(['xcopy', $source, $target, '/E', '/I', '/Q', '/Y'], $output);
|
||||||
$result = $this->getProcess()->execute($command, $output);
|
|
||||||
|
|
||||||
// clear stat cache because external processes aren't tracked by the php stat cache
|
// clear stat cache because external processes aren't tracked by the php stat cache
|
||||||
clearstatcache();
|
clearstatcache();
|
||||||
|
@ -441,8 +440,7 @@ class Filesystem
|
||||||
} else {
|
} else {
|
||||||
// We do not use PHP's "rename" function here since it does not support
|
// We do not use PHP's "rename" function here since it does not support
|
||||||
// the case where $source, and $target are located on different partitions.
|
// the case where $source, and $target are located on different partitions.
|
||||||
$command = sprintf('mv %s %s', ProcessExecutor::escape($source), ProcessExecutor::escape($target));
|
$result = $this->getProcess()->execute(['mv', $source, $target], $output);
|
||||||
$result = $this->getProcess()->execute($command, $output);
|
|
||||||
|
|
||||||
// clear stat cache because external processes aren't tracked by the php stat cache
|
// clear stat cache because external processes aren't tracked by the php stat cache
|
||||||
clearstatcache();
|
clearstatcache();
|
||||||
|
@ -841,11 +839,7 @@ class Filesystem
|
||||||
@rmdir($junction);
|
@rmdir($junction);
|
||||||
}
|
}
|
||||||
|
|
||||||
$cmd = sprintf(
|
$cmd = ['mklink', '/J', str_replace('/', DIRECTORY_SEPARATOR, $junction), Platform::realpath($target)];
|
||||||
'mklink /J %s %s',
|
|
||||||
ProcessExecutor::escape(str_replace('/', DIRECTORY_SEPARATOR, $junction)),
|
|
||||||
ProcessExecutor::escape(realpath($target))
|
|
||||||
);
|
|
||||||
if ($this->getProcess()->execute($cmd, $output) !== 0) {
|
if ($this->getProcess()->execute($cmd, $output) !== 0) {
|
||||||
throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target);
|
throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target);
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,26 +63,90 @@ class Git
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Runs a set of commands using the $url or a variation of it (with auth, ssh, ..)
|
||||||
|
*
|
||||||
|
* Commands should use %url% placeholders for the URL instead of inlining it to allow this function to do its job
|
||||||
|
* %sanitizedUrl% is also automatically replaced by the url without user/pass
|
||||||
|
*
|
||||||
|
* As soon as a single command fails it will halt, so assume the commands are run as && in bash
|
||||||
|
*
|
||||||
|
* @param non-empty-array<non-empty-list<string>> $commands
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
public function runCommands(array $commands, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
|
||||||
|
{
|
||||||
|
$callables = [];
|
||||||
|
foreach ($commands as $cmd) {
|
||||||
|
$callables[] = static function (string $url) use ($cmd): array {
|
||||||
|
$map = [
|
||||||
|
'%url%' => $url,
|
||||||
|
'%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url),
|
||||||
|
];
|
||||||
|
|
||||||
|
return array_map(static function ($value) use ($map): string {
|
||||||
|
return $map[$value] ?? $value;
|
||||||
|
}, $cmd);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// @phpstan-ignore method.deprecated
|
||||||
|
$this->runCommand($callables, $url, $cwd, $initialClone, $commandOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable|array<callable> $commandCallable
|
||||||
* @param mixed $commandOutput the output will be written into this var if passed by ref
|
* @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
|
* if a callable is passed it will be used as output handler
|
||||||
|
* @deprecated Use runCommands with placeholders instead of callbacks for simplicity
|
||||||
*/
|
*/
|
||||||
public function runCommand(callable $commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
|
public function runCommand($commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
|
||||||
{
|
{
|
||||||
|
$commandCallables = is_callable($commandCallable) ? [$commandCallable] : $commandCallable;
|
||||||
|
$lastCommand = '';
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
if ($initialClone) {
|
if ($initialClone) {
|
||||||
$origCwd = $cwd;
|
$origCwd = $cwd;
|
||||||
$cwd = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$runCommands = function ($url) use ($commandCallables, $cwd, &$commandOutput, &$lastCommand, $initialClone) {
|
||||||
|
$collectOutputs = !is_callable($commandOutput);
|
||||||
|
$outputs = [];
|
||||||
|
|
||||||
|
$status = 0;
|
||||||
|
$counter = 0;
|
||||||
|
foreach ($commandCallables as $callable) {
|
||||||
|
$lastCommand = $callable($url);
|
||||||
|
if ($collectOutputs) {
|
||||||
|
$outputs[] = '';
|
||||||
|
$output = &$outputs[count($outputs) - 1];
|
||||||
|
} else {
|
||||||
|
$output = &$commandOutput;
|
||||||
|
}
|
||||||
|
$status = $this->process->execute($lastCommand, $output, $initialClone && $counter === 0 ? null : $cwd);
|
||||||
|
if ($status !== 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($collectOutputs) {
|
||||||
|
$commandOutput = implode('', $outputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
};
|
||||||
|
|
||||||
if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) {
|
if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) {
|
||||||
throw new \InvalidArgumentException('The source URL ' . $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.');
|
throw new \InvalidArgumentException('The source URL ' . $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.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$initialClone) {
|
if (!$initialClone) {
|
||||||
// capture username/password from URL if there is one and we have no auth configured yet
|
// capture username/password from URL if there is one and we have no auth configured yet
|
||||||
$this->process->execute('git remote -v', $output, $cwd);
|
$this->process->execute(['git', 'remote', '-v'], $output, $cwd);
|
||||||
if (Preg::isMatchStrictGroups('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) {
|
if (Preg::isMatchStrictGroups('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) {
|
||||||
$this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2]));
|
$this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2]));
|
||||||
}
|
}
|
||||||
|
@ -100,7 +164,7 @@ class Git
|
||||||
$protoUrl = $protocol . "://" . $match[1] . "/" . $match[2];
|
$protoUrl = $protocol . "://" . $match[1] . "/" . $match[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 === $this->process->execute($commandCallable($protoUrl), $commandOutput, $cwd)) {
|
if (0 === $runCommands($protoUrl)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput());
|
$messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput());
|
||||||
|
@ -119,11 +183,9 @@ class Git
|
||||||
// if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https
|
// if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https
|
||||||
$bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true);
|
$bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true);
|
||||||
|
|
||||||
$command = $commandCallable($url);
|
|
||||||
|
|
||||||
$auth = null;
|
$auth = null;
|
||||||
$credentials = [];
|
$credentials = [];
|
||||||
if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $commandOutput, $cwd)) {
|
if ($bypassSshForGitHub || 0 !== $runCommands($url)) {
|
||||||
$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
|
||||||
// @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
|
// @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
|
||||||
|
@ -143,8 +205,7 @@ class Git
|
||||||
if ($this->io->hasAuthentication($match[1])) {
|
if ($this->io->hasAuthentication($match[1])) {
|
||||||
$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 = $commandCallable($authUrl);
|
if (0 === $runCommands($authUrl)) {
|
||||||
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,8 +241,7 @@ class Git
|
||||||
$auth = $this->io->getAuthentication($domain);
|
$auth = $this->io->getAuthentication($domain);
|
||||||
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;
|
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;
|
||||||
|
|
||||||
$command = $commandCallable($authUrl);
|
if (0 === $runCommands($authUrl)) {
|
||||||
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
|
|
||||||
// Well if that succeeded on our first try, let's just
|
// Well if that succeeded on our first try, let's just
|
||||||
// take the win.
|
// take the win.
|
||||||
return;
|
return;
|
||||||
|
@ -199,8 +259,7 @@ class Git
|
||||||
if ($this->io->hasAuthentication($domain)) {
|
if ($this->io->hasAuthentication($domain)) {
|
||||||
$auth = $this->io->getAuthentication($domain);
|
$auth = $this->io->getAuthentication($domain);
|
||||||
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;
|
$authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;
|
||||||
$command = $commandCallable($authUrl);
|
if (0 === $runCommands($authUrl)) {
|
||||||
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,8 +268,7 @@ class Git
|
||||||
//Falling back to ssh
|
//Falling back to ssh
|
||||||
$sshUrl = 'git@bitbucket.org:' . $repo_with_git_part;
|
$sshUrl = 'git@bitbucket.org:' . $repo_with_git_part;
|
||||||
$this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.');
|
$this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.');
|
||||||
$command = $commandCallable($sshUrl);
|
if (0 === $runCommands($sshUrl)) {
|
||||||
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,8 +300,7 @@ class Git
|
||||||
$authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3];
|
$authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
$command = $commandCallable($authUrl);
|
if (0 === $runCommands($authUrl)) {
|
||||||
if (0 === $this->process->execute($command, $commandOutput, $cwd)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,8 +337,7 @@ class Git
|
||||||
if (null !== $auth) {
|
if (null !== $auth) {
|
||||||
$authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3];
|
$authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3];
|
||||||
|
|
||||||
$command = $commandCallable($authUrl);
|
if (0 === $runCommands($authUrl)) {
|
||||||
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);
|
||||||
|
@ -298,11 +354,12 @@ class Git
|
||||||
$this->filesystem->removeDirectory($origCwd);
|
$this->filesystem->removeDirectory($origCwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$lastCommand = implode(' ', $lastCommand);
|
||||||
if (count($credentials) > 0) {
|
if (count($credentials) > 0) {
|
||||||
$command = $this->maskCredentials($command, $credentials);
|
$lastCommand = $this->maskCredentials($lastCommand, $credentials);
|
||||||
$errorMsg = $this->maskCredentials($errorMsg, $credentials);
|
$errorMsg = $this->maskCredentials($errorMsg, $credentials);
|
||||||
}
|
}
|
||||||
$this->throwException('Failed to execute ' . $command . "\n\n" . $errorMsg, $url);
|
$this->throwException('Failed to execute ' . $lastCommand . "\n\n" . $errorMsg, $url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,14 +372,16 @@ class Git
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the repo if it is a valid git repository
|
// update the repo if it is a valid git repository
|
||||||
if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
|
if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') {
|
||||||
try {
|
try {
|
||||||
$commandCallable = static function ($url): string {
|
$commands = [
|
||||||
$sanitizedUrl = Preg::replace('{://([^@]+?):(.+?)@}', '://', $url);
|
['git', 'remote', 'set-url', 'origin', '--', '%url%'],
|
||||||
|
['git', 'remote', 'update', '--prune', 'origin'],
|
||||||
|
['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
|
||||||
|
['git', 'gc', '--auto'],
|
||||||
|
];
|
||||||
|
|
||||||
return sprintf('git remote set-url origin -- %s && git remote update --prune origin && git remote set-url origin -- %s && git gc --auto', ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl));
|
$this->runCommands($commands, $url, $dir);
|
||||||
};
|
|
||||||
$this->runCommand($commandCallable, $url, $dir);
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->io->writeError('<error>Sync mirror failed: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);
|
$this->io->writeError('<error>Sync mirror failed: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);
|
||||||
|
|
||||||
|
@ -336,11 +395,7 @@ class Git
|
||||||
// clean up directory and do a fresh clone into it
|
// clean up directory and do a fresh clone into it
|
||||||
$this->filesystem->removeDirectory($dir);
|
$this->filesystem->removeDirectory($dir);
|
||||||
|
|
||||||
$commandCallable = static function ($url) use ($dir): string {
|
$this->runCommands([['git', 'clone', '--mirror', '--', '%url%', $dir]], $url, $dir, true);
|
||||||
return sprintf('git clone --mirror -- %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($dir));
|
|
||||||
};
|
|
||||||
|
|
||||||
$this->runCommand($commandCallable, $url, $dir, true);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -352,10 +407,10 @@ class Git
|
||||||
$branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion);
|
$branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion);
|
||||||
$branches = null;
|
$branches = null;
|
||||||
$tags = null;
|
$tags = null;
|
||||||
if (0 === $this->process->execute('git branch', $output, $dir)) {
|
if (0 === $this->process->execute(['git', 'branch'], $output, $dir)) {
|
||||||
$branches = $output;
|
$branches = $output;
|
||||||
}
|
}
|
||||||
if (0 === $this->process->execute('git tag', $output, $dir)) {
|
if (0 === $this->process->execute(['git', 'tag'], $output, $dir)) {
|
||||||
$tags = $output;
|
$tags = $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,11 +445,23 @@ class Git
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
public static function getNoShowSignatureFlags(ProcessExecutor $process): array
|
||||||
|
{
|
||||||
|
$flags = static::getNoShowSignatureFlag($process);
|
||||||
|
if ('' === $flags) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return explode(' ', substr($flags, 1));
|
||||||
|
}
|
||||||
|
|
||||||
private function checkRefIsInMirror(string $dir, string $ref): bool
|
private function checkRefIsInMirror(string $dir, string $ref): bool
|
||||||
{
|
{
|
||||||
if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
|
if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') {
|
||||||
$escapedRef = ProcessExecutor::escape($ref.'^{commit}');
|
$exitCode = $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $ignoredOutput, $dir);
|
||||||
$exitCode = $this->process->execute(sprintf('git rev-parse --quiet --verify %s', $escapedRef), $ignoredOutput, $dir);
|
|
||||||
if ($exitCode === 0) {
|
if ($exitCode === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -439,15 +506,15 @@ class Git
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($isLocalPathRepository) {
|
if ($isLocalPathRepository) {
|
||||||
$this->process->execute('git remote show origin', $output, $dir);
|
$this->process->execute(['git', 'remote', 'show', 'origin'], $output, $dir);
|
||||||
} else {
|
} else {
|
||||||
$commandCallable = static function ($url): string {
|
$commands = [
|
||||||
$sanitizedUrl = Preg::replace('{://([^@]+?):(.+?)@}', '://', $url);
|
['git', 'remote', 'set-url', 'origin', '--', '%url%'],
|
||||||
|
['git', 'remote', 'show', 'origin'],
|
||||||
|
['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
|
||||||
|
];
|
||||||
|
|
||||||
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->runCommands($commands, $url, $dir, false, $output);
|
||||||
};
|
|
||||||
|
|
||||||
$this->runCommand($commandCallable, $url, $dir, false, $output);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$lines = $this->process->splitLines($output);
|
$lines = $this->process->splitLines($output);
|
||||||
|
@ -513,7 +580,7 @@ class Git
|
||||||
// git might delete a directory when it fails and php will not know
|
// git might delete a directory when it fails and php will not know
|
||||||
clearstatcache();
|
clearstatcache();
|
||||||
|
|
||||||
if (0 !== $this->process->execute('git --version', $ignoredOutput)) {
|
if (0 !== $this->process->execute(['git', '--version'], $ignoredOutput)) {
|
||||||
throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
|
throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,7 +596,7 @@ class Git
|
||||||
{
|
{
|
||||||
if (false === self::$version) {
|
if (false === self::$version) {
|
||||||
self::$version = null;
|
self::$version = null;
|
||||||
if (0 === $process->execute('git --version', $output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) {
|
if (0 === $process->execute(['git', '--version'], $output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) {
|
||||||
self::$version = $matches[1];
|
self::$version = $matches[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ class GitHub
|
||||||
}
|
}
|
||||||
|
|
||||||
// if available use token from git config
|
// if available use token from git config
|
||||||
if (0 === $this->process->execute('git config github.accesstoken', $output)) {
|
if (0 === $this->process->execute(['git', 'config', 'github.accesstoken'], $output)) {
|
||||||
$this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic');
|
$this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -86,7 +86,7 @@ class GitHub
|
||||||
}
|
}
|
||||||
|
|
||||||
$note = 'Composer';
|
$note = 'Composer';
|
||||||
if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute('hostname', $output)) {
|
if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute(['hostname'], $output)) {
|
||||||
$note .= ' on ' . trim($output);
|
$note .= ' on ' . trim($output);
|
||||||
}
|
}
|
||||||
$note .= ' ' . date('Y-m-d Hi');
|
$note .= ' ' . date('Y-m-d Hi');
|
||||||
|
|
|
@ -65,14 +65,14 @@ class GitLab
|
||||||
}
|
}
|
||||||
|
|
||||||
// if available use token from git config
|
// if available use token from git config
|
||||||
if (0 === $this->process->execute('git config gitlab.accesstoken', $output)) {
|
if (0 === $this->process->execute(['git', 'config', 'gitlab.accesstoken'], $output)) {
|
||||||
$this->io->setAuthentication($originUrl, trim($output), 'oauth2');
|
$this->io->setAuthentication($originUrl, trim($output), 'oauth2');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if available use deploy token from git config
|
// if available use deploy token from git config
|
||||||
if (0 === $this->process->execute('git config gitlab.deploytoken.user', $tokenUser) && 0 === $this->process->execute('git config gitlab.deploytoken.token', $tokenPassword)) {
|
if (0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.user'], $tokenUser) && 0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.token'], $tokenPassword)) {
|
||||||
$this->io->setAuthentication($originUrl, trim($tokenUser), trim($tokenPassword));
|
$this->io->setAuthentication($originUrl, trim($tokenUser), trim($tokenPassword));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -111,7 +111,7 @@ class Hg
|
||||||
{
|
{
|
||||||
if (false === self::$version) {
|
if (false === self::$version) {
|
||||||
self::$version = null;
|
self::$version = null;
|
||||||
if (0 === $process->execute('hg --version', $output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/', $output, $matches)) {
|
if (0 === $process->execute(['hg', '--version'], $output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/', $output, $matches)) {
|
||||||
self::$version = $matches[1];
|
self::$version = $matches[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Composer\Util;
|
||||||
|
|
||||||
use Composer\IO\IOInterface;
|
use Composer\IO\IOInterface;
|
||||||
use Composer\Pcre\Preg;
|
use Composer\Pcre\Preg;
|
||||||
|
use Symfony\Component\Process\ExecutableFinder;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,7 +82,7 @@ class Perforce
|
||||||
|
|
||||||
public static function checkServerExists(string $url, ProcessExecutor $processExecutor): bool
|
public static function checkServerExists(string $url, ProcessExecutor $processExecutor): bool
|
||||||
{
|
{
|
||||||
return 0 === $processExecutor->execute('p4 -p ' . ProcessExecutor::escape($url) . ' info -s', $ignoredOutput);
|
return 0 === $processExecutor->execute(['p4', '-p', $url, 'info', '-s'], $ignoredOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -248,7 +249,7 @@ class Perforce
|
||||||
}
|
}
|
||||||
$this->p4User = $this->io->ask('Enter P4 User:');
|
$this->p4User = $this->io->ask('Enter P4 User:');
|
||||||
if ($this->windowsFlag) {
|
if ($this->windowsFlag) {
|
||||||
$command = 'p4 set P4USER=' . $this->p4User;
|
$command = $this->getP4Executable().' set P4USER=' . $this->p4User;
|
||||||
} else {
|
} else {
|
||||||
$command = 'export P4USER=' . $this->p4User;
|
$command = 'export P4USER=' . $this->p4User;
|
||||||
}
|
}
|
||||||
|
@ -261,7 +262,7 @@ class Perforce
|
||||||
protected function getP4variable(string $name): ?string
|
protected function getP4variable(string $name): ?string
|
||||||
{
|
{
|
||||||
if ($this->windowsFlag) {
|
if ($this->windowsFlag) {
|
||||||
$command = 'p4 set';
|
$command = $this->getP4Executable().' set';
|
||||||
$this->executeCommand($command);
|
$this->executeCommand($command);
|
||||||
$result = trim($this->commandResult);
|
$result = trim($this->commandResult);
|
||||||
$resArray = explode(PHP_EOL, $result);
|
$resArray = explode(PHP_EOL, $result);
|
||||||
|
@ -309,7 +310,7 @@ class Perforce
|
||||||
*/
|
*/
|
||||||
public function generateP4Command(string $command, bool $useClient = true): string
|
public function generateP4Command(string $command, bool $useClient = true): string
|
||||||
{
|
{
|
||||||
$p4Command = 'p4 ';
|
$p4Command = $this->getP4Executable().' ';
|
||||||
$p4Command .= '-u ' . $this->getUser() . ' ';
|
$p4Command .= '-u ' . $this->getUser() . ' ';
|
||||||
if ($useClient) {
|
if ($useClient) {
|
||||||
$p4Command .= '-c ' . $this->getClient() . ' ';
|
$p4Command .= '-c ' . $this->getClient() . ' ';
|
||||||
|
@ -620,4 +621,17 @@ class Perforce
|
||||||
{
|
{
|
||||||
$this->filesystem = $fs;
|
$this->filesystem = $fs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getP4Executable(): string
|
||||||
|
{
|
||||||
|
static $p4Executable;
|
||||||
|
|
||||||
|
if ($p4Executable) {
|
||||||
|
return $p4Executable;
|
||||||
|
}
|
||||||
|
|
||||||
|
$finder = new ExecutableFinder();
|
||||||
|
|
||||||
|
return $p4Executable = $finder->find('p4') ?? 'p4';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,19 @@ class Platform
|
||||||
return $cwd;
|
return $cwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infallible realpath version that falls back on the given $path if realpath is not working
|
||||||
|
*/
|
||||||
|
public static function realpath(string $path): string
|
||||||
|
{
|
||||||
|
$realPath = realpath($path);
|
||||||
|
if ($realPath === false) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $realPath;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getenv() equivalent but reads from the runtime global variables first
|
* getenv() equivalent but reads from the runtime global variables first
|
||||||
*
|
*
|
||||||
|
@ -308,7 +321,7 @@ class Platform
|
||||||
if (defined('PHP_OS_FAMILY') && PHP_OS_FAMILY === 'Linux') {
|
if (defined('PHP_OS_FAMILY') && PHP_OS_FAMILY === 'Linux') {
|
||||||
$process = new ProcessExecutor();
|
$process = new ProcessExecutor();
|
||||||
try {
|
try {
|
||||||
if (0 === $process->execute('lsmod | grep vboxguest', $ignoredOutput)) {
|
if (0 === $process->execute(['lsmod'], $output) && str_contains($output, 'vboxguest')) {
|
||||||
return self::$isVirtualBoxGuest = true;
|
return self::$isVirtualBoxGuest = true;
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ use Symfony\Component\Process\Process;
|
||||||
use Symfony\Component\Process\Exception\RuntimeException;
|
use Symfony\Component\Process\Exception\RuntimeException;
|
||||||
use React\Promise\Promise;
|
use React\Promise\Promise;
|
||||||
use React\Promise\PromiseInterface;
|
use React\Promise\PromiseInterface;
|
||||||
|
use Symfony\Component\Process\ExecutableFinder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||||
|
@ -33,6 +34,14 @@ class ProcessExecutor
|
||||||
private const STATUS_FAILED = 4;
|
private const STATUS_FAILED = 4;
|
||||||
private const STATUS_ABORTED = 5;
|
private const STATUS_ABORTED = 5;
|
||||||
|
|
||||||
|
private const BUILTIN_CMD_COMMANDS = [
|
||||||
|
'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date',
|
||||||
|
'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto',
|
||||||
|
'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause',
|
||||||
|
'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set',
|
||||||
|
'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol',
|
||||||
|
];
|
||||||
|
|
||||||
private const GIT_CMDS_NEED_GIT_DIR = [
|
private const GIT_CMDS_NEED_GIT_DIR = [
|
||||||
['show'],
|
['show'],
|
||||||
['log'],
|
['log'],
|
||||||
|
@ -63,6 +72,9 @@ class ProcessExecutor
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $allowAsync = false;
|
private $allowAsync = false;
|
||||||
|
|
||||||
|
/** @var array<string, string> */
|
||||||
|
private static $executables = [];
|
||||||
|
|
||||||
public function __construct(?IOInterface $io = null)
|
public function __construct(?IOInterface $io = null)
|
||||||
{
|
{
|
||||||
$this->io = $io;
|
$this->io = $io;
|
||||||
|
@ -71,7 +83,7 @@ class ProcessExecutor
|
||||||
/**
|
/**
|
||||||
* runs a process on the commandline
|
* runs a process on the commandline
|
||||||
*
|
*
|
||||||
* @param string|list<string> $command the command to execute
|
* @param string|non-empty-list<string> $command the command to execute
|
||||||
* @param mixed $output the output will be written into this var if passed by ref
|
* @param mixed $output the output will be written into this var if passed by ref
|
||||||
* if a callable is passed it will be used as output handler
|
* if a callable is passed it will be used as output handler
|
||||||
* @param null|string $cwd the working directory
|
* @param null|string $cwd the working directory
|
||||||
|
@ -89,7 +101,7 @@ class ProcessExecutor
|
||||||
/**
|
/**
|
||||||
* runs a process on the commandline in TTY mode
|
* runs a process on the commandline in TTY mode
|
||||||
*
|
*
|
||||||
* @param string|list<string> $command the command to execute
|
* @param string|non-empty-list<string> $command the command to execute
|
||||||
* @param null|string $cwd the working directory
|
* @param null|string $cwd the working directory
|
||||||
* @return int statuscode
|
* @return int statuscode
|
||||||
*/
|
*/
|
||||||
|
@ -103,15 +115,26 @@ class ProcessExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string|list<string> $command
|
* @param string|non-empty-list<string> $command
|
||||||
* @param array<string, string>|null $env
|
* @param array<string, string>|null $env
|
||||||
* @param mixed $output
|
* @param mixed $output
|
||||||
*/
|
*/
|
||||||
private function runProcess($command, ?string $cwd, ?array $env, bool $tty, &$output = null): ?int
|
private function runProcess($command, ?string $cwd, ?array $env, bool $tty, &$output = null): ?int
|
||||||
{
|
{
|
||||||
|
// On Windows, we don't rely on the OS to find the executable if possible to avoid lookups
|
||||||
|
// in the current directory which could be untrusted. Instead we use the ExecutableFinder.
|
||||||
|
|
||||||
if (is_string($command)) {
|
if (is_string($command)) {
|
||||||
|
if (Platform::isWindows() && Preg::isMatch('{^([^:/\\\\]++) }', $command, $match)) {
|
||||||
|
$command = substr_replace($command, self::escape(self::getExecutable($match[1])), 0, strlen($match[1]));
|
||||||
|
}
|
||||||
|
|
||||||
$process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout());
|
$process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout());
|
||||||
} else {
|
} else {
|
||||||
|
if (Platform::isWindows() && \strlen($command[0]) === strcspn($command[0], ':/\\')) {
|
||||||
|
$command[0] = self::getExecutable($command[0]);
|
||||||
|
}
|
||||||
|
|
||||||
$process = new Process($command, $cwd, $env, null, static::getTimeout());
|
$process = new Process($command, $cwd, $env, null, static::getTimeout());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +184,7 @@ class ProcessExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string|list<string> $command
|
* @param string|non-empty-list<string> $command
|
||||||
* @param mixed $output
|
* @param mixed $output
|
||||||
*/
|
*/
|
||||||
private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int
|
private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int
|
||||||
|
@ -178,7 +201,7 @@ class ProcessExecutor
|
||||||
$isBareRepository = !is_dir(sprintf('%s/.git', rtrim($cwd, '/')));
|
$isBareRepository = !is_dir(sprintf('%s/.git', rtrim($cwd, '/')));
|
||||||
if ($isBareRepository) {
|
if ($isBareRepository) {
|
||||||
$configValue = '';
|
$configValue = '';
|
||||||
$this->runProcess('git config safe.bareRepository', $cwd, ['GIT_DIR' => $cwd], $tty, $configValue);
|
$this->runProcess(['git', 'config', 'safe.bareRepository'], $cwd, ['GIT_DIR' => $cwd], $tty, $configValue);
|
||||||
$configValue = trim($configValue);
|
$configValue = trim($configValue);
|
||||||
if ($configValue === 'explicit') {
|
if ($configValue === 'explicit') {
|
||||||
$env = ['GIT_DIR' => $cwd];
|
$env = ['GIT_DIR' => $cwd];
|
||||||
|
@ -550,4 +573,23 @@ class ProcessExecutor
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves executable paths on Windows
|
||||||
|
*/
|
||||||
|
private static function getExecutable(string $name): string
|
||||||
|
{
|
||||||
|
if (\in_array(strtolower($name), self::BUILTIN_CMD_COMMANDS, true)) {
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset(self::$executables[$name])) {
|
||||||
|
$path = (new ExecutableFinder())->find($name, $name);
|
||||||
|
if ($path !== null) {
|
||||||
|
self::$executables[$name] = $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$executables[$name] ?? $name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ class Svn
|
||||||
* Execute an SVN remote command and try to fix up the process with credentials
|
* Execute an SVN remote command and try to fix up the process with credentials
|
||||||
* if necessary.
|
* if necessary.
|
||||||
*
|
*
|
||||||
* @param string $command SVN command to run
|
* @param non-empty-list<string> $command SVN command to run
|
||||||
* @param string $url SVN url
|
* @param string $url SVN url
|
||||||
* @param ?string $cwd Working directory
|
* @param ?string $cwd Working directory
|
||||||
* @param ?string $path Target for a checkout
|
* @param ?string $path Target for a checkout
|
||||||
|
@ -98,7 +98,7 @@ class Svn
|
||||||
*
|
*
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
*/
|
*/
|
||||||
public function execute(string $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = false): string
|
public function execute(array $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = false): string
|
||||||
{
|
{
|
||||||
// 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);
|
||||||
|
@ -110,20 +110,23 @@ class Svn
|
||||||
* Execute an SVN local command and try to fix up the process with credentials
|
* Execute an SVN local command and try to fix up the process with credentials
|
||||||
* if necessary.
|
* if necessary.
|
||||||
*
|
*
|
||||||
* @param string $command SVN command to run
|
* @param non-empty-list<string> $command SVN command to run
|
||||||
* @param string $path Path argument passed thru to the command
|
* @param string $path Path argument passed thru to the command
|
||||||
* @param string $cwd Working directory
|
* @param string $cwd Working directory
|
||||||
* @param bool $verbose Output all output to the user
|
* @param bool $verbose Output all output to the user
|
||||||
*
|
*
|
||||||
* @throws \RuntimeException
|
* @throws \RuntimeException
|
||||||
*/
|
*/
|
||||||
public function executeLocal(string $command, string $path, ?string $cwd = null, bool $verbose = false): string
|
public function executeLocal(array $command, string $path, ?string $cwd = null, bool $verbose = false): string
|
||||||
{
|
{
|
||||||
// A local command has no remote url
|
// A local command has no remote url
|
||||||
return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose);
|
return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function executeWithAuthRetry(string $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose): ?string
|
/**
|
||||||
|
* @param non-empty-list<string> $svnCommand
|
||||||
|
*/
|
||||||
|
private function executeWithAuthRetry(array $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose): ?string
|
||||||
{
|
{
|
||||||
// Regenerate the command at each try, to use the newly user-provided credentials
|
// Regenerate the command at each try, to use the newly user-provided credentials
|
||||||
$command = $this->getCommand($svnCommand, $url, $path);
|
$command = $this->getCommand($svnCommand, $url, $path);
|
||||||
|
@ -209,22 +212,23 @@ class Svn
|
||||||
/**
|
/**
|
||||||
* A method to create the svn commands run.
|
* A method to create the svn commands run.
|
||||||
*
|
*
|
||||||
* @param string $cmd Usually 'svn ls' or something like that.
|
* @param non-empty-list<string> $cmd Usually 'svn ls' or something like that.
|
||||||
* @param string $url Repo URL.
|
* @param string $url Repo URL.
|
||||||
* @param string $path Target for a checkout
|
* @param string $path Target for a checkout
|
||||||
|
*
|
||||||
|
* @return non-empty-list<string>
|
||||||
*/
|
*/
|
||||||
protected function getCommand(string $cmd, string $url, ?string $path = null): string
|
protected function getCommand(array $cmd, string $url, ?string $path = null): array
|
||||||
{
|
{
|
||||||
$cmd = sprintf(
|
$cmd = array_merge(
|
||||||
'%s %s%s -- %s',
|
|
||||||
$cmd,
|
$cmd,
|
||||||
'--non-interactive ',
|
['--non-interactive'],
|
||||||
$this->getCredentialString(),
|
$this->getCredentialArgs(),
|
||||||
ProcessExecutor::escape($url)
|
['--', $url]
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($path) {
|
if ($path !== null) {
|
||||||
$cmd .= ' ' . ProcessExecutor::escape($path);
|
$cmd[] = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $cmd;
|
return $cmd;
|
||||||
|
@ -234,18 +238,18 @@ class Svn
|
||||||
* Return the credential string for the svn command.
|
* Return the credential string for the svn command.
|
||||||
*
|
*
|
||||||
* Adds --no-auth-cache when credentials are present.
|
* Adds --no-auth-cache when credentials are present.
|
||||||
|
*
|
||||||
|
* @return list<string>
|
||||||
*/
|
*/
|
||||||
protected function getCredentialString(): string
|
protected function getCredentialArgs(): array
|
||||||
{
|
{
|
||||||
if (!$this->hasAuth()) {
|
if (!$this->hasAuth()) {
|
||||||
return '';
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf(
|
return array_merge(
|
||||||
' %s--username %s --password %s ',
|
$this->getAuthCacheArgs(),
|
||||||
$this->getAuthCache(),
|
['--username', $this->getUsername(), '--password', $this->getPassword()]
|
||||||
ProcessExecutor::escape($this->getUsername()),
|
|
||||||
ProcessExecutor::escape($this->getPassword())
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,10 +299,12 @@ class Svn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the no-auth-cache switch.
|
* Return the no-auth-cache switch.
|
||||||
|
*
|
||||||
|
* @return list<string>
|
||||||
*/
|
*/
|
||||||
protected function getAuthCache(): string
|
protected function getAuthCacheArgs(): array
|
||||||
{
|
{
|
||||||
return $this->cacheCredentials ? '' : '--no-auth-cache ';
|
return $this->cacheCredentials ? [] : ['--no-auth-cache'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -349,7 +355,7 @@ class Svn
|
||||||
public function binaryVersion(): ?string
|
public function binaryVersion(): ?string
|
||||||
{
|
{
|
||||||
if (!self::$version) {
|
if (!self::$version) {
|
||||||
if (0 === $this->process->execute('svn --version', $output)) {
|
if (0 === $this->process->execute(['svn', '--version'], $output)) {
|
||||||
if (Preg::isMatch('{(\d+(?:\.\d+)+)}', $output, $match)) {
|
if (Preg::isMatch('{(\d+(?:\.\d+)+)}', $output, $match)) {
|
||||||
self::$version = $match[1];
|
self::$version = $match[1];
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ class AllFunctionalTest extends TestCase
|
||||||
}
|
}
|
||||||
|
|
||||||
$proc = new Process([PHP_BINARY, '-dphar.readonly=0', './bin/compile'], $target);
|
$proc = new Process([PHP_BINARY, '-dphar.readonly=0', './bin/compile'], $target);
|
||||||
|
$proc->setTimeout(300);
|
||||||
$exitcode = $proc->run();
|
$exitcode = $proc->run();
|
||||||
|
|
||||||
if ($exitcode !== 0 || trim($proc->getOutput()) !== '') {
|
if ($exitcode !== 0 || trim($proc->getOutput()) !== '') {
|
||||||
|
|
|
@ -76,9 +76,9 @@ class FossilDownloaderTest extends TestCase
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
self::getCmd('fossil clone -- \'http://fossil.kd2.org/kd2fw/\' \''.$this->workingDir.'.fossil\''),
|
['fossil', 'clone', '--', 'http://fossil.kd2.org/kd2fw/', $this->workingDir.'.fossil'],
|
||||||
self::getCmd('fossil open --nested -- \''.$this->workingDir.'.fossil\''),
|
['fossil', 'open', '--nested', '--', $this->workingDir.'.fossil'],
|
||||||
self::getCmd('fossil update -- \'trunk\''),
|
['fossil', 'update', '--', 'trunk'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$downloader = $this->getDownloaderMock(null, null, $process);
|
$downloader = $this->getDownloaderMock(null, null, $process);
|
||||||
|
@ -123,8 +123,9 @@ class FossilDownloaderTest extends TestCase
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
self::getCmd("fossil changes"),
|
['fossil', 'changes'],
|
||||||
self::getCmd("fossil pull && fossil up 'trunk'"),
|
['fossil', 'pull'],
|
||||||
|
['fossil', 'up', 'trunk'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$downloader = $this->getDownloaderMock(null, null, $process);
|
$downloader = $this->getDownloaderMock(null, null, $process);
|
||||||
|
@ -143,7 +144,7 @@ class FossilDownloaderTest extends TestCase
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
self::getCmd('fossil changes'),
|
['fossil', 'changes'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock();
|
$filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock();
|
||||||
|
|
|
@ -122,10 +122,16 @@ class GitDownloaderTest extends TestCase
|
||||||
->will($this->returnValue('dev-master'));
|
->will($this->returnValue('dev-master'));
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
|
$expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath';
|
||||||
$process->expects([
|
$process->expects([
|
||||||
$this->winCompat("git clone --no-checkout -- 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer -- 'https://example.com/composer/composer' && git fetch composer && git remote set-url origin -- 'https://example.com/composer/composer' && git remote set-url composer -- 'https://example.com/composer/composer'"),
|
['git', 'clone', '--no-checkout', '--', 'https://example.com/composer/composer', $expectedPath],
|
||||||
$this->winCompat("git branch -r"),
|
['git', 'remote', 'add', 'composer', '--', 'https://example.com/composer/composer'],
|
||||||
$this->winCompat("(git checkout 'master' -- || git checkout -B 'master' 'composer/master' --) && git reset --hard '1234567890123456789012345678901234567890' --"),
|
['git', 'fetch', 'composer'],
|
||||||
|
['git', 'remote', 'set-url', 'origin', '--', 'https://example.com/composer/composer'],
|
||||||
|
['git', 'remote', 'set-url', 'composer', '--', 'https://example.com/composer/composer'],
|
||||||
|
['git', 'branch', '-r'],
|
||||||
|
['git', 'checkout', 'master', '--'],
|
||||||
|
['git', 'reset', '--hard', '1234567890123456789012345678901234567890', '--'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$downloader = $this->getDownloaderMock(null, null, $process);
|
$downloader = $this->getDownloaderMock(null, null, $process);
|
||||||
|
@ -160,16 +166,24 @@ class GitDownloaderTest extends TestCase
|
||||||
$filesystem = new \Composer\Util\Filesystem;
|
$filesystem = new \Composer\Util\Filesystem;
|
||||||
$filesystem->removeDirectory($cachePath);
|
$filesystem->removeDirectory($cachePath);
|
||||||
|
|
||||||
|
$expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath';
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
['cmd' => $this->winCompat(sprintf("git clone --mirror -- 'https://example.com/composer/composer' '%s'", $cachePath)), 'callback' => static function () use ($cachePath): void {
|
[
|
||||||
@mkdir($cachePath, 0777, true);
|
'cmd' => ['git', 'clone', '--mirror', '--', 'https://example.com/composer/composer', $cachePath],
|
||||||
}],
|
'callback' => static function () use ($cachePath): void {
|
||||||
['cmd' => 'git rev-parse --git-dir', 'stdout' => '.'],
|
@mkdir($cachePath, 0777, true);
|
||||||
$this->winCompat('git rev-parse --quiet --verify \'1234567890123456789012345678901234567890^{commit}\''),
|
}
|
||||||
$this->winCompat(sprintf("git clone --no-checkout '%1\$s' 'composerPath' --dissociate --reference '%1\$s' && cd 'composerPath' && git remote set-url origin -- 'https://example.com/composer/composer' && git remote add composer -- 'https://example.com/composer/composer'", $cachePath)),
|
],
|
||||||
'git branch -r',
|
['cmd' => ['git', 'rev-parse', '--git-dir'], 'stdout' => '.'],
|
||||||
$this->winCompat("(git checkout 'master' -- || git checkout -B 'master' 'composer/master' --) && git reset --hard '1234567890123456789012345678901234567890' --"),
|
['git', 'rev-parse', '--quiet', '--verify', '1234567890123456789012345678901234567890^{commit}'],
|
||||||
|
['git', 'clone', '--no-checkout', $cachePath, $expectedPath, '--dissociate', '--reference', $cachePath],
|
||||||
|
['git', 'remote', 'set-url', 'origin', '--', 'https://example.com/composer/composer'],
|
||||||
|
['git', 'remote', 'add', 'composer', '--', 'https://example.com/composer/composer'],
|
||||||
|
['git', 'branch', '-r'],
|
||||||
|
['cmd' => ['git', 'checkout', 'master', '--'], 'return' => 1],
|
||||||
|
['git', 'checkout', '-B', 'master', 'composer/master', '--'],
|
||||||
|
['git', 'reset', '--hard', '1234567890123456789012345678901234567890', '--'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$downloader = $this->getDownloaderMock(null, $config, $process);
|
$downloader = $this->getDownloaderMock(null, $config, $process);
|
||||||
|
@ -197,17 +211,21 @@ class GitDownloaderTest extends TestCase
|
||||||
->will($this->returnValue('1.0.0'));
|
->will($this->returnValue('1.0.0'));
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
|
$expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath';
|
||||||
$process->expects([
|
$process->expects([
|
||||||
[
|
['cmd' => ['git', 'clone', '--no-checkout', '--', 'https://github.com/mirrors/composer', $expectedPath], 'return' => 1, 'stderr' => 'Error1'],
|
||||||
'cmd' => $this->winCompat("git clone --no-checkout -- 'https://github.com/mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer -- 'https://github.com/mirrors/composer' && git fetch composer && git remote set-url origin -- 'https://github.com/mirrors/composer' && git remote set-url composer -- 'https://github.com/mirrors/composer'"),
|
|
||||||
'return' => 1,
|
['git', 'clone', '--no-checkout', '--', 'git@github.com:mirrors/composer', $expectedPath],
|
||||||
'stderr' => 'Error1',
|
['git', 'remote', 'add', 'composer', '--', 'git@github.com:mirrors/composer'],
|
||||||
],
|
['git', 'fetch', 'composer'],
|
||||||
$this->winCompat("git clone --no-checkout -- 'git@github.com:mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer -- 'git@github.com:mirrors/composer' && git fetch composer && git remote set-url origin -- 'git@github.com:mirrors/composer' && git remote set-url composer -- 'git@github.com:mirrors/composer'"),
|
['git', 'remote', 'set-url', 'origin', '--', 'git@github.com:mirrors/composer'],
|
||||||
$this->winCompat("git remote set-url origin -- 'https://github.com/composer/composer'"),
|
['git', 'remote', 'set-url', 'composer', '--', 'git@github.com:mirrors/composer'],
|
||||||
$this->winCompat("git remote set-url --push origin -- 'git@github.com:composer/composer.git'"),
|
|
||||||
'git branch -r',
|
['git', 'remote', 'set-url', 'origin', '--', 'https://github.com/composer/composer'],
|
||||||
$this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"),
|
['git', 'remote', 'set-url', '--push', 'origin', '--', 'git@github.com:composer/composer.git'],
|
||||||
|
['git', 'branch', '-r'],
|
||||||
|
['git', 'checkout', 'ref', '--'],
|
||||||
|
['git', 'reset', '--hard', 'ref', '--'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$downloader = $this->getDownloaderMock(null, new Config(), $process);
|
$downloader = $this->getDownloaderMock(null, new Config(), $process);
|
||||||
|
@ -250,11 +268,18 @@ class GitDownloaderTest extends TestCase
|
||||||
->will($this->returnValue('1.0.0'));
|
->will($this->returnValue('1.0.0'));
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
|
$expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath';
|
||||||
$process->expects([
|
$process->expects([
|
||||||
$this->winCompat("git clone --no-checkout -- '{$url}' 'composerPath' && cd 'composerPath' && git remote add composer -- '{$url}' && git fetch composer && git remote set-url origin -- '{$url}' && git remote set-url composer -- '{$url}'"),
|
['git', 'clone', '--no-checkout', '--', $url, $expectedPath],
|
||||||
$this->winCompat("git remote set-url --push origin -- '{$pushUrl}'"),
|
['git', 'remote', 'add', 'composer', '--', $url],
|
||||||
'git branch -r',
|
['git', 'fetch', 'composer'],
|
||||||
$this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"),
|
['git', 'remote', 'set-url', 'origin', '--', $url],
|
||||||
|
['git', 'remote', 'set-url', 'composer', '--', $url],
|
||||||
|
|
||||||
|
['git', 'remote', 'set-url', '--push', 'origin', '--', $pushUrl],
|
||||||
|
['git', 'branch', '-r'],
|
||||||
|
['git', 'checkout', 'ref', '--'],
|
||||||
|
['git', 'reset', '--hard', 'ref', '--'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$config = new Config();
|
$config = new Config();
|
||||||
|
@ -284,28 +309,21 @@ class GitDownloaderTest extends TestCase
|
||||||
->will($this->returnValue('1.0.0'));
|
->will($this->returnValue('1.0.0'));
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
|
$expectedPath = Platform::isWindows() ? Platform::getCwd().'/composerPath' : 'composerPath';
|
||||||
$process->expects([
|
$process->expects([
|
||||||
[
|
[
|
||||||
'cmd' => $this->winCompat("git clone --no-checkout -- 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer -- 'https://example.com/composer/composer' && git fetch composer && git remote set-url origin -- 'https://example.com/composer/composer' && git remote set-url composer -- 'https://example.com/composer/composer'"),
|
'cmd' => ['git', 'clone', '--no-checkout', '--', 'https://example.com/composer/composer', $expectedPath],
|
||||||
'return' => 1,
|
'return' => 1,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// not using PHPUnit's expected exception because Prophecy exceptions extend from RuntimeException too so it is not safe
|
self::expectException('RuntimeException');
|
||||||
try {
|
self::expectExceptionMessage('Failed to execute git clone --no-checkout -- https://example.com/composer/composer '.$expectedPath);
|
||||||
$downloader = $this->getDownloaderMock(null, null, $process);
|
$downloader = $this->getDownloaderMock(null, null, $process);
|
||||||
$downloader->download($packageMock, 'composerPath');
|
$downloader->download($packageMock, 'composerPath');
|
||||||
$downloader->prepare('install', $packageMock, 'composerPath');
|
$downloader->prepare('install', $packageMock, 'composerPath');
|
||||||
$downloader->install($packageMock, 'composerPath');
|
$downloader->install($packageMock, 'composerPath');
|
||||||
$downloader->cleanup('install', $packageMock, 'composerPath');
|
$downloader->cleanup('install', $packageMock, 'composerPath');
|
||||||
|
|
||||||
$this->fail('This test should throw');
|
|
||||||
} catch (\RuntimeException $e) {
|
|
||||||
if ('RuntimeException' !== get_class($e)) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
self::assertEquals('RuntimeException', get_class($e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUpdateforPackageWithoutSourceReference(): void
|
public function testUpdateforPackageWithoutSourceReference(): void
|
||||||
|
@ -327,8 +345,6 @@ class GitDownloaderTest extends TestCase
|
||||||
|
|
||||||
public function testUpdate(): void
|
public function testUpdate(): void
|
||||||
{
|
{
|
||||||
$expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer -- 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'https://github.com/composer/composer'");
|
|
||||||
|
|
||||||
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
||||||
$packageMock->expects($this->any())
|
$packageMock->expects($this->any())
|
||||||
->method('getSourceReference')
|
->method('getSourceReference')
|
||||||
|
@ -345,13 +361,23 @@ class GitDownloaderTest extends TestCase
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
$this->winCompat('git show-ref --head -d'),
|
['git', 'show-ref', '--head', '-d'],
|
||||||
$this->winCompat('git status --porcelain --untracked-files=no'),
|
['git', 'status', '--porcelain', '--untracked-files=no'],
|
||||||
$this->winCompat('git remote -v'),
|
['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 1],
|
||||||
$expectedGitUpdateCommand,
|
|
||||||
$this->winCompat('git branch -r'),
|
// fallback commands for the above failing
|
||||||
$this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"),
|
['git', 'remote', '-v'],
|
||||||
$this->winCompat('git remote -v'),
|
['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'],
|
||||||
|
['git', 'fetch', 'composer'],
|
||||||
|
['git', 'fetch', '--tags', 'composer'],
|
||||||
|
|
||||||
|
['git', 'remote', '-v'],
|
||||||
|
['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'],
|
||||||
|
|
||||||
|
['git', 'branch', '-r'],
|
||||||
|
['git', 'checkout', 'ref', '--'],
|
||||||
|
['git', 'reset', '--hard', 'ref', '--'],
|
||||||
|
['git', 'remote', '-v'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
||||||
|
@ -364,8 +390,6 @@ class GitDownloaderTest extends TestCase
|
||||||
|
|
||||||
public function testUpdateWithNewRepoUrl(): void
|
public function testUpdateWithNewRepoUrl(): void
|
||||||
{
|
{
|
||||||
$expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer -- 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'https://github.com/composer/composer'");
|
|
||||||
|
|
||||||
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
||||||
$packageMock->expects($this->any())
|
$packageMock->expects($this->any())
|
||||||
->method('getSourceReference')
|
->method('getSourceReference')
|
||||||
|
@ -385,22 +409,26 @@ class GitDownloaderTest extends TestCase
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
$this->winCompat("git show-ref --head -d"),
|
['git', 'show-ref', '--head', '-d'],
|
||||||
$this->winCompat("git status --porcelain --untracked-files=no"),
|
['git', 'status', '--porcelain', '--untracked-files=no'],
|
||||||
$this->winCompat("git remote -v"),
|
['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 0],
|
||||||
$this->winCompat($expectedGitUpdateCommand),
|
|
||||||
'git branch -r',
|
['git', 'remote', '-v'],
|
||||||
$this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"),
|
['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'],
|
||||||
|
|
||||||
|
['git', 'branch', '-r'],
|
||||||
|
['git', 'checkout', 'ref', '--'],
|
||||||
|
['git', 'reset', '--hard', 'ref', '--'],
|
||||||
[
|
[
|
||||||
'cmd' => $this->winCompat("git remote -v"),
|
'cmd' => ['git', 'remote', '-v'],
|
||||||
'stdout' => 'origin https://github.com/old/url (fetch)
|
'stdout' => 'origin https://github.com/old/url (fetch)
|
||||||
origin https://github.com/old/url (push)
|
origin https://github.com/old/url (push)
|
||||||
composer https://github.com/old/url (fetch)
|
composer https://github.com/old/url (fetch)
|
||||||
composer https://github.com/old/url (push)
|
composer https://github.com/old/url (push)
|
||||||
',
|
',
|
||||||
],
|
],
|
||||||
$this->winCompat("git remote set-url origin -- 'https://github.com/composer/composer'"),
|
['git', 'remote', 'set-url', 'origin', '--', 'https://github.com/composer/composer'],
|
||||||
$this->winCompat("git remote set-url --push origin -- 'git@github.com:composer/composer.git'"),
|
['git', 'remote', 'set-url', '--push', 'origin', '--', 'git@github.com:composer/composer.git'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
||||||
|
@ -416,9 +444,6 @@ composer https://github.com/old/url (push)
|
||||||
*/
|
*/
|
||||||
public function testUpdateThrowsRuntimeExceptionIfGitCommandFails(): void
|
public function testUpdateThrowsRuntimeExceptionIfGitCommandFails(): void
|
||||||
{
|
{
|
||||||
$expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer -- 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'https://github.com/composer/composer'");
|
|
||||||
$expectedGitUpdateCommand2 = $this->winCompat("(git remote set-url composer -- 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'git@github.com:composer/composer'");
|
|
||||||
|
|
||||||
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
||||||
$packageMock->expects($this->any())
|
$packageMock->expects($this->any())
|
||||||
->method('getSourceReference')
|
->method('getSourceReference')
|
||||||
|
@ -432,44 +457,38 @@ composer https://github.com/old/url (push)
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
$this->winCompat('git show-ref --head -d'),
|
['git', 'show-ref', '--head', '-d'],
|
||||||
$this->winCompat('git status --porcelain --untracked-files=no'),
|
['git', 'status', '--porcelain', '--untracked-files=no'],
|
||||||
$this->winCompat('git remote -v'),
|
|
||||||
[
|
// commit not yet in so we try to fetch
|
||||||
'cmd' => $expectedGitUpdateCommand,
|
['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 1],
|
||||||
'return' => 1,
|
|
||||||
],
|
// fail first fetch
|
||||||
[
|
['git', 'remote', '-v'],
|
||||||
'cmd' => $expectedGitUpdateCommand2,
|
['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'],
|
||||||
'return' => 1,
|
['cmd' => ['git', 'fetch', 'composer'], 'return' => 1],
|
||||||
],
|
|
||||||
$this->winCompat('git --version'),
|
// fail second fetch
|
||||||
|
['git', 'remote', 'set-url', 'composer', '--', 'git@github.com:composer/composer'],
|
||||||
|
['cmd' => ['git', 'fetch', 'composer'], 'return' => 1],
|
||||||
|
|
||||||
|
['git', '--version'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
||||||
|
|
||||||
// not using PHPUnit's expected exception because Prophecy exceptions extend from RuntimeException too so it is not safe
|
self::expectException('RuntimeException');
|
||||||
try {
|
self::expectExceptionMessage('Failed to clone https://github.com/composer/composer via https, ssh protocols, aborting.');
|
||||||
$downloader = $this->getDownloaderMock(null, new Config(), $process);
|
self::expectExceptionMessageMatches('{git@github\.com:composer/composer}');
|
||||||
$downloader->download($packageMock, $this->workingDir, $packageMock);
|
$downloader = $this->getDownloaderMock(null, new Config(), $process);
|
||||||
$downloader->prepare('update', $packageMock, $this->workingDir, $packageMock);
|
$downloader->download($packageMock, $this->workingDir, $packageMock);
|
||||||
$downloader->update($packageMock, $packageMock, $this->workingDir);
|
$downloader->prepare('update', $packageMock, $this->workingDir, $packageMock);
|
||||||
$downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock);
|
$downloader->update($packageMock, $packageMock, $this->workingDir);
|
||||||
|
$downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock);
|
||||||
$this->fail('This test should throw');
|
|
||||||
} catch (\RuntimeException $e) {
|
|
||||||
if ('RuntimeException' !== get_class($e)) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
self::assertEquals('RuntimeException', get_class($e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover(): void
|
public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover(): void
|
||||||
{
|
{
|
||||||
$expectedFirstGitUpdateCommand = $this->winCompat("(git remote set-url composer -- '".(Platform::isWindows() ? 'C:\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- '".(Platform::isWindows() ? 'C:\\' : '/')."'");
|
|
||||||
$expectedSecondGitUpdateCommand = $this->winCompat("(git remote set-url composer -- 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'https://github.com/composer/composer'");
|
|
||||||
|
|
||||||
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
||||||
$packageMock->expects($this->any())
|
$packageMock->expects($this->any())
|
||||||
->method('getSourceReference')
|
->method('getSourceReference')
|
||||||
|
@ -486,22 +505,33 @@ composer https://github.com/old/url (push)
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
$this->winCompat('git show-ref --head -d'),
|
['git', 'show-ref', '--head', '-d'],
|
||||||
$this->winCompat('git status --porcelain --untracked-files=no'),
|
['git', 'status', '--porcelain', '--untracked-files=no'],
|
||||||
$this->winCompat('git remote -v'),
|
|
||||||
[
|
// commit not yet in so we try to fetch
|
||||||
'cmd' => $expectedFirstGitUpdateCommand,
|
['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 1],
|
||||||
'return' => 1,
|
|
||||||
],
|
// fail first source URL
|
||||||
$this->winCompat('git --version'),
|
['git', 'remote', '-v'],
|
||||||
$this->winCompat('git remote -v'),
|
['git', 'remote', 'set-url', 'composer', '--', Platform::isWindows() ? 'C:\\' : '/'],
|
||||||
[
|
['cmd' => ['git', 'fetch', 'composer'], 'return' => 1],
|
||||||
'cmd' => $expectedSecondGitUpdateCommand,
|
['git', '--version'],
|
||||||
'return' => 0,
|
|
||||||
],
|
// commit not yet in so we try to fetch
|
||||||
$this->winCompat('git branch -r'),
|
['cmd' => ['git', 'rev-parse', '--quiet', '--verify', 'ref^{commit}'], 'return' => 1],
|
||||||
$this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"),
|
|
||||||
$this->winCompat('git remote -v'),
|
// pass second source URL
|
||||||
|
['git', 'remote', '-v'],
|
||||||
|
['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'],
|
||||||
|
['cmd' => ['git', 'fetch', 'composer'], 'return' => 0],
|
||||||
|
['git', 'fetch', '--tags', 'composer'],
|
||||||
|
['git', 'remote', '-v'],
|
||||||
|
['git', 'remote', 'set-url', 'composer', '--', 'https://github.com/composer/composer'],
|
||||||
|
|
||||||
|
['git', 'branch', '-r'],
|
||||||
|
['git', 'checkout', 'ref', '--'],
|
||||||
|
['git', 'reset', '--hard', 'ref', '--'],
|
||||||
|
['git', 'remote', '-v'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
||||||
|
@ -604,13 +634,11 @@ composer https://github.com/old/url (push)
|
||||||
|
|
||||||
public function testRemove(): void
|
public function testRemove(): void
|
||||||
{
|
{
|
||||||
$expectedGitResetCommand = $this->winCompat("git status --porcelain --untracked-files=no");
|
|
||||||
|
|
||||||
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
'git show-ref --head -d',
|
['git', 'show-ref', '--head', '-d'],
|
||||||
$expectedGitResetCommand,
|
['git', 'status', '--porcelain', '--untracked-files=no'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
|
||||||
|
@ -633,16 +661,4 @@ composer https://github.com/old/url (push)
|
||||||
|
|
||||||
self::assertEquals('source', $downloader->getInstallationSource());
|
self::assertEquals('source', $downloader->getInstallationSource());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function winCompat(string $cmd): string
|
|
||||||
{
|
|
||||||
if (Platform::isWindows()) {
|
|
||||||
$cmd = str_replace('cd ', 'cd /D ', $cmd);
|
|
||||||
$cmd = str_replace('composerPath', Platform::getCwd().'/composerPath', $cmd);
|
|
||||||
|
|
||||||
return self::getCmd($cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $cmd;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,8 +76,8 @@ class HgDownloaderTest extends TestCase
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
self::getCmd('hg clone -- \'https://mercurial.dev/l3l0/composer\' \''.$this->workingDir.'\''),
|
['hg', 'clone', '--', 'https://mercurial.dev/l3l0/composer', $this->workingDir],
|
||||||
self::getCmd('hg up -- \'ref\''),
|
['hg', 'up', '--', 'ref'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$downloader = $this->getDownloaderMock(null, null, $process);
|
$downloader = $this->getDownloaderMock(null, null, $process);
|
||||||
|
@ -117,8 +117,9 @@ class HgDownloaderTest extends TestCase
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
self::getCmd('hg st'),
|
['hg', 'st'],
|
||||||
self::getCmd("hg pull -- 'https://github.com/l3l0/composer' && hg up -- 'ref'"),
|
['hg', 'pull', '--', 'https://github.com/l3l0/composer'],
|
||||||
|
['hg', 'up', '--', 'ref'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$downloader = $this->getDownloaderMock(null, null, $process);
|
$downloader = $this->getDownloaderMock(null, null, $process);
|
||||||
|
@ -135,7 +136,7 @@ class HgDownloaderTest extends TestCase
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
self::getCmd('hg st'),
|
['hg', 'st'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock();
|
$filesystem = $this->getMockBuilder('Composer\Util\Filesystem')->getMock();
|
||||||
|
|
|
@ -57,16 +57,16 @@ class ProcessExecutorMock extends ProcessExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string|array{cmd: string|list<string>, return?: int, stdout?: string, stderr?: string, callback?: callable}> $expectations
|
* @param array<string|non-empty-list<string>|array{cmd: string|non-empty-list<string>, return?: int, stdout?: string, stderr?: string, callback?: callable}> $expectations
|
||||||
* @param bool $strict set to true if you want to provide *all* expected commands, and not just a subset you are interested in testing
|
* @param bool $strict set to true if you want to provide *all* expected commands, and not just a subset you are interested in testing
|
||||||
* @param array{return: int, stdout?: string, stderr?: string} $defaultHandler default command handler for undefined commands if not in strict mode
|
* @param array{return: int, stdout?: string, stderr?: string} $defaultHandler default command handler for undefined commands if not in strict mode
|
||||||
*/
|
*/
|
||||||
public function expects(array $expectations, bool $strict = false, array $defaultHandler = ['return' => 0, 'stdout' => '', 'stderr' => '']): void
|
public function expects(array $expectations, bool $strict = false, array $defaultHandler = ['return' => 0, 'stdout' => '', 'stderr' => '']): void
|
||||||
{
|
{
|
||||||
/** @var array{cmd: string|list<string>, return: int, stdout: string, stderr: string, callback: callable|null} $default */
|
/** @var array{cmd: string|non-empty-list<string>, return: int, stdout: string, stderr: string, callback: callable|null} $default */
|
||||||
$default = ['cmd' => '', 'return' => 0, 'stdout' => '', 'stderr' => '', 'callback' => null];
|
$default = ['cmd' => '', 'return' => 0, 'stdout' => '', 'stderr' => '', 'callback' => null];
|
||||||
$this->expectations = array_map(static function ($expect) use ($default): array {
|
$this->expectations = array_map(static function ($expect) use ($default): array {
|
||||||
if (is_string($expect)) {
|
if (is_string($expect) || array_is_list($expect)) {
|
||||||
$command = $expect;
|
$command = $expect;
|
||||||
$expect = $default;
|
$expect = $default;
|
||||||
$expect['cmd'] = $command;
|
$expect['cmd'] = $command;
|
||||||
|
|
|
@ -36,11 +36,11 @@ class VersionGuesserTest extends TestCase
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
['cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], 'return' => 128],
|
['cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], 'return' => 128],
|
||||||
['cmd' => 'git describe --exact-match --tags', 'return' => 128],
|
['cmd' => ['git', 'describe', '--exact-match', '--tags'], 'return' => 128],
|
||||||
['cmd' => 'git log --pretty="%H" -n1 HEAD'.GitUtil::getNoShowSignatureFlag($process), 'return' => 128],
|
['cmd' => array_merge(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], GitUtil::getNoShowSignatureFlags($process)), 'return' => 128],
|
||||||
['cmd' => 'hg branch', 'return' => 0, 'stdout' => $branch],
|
['cmd' => ['hg', 'branch'], 'return' => 0, 'stdout' => $branch],
|
||||||
['cmd' => 'hg branches', 'return' => 0],
|
['cmd' => ['hg', 'branches'], 'return' => 0],
|
||||||
['cmd' => 'hg bookmarks', 'return' => 0],
|
['cmd' => ['hg', 'bookmarks'], 'return' => 0],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
GitUtil::getVersion(new ProcessExecutor);
|
GitUtil::getVersion(new ProcessExecutor);
|
||||||
|
@ -201,7 +201,7 @@ class VersionGuesserTest extends TestCase
|
||||||
'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'],
|
'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'],
|
||||||
'stdout' => "* (no branch) $commitHash Commit message\n",
|
'stdout' => "* (no branch) $commitHash Commit message\n",
|
||||||
],
|
],
|
||||||
'git describe --exact-match --tags',
|
['git', 'describe', '--exact-match', '--tags'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$config = new Config;
|
$config = new Config;
|
||||||
|
@ -223,7 +223,7 @@ class VersionGuesserTest extends TestCase
|
||||||
'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'],
|
'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'],
|
||||||
'stdout' => "* (HEAD detached at FETCH_HEAD) $commitHash Commit message\n",
|
'stdout' => "* (HEAD detached at FETCH_HEAD) $commitHash Commit message\n",
|
||||||
],
|
],
|
||||||
'git describe --exact-match --tags',
|
['git', 'describe', '--exact-match', '--tags'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$config = new Config;
|
$config = new Config;
|
||||||
|
@ -245,7 +245,7 @@ class VersionGuesserTest extends TestCase
|
||||||
'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'],
|
'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'],
|
||||||
'stdout' => "* (HEAD detached at 03a15d220) $commitHash Commit message\n",
|
'stdout' => "* (HEAD detached at 03a15d220) $commitHash Commit message\n",
|
||||||
],
|
],
|
||||||
'git describe --exact-match --tags',
|
['git', 'describe', '--exact-match', '--tags'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$config = new Config;
|
$config = new Config;
|
||||||
|
@ -266,7 +266,7 @@ class VersionGuesserTest extends TestCase
|
||||||
'stdout' => "* (HEAD detached at v2.0.5-alpha2) 433b98d4218c181bae01865901aac045585e8a1a Commit message\n",
|
'stdout' => "* (HEAD detached at v2.0.5-alpha2) 433b98d4218c181bae01865901aac045585e8a1a Commit message\n",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'cmd' => 'git describe --exact-match --tags',
|
'cmd' => ['git', 'describe', '--exact-match', '--tags'],
|
||||||
'stdout' => "v2.0.5-alpha2",
|
'stdout' => "v2.0.5-alpha2",
|
||||||
],
|
],
|
||||||
], true);
|
], true);
|
||||||
|
@ -289,7 +289,7 @@ class VersionGuesserTest extends TestCase
|
||||||
'stdout' => "* (HEAD detached at 1.0.0) c006f0c12bbbf197b5c071ffb1c0e9812bb14a4d Commit message\n",
|
'stdout' => "* (HEAD detached at 1.0.0) c006f0c12bbbf197b5c071ffb1c0e9812bb14a4d Commit message\n",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'cmd' => 'git describe --exact-match --tags',
|
'cmd' => ['git', 'describe', '--exact-match', '--tags'],
|
||||||
'stdout' => '1.0.0',
|
'stdout' => '1.0.0',
|
||||||
],
|
],
|
||||||
], true);
|
], true);
|
||||||
|
|
|
@ -72,7 +72,7 @@ GIT;
|
||||||
|
|
||||||
$process
|
$process
|
||||||
->expects([[
|
->expects([[
|
||||||
'cmd' => 'git branch --no-color',
|
'cmd' => ['git', 'branch', '--no-color'],
|
||||||
'stdout' => $stdout,
|
'stdout' => $stdout,
|
||||||
]], true);
|
]], true);
|
||||||
|
|
||||||
|
@ -102,11 +102,17 @@ GIT;
|
||||||
|
|
||||||
$process
|
$process
|
||||||
->expects([[
|
->expects([[
|
||||||
'cmd' => 'git remote -v',
|
'cmd' => ['git', 'remote', '-v'],
|
||||||
'stdout' => '',
|
'stdout' => '',
|
||||||
], [
|
], [
|
||||||
'cmd' => Platform::isWindows() ? "git remote set-url origin -- https://example.org/acme.git && git remote show origin && git remote set-url origin -- https://example.org/acme.git" : "git remote set-url origin -- 'https://example.org/acme.git' && git remote show origin && git remote set-url origin -- 'https://example.org/acme.git'",
|
'cmd' => ['git', 'remote', 'set-url', 'origin', '--', 'https://example.org/acme.git'],
|
||||||
|
'stdout' => '',
|
||||||
|
], [
|
||||||
|
'cmd' => ['git', 'remote', 'show', 'origin'],
|
||||||
'stdout' => $stdout,
|
'stdout' => $stdout,
|
||||||
|
], [
|
||||||
|
'cmd' => ['git', 'remote', 'set-url', 'origin', '--', 'https://example.org/acme.git'],
|
||||||
|
'stdout' => '',
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
self::assertSame('main', $driver->getRootIdentifier());
|
self::assertSame('main', $driver->getRootIdentifier());
|
||||||
|
@ -130,7 +136,7 @@ GIT;
|
||||||
|
|
||||||
$process
|
$process
|
||||||
->expects([[
|
->expects([[
|
||||||
'cmd' => 'git branch --no-color',
|
'cmd' => ['git', 'branch', '--no-color'],
|
||||||
'stdout' => $stdout,
|
'stdout' => $stdout,
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
|
@ -155,7 +161,7 @@ GIT;
|
||||||
|
|
||||||
$process
|
$process
|
||||||
->expects([[
|
->expects([[
|
||||||
'cmd' => 'git branch --no-color --no-abbrev -v',
|
'cmd' => ['git', 'branch', '--no-color', '--no-abbrev', '-v'],
|
||||||
'stdout' => $stdout,
|
'stdout' => $stdout,
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
|
|
|
@ -303,18 +303,18 @@ class GitHubDriverTest extends TestCase
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$process->expects([
|
$process->expects([
|
||||||
['cmd' => 'git config github.accesstoken', 'return' => 1],
|
['cmd' => ['git', 'config', 'github.accesstoken'], 'return' => 1],
|
||||||
'git clone --mirror -- '.ProcessExecutor::escape($repoSshUrl).' '.ProcessExecutor::escape($this->config->get('cache-vcs-dir').'/git-github.com-composer-packagist.git/'),
|
['git', 'clone', '--mirror', '--', $repoSshUrl, $this->config->get('cache-vcs-dir').'/git-github.com-composer-packagist.git/'],
|
||||||
[
|
[
|
||||||
'cmd' => 'git show-ref --tags --dereference',
|
'cmd' => ['git', 'show-ref', '--tags', '--dereference'],
|
||||||
'stdout' => $sha.' refs/tags/'.$identifier,
|
'stdout' => $sha.' refs/tags/'.$identifier,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'cmd' => 'git branch --no-color --no-abbrev -v',
|
'cmd' => ['git', 'branch', '--no-color', '--no-abbrev', '-v'],
|
||||||
'stdout' => ' test_master edf93f1fccaebd8764383dc12016d0a1a9672d89 Fix test & behavior',
|
'stdout' => ' test_master edf93f1fccaebd8764383dc12016d0a1a9672d89 Fix test & behavior',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'cmd' => 'git branch --no-color',
|
'cmd' => ['git', 'branch', '--no-color'],
|
||||||
'stdout' => '* test_master',
|
'stdout' => '* test_master',
|
||||||
],
|
],
|
||||||
], true);
|
], true);
|
||||||
|
|
|
@ -85,10 +85,10 @@ HG_BOOKMARKS;
|
||||||
|
|
||||||
$process
|
$process
|
||||||
->expects([[
|
->expects([[
|
||||||
'cmd' => 'hg branches',
|
'cmd' => ['hg', 'branches'],
|
||||||
'stdout' => $stdout,
|
'stdout' => $stdout,
|
||||||
], [
|
], [
|
||||||
'cmd' => 'hg bookmarks',
|
'cmd' => ['hg', 'bookmarks'],
|
||||||
'stdout' => $stdout1,
|
'stdout' => $stdout1,
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
|
|
|
@ -60,12 +60,7 @@ class SvnDriverTest extends TestCase
|
||||||
$output .= " rejected Basic challenge (https://corp.svn.local/)";
|
$output .= " rejected Basic challenge (https://corp.svn.local/)";
|
||||||
|
|
||||||
$process = $this->getProcessExecutorMock();
|
$process = $this->getProcessExecutorMock();
|
||||||
$authedCommand = sprintf(
|
$authedCommand = ['svn', 'ls', '--verbose', '--non-interactive', '--username', 'till', '--password', 'secret', '--', 'https://till:secret@corp.svn.local/repo/trunk'];
|
||||||
'svn ls --verbose --non-interactive --username %s --password %s -- %s',
|
|
||||||
ProcessExecutor::escape('till'),
|
|
||||||
ProcessExecutor::escape('secret'),
|
|
||||||
ProcessExecutor::escape('https://till:secret@corp.svn.local/repo/trunk')
|
|
||||||
);
|
|
||||||
$process->expects([
|
$process->expects([
|
||||||
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
||||||
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
||||||
|
@ -73,7 +68,7 @@ class SvnDriverTest extends TestCase
|
||||||
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
||||||
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
||||||
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
['cmd' => $authedCommand, 'return' => 1, 'stderr' => $output],
|
||||||
['cmd' => 'svn --version', 'return' => 0, 'stdout' => '1.2.3'],
|
['cmd' => ['svn', '--version'], 'return' => 0, 'stdout' => '1.2.3'],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
$repoConfig = [
|
$repoConfig = [
|
||||||
|
|
|
@ -57,6 +57,7 @@ class GitTest extends TestCase
|
||||||
|
|
||||||
$this->process->expects(['git command'], true);
|
$this->process->expects(['git command'], true);
|
||||||
|
|
||||||
|
// @phpstan-ignore method.deprecated
|
||||||
$this->git->runCommand($commandCallable, 'https://github.com/acme/repo', null, true);
|
$this->git->runCommand($commandCallable, 'https://github.com/acme/repo', null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,9 +83,10 @@ class GitTest extends TestCase
|
||||||
|
|
||||||
$this->process->expects([
|
$this->process->expects([
|
||||||
['cmd' => 'git command', 'return' => 1],
|
['cmd' => 'git command', 'return' => 1],
|
||||||
['cmd' => 'git --version', 'return' => 0],
|
['cmd' => ['git', '--version'], 'return' => 0],
|
||||||
], true);
|
], true);
|
||||||
|
|
||||||
|
// @phpstan-ignore method.deprecated
|
||||||
$this->git->runCommand($commandCallable, 'https://github.com/acme/repo', null, true);
|
$this->git->runCommand($commandCallable, 'https://github.com/acme/repo', null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +126,7 @@ class GitTest extends TestCase
|
||||||
->with($this->equalTo('github.com'))
|
->with($this->equalTo('github.com'))
|
||||||
->willReturn(['username' => 'token', 'password' => $gitHubToken]);
|
->willReturn(['username' => 'token', 'password' => $gitHubToken]);
|
||||||
|
|
||||||
|
// @phpstan-ignore method.deprecated
|
||||||
$this->git->runCommand($commandCallable, $gitUrl, null, true);
|
$this->git->runCommand($commandCallable, $gitUrl, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +155,7 @@ class GitTest extends TestCase
|
||||||
// When we are testing what happens without auth saved, and URLs
|
// When we are testing what happens without auth saved, and URLs
|
||||||
// with https, there will also be an attempt to find the token in
|
// with https, there will also be an attempt to find the token in
|
||||||
// the git config for the folder and repo, locally.
|
// the git config for the folder and repo, locally.
|
||||||
$additional_calls = array_fill(0, $bitbucket_git_auth_calls, ['cmd' => 'git config bitbucket.accesstoken', 'return' => 1]);
|
$additional_calls = array_fill(0, $bitbucket_git_auth_calls, ['cmd' => ['git', 'config', 'bitbucket.accesstoken'], 'return' => 1]);
|
||||||
foreach ($additional_calls as $call) {
|
foreach ($additional_calls as $call) {
|
||||||
$expectedCalls[] = $call;
|
$expectedCalls[] = $call;
|
||||||
}
|
}
|
||||||
|
@ -177,6 +180,7 @@ class GitTest extends TestCase
|
||||||
->with($this->equalTo('bitbucket.org'))
|
->with($this->equalTo('bitbucket.org'))
|
||||||
->willReturn(['username' => 'token', 'password' => $bitbucketToken]);
|
->willReturn(['username' => 'token', 'password' => $bitbucketToken]);
|
||||||
}
|
}
|
||||||
|
// @phpstan-ignore method.deprecated
|
||||||
$this->git->runCommand($commandCallable, $gitUrl, null, true);
|
$this->git->runCommand($commandCallable, $gitUrl, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +206,7 @@ class GitTest extends TestCase
|
||||||
if (count($initial_config) > 0) {
|
if (count($initial_config) > 0) {
|
||||||
$expectedCalls[] = ['cmd' => 'git command failing', 'return' => 1];
|
$expectedCalls[] = ['cmd' => 'git command failing', 'return' => 1];
|
||||||
} else {
|
} else {
|
||||||
$expectedCalls[] = ['cmd' => 'git config bitbucket.accesstoken', 'return' => 1];
|
$expectedCalls[] = ['cmd' => ['git', 'config', 'bitbucket.accesstoken'], 'return' => 1];
|
||||||
}
|
}
|
||||||
$expectedCalls[] = ['cmd' => 'git command ok', 'return' => 0];
|
$expectedCalls[] = ['cmd' => 'git command ok', 'return' => 0];
|
||||||
$this->process->expects($expectedCalls, true);
|
$this->process->expects($expectedCalls, true);
|
||||||
|
@ -259,6 +263,7 @@ class GitTest extends TestCase
|
||||||
['url' => 'https://bitbucket.org/site/oauth2/access_token', 'status' => 200, 'body' => '{"expires_in": 600, "access_token": "my-access-token"}']
|
['url' => 'https://bitbucket.org/site/oauth2/access_token', 'status' => 200, 'body' => '{"expires_in": 600, "access_token": "my-access-token"}']
|
||||||
]);
|
]);
|
||||||
$this->git->setHttpDownloader($downloader_mock);
|
$this->git->setHttpDownloader($downloader_mock);
|
||||||
|
// @phpstan-ignore method.deprecated
|
||||||
$this->git->runCommand($commandCallable, $gitUrl, null, true);
|
$this->git->runCommand($commandCallable, $gitUrl, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -561,7 +561,9 @@ class PerforceTest extends TestCase
|
||||||
public function testCheckServerExists(): void
|
public function testCheckServerExists(): void
|
||||||
{
|
{
|
||||||
$this->processExecutor->expects(
|
$this->processExecutor->expects(
|
||||||
['p4 -p '.ProcessExecutor::escape('perforce.does.exist:port').' info -s'],
|
[
|
||||||
|
['p4', '-p', 'perforce.does.exist:port', 'info', '-s']
|
||||||
|
],
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -578,7 +580,7 @@ class PerforceTest extends TestCase
|
||||||
{
|
{
|
||||||
$processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
|
$processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
|
||||||
|
|
||||||
$expectedCommand = 'p4 -p '.ProcessExecutor::escape('perforce.does.exist:port').' info -s';
|
$expectedCommand = ['p4', '-p', 'perforce.does.exist:port', 'info', '-s'];
|
||||||
$processExecutor->expects($this->once())
|
$processExecutor->expects($this->once())
|
||||||
->method('execute')
|
->method('execute')
|
||||||
->with($this->equalTo($expectedCommand), $this->equalTo(null))
|
->with($this->equalTo($expectedCommand), $this->equalTo(null))
|
||||||
|
|
|
@ -23,14 +23,14 @@ class SvnTest extends TestCase
|
||||||
* Test the credential string.
|
* Test the credential string.
|
||||||
*
|
*
|
||||||
* @param string $url The SVN url.
|
* @param string $url The SVN url.
|
||||||
* @param string $expect The expectation for the test.
|
* @param non-empty-list<string> $expect The expectation for the test.
|
||||||
*
|
*
|
||||||
* @dataProvider urlProvider
|
* @dataProvider urlProvider
|
||||||
*/
|
*/
|
||||||
public function testCredentials(string $url, string $expect): void
|
public function testCredentials(string $url, array $expect): void
|
||||||
{
|
{
|
||||||
$svn = new Svn($url, new NullIO, new Config());
|
$svn = new Svn($url, new NullIO, new Config());
|
||||||
$reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString');
|
$reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialArgs');
|
||||||
$reflMethod->setAccessible(true);
|
$reflMethod->setAccessible(true);
|
||||||
|
|
||||||
self::assertEquals($expect, $reflMethod->invoke($svn));
|
self::assertEquals($expect, $reflMethod->invoke($svn));
|
||||||
|
@ -39,9 +39,9 @@ class SvnTest extends TestCase
|
||||||
public static function urlProvider(): array
|
public static function urlProvider(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['http://till:test@svn.example.org/', self::getCmd(" --username 'till' --password 'test' ")],
|
['http://till:test@svn.example.org/', ['--username', 'till', '--password', 'test']],
|
||||||
['http://svn.apache.org/', ''],
|
['http://svn.apache.org/', []],
|
||||||
['svn://johndoe@example.org', self::getCmd(" --username 'johndoe' --password '' ")],
|
['svn://johndoe@example.org', ['--username', 'johndoe', '--password', '']],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,8 +54,8 @@ class SvnTest extends TestCase
|
||||||
$reflMethod->setAccessible(true);
|
$reflMethod->setAccessible(true);
|
||||||
|
|
||||||
self::assertEquals(
|
self::assertEquals(
|
||||||
self::getCmd("svn ls --non-interactive -- 'http://svn.example.org'"),
|
['svn', 'ls', '--non-interactive', '--', 'http://svn.example.org'],
|
||||||
$reflMethod->invokeArgs($svn, ['svn ls', $url])
|
$reflMethod->invokeArgs($svn, [['svn', 'ls'], $url])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +73,10 @@ class SvnTest extends TestCase
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$svn = new Svn($url, new NullIO, $config);
|
$svn = new Svn($url, new NullIO, $config);
|
||||||
$reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString');
|
$reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialArgs');
|
||||||
$reflMethod->setAccessible(true);
|
$reflMethod->setAccessible(true);
|
||||||
|
|
||||||
self::assertEquals(self::getCmd(" --username 'foo' --password 'bar' "), $reflMethod->invoke($svn));
|
self::assertEquals(['--username', 'foo', '--password', 'bar'], $reflMethod->invoke($svn));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCredentialsFromConfigWithCacheCredentialsTrue(): void
|
public function testCredentialsFromConfigWithCacheCredentialsTrue(): void
|
||||||
|
@ -96,10 +96,10 @@ class SvnTest extends TestCase
|
||||||
|
|
||||||
$svn = new Svn($url, new NullIO, $config);
|
$svn = new Svn($url, new NullIO, $config);
|
||||||
$svn->setCacheCredentials(true);
|
$svn->setCacheCredentials(true);
|
||||||
$reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString');
|
$reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialArgs');
|
||||||
$reflMethod->setAccessible(true);
|
$reflMethod->setAccessible(true);
|
||||||
|
|
||||||
self::assertEquals(self::getCmd(" --username 'foo' --password 'bar' "), $reflMethod->invoke($svn));
|
self::assertEquals(['--username', 'foo', '--password', 'bar'], $reflMethod->invoke($svn));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCredentialsFromConfigWithCacheCredentialsFalse(): void
|
public function testCredentialsFromConfigWithCacheCredentialsFalse(): void
|
||||||
|
@ -119,9 +119,9 @@ class SvnTest extends TestCase
|
||||||
|
|
||||||
$svn = new Svn($url, new NullIO, $config);
|
$svn = new Svn($url, new NullIO, $config);
|
||||||
$svn->setCacheCredentials(false);
|
$svn->setCacheCredentials(false);
|
||||||
$reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialString');
|
$reflMethod = new \ReflectionMethod('Composer\\Util\\Svn', 'getCredentialArgs');
|
||||||
$reflMethod->setAccessible(true);
|
$reflMethod->setAccessible(true);
|
||||||
|
|
||||||
self::assertEquals(self::getCmd(" --no-auth-cache --username 'foo' --password 'bar' "), $reflMethod->invoke($svn));
|
self::assertEquals(['--no-auth-cache', '--username', 'foo', '--password', 'bar'], $reflMethod->invoke($svn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue