Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/en/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,21 @@ You can also output the results as a JSON formatted string using the
You can also use the ``--source``, ``--connection`` and ``--plugin`` options
just like for the ``migrate`` command.

Cleaning up missing migrations
-------------------------------

Sometimes migration files may be deleted from the filesystem but still exist
in the phinxlog table. These migrations will be marked as **MISSING** in the
status output. You can remove these entries from the phinxlog table using the
``--cleanup`` option:

.. code-block:: bash

bin/cake migrations status --cleanup

This will remove all migration entries from the phinxlog table that no longer
have corresponding migration files in the filesystem.

Marking a migration as migrated
===============================

Expand Down
19 changes: 19 additions & 0 deletions src/Command/StatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
'',
'<info>migrations status -c secondary</info>',
'<info>migrations status -c secondary -f json</info>',
'<info>migrations status --cleanup</info>',
'Remove *MISSING* migrations from the phinxlog table',
])->addOption('plugin', [
'short' => 'p',
'help' => 'The plugin to run migrations for',
Expand All @@ -79,6 +81,10 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
'help' => 'The output format: text or json. Defaults to text.',
'choices' => ['text', 'json'],
'default' => 'text',
])->addOption('cleanup', [
'help' => 'Remove MISSING migrations from the phinxlog table',
'boolean' => true,
'default' => false,
]);

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

$factory = new ManagerFactory([
'plugin' => $args->getOption('plugin'),
Expand All @@ -103,6 +110,18 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
'dry-run' => $args->getOption('dry-run'),
]);
$manager = $factory->createManager($io);

if ($clean) {
$removed = $manager->cleanupMissingMigrations();
if ($removed === 0) {
$io->out('<info>No missing migrations to clean up.</info>');
} else {
$io->out(sprintf('<info>Removed %d missing migration(s) from migration log.</info>', $removed));
}

return Command::CODE_SUCCESS;
}

$migrations = $manager->printStatus($format);

switch ($format) {
Expand Down
43 changes: 43 additions & 0 deletions src/Migration/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -1183,4 +1183,47 @@ public function resetSeeds(): void
{
$this->seeds = null;
}

/**
* Cleanup missing migrations from the phinxlog table
*
* Removes entries from the phinxlog table for migrations that no longer exist
* in the migrations directory (marked as MISSING in status output).
*
* @return int The number of missing migrations removed
*/
public function cleanupMissingMigrations(): int
{
$defaultMigrations = $this->getMigrations();
$env = $this->getEnvironment();
$versions = $env->getVersionLog();
$adapter = $env->getAdapter();

// Find missing migrations (those in phinxlog but not in filesystem)
$missingVersions = [];
foreach ($versions as $versionId => $versionInfo) {
if (!isset($defaultMigrations[$versionId])) {
$missingVersions[] = $versionId;
}
}

if (!$missingVersions) {
return 0;
}

// Remove missing migrations from phinxlog
$adapter->beginTransaction();
try {
$delete = $adapter->getDeleteBuilder()
->from($env->getSchemaTableName())
->where(['version IN' => $missingVersions]);
$delete->execute();
$adapter->commitTransaction();
} catch (Exception $e) {
$adapter->rollbackTransaction();
throw $e;
}

return count($missingVersions);
}
}
2 changes: 1 addition & 1 deletion tests/TestCase/Command/CompletionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public function testMigrationsOptionsStatus()
$this->exec('completion options migrations.migrations status');
$this->assertCount(1, $this->_out->messages());
$output = $this->_out->messages()[0];
$expected = '--connection -c --format -f --help -h --plugin -p --quiet -q --source -s --verbose -v';
$expected = '--cleanup --connection -c --format -f --help -h --plugin -p --quiet -q --source -s --verbose -v';
$outputExplode = explode(' ', trim($output));
sort($outputExplode);
$expectedExplode = explode(' ', $expected);
Expand Down
42 changes: 42 additions & 0 deletions tests/TestCase/Command/StatusCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,46 @@ public function testExecuteConnectionDoesNotExist(): void
$this->expectException(RuntimeException::class);
$this->exec('migrations status -c lolnope');
}

public function testCleanNoMissingMigrations(): void
{
$this->exec('migrations status -c test --cleanup');
$this->assertExitSuccess();
$this->assertOutputContains('No missing migrations to clean up.');
}

public function testCleanWithMissingMigrations(): void
{
// First, insert a fake migration entry that doesn't exist in filesystem
$table = $this->fetchTable('Phinxlog');
$entity = $table->newEntity([
'version' => 99999999999999,
'migration_name' => 'FakeMissingMigration',
'start_time' => '2024-01-01 00:00:00',
'end_time' => '2024-01-01 00:00:01',
'breakpoint' => false,
]);
$table->save($entity);

// Verify the fake migration is in the table
$count = $table->find()->where(['version' => 99999999999999])->count();
$this->assertEquals(1, $count);

// Run the clean command
$this->exec('migrations status -c test --cleanup');
$this->assertExitSuccess();
$this->assertOutputContains('Removed 1 missing migration(s) from migration log.');

// Verify the fake migration was removed
$count = $table->find()->where(['version' => 99999999999999])->count();
$this->assertEquals(0, $count);
}

public function testCleanHelp(): void
{
$this->exec('migrations status --help');
$this->assertExitSuccess();
$this->assertOutputContains('--cleanup');
$this->assertOutputContains('Remove MISSING migrations from the phinxlog table');
}
}
Loading