1
0
Fork 0

Fix issue extracting archives into paths that already exist, fixes composer/installers#479

pull/9734/head
Jordi Boggiano 2021-03-09 11:53:52 +01:00
parent 0879e80d56
commit b451bcb1ac
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
1 changed files with 31 additions and 6 deletions

View File

@ -109,6 +109,34 @@ abstract class ArchiveDownloader extends FileDownloader
return iterator_to_array($finder); return iterator_to_array($finder);
}; };
$renameRecursively = null;
/**
* Renames (and recursively merges if needed) a folder into another one
*
* For custom installers, where packages may share paths, and given Composer 2's parallelism, we need to make sure
* that the source directory gets merged into the target one if the target exists. Otherwise rename() by default would
* put the source into the target e.g. src/ => target/src/ (assuming target exists) instead of src/ => target/
*
* @param string $from Directory
* @param string $to Directory
* @return void
*/
$renameRecursively = function ($from, $to) use ($filesystem, $getFolderContent, $package, &$renameRecursively) {
$contentDir = $getFolderContent($from);
// move files back out of the temp dir
foreach ($contentDir as $file) {
$file = (string) $file;
if (is_dir($to . '/' . basename($file))) {
if (!is_dir($file)) {
throw new \RuntimeException('Installing '.$package.' would lead to overwriting the '.$to.'/'.basename($file).' directory with a file from the package, invalid operation.');
}
$renameRecursively($file, $to . '/' . basename($file));
} else {
$filesystem->rename($file, $to . '/' . basename($file));
}
}
};
$renameAsOne = false; $renameAsOne = false;
if (!file_exists($path) || ($filesystem->isDirEmpty($path) && $filesystem->removeDirectory($path))) { if (!file_exists($path) || ($filesystem->isDirEmpty($path) && $filesystem->removeDirectory($path))) {
@ -128,15 +156,12 @@ abstract class ArchiveDownloader extends FileDownloader
$filesystem->rename($extractedDir, $path); $filesystem->rename($extractedDir, $path);
} else { } else {
// only one dir in the archive, extract its contents out of it // only one dir in the archive, extract its contents out of it
$from = $temporaryDir;
if ($singleDirAtTopLevel) { if ($singleDirAtTopLevel) {
$contentDir = $getFolderContent((string) reset($contentDir)); $from = (string) reset($contentDir);
} }
// move files back out of the temp dir $renameRecursively($from, $path);
foreach ($contentDir as $file) {
$file = (string) $file;
$filesystem->rename($file, $path . '/' . basename($file));
}
} }
$filesystem->removeDirectory($temporaryDir); $filesystem->removeDirectory($temporaryDir);