From 0eabef5834738945054035c30de8cc5210dea189 Mon Sep 17 00:00:00 2001 From: Yves P Date: Mon, 14 Sep 2015 22:08:03 +0200 Subject: [PATCH 1/4] Allow to migrate all migrations with one "mark_migrated" call Added a special value "all" for the "version" argument of the "mark_migrated" command. It allows to mark all migrations found (based on the other arguments) as migrated in one call. Everything is done in one transaction. If one the marking as migrated operations raises an exception, the entire process is cancelled and the transaction rollbacked. --- src/Command/MarkMigrated.php | 80 ++++++++++++++++++++- tests/TestCase/Command/MarkMigratedTest.php | 71 ++++++++++++++++++ tests/TestCase/MigrationsTest.php | 3 +- 3 files changed, 151 insertions(+), 3 deletions(-) diff --git a/src/Command/MarkMigrated.php b/src/Command/MarkMigrated.php index 81932144..5fecfe27 100644 --- a/src/Command/MarkMigrated.php +++ b/src/Command/MarkMigrated.php @@ -22,6 +22,25 @@ class MarkMigrated extends AbstractCommand use ConfigurationTrait; + /** + * The console output instance + * + * @var \Symfony\Component\Console\Output\OutputInterface + */ + protected $output; + + /** + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return mixed + */ + public function output(OutputInterface $output = null) + { + if ($output !== null) { + $this->output = $output; + } + return $this->output; + } + /** * {@inheritdoc} */ @@ -29,7 +48,11 @@ protected function configure() { $this->setName('mark_migrated') ->setDescription('Mark a migration as migrated') - ->addArgument('version', InputArgument::REQUIRED, 'What is the version of the migration?') + ->addArgument( + 'version', + InputArgument::REQUIRED, + 'What is the version of the migration? Use the special value `all` to mark all migrations as migrated.' + ) ->setHelp(sprintf( '%sMark a migration migrated based on its version number%s', PHP_EOL, @@ -42,6 +65,8 @@ protected function configure() /** * Mark a migration migrated + * If the `version` argument has the value `all`, all migrations found will be marked as + * migrated * * @param \Symfony\Component\Console\Input\InputInterface $input the input object * @param \Symfony\Component\Console\Output\OutputInterface $output the output object @@ -51,10 +76,16 @@ protected function execute(InputInterface $input, OutputInterface $output) { $this->setInput($input); $this->bootstrap($input, $output); + $this->output($output); $path = $this->getConfig()->getMigrationPath(); $version = $input->getArgument('version'); + if ($version === 'all') { + $this->markAllMigrated($path); + return; + } + if ($this->getManager()->isMigrated($version)) { $output->writeln( sprintf( @@ -72,4 +103,51 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(sprintf('An error occurred : %s', $e->getMessage())); } } + + /** + * Mark all migrations found in $path as migrated + * + * It will start a transaction and rollback in case one of the operation raises an exception + * + * @param string $path Path where to look for migrations + * @return void + */ + protected function markAllMigrated($path) + { + $manager = $this->getManager(); + $adapter = $manager->getEnvironment('default')->getAdapter(); + $migrations = $manager->getMigrations(); + $output = $this->output(); + + if (empty($migrations)) { + $output->writeln('No migrations were found. Nothing to mark as migrated.'); + return; + } + + $adapter->beginTransaction(); + foreach ($migrations as $version => $migration) { + if ($manager->isMigrated($version)) { + $output->writeln(sprintf('Skipping migration `%s` (already migrated).', $version)); + } else { + try { + $this->getManager()->markMigrated($version, $path); + $output->writeln( + sprintf('Migration `%s` successfully marked migrated !', $version) + ); + } catch (\Exception $e) { + $adapter->rollbackTransaction(); + $output->writeln( + sprintf( + 'An error occurred while marking migration `%s` as migrated : %s', + $version, + $e->getMessage() + ) + ); + $output->writeln('All marked migrations during this process were unmarked.'); + return; + } + } + } + $adapter->commitTransaction(); + } } diff --git a/tests/TestCase/Command/MarkMigratedTest.php b/tests/TestCase/Command/MarkMigratedTest.php index 24f9de8c..55b6ac2b 100644 --- a/tests/TestCase/Command/MarkMigratedTest.php +++ b/tests/TestCase/Command/MarkMigratedTest.php @@ -14,6 +14,7 @@ use Cake\Datasource\ConnectionManager; use Cake\TestSuite\TestCase; use Migrations\MigrationsDispatcher; +use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Tester\CommandTester; /** @@ -133,4 +134,74 @@ public function testExecute() $result = $this->Connection->newQuery()->select(['*'])->from('phinxlog')->execute()->count(); $this->assertEquals(1, $result); } + + /** + * Test executing "mark_migration" + * + * @return void + */ + public function testExecuteAll() + { + $this->commandTester->execute([ + 'command' => $this->command->getName(), + 'version' => 'all', + '--connection' => 'test', + '--source' => 'TestsMigrations' + ]); + + $this->assertContains('Migration `20150826191400` successfully marked migrated !', $this->commandTester->getDisplay()); + $this->assertContains('Migration `20150724233100` successfully marked migrated !', $this->commandTester->getDisplay()); + $this->assertContains('Migration `20150704160200` successfully marked migrated !', $this->commandTester->getDisplay()); + + $result = $this->Connection->newQuery()->select(['*'])->from('phinxlog')->execute()->fetchAll('assoc'); + $this->assertEquals('20150704160200', $result[0]['version']); + $this->assertEquals('20150724233100', $result[1]['version']); + $this->assertEquals('20150826191400', $result[2]['version']); + + $this->commandTester->execute([ + 'command' => $this->command->getName(), + 'version' => 'all', + '--connection' => 'test', + '--source' => 'TestsMigrations' + ]); + + $this->assertContains('Skipping migration `20150704160200` (already migrated).', $this->commandTester->getDisplay()); + $this->assertContains('Skipping migration `20150724233100` (already migrated).', $this->commandTester->getDisplay()); + $this->assertContains('Skipping migration `20150826191400` (already migrated).', $this->commandTester->getDisplay()); + + $config = $this->command->getConfig(); + $env = $this->command->getManager()->getEnvironment('default'); + $migrations = $this->command->getManager()->getMigrations(); + + $manager = $this->getMock( + '\Migrations\CakeManager', + ['getEnvironment', 'markMigrated'], + [$config, new StreamOutput(fopen('php://memory', 'a', false))] + ); + + $manager->expects($this->any()) + ->method('getEnvironment')->will($this->returnValue($env)); + $manager->expects($this->any()) + ->method('getMigrations')->will($this->returnValue($migrations)); + $manager + ->method('markMigrated')->will($this->throwException(new \Exception('Error during marking process'))); + + $this->Connection->execute('DELETE FROM phinxlog'); + + $application = new MigrationsDispatcher('testing'); + $buggyCommand = $application->find('mark_migrated'); + $buggyCommand->setManager($manager); + $buggyCommandTester = new CommandTester($buggyCommand); + $buggyCommandTester->execute([ + 'command' => $this->command->getName(), + 'version' => 'all', + '--connection' => 'test', + '--source' => 'TestsMigrations' + ]); + + $this->assertContains( + 'An error occurred while marking migration `20150704160200` as migrated : Error during marking process', + $buggyCommandTester->getDisplay() + ); + } } diff --git a/tests/TestCase/MigrationsTest.php b/tests/TestCase/MigrationsTest.php index 51d31fd7..e014c139 100644 --- a/tests/TestCase/MigrationsTest.php +++ b/tests/TestCase/MigrationsTest.php @@ -208,8 +208,7 @@ public function testMigrateErrors() */ public function testRollbackErrors() { - $this->migrations->markMigrated(20150704160200); - $this->migrations->markMigrated(20150724233100); + $this->migrations->markMigrated('all'); $this->migrations->rollback(); } From a848cec4e184fb18ea0d78e92d4c6ff5fe97ba3d Mon Sep 17 00:00:00 2001 From: Yves P Date: Tue, 15 Sep 2015 19:01:41 +0200 Subject: [PATCH 2/4] Update the README with the new `all` option of the `mark_migrated` command --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index bd6e266d..85a80f7d 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,10 @@ bin/cake migrations status -c my_datasource # The following will mark targeted migration as marked without actually running it. # The expected argument is the migration version number bin/cake migrations mark_migrated 20150417223600 + +# Since Migrations 1.3.1, a new `all` special value for the version argumentwas added. +# The following will mark all migrations found as migrated. +bin/cake migrations mark_migrated all ``` ### Creating Migrations From 7f1b25b2f502d3b3a2acbbc65dca5fad12efcf24 Mon Sep 17 00:00:00 2001 From: Yves P Date: Wed, 16 Sep 2015 19:34:54 +0200 Subject: [PATCH 3/4] Add support the "*" value for the version argument. --- src/Command/MarkMigrated.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/MarkMigrated.php b/src/Command/MarkMigrated.php index 5fecfe27..99194067 100644 --- a/src/Command/MarkMigrated.php +++ b/src/Command/MarkMigrated.php @@ -81,7 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $path = $this->getConfig()->getMigrationPath(); $version = $input->getArgument('version'); - if ($version === 'all') { + if ($version === 'all' || $version === '*') { $this->markAllMigrated($path); return; } From b38c91d33230ba2eaa3e46ee9b45098bfc6b475d Mon Sep 17 00:00:00 2001 From: Yves P Date: Wed, 30 Sep 2015 19:38:38 +0200 Subject: [PATCH 4/4] Fix CS --- tests/TestCase/Command/MarkMigratedTest.php | 30 ++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/tests/TestCase/Command/MarkMigratedTest.php b/tests/TestCase/Command/MarkMigratedTest.php index 55b6ac2b..ad6531de 100644 --- a/tests/TestCase/Command/MarkMigratedTest.php +++ b/tests/TestCase/Command/MarkMigratedTest.php @@ -149,9 +149,18 @@ public function testExecuteAll() '--source' => 'TestsMigrations' ]); - $this->assertContains('Migration `20150826191400` successfully marked migrated !', $this->commandTester->getDisplay()); - $this->assertContains('Migration `20150724233100` successfully marked migrated !', $this->commandTester->getDisplay()); - $this->assertContains('Migration `20150704160200` successfully marked migrated !', $this->commandTester->getDisplay()); + $this->assertContains( + 'Migration `20150826191400` successfully marked migrated !', + $this->commandTester->getDisplay() + ); + $this->assertContains( + 'Migration `20150724233100` successfully marked migrated !', + $this->commandTester->getDisplay() + ); + $this->assertContains( + 'Migration `20150704160200` successfully marked migrated !', + $this->commandTester->getDisplay() + ); $result = $this->Connection->newQuery()->select(['*'])->from('phinxlog')->execute()->fetchAll('assoc'); $this->assertEquals('20150704160200', $result[0]['version']); @@ -165,9 +174,18 @@ public function testExecuteAll() '--source' => 'TestsMigrations' ]); - $this->assertContains('Skipping migration `20150704160200` (already migrated).', $this->commandTester->getDisplay()); - $this->assertContains('Skipping migration `20150724233100` (already migrated).', $this->commandTester->getDisplay()); - $this->assertContains('Skipping migration `20150826191400` (already migrated).', $this->commandTester->getDisplay()); + $this->assertContains( + 'Skipping migration `20150704160200` (already migrated).', + $this->commandTester->getDisplay() + ); + $this->assertContains( + 'Skipping migration `20150724233100` (already migrated).', + $this->commandTester->getDisplay() + ); + $this->assertContains( + 'Skipping migration `20150826191400` (already migrated).', + $this->commandTester->getDisplay() + ); $config = $this->command->getConfig(); $env = $this->command->getManager()->getEnvironment('default');