Merge branch 'master' into 2.0

main
Jordi Boggiano 5 years ago
commit 4517c00d40

@ -1,3 +1,14 @@
### [1.8.1] 2019-01-29
* Deprecated support for non-standard package names (anything with uppercase, or no / in it). Make sure to follow the warnings if you see any to avoid problems in 2.0.
* Fixed some packages missing from the autoloader config when installing with --no-dev
* Fixed support for cloning GitLab repos using OAuth tokens instead of SSH keys
* Fixed metapackage installs/updates missing from output
* Fixed --with-dependencies / --with-all-dependencies not updating some packages in some edge cases
* Fixed compatibility with Symfony 4.2 deprecations
* Fixed temp dir not being cleaned up on download error while archiving packages
* Updated to latest ca-bundle
### [1.8.0] 2018-12-03
* Changed `post-package-install` / `post-package-update` event to be fired *after* the lock file has been updated as opposed to before

@ -234,6 +234,14 @@ github API will have a date instead of the machine hostname.
Defaults to `["gitlab.com"]`. A list of domains of GitLab servers.
This is used if you use the `gitlab` repository type.
## use-github-api
Defaults to `true`. Similar to the `no-api` key on a specific repository,
setting `use-github-api` to `false` will define the global behavior for all
GitHub repositories to clone the repository as it would with any other git
repository instead of using the GitHub API. But unlike using the `git`
driver directly, Composer will still attempt to use GitHub's zip files.
## notify-on-install
Defaults to `true`. Composer allows repositories to define a notification URL,

@ -271,6 +271,10 @@
"type": "string"
}
},
"use-github-api": {
"type": "boolean",
"description": "Defaults to true. If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository."
},
"archive-format": {
"type": "string",
"description": "The default archiving format when not provided on cli, defaults to \"tar\"."

@ -302,6 +302,7 @@ EOT
$uniqueConfigValues = array(
'process-timeout' => array('is_numeric', 'intval'),
'use-include-path' => array($booleanValidator, $booleanNormalizer),
'use-github-api' => array($booleanValidator, $booleanNormalizer),
'preferred-install' => array(
function ($val) {
return in_array($val, array('auto', 'source', 'dist'), true);

@ -61,6 +61,7 @@ class Config
'archive-format' => 'tar',
'archive-dir' => '.',
'htaccess-protect' => true,
'use-github-api' => true,
// valid keys without defaults (auth config stuff):
// bitbucket-oauth
// github-oauth
@ -323,10 +324,10 @@ class Config
case 'disable-tls':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'secure-http':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'use-github-api':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
default:
if (!isset($this->config[$key])) {
return null;

@ -251,8 +251,8 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
public function update(PackageInterface $initial, PackageInterface $target, $path)
{
$name = $target->getName();
$from = $initial->getPrettyVersion();
$to = $target->getPrettyVersion();
$from = $initial->getFullPrettyVersion();
$to = $target->getFullPrettyVersion();
$actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);

@ -349,7 +349,7 @@ class Factory
// load package
$parser = new VersionParser;
$guesser = new VersionGuesser($config, new ProcessExecutor($io), $parser);
$loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser);
$loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io);
$package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd);
$composer->setPackage($package);
@ -542,7 +542,7 @@ class Factory
$im->addInstaller(new Installer\LibraryInstaller($io, $composer, null));
$im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library'));
$im->addInstaller(new Installer\PluginInstaller($io, $composer));
$im->addInstaller(new Installer\MetapackageInstaller());
$im->addInstaller(new Installer\MetapackageInstaller($io));
}
/**

@ -14,6 +14,8 @@ namespace Composer\Installer;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\IO\IOInterface;
/**
* Metapackage installation manager.
@ -22,6 +24,13 @@ use Composer\Package\PackageInterface;
*/
class MetapackageInstaller implements InstallerInterface
{
private $io;
public function __construct(IOInterface $io)
{
$this->io = $io;
}
/**
* {@inheritDoc}
*/
@ -51,6 +60,8 @@ class MetapackageInstaller implements InstallerInterface
*/
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
{
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
$repo->addPackage(clone $package);
}
@ -63,6 +74,12 @@ class MetapackageInstaller implements InstallerInterface
throw new \InvalidArgumentException('Package is not installed: '.$initial);
}
$name = $target->getName();
$from = $initial->getFullPrettyVersion();
$to = $target->getFullPrettyVersion();
$actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");
$repo->removePackage($initial);
$repo->addPackage(clone $target);
}
@ -76,6 +93,8 @@ class MetapackageInstaller implements InstallerInterface
throw new \InvalidArgumentException('Package is not installed: '.$package);
}
$this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
$repo->removePackage($package);
}

@ -15,6 +15,7 @@ namespace Composer\Package\Loader;
use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Package\RootPackageInterface;
use Composer\Repository\RepositoryFactory;
use Composer\Package\Version\VersionGuesser;
@ -46,13 +47,19 @@ class RootPackageLoader extends ArrayLoader
*/
private $versionGuesser;
public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null)
/**
* @var IOInterface
*/
private $io;
public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null, IOInterface $io = null)
{
parent::__construct($parser);
$this->manager = $manager;
$this->config = $config;
$this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor(), $this->versionParser);
$this->io = $io;
}
/**
@ -65,6 +72,10 @@ class RootPackageLoader extends ArrayLoader
{
if (!isset($config['name'])) {
$config['name'] = '__root__';
} elseif ($this->io) {
if ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) {
$this->io->writeError('<warning>Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.</warning>');
}
}
$autoVersioned = false;
if (!isset($config['version'])) {
@ -131,6 +142,18 @@ class RootPackageLoader extends ArrayLoader
}
}
if ($this->io) {
foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
if (isset($config[$linkType])) {
foreach ($config[$linkType] as $linkName => $constraint) {
if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) {
$this->io->writeError('<warning>Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.</warning>');
}
}
}
}
}
if (isset($links[$config['name']])) {
throw new \InvalidArgumentException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL .
'Did you accidentally name your root package after an external package?', $config['name']));

@ -336,6 +336,38 @@ class ValidatingArrayLoader implements LoaderInterface
return $this->errors;
}
public static function hasPackageNamingError($name, $isLink = false)
{
if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
return;
}
if (!preg_match('{^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[a-z0-9]+)*$}iD', $name)) {
return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[a-z0-9]+)*".';
}
$reservedNames = array('nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9');
$bits = explode('/', strtolower($name));
if (in_array($bits[0], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) {
return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.';
}
if (preg_match('{\.json$}', $name)) {
return $name.' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.';
}
if (preg_match('{[A-Z]}', $name)) {
if ($isLink) {
return $name.' is invalid, it should not contain uppercase characters. Please use '.strtolower($name).' instead.';
}
$suggestName = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
$suggestName = strtolower($suggestName);
return $name.' is invalid, it should not contain uppercase characters. We suggest using '.$suggestName.' instead.';
}
}
private function validateRegex($property, $regex, $mandatory = false)
{
if (!$this->validateString($property, $mandatory)) {

@ -24,7 +24,7 @@ use Composer\XdebugHandler\XdebugHandler;
*/
class PlatformRepository extends ArrayRepository
{
const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[^/ ]+)$}i';
const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:-?[a-z0-9]+)*)$}iD';
private $versionParser;

@ -58,7 +58,7 @@ class GitHubDriver extends VcsDriver
}
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);
if (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api']) {
if ( $this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){
$this->setupGitDriver($this->url);
return;

@ -153,6 +153,28 @@ class Git
return;
}
}
} elseif (preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
if (!$this->io->hasAuthentication($match[2])) {
$gitLabUtil = new GitLab($this->io, $this->config, $this->process);
$message = 'Cloning failed, enter your GitLab credentials to access private repos';
if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) {
$gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message);
}
}
if ($this->io->hasAuthentication($match[2])) {
$auth = $this->io->getAuthentication($match[2]);
if($auth['password'] === 'private-token' || $auth['password'] === 'oauth2') {
$authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password
} else {
$authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3];
}
$command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
return;
}
}
} elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate
if (strpos($match[2], '@')) {
list($authParts, $match[2]) = explode('@', $match[2], 2);
@ -304,6 +326,11 @@ class Git
return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')';
}
public static function getGitLabDomainsRegex(Config $config)
{
return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')';
}
public static function sanitizeUrl($message)
{
return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {

@ -223,7 +223,7 @@ class FileDownloaderTest extends TestCase
{
$oldPackage = $this->getMock('Composer\Package\PackageInterface');
$oldPackage->expects($this->once())
->method('getPrettyVersion')
->method('getFullPrettyVersion')
->will($this->returnValue('1.2.0'));
$oldPackage->expects($this->once())
->method('getVersion')
@ -231,7 +231,7 @@ class FileDownloaderTest extends TestCase
$newPackage = $this->getMock('Composer\Package\PackageInterface');
$newPackage->expects($this->once())
->method('getPrettyVersion')
->method('getFullPrettyVersion')
->will($this->returnValue('1.0.0'));
$newPackage->expects($this->once())
->method('getVersion')

@ -13,12 +13,12 @@ Present a clear error message when config.platform.php version results in a conf
{
"type": "package",
"package": [
{ "name": "a", "version": "1.0.0", "require": { "php": "5.5" } }
{ "name": "a/a", "version": "1.0.0", "require": { "php": "5.5" } }
]
}
],
"require": {
"a": "~1.0"
"a/a": "~1.0"
},
"config": {
"platform": {
@ -36,8 +36,8 @@ Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Installation request for a ~1.0 -> satisfiable by a[1.0.0].
- a 1.0.0 requires php 5.5 -> your PHP version (%s) overridden by "config.platform.php" version (5.3) does not satisfy that requirement.
- Installation request for a/a ~1.0 -> satisfiable by a/a[1.0.0].
- a/a 1.0.0 requires php 5.5 -> your PHP version (%s) overridden by "config.platform.php" version (5.3) does not satisfy that requirement.
--EXPECT--

@ -11,27 +11,27 @@ that are also a root package, when that root package is also explicitly whitelis
{
"type": "package",
"package": [
{ "name": "a", "version": "1.0.0" },
{ "name": "a", "version": "1.1.0" },
{ "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } },
{ "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } }
{ "name": "a/a", "version": "1.0.0" },
{ "name": "a/a", "version": "1.1.0" },
{ "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } },
{ "name": "b/b", "version": "1.1.0", "require": { "a/a": "~1.1" } }
]
}
],
"require": {
"a": "~1.0",
"b": "~1.0"
"a/a": "~1.0",
"b/b": "~1.0"
}
}
--INSTALLED--
[
{ "name": "a", "version": "1.0.0" },
{ "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }
{ "name": "a/a", "version": "1.0.0" },
{ "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } }
]
--RUN--
update a b --with-dependencies
update a/a b/b --with-dependencies
--EXPECT-OUTPUT--
Loading composer repositories with package information
@ -41,5 +41,5 @@ Writing lock file
Generating autoload files
--EXPECT--
Updating a (1.0.0) to a (1.1.0)
Updating b (1.0.0) to b (1.1.0)
Updating a/a (1.0.0) to a/a (1.1.0)
Updating b/b (1.0.0) to b/b (1.1.0)

@ -11,30 +11,30 @@ dependency of one the requirements that is whitelisted for update.
{
"type": "package",
"package": [
{ "name": "a", "version": "1.0.0" },
{ "name": "a", "version": "1.1.0" },
{ "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } },
{ "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } }
{ "name": "a/a", "version": "1.0.0" },
{ "name": "a/a", "version": "1.1.0" },
{ "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } },
{ "name": "b/b", "version": "1.1.0", "require": { "a/b": "~1.1" } }
]
}
],
"require": {
"a": "~1.0",
"b": "~1.0"
"a/a": "~1.0",
"b/b": "~1.0"
}
}
--INSTALLED--
[
{ "name": "a", "version": "1.0.0" },
{ "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }
{ "name": "a/a", "version": "1.0.0" },
{ "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } }
]
--RUN--
update b --with-dependencies
update b/b --with-dependencies
--EXPECT-OUTPUT--
<warning>Dependency "a" is also a root requirement, but is not explicitly whitelisted. Ignoring.</warning>
<warning>Dependency "a/a" is also a root requirement, but is not explicitly whitelisted. Ignoring.</warning>
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update

@ -8,30 +8,30 @@ Test the error output of solver problems.
"package": [
{ "name": "unstable/package", "version": "2.0.0-alpha" },
{ "name": "unstable/package", "version": "1.0.0" },
{ "name": "requirer", "version": "1.0.0", "require": {"dependency": "1.0.0" } },
{ "name": "dependency", "version": "2.0.0" },
{ "name": "dependency", "version": "1.0.0" },
{ "name": "stable-requiree-excluded", "version": "1.0.1" },
{ "name": "stable-requiree-excluded", "version": "1.0.0" }
{ "name": "requirer/pkg", "version": "1.0.0", "require": {"dependency/pkg": "1.0.0" } },
{ "name": "dependency/pkg", "version": "2.0.0" },
{ "name": "dependency/pkg", "version": "1.0.0" },
{ "name": "stable-requiree-excluded/pkg", "version": "1.0.1" },
{ "name": "stable-requiree-excluded/pkg", "version": "1.0.0" }
]
}
],
"require": {
"unstable/package": "2.*",
"bogus": "1.*",
"requirer": "1.*",
"dependency": "2.*",
"stable-requiree-excluded": "1.0.1"
"bogus/pkg": "1.*",
"requirer/pkg": "1.*",
"dependency/pkg": "2.*",
"stable-requiree-excluded/pkg": "1.0.1"
}
}
--INSTALLED--
[
{ "name": "stable-requiree-excluded", "version": "1.0.0" }
{ "name": "stable-requiree-excluded/pkg", "version": "1.0.0" }
]
--RUN--
update unstable/package requirer dependency
update unstable/package requirer/pkg dependency/pkg
--EXPECT-EXIT-CODE--
2
@ -44,14 +44,14 @@ Your requirements could not be resolved to an installable set of packages.
Problem 1
- The requested package unstable/package could not be found in any version, there may be a typo in the package name.
Problem 2
- The requested package bogus could not be found in any version, there may be a typo in the package name.
- The requested package bogus/pkg could not be found in any version, there may be a typo in the package name.
Problem 3
- The requested package stable-requiree-excluded 1.0.1 exists as stable-requiree-excluded[1.0.0] but these are rejected by your constraint.
- The requested package stable-requiree-excluded/pkg 1.0.1 exists as stable-requiree-excluded/pkg[1.0.0] but these are rejected by your constraint.
Problem 4
- The requested package stable-requiree-excluded (installed at 1.0.0, required as 1.0.1) is satisfiable by stable-requiree-excluded[1.0.0] but these conflict with your requirements or minimum-stability.
- The requested package stable-requiree-excluded/pkg (installed at 1.0.0, required as 1.0.1) is satisfiable by stable-requiree-excluded/pkg[1.0.0] but these conflict with your requirements or minimum-stability.
Problem 5
- Installation request for requirer 1.* -> satisfiable by requirer[1.0.0].
- requirer 1.0.0 requires dependency 1.0.0 -> no matching package found.
- Installation request for requirer/pkg 1.* -> satisfiable by requirer/pkg[1.0.0].
- requirer/pkg 1.0.0 requires dependency/pkg 1.0.0 -> no matching package found.
Potential causes:
- A typo in the package name

@ -10,27 +10,27 @@ When `--with-all-dependencies` is used, Composer\Installer::whitelistUpdateDepen
{
"type": "package",
"package": [
{ "name": "a", "version": "1.0.0" },
{ "name": "a", "version": "1.1.0" },
{ "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } },
{ "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } }
{ "name": "a/a", "version": "1.0.0" },
{ "name": "a/a", "version": "1.1.0" },
{ "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } },
{ "name": "b/b", "version": "1.1.0", "require": { "a/a": "~1.1" } }
]
}
],
"require": {
"a": "~1.0",
"b": "~1.0"
"a/a": "~1.0",
"b/b": "~1.0"
}
}
--INSTALLED--
[
{ "name": "a", "version": "1.0.0" },
{ "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }
{ "name": "a/a", "version": "1.0.0" },
{ "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } }
]
--RUN--
update b --with-all-dependencies
update b/b --with-all-dependencies
--EXPECT-OUTPUT--
Loading composer repositories with package information
@ -40,5 +40,5 @@ Writing lock file
Generating autoload files
--EXPECT--
Updating a (1.0.0) to a (1.1.0)
Updating b (1.0.0) to b (1.1.0)
Updating a/a (1.0.0) to a/a (1.1.0)
Updating b/b (1.0.0) to b/b (1.1.0)

@ -27,7 +27,7 @@ class MetapackageInstallerTest extends TestCase
$this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
$this->installer = new MetapackageInstaller();
$this->installer = new MetapackageInstaller($this->io);
}
public function testInstall()
@ -45,7 +45,13 @@ class MetapackageInstallerTest extends TestCase
public function testUpdate()
{
$initial = $this->createPackageMock();
$initial->expects($this->once())
->method('getVersion')
->will($this->returnValue('1.0.0'));
$target = $this->createPackageMock();
$target->expects($this->once())
->method('getVersion')
->will($this->returnValue('1.0.1'));
$this->repository
->expects($this->exactly(2))

Loading…
Cancel
Save