diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
index b5015adba..a260e3fc7 100644
--- a/.github/workflows/phpstan.yml
+++ b/.github/workflows/phpstan.yml
@@ -45,7 +45,7 @@ jobs:
- name: "Determine composer cache directory"
id: "determine-composer-cache-directory"
- run: "echo \"::set-output name=directory::$(composer config cache-dir)\""
+ run: "echo \"directory=$(composer config cache-dir)\" >> $GITHUB_OUTPUT"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v3"
diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php
index 8a5ab1a71..4e1fa7429 100644
--- a/src/Composer/Cache.php
+++ b/src/Composer/Cache.php
@@ -137,6 +137,8 @@ class Cache
*/
public function write(string $file, string $contents)
{
+ $wasEnabled = $this->enabled === true;
+
if ($this->isEnabled() && !$this->readOnly) {
$file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
@@ -146,6 +148,14 @@ class Cache
try {
return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file);
} catch (\ErrorException $e) {
+ // If the write failed despite isEnabled checks passing earlier, rerun the isEnabled checks to
+ // see if they are still current and recreate the cache dir if needed. Refs https://github.com/composer/composer/issues/11076
+ if ($wasEnabled) {
+ clearstatcache();
+ $this->enabled = null;
+ return $this->write($file, $contents);
+ }
+
$this->io->writeError('Failed to write into cache: '.$e->getMessage().'', true, IOInterface::DEBUG);
if (Preg::isMatch('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) {
// Remove partial file.
diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php
index dd32e6537..c7497d390 100644
--- a/src/Composer/Command/ShowCommand.php
+++ b/src/Composer/Command/ShowCommand.php
@@ -600,19 +600,19 @@ EOT
}
}
- $io->write('');
- $io->write('Direct dependencies required in composer.json:>');
+ $io->writeError('');
+ $io->writeError('Direct dependencies required in composer.json:>');
if (\count($directDeps) > 0) {
$this->printPackages($io, $directDeps, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
} else {
- $io->write('Everything up to date');
+ $io->writeError('Everything up to date');
}
- $io->write('');
- $io->write('Transitive dependencies not required in composer.json:>');
+ $io->writeError('');
+ $io->writeError('Transitive dependencies not required in composer.json:>');
if (\count($transitiveDeps) > 0) {
$this->printPackages($io, $transitiveDeps, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
} else {
- $io->write('Everything up to date');
+ $io->writeError('Everything up to date');
}
} else {
$this->printPackages($io, $packages, $indent, $versionFits, $latestFits, $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php
index 4cd51dc4b..872670f95 100644
--- a/src/Composer/Console/Application.php
+++ b/src/Composer/Console/Application.php
@@ -17,6 +17,7 @@ use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use LogicException;
+use RuntimeException;
use Seld\Signal\SignalHandler;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Exception\CommandNotFoundException;
@@ -514,6 +515,10 @@ class Application extends BaseApplication
if ($required) {
throw $e;
}
+ } catch (RuntimeException $e) {
+ if ($required) {
+ throw $e;
+ }
}
}
diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php
index 206ee662e..d4f851f8a 100644
--- a/src/Composer/Json/JsonFile.php
+++ b/src/Composer/Json/JsonFile.php
@@ -13,6 +13,7 @@
namespace Composer\Json;
use Composer\Pcre\Preg;
+use Composer\Util\Filesystem;
use JsonSchema\Validator;
use Seld\JsonLint\JsonParser;
use Seld\JsonLint\ParsingException;
@@ -93,7 +94,7 @@ class JsonFile
if ($this->httpDownloader) {
$json = $this->httpDownloader->get($this->path)->getBody();
} else {
- if (!is_readable($this->path)) {
+ if (!Filesystem::isReadable($this->path)) {
throw new \RuntimeException('The file "'.$this->path.'" is not readable.');
}
if ($this->io && $this->io->isDebug()) {
@@ -193,7 +194,7 @@ class JsonFile
*/
public function validateSchema(int $schema = self::STRICT_SCHEMA, ?string $schemaFile = null): bool
{
- if (!is_readable($this->path)) {
+ if (!Filesystem::isReadable($this->path)) {
throw new \RuntimeException('The file "'.$this->path.'" is not readable.');
}
$content = file_get_contents($this->path);
diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php
index 2372bbfc3..040d17db0 100644
--- a/src/Composer/Util/Url.php
+++ b/src/Composer/Util/Url.php
@@ -111,7 +111,7 @@ class Url
$url = Preg::replaceCallback('{^(?P[a-z0-9]+://)?(?P[^:/\s@]+):(?P[^@\s/]+)@}i', static function ($m): string {
// if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that
- if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+)$}', $m['user'])) {
+ if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}', $m['user'])) {
return $m['prefix'].'***:***@';
}
diff --git a/tests/Composer/Test/Util/UrlTest.php b/tests/Composer/Test/Util/UrlTest.php
index 7b1b4bc3a..267b1c1bd 100644
--- a/tests/Composer/Test/Util/UrlTest.php
+++ b/tests/Composer/Test/Util/UrlTest.php
@@ -82,6 +82,7 @@ class UrlTest extends TestCase
['https://example.org/foo/bar?access_token=***', 'https://example.org/foo/bar?access_token=abcdef'],
['https://example.org/foo/bar?foo=bar&access_token=***', 'https://example.org/foo/bar?foo=bar&access_token=abcdef'],
['https://***:***@github.com/acme/repo', 'https://ghp_1234567890abcdefghijklmnopqrstuvwxyzAB:x-oauth-basic@github.com/acme/repo'],
+ ['https://***:***@github.com/acme/repo', 'https://github_pat_1234567890abcdefghijkl_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW:x-oauth-basic@github.com/acme/repo'],
// without scheme
['foo:***@example.org/', 'foo:bar@example.org/'],
['foo@example.org/', 'foo@example.org/'],