Skip to content

Commit 03ac318

Browse files
committed
Don't remove still-referenced files when updating recipes
1 parent 4ae50d3 commit 03ac318

File tree

3 files changed

+66
-11
lines changed

3 files changed

+66
-11
lines changed

src/Command/UpdateRecipesCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
153153
$recipeUpdate = new RecipeUpdate($originalRecipe, $newRecipe, $symfonyLock, $this->rootDir);
154154
$this->configurator->populateUpdate($recipeUpdate);
155155
$originalComposerJsonHash = $this->flex->getComposerJsonHash();
156-
$patcher = new RecipePatcher($this->rootDir, $io);
156+
$patcher = new RecipePatcher($this->rootDir, $io, $symfonyLock);
157157

158158
try {
159159
$patch = $patcher->generatePatch($recipeUpdate->getOriginalFiles(), $recipeUpdate->getNewFiles());
160-
$hasConflicts = !$patcher->applyPatch($patch);
160+
$hasConflicts = !$patcher->applyPatch($patch, $packageName);
161161
} catch (\Throwable $throwable) {
162162
$io->writeError([
163163
'<bg=red;fg=white>There was an error applying the recipe update patch</>',

src/Update/RecipePatcher.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,22 @@
1515
use Composer\Util\ProcessExecutor;
1616
use Symfony\Component\Filesystem\Exception\IOException;
1717
use Symfony\Component\Filesystem\Filesystem;
18+
use Symfony\Flex\Lock;
1819

1920
class RecipePatcher
2021
{
2122
private $rootDir;
2223
private $filesystem;
2324
private $io;
2425
private $processExecutor;
26+
private $symfonyLock;
2527

26-
public function __construct(string $rootDir, IOInterface $io)
28+
public function __construct(string $rootDir, IOInterface $io, Lock $symfonyLock)
2729
{
2830
$this->rootDir = $rootDir;
2931
$this->filesystem = new Filesystem();
3032
$this->io = $io;
33+
$this->symfonyLock = $symfonyLock;
3134
$this->processExecutor = new ProcessExecutor($io);
3235
}
3336

@@ -36,14 +39,33 @@ public function __construct(string $rootDir, IOInterface $io)
3639
*
3740
* @return bool returns true if fully successful, false if conflicts were encountered
3841
*/
39-
public function applyPatch(RecipePatch $patch): bool
42+
public function applyPatch(RecipePatch $patch, ?string $packageName = null): bool
4043
{
4144
$withConflicts = $this->_applyPatchFile($patch);
45+
$lockedFiles = $packageName ? array_count_values(array_merge(...array_column(array_filter($this->symfonyLock->all(), fn ($package) => $package !== $packageName, \ARRAY_FILTER_USE_KEY), 'files'))) : [];
4246

47+
$nonRemovableFiles = [];
4348
foreach ($patch->getDeletedFiles() as $deletedFile) {
44-
if (file_exists($this->rootDir.'/'.$deletedFile)) {
45-
$this->execute(\sprintf('git rm %s', ProcessExecutor::escape($deletedFile)), $this->rootDir);
49+
if (!file_exists($this->rootDir.'/'.$deletedFile)) {
50+
continue;
51+
}
52+
53+
if (isset($lockedFiles[$deletedFile])) {
54+
$nonRemovableFiles[] = $deletedFile;
55+
56+
continue;
4657
}
58+
59+
$this->execute(\sprintf('git rm %s', ProcessExecutor::escape($deletedFile)), $this->rootDir);
60+
}
61+
62+
if ($nonRemovableFiles) {
63+
$this->io->writeError(' <warning>The following files were removed in the recipe, but are still referenced by other recipes. You might need to adjust them manually:</warning>');
64+
foreach ($nonRemovableFiles as $file) {
65+
$this->io->writeError(' - '.$file);
66+
}
67+
68+
$this->io->writeError('');
4769
}
4870

4971
return $withConflicts;

tests/Update/RecipePatcherTest.php

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use PHPUnit\Framework\TestCase;
1616
use Symfony\Component\Filesystem\Filesystem;
1717
use Symfony\Component\Process\Process;
18+
use Symfony\Flex\Lock;
1819
use Symfony\Flex\Update\RecipePatch;
1920
use Symfony\Flex\Update\RecipePatcher;
2021

@@ -52,7 +53,7 @@ public function testGeneratePatch(array $originalFiles, array $newFiles, string
5253
(new Process(['git', 'commit', '-m', '"original files"'], FLEX_TEST_DIR))->mustRun();
5354
}
5455

55-
$patcher = new RecipePatcher(FLEX_TEST_DIR, $this->createMock(IOInterface::class));
56+
$patcher = new RecipePatcher(FLEX_TEST_DIR, $this->createMock(IOInterface::class), $this->createMock(Lock::class));
5657

5758
$patch = $patcher->generatePatch($originalFiles, $newFiles);
5859
$this->assertSame($expectedPatch, rtrim($patch->getPatch(), "\n"));
@@ -189,7 +190,7 @@ public function testGeneratePatchOnDeletedFile()
189190
$this->getFilesystem()->remove(FLEX_TEST_DIR);
190191
$this->getFilesystem()->mkdir(FLEX_TEST_DIR);
191192

192-
$patcher = new RecipePatcher(FLEX_TEST_DIR, $this->createMock(IOInterface::class));
193+
$patcher = new RecipePatcher(FLEX_TEST_DIR, $this->createMock(IOInterface::class), $this->createMock(Lock::class));
193194

194195
// try to update a file that does not exist in the project
195196
$patch = $patcher->generatePatch(['.env' => 'original contents'], ['.env' => 'new contents']);
@@ -217,7 +218,7 @@ public function testApplyPatch(array $filesCurrentlyInApp, RecipePatch $recipePa
217218
(new Process(['git', 'commit', '-m', 'Committing original files'], FLEX_TEST_DIR))->mustRun();
218219
}
219220

220-
$patcher = new RecipePatcher(FLEX_TEST_DIR, $this->createMock(IOInterface::class));
221+
$patcher = new RecipePatcher(FLEX_TEST_DIR, $this->createMock(IOInterface::class), $this->createMock(Lock::class));
221222
$hadConflicts = !$patcher->applyPatch($recipePatch);
222223

223224
foreach ($expectedFiles as $file => $expectedContents) {
@@ -233,6 +234,38 @@ public function testApplyPatch(array $filesCurrentlyInApp, RecipePatch $recipePa
233234
$this->assertSame($expectedConflicts, $hadConflicts);
234235
}
235236

237+
public function testApplyPatchFileOwnedByMultipleRecipes()
238+
{
239+
(new Process(['git', 'init'], FLEX_TEST_DIR))->mustRun();
240+
(new Process(['git', 'config', 'user.name', 'Unit test'], FLEX_TEST_DIR))->mustRun();
241+
(new Process(['git', 'config', 'user.email', ''], FLEX_TEST_DIR))->mustRun();
242+
243+
$dir = FLEX_TEST_DIR.'/config/packages';
244+
@mkdir($dir, 0777, true);
245+
file_put_contents($dir.'/security.yaml', '# contents');
246+
(new Process(['git', 'add', '-A'], FLEX_TEST_DIR))->mustRun();
247+
(new Process(['git', 'commit', '-m', 'Committing original files'], FLEX_TEST_DIR))->mustRun();
248+
249+
250+
$lock = $this->createMock(Lock::class);
251+
$lock->expects($this->any())->method('all')->willReturn([
252+
'symfony/security-bundle' => ['files' => ['config/packages/security.yaml']],
253+
'symfony/security' => ['files' => ['config/packages/security.yaml']],
254+
]);
255+
$patcher = new RecipePatcher(FLEX_TEST_DIR, $this->createMock(IOInterface::class), $lock);
256+
257+
$patchData = $this->generatePatchData('config/packages/security.yaml', '# contents', null);
258+
$hadConflicts = !$patcher->applyPatch(new RecipePatch(
259+
'',
260+
[$patchData['hash'] => $patchData['blob']],
261+
['config/packages/security.yaml']
262+
), 'symfony/security-bundle');
263+
264+
$this->assertFileExists($dir.'/security.yaml');
265+
$this->assertSame('# contents', file_get_contents($dir.'/security.yaml'));
266+
$this->assertFalse($hadConflicts);
267+
}
268+
236269
/**
237270
* @dataProvider getApplyPatchTests
238271
*/
@@ -261,7 +294,7 @@ public function testApplyPatchOnSubfolder(array $filesCurrentlyInApp, RecipePatc
261294
(new Process(['git', 'commit', '-m', 'Committing original files'], $subProjectPath))->mustRun();
262295
}
263296

264-
$patcher = new RecipePatcher($subProjectPath, $this->createMock(IOInterface::class));
297+
$patcher = new RecipePatcher($subProjectPath, $this->createMock(IOInterface::class), $this->createMock(Lock::class));
265298
$hadConflicts = !$patcher->applyPatch($recipePatch);
266299

267300
foreach ($expectedFiles as $file => $expectedContents) {
@@ -390,7 +423,7 @@ public function testIntegration(bool $useNullForMissingFiles)
390423
(new Process(['git', 'add', '-A'], FLEX_TEST_DIR))->mustRun();
391424
(new Process(['git', 'commit', '-m', 'committing in app start files'], FLEX_TEST_DIR))->mustRun();
392425

393-
$patcher = new RecipePatcher(FLEX_TEST_DIR, $this->createMock(IOInterface::class));
426+
$patcher = new RecipePatcher(FLEX_TEST_DIR, $this->createMock(IOInterface::class), $this->createMock(Lock::class));
394427
$originalFiles = [
395428
'.env' => $files['dot_env_clean']['original_recipe'],
396429
'package.json' => $files['package_json_conflict']['original_recipe'],

0 commit comments

Comments
 (0)