Skip to content

Commit aa90092

Browse files
Add cleanup option for status. (#919)
* Add cleanup option for status. * Add cleanup option for status. * Docs. * Use bulk delete query * Update src/Command/StatusCommand.php Co-authored-by: Mark Story <mark@mark-story.com>
1 parent 19629b5 commit aa90092

File tree

5 files changed

+120
-1
lines changed

5 files changed

+120
-1
lines changed

docs/en/index.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,21 @@ You can also output the results as a JSON formatted string using the
550550
You can also use the ``--source``, ``--connection`` and ``--plugin`` options
551551
just like for the ``migrate`` command.
552552

553+
Cleaning up missing migrations
554+
-------------------------------
555+
556+
Sometimes migration files may be deleted from the filesystem but still exist
557+
in the phinxlog table. These migrations will be marked as **MISSING** in the
558+
status output. You can remove these entries from the phinxlog table using the
559+
``--cleanup`` option:
560+
561+
.. code-block:: bash
562+
563+
bin/cake migrations status --cleanup
564+
565+
This will remove all migration entries from the phinxlog table that no longer
566+
have corresponding migration files in the filesystem.
567+
553568
Marking a migration as migrated
554569
===============================
555570

src/Command/StatusCommand.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
6363
'',
6464
'<info>migrations status -c secondary</info>',
6565
'<info>migrations status -c secondary -f json</info>',
66+
'<info>migrations status --cleanup</info>',
67+
'Remove *MISSING* migrations from the phinxlog table',
6668
])->addOption('plugin', [
6769
'short' => 'p',
6870
'help' => 'The plugin to run migrations for',
@@ -79,6 +81,10 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
7981
'help' => 'The output format: text or json. Defaults to text.',
8082
'choices' => ['text', 'json'],
8183
'default' => 'text',
84+
])->addOption('cleanup', [
85+
'help' => 'Remove MISSING migrations from the phinxlog table',
86+
'boolean' => true,
87+
'default' => false,
8288
]);
8389

8490
return $parser;
@@ -95,6 +101,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
95101
{
96102
/** @var string|null $format */
97103
$format = $args->getOption('format');
104+
$clean = $args->getOption('cleanup');
98105

99106
$factory = new ManagerFactory([
100107
'plugin' => $args->getOption('plugin'),
@@ -103,6 +110,18 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
103110
'dry-run' => $args->getOption('dry-run'),
104111
]);
105112
$manager = $factory->createManager($io);
113+
114+
if ($clean) {
115+
$removed = $manager->cleanupMissingMigrations();
116+
if ($removed === 0) {
117+
$io->out('<info>No missing migrations to clean up.</info>');
118+
} else {
119+
$io->out(sprintf('<info>Removed %d missing migration(s) from migration log.</info>', $removed));
120+
}
121+
122+
return Command::CODE_SUCCESS;
123+
}
124+
106125
$migrations = $manager->printStatus($format);
107126

108127
switch ($format) {

src/Migration/Manager.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,4 +1183,47 @@ public function resetSeeds(): void
11831183
{
11841184
$this->seeds = null;
11851185
}
1186+
1187+
/**
1188+
* Cleanup missing migrations from the phinxlog table
1189+
*
1190+
* Removes entries from the phinxlog table for migrations that no longer exist
1191+
* in the migrations directory (marked as MISSING in status output).
1192+
*
1193+
* @return int The number of missing migrations removed
1194+
*/
1195+
public function cleanupMissingMigrations(): int
1196+
{
1197+
$defaultMigrations = $this->getMigrations();
1198+
$env = $this->getEnvironment();
1199+
$versions = $env->getVersionLog();
1200+
$adapter = $env->getAdapter();
1201+
1202+
// Find missing migrations (those in phinxlog but not in filesystem)
1203+
$missingVersions = [];
1204+
foreach ($versions as $versionId => $versionInfo) {
1205+
if (!isset($defaultMigrations[$versionId])) {
1206+
$missingVersions[] = $versionId;
1207+
}
1208+
}
1209+
1210+
if (!$missingVersions) {
1211+
return 0;
1212+
}
1213+
1214+
// Remove missing migrations from phinxlog
1215+
$adapter->beginTransaction();
1216+
try {
1217+
$delete = $adapter->getDeleteBuilder()
1218+
->from($env->getSchemaTableName())
1219+
->where(['version IN' => $missingVersions]);
1220+
$delete->execute();
1221+
$adapter->commitTransaction();
1222+
} catch (Exception $e) {
1223+
$adapter->rollbackTransaction();
1224+
throw $e;
1225+
}
1226+
1227+
return count($missingVersions);
1228+
}
11861229
}

tests/TestCase/Command/CompletionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public function testMigrationsOptionsStatus()
124124
$this->exec('completion options migrations.migrations status');
125125
$this->assertCount(1, $this->_out->messages());
126126
$output = $this->_out->messages()[0];
127-
$expected = '--connection -c --format -f --help -h --plugin -p --quiet -q --source -s --verbose -v';
127+
$expected = '--cleanup --connection -c --format -f --help -h --plugin -p --quiet -q --source -s --verbose -v';
128128
$outputExplode = explode(' ', trim($output));
129129
sort($outputExplode);
130130
$expectedExplode = explode(' ', $expected);

tests/TestCase/Command/StatusCommandTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,46 @@ public function testExecuteConnectionDoesNotExist(): void
7878
$this->expectException(RuntimeException::class);
7979
$this->exec('migrations status -c lolnope');
8080
}
81+
82+
public function testCleanNoMissingMigrations(): void
83+
{
84+
$this->exec('migrations status -c test --cleanup');
85+
$this->assertExitSuccess();
86+
$this->assertOutputContains('No missing migrations to clean up.');
87+
}
88+
89+
public function testCleanWithMissingMigrations(): void
90+
{
91+
// First, insert a fake migration entry that doesn't exist in filesystem
92+
$table = $this->fetchTable('Phinxlog');
93+
$entity = $table->newEntity([
94+
'version' => 99999999999999,
95+
'migration_name' => 'FakeMissingMigration',
96+
'start_time' => '2024-01-01 00:00:00',
97+
'end_time' => '2024-01-01 00:00:01',
98+
'breakpoint' => false,
99+
]);
100+
$table->save($entity);
101+
102+
// Verify the fake migration is in the table
103+
$count = $table->find()->where(['version' => 99999999999999])->count();
104+
$this->assertEquals(1, $count);
105+
106+
// Run the clean command
107+
$this->exec('migrations status -c test --cleanup');
108+
$this->assertExitSuccess();
109+
$this->assertOutputContains('Removed 1 missing migration(s) from migration log.');
110+
111+
// Verify the fake migration was removed
112+
$count = $table->find()->where(['version' => 99999999999999])->count();
113+
$this->assertEquals(0, $count);
114+
}
115+
116+
public function testCleanHelp(): void
117+
{
118+
$this->exec('migrations status --help');
119+
$this->assertExitSuccess();
120+
$this->assertOutputContains('--cleanup');
121+
$this->assertOutputContains('Remove MISSING migrations from the phinxlog table');
122+
}
81123
}

0 commit comments

Comments
 (0)