1
0
Fork 0

Merge remote-tracking branch 'upstream/main' into git-merge-composer.lock-detection

pull/11517/head
Dan Wallis 2023-07-19 14:14:53 +01:00
commit da2ecc6228
No known key found for this signature in database
GPG Key ID: A02198884F2740BC
26 changed files with 254 additions and 366 deletions

3
.gitattributes vendored
View File

@ -24,3 +24,6 @@
/PORTING_INFO export-ignore
/README.md export-ignore
/UPGRADE-2.0.md export-ignore
# Ref https://github.com/composer/composer/issues/11507
/phpstan/rules.neon -export-ignore

1
.gitignore vendored
View File

@ -9,4 +9,5 @@ phpunit.xml
.vagrant
Vagrantfile
.idea
.vscode
.php-cs-fixer.cache

View File

@ -50,6 +50,24 @@ PHP versions 5.3.2 - 8.1 are still supported via the LTS releases of Composer (2
run the installer or the `self-update` command the appropriate Composer version for your PHP
should be automatically selected.
#### Binary dependencies
- `7z` (or `7zz`)
- `unzip` (if `7z` is missing)
- `gzip`
- `tar`
- `unrar`
- `xz`
- Git (`git`)
- Mercurial (`hg`)
- Fossil (`fossil`)
- Perforce (`p4`)
- Subversion (`svn`)
It's important to note that the need for these binary dependencies may vary
depending on individual use cases. However, for most users, only 2 dependencies
are essential for Composer: `7z` (or `7zz` or `unzip`), and `git`.
Authors
-------

View File

@ -26,7 +26,7 @@
"composer/ca-bundle": "^1.0",
"composer/class-map-generator": "^1.0",
"composer/metadata-minifier": "^1.0",
"composer/semver": "^3.0",
"composer/semver": "^3.2.5",
"composer/spdx-licenses": "^1.5.7",
"composer/xdebug-handler": "^2.0.2 || ^3.0.3",
"justinrainbow/json-schema": "^5.2.11",

52
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "cc535e8c9fc8f1414a1cede463898453",
"content-hash": "c50c89580fa044b7523cb55c2d557c87",
"packages": [
{
"name": "composer/ca-bundle",
@ -84,22 +84,22 @@
},
{
"name": "composer/class-map-generator",
"version": "1.0.0",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/composer/class-map-generator.git",
"reference": "1e1cb2b791facb2dfe32932a7718cf2571187513"
"reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/class-map-generator/zipball/1e1cb2b791facb2dfe32932a7718cf2571187513",
"reference": "1e1cb2b791facb2dfe32932a7718cf2571187513",
"url": "https://api.github.com/repos/composer/class-map-generator/zipball/953cc4ea32e0c31f2185549c7d216d7921f03da9",
"reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9",
"shasum": ""
},
"require": {
"composer/pcre": "^2 || ^3",
"composer/pcre": "^2.1 || ^3.1",
"php": "^7.2 || ^8.0",
"symfony/finder": "^4.4 || ^5.3 || ^6"
"symfony/finder": "^4.4 || ^5.3 || ^6 || ^7"
},
"require-dev": {
"phpstan/phpstan": "^1.6",
@ -137,7 +137,7 @@
],
"support": {
"issues": "https://github.com/composer/class-map-generator/issues",
"source": "https://github.com/composer/class-map-generator/tree/1.0.0"
"source": "https://github.com/composer/class-map-generator/tree/1.1.0"
},
"funding": [
{
@ -153,7 +153,7 @@
"type": "tidelift"
}
],
"time": "2022-06-19T11:31:27+00:00"
"time": "2023-06-30T13:58:57+00:00"
},
{
"name": "composer/metadata-minifier",
@ -1103,16 +1103,16 @@
},
{
"name": "symfony/filesystem",
"version": "v5.4.23",
"version": "v5.4.25",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5"
"reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5",
"reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/0ce3a62c9579a53358d3a7eb6b3dfb79789a6364",
"reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364",
"shasum": ""
},
"require": {
@ -1147,7 +1147,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v5.4.23"
"source": "https://github.com/symfony/filesystem/tree/v5.4.25"
},
"funding": [
{
@ -1163,7 +1163,7 @@
"type": "tidelift"
}
],
"time": "2023-03-02T11:38:35+00:00"
"time": "2023-05-31T13:04:02+00:00"
},
{
"name": "symfony/finder",
@ -2034,16 +2034,16 @@
"packages-dev": [
{
"name": "phpstan/phpstan",
"version": "1.10.16",
"version": "1.10.25",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "352bdbb960bb523e3d71b834862589f910921c23"
"reference": "578f4e70d117f9a90699324c555922800ac38d8c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/352bdbb960bb523e3d71b834862589f910921c23",
"reference": "352bdbb960bb523e3d71b834862589f910921c23",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/578f4e70d117f9a90699324c555922800ac38d8c",
"reference": "578f4e70d117f9a90699324c555922800ac38d8c",
"shasum": ""
},
"require": {
@ -2092,7 +2092,7 @@
"type": "tidelift"
}
],
"time": "2023-06-05T08:21:46+00:00"
"time": "2023-07-06T12:11:37+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@ -2316,16 +2316,16 @@
},
{
"name": "symfony/phpunit-bridge",
"version": "v6.3.0",
"version": "v6.3.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
"reference": "f8d75b4d9bf7243979b2c2e5e6cd73f03e10579f"
"reference": "0b0bf59b0d9bd1422145a123a67fb12af546ef0d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/f8d75b4d9bf7243979b2c2e5e6cd73f03e10579f",
"reference": "f8d75b4d9bf7243979b2c2e5e6cd73f03e10579f",
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/0b0bf59b0d9bd1422145a123a67fb12af546ef0d",
"reference": "0b0bf59b0d9bd1422145a123a67fb12af546ef0d",
"shasum": ""
},
"require": {
@ -2377,7 +2377,7 @@
"description": "Provides utilities for PHPUnit, especially user deprecation notices management",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/phpunit-bridge/tree/v6.3.0"
"source": "https://github.com/symfony/phpunit-bridge/tree/v6.3.1"
},
"funding": [
{
@ -2393,7 +2393,7 @@
"type": "tidelift"
}
],
"time": "2023-05-30T09:01:24+00:00"
"time": "2023-06-23T13:25:16+00:00"
}
],
"aliases": [],

View File

@ -39,8 +39,14 @@ a legacy PHP version. A few sensitive php settings and compile flags are also
required, but when using the installer you will be warned about any
incompatibilities.
To install packages from sources instead of plain zip archives, you will need
git, svn, fossil or hg depending on how the package is version-controlled.
Composer needs several supporting applications to work effectively, making the
process of handling package dependencies more efficient. For decompressing
files, Composer relies on tools like `7z` (or `7zz`), `gzip`, `tar`, `unrar`,
`unzip` and `xz`. As for version control systems, Composer integrates seamlessly
with Fossil, Git, Mercurial, Perforce and Subversion, thereby ensuring the
application's smooth operation and management of library repositories. Before
using Composer, ensure that these dependencies are correctly installed on your
system.
Composer is multi-platform and we strive to make it run equally well on Windows,
Linux and macOS.

View File

@ -35,7 +35,7 @@ separated by `/`. Examples:
* igorw/event-source
The name must be lowercase and consist of words separated by `-`, `.` or `_`.
The complete name should match `^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$`.
The complete name should match `^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$`.
The `name` property is required for published packages (libraries).

View File

@ -284,6 +284,10 @@ Now the `custom-plugin-command` is available alongside Composer commands.
Plugins for an event can be run manually by the `run-script` command. This works the same way as
[running scripts manually](scripts.md#running-scripts-manually).
If it is another type of plugin the best way to test it is probably using a [path repository](../05-repositories.md#path)
to require the plugin in a test project, and then `rm -rf vendor && composer update`
every time you want to install/run it again.
## Using Plugins
Plugin packages are automatically loaded as soon as they are installed and will

View File

@ -85,11 +85,6 @@ parameters:
count: 1
path: ../src/Composer/Downloader/GzipDownloader.php
-
message: "#^Parameter \\#1 \\$string of function rawurldecode expects string, string\\|false given\\.$#"
count: 1
path: ../src/Composer/Downloader/VcsDownloader.php
-
message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\<0, max\\>\\|false given\\.$#"
count: 1

View File

@ -645,26 +645,11 @@ parameters:
count: 1
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Cannot call method getId\\(\\) on Composer\\\\Package\\\\BasePackage\\|int\\.$#"
count: 1
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Cannot call method getInstallationManager\\(\\) on Composer\\\\Composer\\|null\\.$#"
count: 2
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Cannot call method getPrettyVersion\\(\\) on Composer\\\\Package\\\\BasePackage\\|int\\.$#"
count: 1
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Cannot call method getVersion\\(\\) on Composer\\\\Package\\\\BasePackage\\|int\\.$#"
count: 1
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Foreach overwrites \\$packages with its value variable\\.$#"
count: 1
@ -675,11 +660,6 @@ parameters:
count: 1
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Method Composer\\\\Command\\\\ShowCommand\\:\\:getPackage\\(\\) should return array\\{Composer\\\\Package\\\\CompletePackageInterface\\|null, array\\<string, string\\>\\} but returns array\\{Composer\\\\Package\\\\BasePackage\\|int\\|null, array\\<string, string\\>\\}\\.$#"
count: 1
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Only booleans are allowed in &&, Composer\\\\Composer\\|null given on the right side\\.$#"
count: 1
@ -720,11 +700,6 @@ parameters:
count: 2
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Only booleans are allowed in a negated boolean, Composer\\\\Package\\\\BasePackage\\|int\\|null given\\.$#"
count: 1
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Only booleans are allowed in a negated boolean, Composer\\\\Repository\\\\RepositorySet\\|null given\\.$#"
count: 1
@ -805,11 +780,6 @@ parameters:
count: 2
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Parameter \\#1 \\$package of method Composer\\\\Repository\\\\CompositeRepository\\:\\:hasPackage\\(\\) expects Composer\\\\Package\\\\PackageInterface, Composer\\\\Package\\\\BasePackage\\|int given\\.$#"
count: 1
path: ../src/Composer/Command/ShowCommand.php
-
message: "#^Parameter \\#1 \\$str of function strtok expects string, array\\|string given\\.$#"
count: 1
@ -1930,36 +1900,11 @@ parameters:
count: 1
path: ../src/Composer/Downloader/VcsDownloader.php
-
message: "#^Parameter \\#1 \\$haystack of function strpos expects string, string\\|false given\\.$#"
count: 2
path: ../src/Composer/Downloader/VcsDownloader.php
-
message: "#^Parameter \\#1 \\$package of method Composer\\\\Downloader\\\\VcsDownloader\\:\\:cleanChanges\\(\\) expects Composer\\\\Package\\\\PackageInterface, Composer\\\\Package\\\\PackageInterface\\|null given\\.$#"
count: 1
path: ../src/Composer/Downloader/VcsDownloader.php
-
message: "#^Parameter \\#1 \\$path of function realpath expects string, string\\|false given\\.$#"
count: 1
path: ../src/Composer/Downloader/VcsDownloader.php
-
message: "#^Parameter \\#1 \\$path of static method Composer\\\\Util\\\\Filesystem\\:\\:isLocalPath\\(\\) expects string, string\\|false given\\.$#"
count: 1
path: ../src/Composer/Downloader/VcsDownloader.php
-
message: "#^Parameter \\#1 \\$str of function rawurldecode expects string, string\\|false given\\.$#"
count: 1
path: ../src/Composer/Downloader/VcsDownloader.php
-
message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|false given\\.$#"
count: 1
path: ../src/Composer/Downloader/VcsDownloader.php
-
message: "#^Parameter \\#2 \\$toReference of method Composer\\\\Downloader\\\\VcsDownloader\\:\\:getCommitLogs\\(\\) expects string, string\\|null given\\.$#"
count: 1
@ -2500,11 +2445,6 @@ parameters:
count: 1
path: ../src/Composer/Installer/SuggestedPackagesReporter.php
-
message: "#^Method Composer\\\\Json\\\\JsonFile\\:\\:encode\\(\\) should return string but returns string\\|false\\.$#"
count: 1
path: ../src/Composer/Json/JsonFile.php
-
message: "#^Only booleans are allowed in &&, Composer\\\\IO\\\\IOInterface\\|null given on the left side\\.$#"
count: 1

View File

@ -1,247 +0,0 @@
<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
use Composer\Pcre\Preg;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
* @internal
*/
class PhpFileCleaner
{
/** @var array<array{name: string, length: int, pattern: non-empty-string}> */
private static $typeConfig;
/** @var non-empty-string */
private static $restPattern;
/**
* @readonly
* @var string
*/
private $contents;
/**
* @readonly
* @var int
*/
private $len;
/**
* @readonly
* @var int
*/
private $maxMatches;
/** @var int */
private $index = 0;
/**
* @param string[] $types
*/
public static function setTypeConfig(array $types): void
{
foreach ($types as $type) {
self::$typeConfig[$type[0]] = [
'name' => $type,
'length' => \strlen($type),
'pattern' => '{.\b(?<![\$:>])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais',
];
}
self::$restPattern = '{[^?"\'</'.implode('', array_keys(self::$typeConfig)).']+}A';
}
public function __construct(string $contents, int $maxMatches)
{
$this->contents = $contents;
$this->len = \strlen($this->contents);
$this->maxMatches = $maxMatches;
}
public function clean(): string
{
$clean = '';
while ($this->index < $this->len) {
$this->skipToPhp();
$clean .= '<?';
while ($this->index < $this->len) {
$char = $this->contents[$this->index];
if ($char === '?' && $this->peek('>')) {
$clean .= '?>';
$this->index += 2;
continue 2;
}
if ($char === '"') {
$this->skipString('"');
$clean .= 'null';
continue;
}
if ($char === "'") {
$this->skipString("'");
$clean .= 'null';
continue;
}
if ($char === "<" && $this->peek('<') && $this->match('{<<<[ \t]*+([\'"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*+)\\1(?:\r\n|\n|\r)}A', $match)) {
$this->index += \strlen($match[0]);
$this->skipHeredoc($match[2]);
$clean .= 'null';
continue;
}
if ($char === '/') {
if ($this->peek('/')) {
$this->skipToNewline();
continue;
}
if ($this->peek('*')) {
$this->skipComment();
continue;
}
}
if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) {
$type = self::$typeConfig[$char];
if (
\substr($this->contents, $this->index, $type['length']) === $type['name']
&& Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1)
) {
$clean .= $match[0];
return $clean;
}
}
$this->index += 1;
if ($this->match(self::$restPattern, $match)) {
$clean .= $char . $match[0];
$this->index += \strlen($match[0]);
} else {
$clean .= $char;
}
}
}
return $clean;
}
private function skipToPhp(): void
{
while ($this->index < $this->len) {
if ($this->contents[$this->index] === '<' && $this->peek('?')) {
$this->index += 2;
break;
}
$this->index += 1;
}
}
private function skipString(string $delimiter): void
{
$this->index += 1;
while ($this->index < $this->len) {
if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) {
$this->index += 2;
continue;
}
if ($this->contents[$this->index] === $delimiter) {
$this->index += 1;
break;
}
$this->index += 1;
}
}
private function skipComment(): void
{
$this->index += 2;
while ($this->index < $this->len) {
if ($this->contents[$this->index] === '*' && $this->peek('/')) {
$this->index += 2;
break;
}
$this->index += 1;
}
}
private function skipToNewline(): void
{
while ($this->index < $this->len) {
if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") {
return;
}
$this->index += 1;
}
}
private function skipHeredoc(string $delimiter): void
{
$firstDelimiterChar = $delimiter[0];
$delimiterLength = \strlen($delimiter);
$delimiterPattern = '{'.preg_quote($delimiter).'(?![a-zA-Z0-9_\x80-\xff])}A';
while ($this->index < $this->len) {
// check if we find the delimiter after some spaces/tabs
switch ($this->contents[$this->index]) {
case "\t":
case " ":
$this->index += 1;
continue 2;
case $firstDelimiterChar:
if (
\substr($this->contents, $this->index, $delimiterLength) === $delimiter
&& $this->match($delimiterPattern)
) {
$this->index += $delimiterLength;
return;
}
break;
}
// skip the rest of the line
while ($this->index < $this->len) {
$this->skipToNewline();
// skip newlines
while ($this->index < $this->len && ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n")) {
$this->index += 1;
}
break;
}
}
}
private function peek(string $char): bool
{
return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char;
}
/**
* @param non-empty-string $regex
* @param null|array<int, string> $match
*/
private function match($regex, ?array &$match = null): bool
{
return Preg::isMatch($regex, $this->contents, $match, 0, $this->index);
}
}

View File

@ -24,10 +24,12 @@ use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Composer\Package\Version\VersionParser;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Util\PackageInfo;
/**
* Base implementation for commands mapping dependency relationships.
@ -180,7 +182,9 @@ abstract class BaseDependencyCommand extends BaseCommand
}
$doubles[$unique] = true;
$version = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '-' : $package->getPrettyVersion();
$rows[] = [$package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())];
$packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package);
$nameWithLink = $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
$rows[] = [$nameWithLink, $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())];
if ($children) {
$queue = array_merge($queue, $children);
}
@ -229,7 +233,9 @@ abstract class BaseDependencyCommand extends BaseCommand
$prevColor = $this->colors[($level - 1) % count($this->colors)];
$isLast = (++$idx === $count);
$versionText = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '' : $package->getPrettyVersion();
$packageText = rtrim(sprintf('<%s>%s</%1$s> %s', $color, $package->getPrettyName(), $versionText));
$packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package);
$nameWithLink = $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
$packageText = rtrim(sprintf('<%s>%s</%1$s> %s', $color, $nameWithLink, $versionText));
$linkText = sprintf('%s <%s>%s</%2$s> %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint());
$circularWarn = $children === false ? '(circular dependency aborted here)' : '';
$this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn)));

View File

@ -15,6 +15,7 @@ namespace Composer\Command;
use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
@ -100,7 +101,7 @@ trait PackageDiscoveryTrait
if (!isset($requirement['version'])) {
// determine the best version automatically
[$name, $version] = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $platformRepo, $preferredStability, $fixed);
[$name, $version] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $requirement['name'], $platformRepo, $preferredStability, $fixed);
// replace package name from packagist.org
$requirement['name'] = $name;
@ -243,7 +244,7 @@ trait PackageDiscoveryTrait
);
if (false === $constraint) {
[, $constraint] = $this->findBestVersionAndNameForPackage($input, $package, $platformRepo, $preferredStability);
[, $constraint] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $package, $platformRepo, $preferredStability);
$io->writeError(sprintf(
'Using version <info>%s</info> for <info>%s</info>',
@ -273,7 +274,7 @@ trait PackageDiscoveryTrait
* @throws \InvalidArgumentException
* @return array{string, string} name version
*/
private function findBestVersionAndNameForPackage(InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $fixed = false): array
private function findBestVersionAndNameForPackage(IOInterface $io, InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $fixed = false): array
{
// handle ignore-platform-reqs flag if present
if ($input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) {
@ -358,6 +359,13 @@ trait PackageDiscoveryTrait
));
}
if ($input->isInteractive()) {
$result = $io->select("<error>Could not find package $name.</error>\nPick one of these or leave empty to abort:", $similar, false, 1);
if ($result !== false) {
return $this->findBestVersionAndNameForPackage($io, $input, $similar[$result], $platformRepo, $preferredStability, $fixed);
}
}
throw new \InvalidArgumentException(sprintf(
"Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n %s",
$name,

View File

@ -756,10 +756,14 @@ EOT
}
// select preferred package according to policy rules
if (!$matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, $matches)) {
if (null === $matchedPackage && $matches && $preferred = $policy->selectPreferredPackages($pool, $matches)) {
$matchedPackage = $pool->literalToPackage($preferred[0]);
}
if ($matchedPackage !== null && !$matchedPackage instanceof CompletePackageInterface) {
throw new \LogicException('ShowCommand::getPackage can only work with CompletePackageInterface, but got '.get_class($matchedPackage));
}
return [$matchedPackage, $versions];
}
@ -1251,6 +1255,9 @@ EOT
$colorIdent = $level % count($this->colors);
$color = $this->colors[$colorIdent];
assert(is_string($require['name']));
assert(is_string($require['version']));
$circularWarn = in_array(
$require['name'],
$currentTree,

View File

@ -557,6 +557,19 @@ class Problem
*/
protected static function constraintToText(?ConstraintInterface $constraint = null): string
{
if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && !str_starts_with($constraint->getVersion(), 'dev-')) {
if (!Preg::isMatch('{^\d+(?:\.\d+)*$}', $constraint->getPrettyString())) {
return ' '.$constraint->getPrettyString() .' (exact version match)';
}
$versions = [$constraint->getPrettyString()];
for ($i = 3 - substr_count($versions[0], '.'); $i > 0; $i--) {
$versions[] = end($versions) . '.0';
}
return ' ' . $constraint->getPrettyString() . ' (exact version match: ' . (count($versions) > 1 ? implode(', ', array_slice($versions, 0, -1)) . ' or ' . end($versions) : $versions[0]) . ')';
}
return $constraint ? ' '.$constraint->getPrettyString() : '';
}
}

View File

@ -436,7 +436,12 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
*/
protected function getFileName(PackageInterface $package, string $path): string
{
return rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . md5($package . spl_object_hash($package)) . '.' . $this->getDistPath($package, PATHINFO_EXTENSION), '.');
$extension = $this->getDistPath($package, PATHINFO_EXTENSION);
if ($extension === '') {
$extension = $package->getDistType();
}
return rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . md5($package . spl_object_hash($package)) . '.' . $extension, '.');
}
/**

View File

@ -42,12 +42,16 @@ class JsonFile
public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json';
public const INDENT_DEFAULT = ' ';
/** @var string */
private $path;
/** @var ?HttpDownloader */
private $httpDownloader;
/** @var ?IOInterface */
private $io;
/** @var string */
private $indent = self::INDENT_DEFAULT;
/**
* Initializes json file reader/parser.
@ -117,6 +121,8 @@ class JsonFile
throw new \RuntimeException('Could not read '.$this->path);
}
$this->indent = self::detectIndenting($json);
return static::parseJson($json, $this->path);
}
@ -131,7 +137,7 @@ class JsonFile
public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
{
if ($this->path === 'php://memory') {
file_put_contents($this->path, static::encode($hash, $options));
file_put_contents($this->path, static::encode($hash, $options, $this->indent));
return;
}
@ -153,7 +159,7 @@ class JsonFile
$retries = 3;
while ($retries--) {
try {
$this->filePutContentsIfModified($this->path, static::encode($hash, $options). ($options & JSON_PRETTY_PRINT ? "\n" : ''));
$this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent). ($options & JSON_PRETTY_PRINT ? "\n" : ''));
break;
} catch (\Exception $e) {
if ($retries > 0) {
@ -262,15 +268,28 @@ class JsonFile
*
* @param mixed $data Data to encode into a formatted JSON string
* @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
* @param string $indent Indentation string
* @return string Encoded json
*/
public static function encode($data, int $options = 448)
public static function encode($data, int $options = 448, string $indent = self::INDENT_DEFAULT): string
{
$json = json_encode($data, $options);
if (false === $json) {
self::throwEncodeError(json_last_error());
}
if (($options & JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT ) {
// Pretty printing and not using default indentation
return Preg::replaceCallback(
'#^ {4,}#m',
static function ($match) use ($indent): string {
return str_repeat($indent, (int)(strlen($match[0] ?? '') / 4));
},
$json
);
}
return $json;
}
@ -279,6 +298,7 @@ class JsonFile
*
* @param int $code return code of json_last_error function
* @throws \RuntimeException
* @return never
*/
private static function throwEncodeError(int $code): void
{
@ -364,4 +384,12 @@ class JsonFile
$result->getDetails());
}
}
public static function detectIndenting(?string $json): string
{
if (Preg::isMatchStrictGroups('#^([ \t]+)"#m', $json ?? '', $match)) {
return $match[1];
}
return self::INDENT_DEFAULT;
}
}

View File

@ -561,10 +561,6 @@ class JsonManipulator
protected function detectIndenting(): void
{
if (Preg::isMatchStrictGroups('{^([ \t]+)"}m', $this->contents, $match)) {
$this->indent = $match[1];
} else {
$this->indent = ' ';
}
$this->indent = JsonFile::detectIndenting($this->contents);
}
}

View File

@ -139,8 +139,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
{
parent::__construct();
if (!Preg::isMatch('{^[\w.]+\??://}', $repoConfig['url'])) {
// assume http as the default protocol
$repoConfig['url'] = 'http://'.$repoConfig['url'];
if (($localFilePath = realpath($repoConfig['url'])) !== false) {
// it is a local path, add file scheme
$repoConfig['url'] = 'file://'.$localFilePath;
} else {
// otherwise, assume http as the default protocol
$repoConfig['url'] = 'http://'.$repoConfig['url'];
}
}
$repoConfig['url'] = rtrim($repoConfig['url'], '/');
if ($repoConfig['url'] === '') {

View File

@ -19,7 +19,7 @@ class PackageInfo
{
public static function getViewSourceUrl(PackageInterface $package): ?string
{
if ($package instanceof CompletePackageInterface && isset($package->getSupport()['source'])) {
if ($package instanceof CompletePackageInterface && isset($package->getSupport()['source']) && '' !== $package->getSupport()['source']) {
return $package->getSupport()['source'];
}
@ -28,6 +28,12 @@ class PackageInfo
public static function getViewSourceOrHomepageUrl(PackageInterface $package): ?string
{
return self::getViewSourceUrl($package) ?? ($package instanceof CompletePackageInterface ? $package->getHomepage() : null);
$url = self::getViewSourceUrl($package) ?? ($package instanceof CompletePackageInterface ? $package->getHomepage() : null);
if ($url === '') {
return null;
}
return $url;
}
}

View File

@ -0,0 +1,69 @@
<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Test\Command;
use Composer\Test\TestCase;
use Generator;
class ReinstallCommandTest extends TestCase
{
/**
* @dataProvider caseProvider
* @param array<string> $packages
* @param string $expected
*/
public function testReinstallCommand(array $packages, string $expected): void
{
$this->initTempComposer([
'require' => [
'root/req' => '1.*',
'root/anotherreq' => '2.*'
]
]);
$rootReqPackage = self::getPackage('root/req');
$anotherReqPackage = self::getPackage('root/anotherreq');
$rootReqPackage->setType('metapackage');
$anotherReqPackage->setType('metapackage');
$this->createComposerLock([$rootReqPackage], [$anotherReqPackage]);
$this->createInstalledJson([$rootReqPackage], [$anotherReqPackage]);
$appTester = $this->getApplicationTester();
$appTester->run([
'command' => 'reinstall',
'--no-progress' => true,
'--no-plugins' => true,
'packages' => $packages
]);
$this->assertSame($expected, trim($appTester->getDisplay(true)));
}
public function caseProvider(): Generator
{
yield 'reinstall a package' => [
['root/req', 'root/anotherreq'],
'- Removing root/req (1.0.0)
- Removing root/anotherreq (1.0.0)
- Installing root/anotherreq (1.0.0)
- Installing root/req (1.0.0)'
];
yield 'reinstall a package that is not installed' => [
['root/unknownreq'],
'<warning>Pattern "root/unknownreq" does not match any currently installed packages.</warning>
<warning>Found no packages to reinstall, aborting.</warning>'
];
}
}

View File

@ -24,7 +24,6 @@ Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires a/aliased 3.0.2 as 3.0.3, found a/aliased[1.2.3] but it does not match the constraint.
- Root composer.json requires a/aliased 3.0.2 as 3.0.3 (exact version match), found a/aliased[1.2.3] but it does not match the constraint.
--EXPECT--

View File

@ -59,7 +59,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 3
- Root composer.json requires linked library lib-icu 1001.* but it has the wrong version installed, try upgrading the intl extension.
Problem 4
- Root composer.json requires PHP extension ext-foobar 1.0.0 but it is missing from your system. Install or enable PHP's foobar extension.
- Root composer.json requires PHP extension ext-foobar 1.0.0 (exact version match: 1.0.0 or 1.0.0.0) but it is missing from your system. Install or enable PHP's foobar extension.
Problem 5
- Root composer.json requires PHP extension ext-pcre ^8 but the ext-pcre package is disabled by your platform config. Enable it again with "composer config platform.ext-pcre --unset".
Problem 6

View File

@ -113,7 +113,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 3
- Root composer.json requires non-existent/pkg, it could not be found in any version, there may be a typo in the package name.
Problem 4
- Root composer.json requires stable-requiree-excluded/pkg 1.0.1, found stable-requiree-excluded/pkg[1.0.1] but the package is fixed to 1.0.0 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
- Root composer.json requires stable-requiree-excluded/pkg 1.0.1 (exact version match: 1.0.1 or 1.0.1.0), found stable-requiree-excluded/pkg[1.0.1] but the package is fixed to 1.0.0 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
Problem 5
- Root composer.json requires linked library lib-xml 1002.* but it has the wrong version installed or is missing from your system, make sure to load the extension providing it.
Problem 6
@ -121,7 +121,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 7
- Root composer.json requires PHP extension ext-xml 1002.* but it has the wrong version installed (%s).
Problem 8
- Root composer.json requires php 1 but your php version (%s) does not satisfy that requirement.
- Root composer.json requires php 1 (exact version match: 1, 1.0, 1.0.0 or 1.0.0.0) but your php version (%s) does not satisfy that requirement.
Problem 9
- Root composer.json requires package/found 2.* -> satisfiable by package/found[2.0.0].
- package/found 2.0.0 requires unstable/package2 2.* -> found unstable/package2[2.0.0-alpha] but it does not match your minimum-stability.

View File

@ -0,0 +1,3 @@
{
"foo": "bar"
}

View File

@ -436,6 +436,29 @@ class JsonFileTest extends TestCase
JsonFile::parseJson($data);
}
public function testPreserveIndentationAfterRead(): void
{
copy(__DIR__.'/Fixtures/tabs.json', __DIR__.'/Fixtures/tabs2.json');
$jsonFile = new JsonFile(__DIR__.'/Fixtures/tabs2.json');
$data = $jsonFile->read();
$jsonFile->write(['foo' => 'baz']);
self::assertSame("{\n\t\"foo\": \"baz\"\n}\n", file_get_contents(__DIR__.'/Fixtures/tabs2.json'));
unlink(__DIR__.'/Fixtures/tabs2.json');
}
public function testOverwritesIndentationByDefault(): void
{
copy(__DIR__.'/Fixtures/tabs.json', __DIR__.'/Fixtures/tabs2.json');
$jsonFile = new JsonFile(__DIR__.'/Fixtures/tabs2.json');
$jsonFile->write(['foo' => 'baz']);
self::assertSame("{\n \"foo\": \"baz\"\n}\n", file_get_contents(__DIR__.'/Fixtures/tabs2.json'));
unlink(__DIR__.'/Fixtures/tabs2.json');
}
private function expectParseException(string $text, string $json): void
{
try {