#!/usr/bin/env perl use strict; use warnings; our $VERSION = '1.0'; =head1 NAME git-dbic-diff =head1 DESCRIPTION git-dbic-diff is a schema management tool for applications utilizing DBIx::Class and hosted in a git repository. dbic-diff will produce the differences in a DBIx::Class schema between any two git revisions. the diff is presented in the form of SQL statements. when applied to a database deployment matching the source version, these statements will yield the target version. =head1 SYNOPSIS $ git dbic-diff [--db <dbtype>] <class> <treeish1> [<treeish2>] $ git-dbic-diff [--db <dbtype>] <class> <treeish1> [<treeish2>] the second form is invoking the script directly rather than via git. this method may be required if you're using perlbrew and git is using the system's perl. dbtype is any database type recognized by SQL::Translator. the default is "MySQL". if the second treeish is not provided, the diff is executed against the current index. note that modified files must be staged in order to appear in the index. =head1 EXAMPLES $ git dbic-diff MyApp::Schema 3.1.6 HEAD $ git dbic-diff MyApp::Schema HEAD^1 HEAD $ git dbic-diff MyApp::Schema fee00055 =head1 AUTHOR Mike Eldridge <diz@cpan.org> =cut =head1 CONTRIBUTORS Devin Austin <dhoss@cpan.org> =cut use Directory::Scratch; use SQL::Translator; use SQL::Translator::Diff; use SQL::Translator::Schema::Constants; use Getopt::Attribute; use POSIX qw(strftime); our $db : Getopt(db=s SQLite); our $write_to_dir : Getopt(write=s); my $schema = shift; my $source = shift; my $target = shift; if (not $schema or not $source) { print "usage: dbic-diff <class> <treeish1> [<treeish2>]\n"; exit 1; } # set autoflush and create a temporary directory structure for storing # the two trees. $| = 1; my $scratch = new Directory::Scratch; my $dirold = ($source and -d $source) ? $source : $scratch->mkdir('old'); my $dirnew = ($target and -d $target) ? $target : $scratch->mkdir('new'); # give the user a synopsis of what source and target we will be using # during the diff print "-- source: $source\n"; print "-- target: " . ($target || 'index') . "\n"; print "-- exporting $source..."; system("git archive $source | tar xf - -C $dirold"); print "done\n"; if ($target) { print "-- exporting $target..."; system("git archive $target | tar xf - -C $dirnew"); } else { print "-- copying contents of index..."; system("git checkout-index -a --prefix=$dirnew/"); } print "done\n"; my $collector = sub { my $aref = shift; return sub { my $path = shift; if ($path->is_dir) { my $dir = $path->subdir('lib'); push @$aref, $dir if $path->contains($dir); } } }; # find any library paths my @pathold; my @pathnew; $dirold->recurse(callback => $collector->(\@pathold)); $dirnew->recurse(callback => $collector->(\@pathnew)); # build argument list for calls to exec() to create the DDLs my @argsold = ( map(('-I', $_->stringify), @pathold), qq{-MSQL::Translator::Schema::Constraint}, qq{-M$schema}, qq{-e}, qq{$schema->connect('dbd:nullp')->create_ddl_dir('$db', 'OLD', '$scratch')} ); my @argsnew = ( map(('-I', $_->stringify), @pathnew), qq{-MSQL::Translator::Schema::Constraint}, qq{-M$schema}, qq{-e}, qq{$schema->connect('dbd:nullp')->create_ddl_dir('$db', 'NEW', '$scratch')} ); # fork and create DDLs for both the OLD and the NEW versions print "-- creating DDLs..."; if (my $pid = fork) { waitpid $pid, 0; } else { close STDOUT; close STDERR; exec perl => @argsold; } if (my $pid = fork) { waitpid $pid, 0; } else { close STDOUT; close STDERR; exec perl => @argsnew; } print "done\n"; # then read the two schemas into SQL::Translator objects and # perform a diff between the two using SQL::Translator::Diff print "-- performing diff..."; (my $file = $schema) =~ s/::/-/g; my $old = new SQL::Translator parser => $db; my $new = new SQL::Translator parser => $db; $old->translate("$scratch/$file-OLD-$db.sql"); $new->translate("$scratch/$file-NEW-$db.sql"); $old->schema->name($source); $new->schema->name($target || 'index'); my $opts = { caseopt => 0, ignore_index_names => 0, ignore_constraint_names => 0, ignore_view_sql => 0, ignore_proc_sql => 0, output_db => $db, no_batch_alters => 1, debug => 1, trace => 1 }; my $diff = SQL::Translator::Diff::schema_diff($old->schema, $db, $new->schema, $db, $opts); print "done\n\n"; write_sql_to_directory($write_to_dir) if $write_to_dir; print $diff; sub write_sql_to_directory { my $dir = shift; $target ||= 'index'; my $file = "$dir/$source-$target"; $file .= ".sql"; print "Writing SQL to $file...\n"; open my $fh, ">", $file || die "Can't open $file: $!"; print $fh $diff; close $fh; print "Done!\n"; }