Skip to content

Commit afbeaaa

Browse files
committed
Add a dedicated file deletion for unconfigure recipes
1 parent 5f92875 commit afbeaaa

26 files changed

+209
-103
lines changed

src/Configurator.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,17 @@ class Configurator
2323
{
2424
private $composer;
2525
private $io;
26+
private $filesManager;
2627
private $options;
2728
private $configurators;
2829
private $postInstallConfigurators;
2930
private $cache;
3031

31-
public function __construct(Composer $composer, IOInterface $io, Options $options)
32+
public function __construct(Composer $composer, IOInterface $io, FilesManager $filesManager, Options $options)
3233
{
3334
$this->composer = $composer;
3435
$this->io = $io;
36+
$this->filesManager = $filesManager;
3537
$this->options = $options;
3638
// ordered list of configurators
3739
$this->configurators = [
@@ -115,6 +117,6 @@ private function get($key): AbstractConfigurator
115117

116118
$class = isset($this->configurators[$key]) ? $this->configurators[$key] : $this->postInstallConfigurators[$key];
117119

118-
return $this->cache[$key] = new $class($this->composer, $this->io, $this->options);
120+
return $this->cache[$key] = new $class($this->composer, $this->io, $this->filesManager, $this->options);
119121
}
120122
}

src/Configurator/AbstractConfigurator.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Composer\Composer;
1515
use Composer\IO\IOInterface;
16+
use Symfony\Flex\FilesManager;
1617
use Symfony\Flex\Lock;
1718
use Symfony\Flex\Options;
1819
use Symfony\Flex\Path;
@@ -26,13 +27,15 @@ abstract class AbstractConfigurator
2627
{
2728
protected $composer;
2829
protected $io;
30+
protected $filesManager;
2931
protected $options;
3032
protected $path;
3133

32-
public function __construct(Composer $composer, IOInterface $io, Options $options)
34+
public function __construct(Composer $composer, IOInterface $io, FilesManager $filesManager, Options $options)
3335
{
3436
$this->composer = $composer;
3537
$this->io = $io;
38+
$this->filesManager = $filesManager;
3639
$this->options = $options;
3740
$this->path = new Path($options->get('root-dir'));
3841
}

src/Configurator/CopyFromPackageConfigurator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public function copyFile(string $source, string $target, array $options)
124124
return;
125125
}
126126

127-
if (!$this->options->shouldWriteFile($target, $options['force'] ?? false, $options['assumeYesForPrompts'] ?? false)) {
127+
if (!$this->filesManager->shouldWriteFile($target, $options['force'] ?? false, $options['assumeYesForPrompts'] ?? false)) {
128128
return;
129129
}
130130

src/Configurator/CopyFromRecipeConfigurator.php

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function configure(Recipe $recipe, $config, Lock $lock, array $options =
3131
public function unconfigure(Recipe $recipe, $config, Lock $lock)
3232
{
3333
$this->write('Removing files from recipe');
34-
$this->removeFiles($config, $this->getRemovableFilesFromRecipeAndLock($recipe, $lock), $this->options->get('root-dir'));
34+
$this->removeFiles($config, $this->filesManager->getRemovableFilesFromRecipeAndLock($recipe), $this->options->get('root-dir'));
3535
}
3636

3737
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
@@ -66,32 +66,6 @@ private function resolveTargetFolder(string $path, array $config): string
6666
return $path;
6767
}
6868

69-
private function getRemovableFilesFromRecipeAndLock(Recipe $recipe, Lock $lock): array
70-
{
71-
$lockedFiles = array_unique(
72-
array_reduce(
73-
array_column($lock->all(), 'files'),
74-
function (array $carry, array $package) {
75-
return array_merge($carry, $package);
76-
},
77-
[]
78-
)
79-
);
80-
81-
$removableFiles = $recipe->getFiles();
82-
83-
$lockedFiles = array_map('realpath', $lockedFiles);
84-
85-
// Compare file paths by their real path to abstract OS differences
86-
foreach (array_keys($removableFiles) as $file) {
87-
if (\in_array(realpath($file), $lockedFiles)) {
88-
unset($removableFiles[$file]);
89-
}
90-
}
91-
92-
return $removableFiles;
93-
}
94-
9569
private function copyFiles(array $manifest, array $files, array $options): array
9670
{
9771
$copiedFiles = [];
@@ -130,7 +104,7 @@ private function copyFile(string $to, string $contents, bool $executable, array
130104
$basePath = $options['root-dir'] ?? '.';
131105
$copiedFile = $this->getLocalFilePath($basePath, $to);
132106

133-
if (!$this->options->shouldWriteFile($to, $options['force'] ?? false, $options['assumeYesForPrompts'] ?? false)) {
107+
if (!$this->filesManager->shouldWriteFile($to, $options['force'] ?? false, $options['assumeYesForPrompts'] ?? false)) {
134108
return $copiedFile;
135109
}
136110

src/Configurator/DockerComposeConfigurator.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Composer\Json\JsonFile;
1818
use Composer\Json\JsonManipulator;
1919
use Symfony\Component\Filesystem\Filesystem;
20+
use Symfony\Flex\FilesManager;
2021
use Symfony\Flex\Lock;
2122
use Symfony\Flex\Options;
2223
use Symfony\Flex\Recipe;
@@ -33,9 +34,9 @@ class DockerComposeConfigurator extends AbstractConfigurator
3334

3435
public static $configureDockerRecipes;
3536

36-
public function __construct(Composer $composer, IOInterface $io, Options $options)
37+
public function __construct(Composer $composer, IOInterface $io, FilesManager $filesManager, Options $options)
3738
{
38-
parent::__construct($composer, $io, $options);
39+
parent::__construct($composer, $io, $filesManager, $options);
3940

4041
$this->filesystem = new Filesystem();
4142
}

src/Configurator/DotenvConfigurator.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,29 @@ class DotenvConfigurator extends AbstractConfigurator
2020
public function configure(Recipe $recipe, $vars, Lock $lock, array $options = [])
2121
{
2222
foreach ($vars as $suffix => $vars) {
23-
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
23+
$configurator = new EnvConfigurator($this->composer, $this->io, $this->filesManager, $this->options, $suffix);
2424
$configurator->configure($recipe, $vars, $lock, $options);
2525
}
2626
}
2727

2828
public function unconfigure(Recipe $recipe, $vars, Lock $lock)
2929
{
3030
foreach ($vars as $suffix => $vars) {
31-
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
31+
$configurator = new EnvConfigurator($this->composer, $this->io, $this->filesManager, $this->options, $suffix);
3232
$configurator->unconfigure($recipe, $vars, $lock);
3333
}
3434
}
3535

3636
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
3737
{
3838
foreach ($originalConfig as $suffix => $vars) {
39-
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
39+
$configurator = new EnvConfigurator($this->composer, $this->io, $this->filesManager, $this->options, $suffix);
4040
$configurator->update($recipeUpdate, $vars, $newConfig[$suffix] ?? []);
4141
}
4242

4343
foreach ($newConfig as $suffix => $vars) {
4444
if (!isset($originalConfig[$suffix])) {
45-
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
45+
$configurator = new EnvConfigurator($this->composer, $this->io, $this->filesManager, $this->options, $suffix);
4646
$configurator->update($recipeUpdate, [], $vars);
4747
}
4848
}

src/Configurator/EnvConfigurator.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Composer\Composer;
1515
use Composer\IO\IOInterface;
16+
use Symfony\Flex\FilesManager;
1617
use Symfony\Flex\Lock;
1718
use Symfony\Flex\Options;
1819
use Symfony\Flex\Recipe;
@@ -25,9 +26,9 @@ class EnvConfigurator extends AbstractConfigurator
2526
{
2627
private string $suffix;
2728

28-
public function __construct(Composer $composer, IOInterface $io, Options $options, string $suffix = '')
29+
public function __construct(Composer $composer, IOInterface $io, FilesManager $filesManager, Options $options, string $suffix = '')
2930
{
30-
parent::__construct($composer, $io, $options);
31+
parent::__construct($composer, $io, $filesManager, $options);
3132
$this->suffix = $suffix;
3233
}
3334

src/FilesManager.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Flex;
13+
14+
use Composer\IO\IOInterface;
15+
use Composer\Util\ProcessExecutor;
16+
17+
/**
18+
* @author Maxime Hélias <[email protected]>
19+
*/
20+
class FilesManager
21+
{
22+
private $io;
23+
protected $path;
24+
25+
private $writtenFiles = [];
26+
private $files;
27+
28+
public function __construct(IOInterface $io, Lock $lock, string $rootDir)
29+
{
30+
$this->io = $io;
31+
32+
$this->path = new Path($rootDir);
33+
$this->files = array_count_values(
34+
array_map(
35+
function (string $file) {
36+
return realpath($file) ?: '';
37+
}, array_reduce(
38+
array_column($lock->all(), 'files'),
39+
function (array $carry, array $package) {
40+
return array_merge($carry, $package);
41+
},
42+
[]
43+
)
44+
)
45+
);
46+
}
47+
48+
public function shouldWriteFile(string $file, bool $overwrite, bool $skipQuestion): bool
49+
{
50+
if (isset($this->writtenFiles[$file])) {
51+
return false;
52+
}
53+
$this->writtenFiles[$file] = true;
54+
55+
if (!file_exists($file)) {
56+
return true;
57+
}
58+
59+
if (!$overwrite) {
60+
return false;
61+
}
62+
63+
if (!filesize($file)) {
64+
return true;
65+
}
66+
67+
if ($skipQuestion) {
68+
return true;
69+
}
70+
71+
exec('git status --short --ignored --untracked-files=all -- '.ProcessExecutor::escape($file).' 2>&1', $output, $status);
72+
73+
if (0 !== $status) {
74+
return $this->io->askConfirmation(\sprintf('Cannot determine the state of the "%s" file, overwrite anyway? [y/N] ', $file), false);
75+
}
76+
77+
if (empty($output[0]) || preg_match('/^[ AMDRCU][ D][ \t]/', $output[0])) {
78+
return true;
79+
}
80+
81+
$name = basename($file);
82+
$name = \strlen($output[0]) - \strlen($name) === strrpos($output[0], $name) ? substr($output[0], 3) : $name;
83+
84+
return $this->io->askConfirmation(\sprintf('File "%s" has uncommitted changes, overwrite? [y/N] ', $name), false);
85+
}
86+
87+
public function getRemovableFilesFromRecipeAndLock(Recipe $recipe): array
88+
{
89+
$removableFiles = $recipe->getFiles();
90+
// Compare file paths by their real path to abstract OS differences
91+
foreach (array_keys($removableFiles) as $file) {
92+
$file = realpath($file);
93+
if (!isset($this->files[$file])) {
94+
continue;
95+
}
96+
97+
--$this->files[$file];
98+
99+
if ($this->files[$file] <= 0) {
100+
unset($removableFiles[$file]);
101+
}
102+
}
103+
104+
return $removableFiles;
105+
}
106+
}

src/Flex.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class Flex implements PluginInterface, EventSubscriberInterface
6666

6767
private $config;
6868
private $options;
69+
private $filesManager;
6970
private $configurator;
7071
private $downloader;
7172

@@ -134,8 +135,9 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
134135
$composerLock = 'json' === pathinfo($composerFile, \PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' : $composerFile.'.lock';
135136
$symfonyLock = str_replace('composer', 'symfony', basename($composerLock));
136137

137-
$this->configurator = new Configurator($composer, $io, $this->options);
138138
$this->lock = new Lock(getenv('SYMFONY_LOCKFILE') ?: \dirname($composerLock).'/'.(basename($composerLock) !== $symfonyLock ? $symfonyLock : 'symfony.lock'));
139+
$this->filesManager = new FilesManager($io, $this->lock, $this->options->get('root-dir'));
140+
$this->configurator = new Configurator($composer, $io, $this->filesManager, $this->options);
139141

140142
$disable = true;
141143
foreach (array_merge($composer->getPackage()->getRequires() ?? [], $composer->getPackage()->getDevRequires() ?? []) as $link) {
@@ -716,7 +718,7 @@ private function initOptions(): Options
716718
'runtime' => $extra['runtime'] ?? [],
717719
], $extra);
718720

719-
return new Options($options, $this->io);
721+
return new Options($options);
720722
}
721723

722724
private function formatOrigin(Recipe $recipe): string

src/Options.php

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,16 @@
1111

1212
namespace Symfony\Flex;
1313

14-
use Composer\IO\IOInterface;
15-
use Composer\Util\ProcessExecutor;
16-
1714
/**
1815
* @author Fabien Potencier <[email protected]>
1916
*/
2017
class Options
2118
{
2219
private $options;
23-
private $writtenFiles = [];
24-
private $io;
2520

26-
public function __construct(array $options = [], ?IOInterface $io = null)
21+
public function __construct(array $options = [])
2722
{
2823
$this->options = $options;
29-
$this->io = $io;
3024
}
3125

3226
public function get(string $name)
@@ -62,45 +56,6 @@ public function expandTargetDir(string $target): string
6256
return file_exists($rootDir.'/'.$otherPhpunitDistFile) ? $otherPhpunitDistFile : $result;
6357
}
6458

65-
public function shouldWriteFile(string $file, bool $overwrite, bool $skipQuestion): bool
66-
{
67-
if (isset($this->writtenFiles[$file])) {
68-
return false;
69-
}
70-
$this->writtenFiles[$file] = true;
71-
72-
if (!file_exists($file)) {
73-
return true;
74-
}
75-
76-
if (!$overwrite) {
77-
return false;
78-
}
79-
80-
if (!filesize($file)) {
81-
return true;
82-
}
83-
84-
if ($skipQuestion) {
85-
return true;
86-
}
87-
88-
exec('git status --short --ignored --untracked-files=all -- '.ProcessExecutor::escape($file).' 2>&1', $output, $status);
89-
90-
if (0 !== $status) {
91-
return $this->io && $this->io->askConfirmation(\sprintf('Cannot determine the state of the "%s" file, overwrite anyway? [y/N] ', $file), false);
92-
}
93-
94-
if (empty($output[0]) || preg_match('/^[ AMDRCU][ D][ \t]/', $output[0])) {
95-
return true;
96-
}
97-
98-
$name = basename($file);
99-
$name = \strlen($output[0]) - \strlen($name) === strrpos($output[0], $name) ? substr($output[0], 3) : $name;
100-
101-
return $this->io && $this->io->askConfirmation(\sprintf('File "%s" has uncommitted changes, overwrite? [y/N] ', $name), false);
102-
}
103-
10459
public function toArray(): array
10560
{
10661
return $this->options;

0 commit comments

Comments
 (0)