[Rt-commit] rt branch, 4.4/serialize-json-initialdata, updated. rt-4.4.1-443-g1cf47f3

Shawn Moore shawn at bestpractical.com
Fri Mar 24 13:59:41 EDT 2017


The branch, 4.4/serialize-json-initialdata has been updated
       via  1cf47f3816e864fd049a13a75f453541c2c84c7f (commit)
       via  2b7e860b10c5f56f678897110ba64930345d2236 (commit)
       via  d93b6de21557b06bcccbfab41faff4ca2dd2dd2f (commit)
       via  5ba8bef3baf8653a4bfeaec50836a603c7ac34a4 (commit)
       via  a2b04e3a7d85ad8b49b51e57a57e3ae39101a8ac (commit)
       via  d5d8803ddd6d4b751b825863c2a2891a2fa31853 (commit)
      from  e3621bc2c0a64b937ce94e844a87f031eeb9e4ea (commit)

Summary of changes:
 lib/RT/Migrate/Serializer/JSON.pm |  10 ++-
 sbin/rt-merge-initialdata.in      | 180 +++++++++++++++++++++++++++++++++++---
 2 files changed, 176 insertions(+), 14 deletions(-)

- Log -----------------------------------------------------------------
commit d5d8803ddd6d4b751b825863c2a2891a2fa31853
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 23 22:07:38 2017 +0000

    Default FollowTickets and FollowTransactions to 0 for initialdata
    
    These don't round-trip anyway

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index 1d7e524..c849ddb 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -59,8 +59,10 @@ sub Init {
     my $self = shift;
 
     my %args = (
-        Directory   => undef,
-        Force       => undef,
+        Directory           => undef,
+        Force               => undef,
+        FollowTickets       => 0,
+        FollowTransactions  => 0,
 
         @_,
     );
@@ -79,7 +81,7 @@ sub Init {
 
     $self->{Records} = {};
 
-    $self->SUPER::Init(@_);
+    $self->SUPER::Init(%args);
 }
 
 sub Export {

commit a2b04e3a7d85ad8b49b51e57a57e3ae39101a8ac
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri Mar 24 17:16:20 2017 +0000

    Default to exporting scrips and ACLs

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index c849ddb..48ebdf8 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -63,6 +63,8 @@ sub Init {
         Force               => undef,
         FollowTickets       => 0,
         FollowTransactions  => 0,
+        FollowScrips        => 1,
+        FollowACL           => 1,
 
         @_,
     );

commit 5ba8bef3baf8653a4bfeaec50836a603c7ac34a4
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri Mar 24 17:46:47 2017 +0000

    Add CLI opt parser and docs for rt-merge-initialdata

diff --git a/sbin/rt-merge-initialdata.in b/sbin/rt-merge-initialdata.in
index 463b29a..dfb3649 100644
--- a/sbin/rt-merge-initialdata.in
+++ b/sbin/rt-merge-initialdata.in
@@ -79,12 +79,27 @@ use RT;
 RT::LoadConfig();
 RT::Init();
 
+use Getopt::Long;
+use Pod::Usage qw//;
 use List::MoreUtils 'uniq';
 use JSON ();
 my $JSON = JSON->new->canonical;
 
-die "usage: $0 base edited\n" unless @ARGV == 2;
-my ($base_file, $edited_file) = @ARGV;
+my %OPT;
+GetOptions(
+    \%OPT,
+    "help|h",
+
+    "base=s",
+    "edited=s",
+);
+
+exit Pod::Usage::pod2usage(-verbose => 1) if $OPT{help};
+
+my $base_file = $OPT{base};
+my $edited_file = $OPT{edited};
+
+exit Pod::Usage::pod2usage unless $base_file && $edited_file;
 
 my $base_records = slurp_json($base_file);
 my $edited_records = slurp_json($edited_file);
@@ -316,3 +331,51 @@ sub decide_merge {
     }
 }
 
+=head1 NAME
+
+rt-merge-initialdata - Merge changes from an edited initialdata
+
+=head1 SYNOPSIS
+
+    rt-merge-initialdata --base initialdata.json --edited updated.json
+
+
+This tool allows you to edit a JSON-based initialdata file and merge
+those changes into your RT instance. The intended workflow is you first
+create an initialdata from your running RT config with:
+
+    sbin/rt-serializer --sync --format JSON
+
+Then, copy the resulting C<initialdata.json> into a new file. Edit that
+C<updated.json> to create, update, and delete records (see below for
+considerations).
+
+Then run F<rt-merge-initialdata> like so:
+
+    rt-merge-initialdata --base initialdata.json --edited updated.json
+
+F<rt-merge-initialdata> will update any records that you edited, create
+any records you added, and disable (or delete) any records you removed.
+Note that the C<id> field is used to track identity across the base,
+edited, and database versions of the same record, so take care when
+dealing with it. (Any record without an C<id> is treated as a new one to
+be created, and any record in the base which does not have a
+corresponding C<id> in the edited file will be disabled/deleted).
+
+=head2 OPTIONS
+
+=over
+
+=item B<--base> I<filename>
+
+The filename of the "base" initialdata. This should be a pristine initialdata
+file exported by F<sbin/rt-serializer>.
+
+=item B<--edited> I<filename>
+
+The filename of the "edited" initialdata. This should be a copy of the
+filename provided to C<--base> with your edits made to it.
+
+=back
+
+=cut

commit d93b6de21557b06bcccbfab41faff4ca2dd2dd2f
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri Mar 24 17:47:34 2017 +0000

    Add --merge-strategy flag

diff --git a/sbin/rt-merge-initialdata.in b/sbin/rt-merge-initialdata.in
index dfb3649..29c4c90 100644
--- a/sbin/rt-merge-initialdata.in
+++ b/sbin/rt-merge-initialdata.in
@@ -92,6 +92,7 @@ GetOptions(
 
     "base=s",
     "edited=s",
+    "merge-strategy=s",
 );
 
 exit Pod::Usage::pod2usage(-verbose => 1) if $OPT{help};
@@ -101,6 +102,13 @@ my $edited_file = $OPT{edited};
 
 exit Pod::Usage::pod2usage unless $base_file && $edited_file;
 
+my $merge_strategy = $OPT{'merge-strategy'};
+
+die "Invalid value for --merge-strategy. Expected 'base', 'current', 'edited', or 'quit'."
+    unless !$merge_strategy || $merge_strategy =~ /^(base|current|edited|quit)$/;
+
+RT->Config->Set( LogToSTDERR => $OPT{log} ) if $OPT{log};
+
 my $base_records = slurp_json($base_file);
 my $edited_records = slurp_json($edited_file);
 
@@ -306,6 +314,13 @@ sub decide_merge {
         print "    Base value:     $args{base_value}\n";
         print "    Edited value:   $args{edited_value}\n";
         print "    Database value: $args{current_value}\n";
+
+        if ($merge_strategy) {
+            print "Using specified merge strategy '$merge_strategy'.\n" unless $OPT{quiet};
+            exit if $merge_strategy eq 'quit';
+            return $merge_strategy;
+        }
+
         print "Would you like to (q)uit, (s)ee more context, or set value to (b)ase, (e)dited, or (d)atabase? ";
 
         my $answer = (scalar <STDIN>) // "";
@@ -362,6 +377,13 @@ dealing with it. (Any record without an C<id> is treated as a new one to
 be created, and any record in the base which does not have a
 corresponding C<id> in the edited file will be disabled/deleted).
 
+Any changes made in the database after the base was exported will be
+untouched by F<rt-merge-initialdata>. If it turns out that a field was
+changed in both the edited initialdata I<and> the database, then a
+conflict will be presented to you with a prompt asking which version to
+keep. The C<--merge-strategy> flag lets you decide in advance to handle
+all such conflicts using a consistent strategy, to avoid prompting.
+
 =head2 OPTIONS
 
 =over
@@ -376,6 +398,35 @@ file exported by F<sbin/rt-serializer>.
 The filename of the "edited" initialdata. This should be a copy of the
 filename provided to C<--base> with your edits made to it.
 
+=item B<--merge-strategy> I<strategy>
+
+Automatically resolve all conflicts without prompting the user. The
+following merge strategies are available:
+
+=over 4
+
+=item C<edited>
+
+Take the edits as specified in the edited initialdata, overwriting the
+change made in the database after the base was exported.
+
+=item C<current>
+
+Keep the database's current value, ignoring the change made in the
+edited initialdata.
+
+=item C<base>
+
+Revert to the base initialdata's value, rolling back the change made in
+the database and ignoring the change made in the edited initialdata.
+
+=item C<quit>
+
+Abort processing at the first conflict. Note that this will likely
+result in a subset of the edits being applied to your database.
+
+=back
+
 =back
 
 =cut

commit 2b7e860b10c5f56f678897110ba64930345d2236
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri Mar 24 17:47:50 2017 +0000

    Add --quiet and --log options

diff --git a/sbin/rt-merge-initialdata.in b/sbin/rt-merge-initialdata.in
index 29c4c90..d5fc6f0 100644
--- a/sbin/rt-merge-initialdata.in
+++ b/sbin/rt-merge-initialdata.in
@@ -89,6 +89,8 @@ my %OPT;
 GetOptions(
     \%OPT,
     "help|h",
+    "quiet|q!",
+    "log=s",
 
     "base=s",
     "edited=s",
@@ -133,6 +135,7 @@ for my $type (@record_types) {
         $edited_records->{$type},
         $type,
     );
+    RT->Logger->info("Merging changes from $type: " . scalar(@$new_records) . " new records, " . scalar(@$updated_records) . " updated, " . scalar(@$deleted_records) . " deleted");
 
     my $collection_class = "RT::" . ($class_type{$type} || $type);
     my $record_class = $collection_class->RecordClass;
@@ -309,11 +312,13 @@ sub decide_merge {
 
     local $| = 1;
     while (1) {
-        print "\n";
-        print "Conflict resolution required for $type #$id $args{column}:\n";
-        print "    Base value:     $args{base_value}\n";
-        print "    Edited value:   $args{edited_value}\n";
-        print "    Database value: $args{current_value}\n";
+        unless ($OPT{quiet} && $merge_strategy) {
+            print "\n";
+            print "Conflict resolution required for $type #$id $args{column}:\n";
+            print "    Base value:     $args{base_value}\n";
+            print "    Edited value:   $args{edited_value}\n";
+            print "    Database value: $args{current_value}\n";
+        }
 
         if ($merge_strategy) {
             print "Using specified merge strategy '$merge_strategy'.\n" unless $OPT{quiet};
@@ -427,6 +432,14 @@ result in a subset of the edits being applied to your database.
 
 =back
 
+=item B<--quiet>
+
+Avoid nonessential output.
+
+=item B<--log> I<level>
+
+Adjust LogToSTDERR config option.
+
 =back
 
 =cut

commit 1cf47f3816e864fd049a13a75f453541c2c84c7f
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri Mar 24 17:58:12 2017 +0000

    Add --dryrun flag

diff --git a/sbin/rt-merge-initialdata.in b/sbin/rt-merge-initialdata.in
index d5fc6f0..911b718 100644
--- a/sbin/rt-merge-initialdata.in
+++ b/sbin/rt-merge-initialdata.in
@@ -77,7 +77,6 @@ BEGIN {
 
 use RT;
 RT::LoadConfig();
-RT::Init();
 
 use Getopt::Long;
 use Pod::Usage qw//;
@@ -91,6 +90,7 @@ GetOptions(
     "help|h",
     "quiet|q!",
     "log=s",
+    "dryrun",
 
     "base=s",
     "edited=s",
@@ -111,6 +111,11 @@ die "Invalid value for --merge-strategy. Expected 'base', 'current', 'edited', o
 
 RT->Config->Set( LogToSTDERR => $OPT{log} ) if $OPT{log};
 
+RT::Init();
+
+my $dryrun = $OPT{'dryrun'};
+RT->Logger->debug("dryrun enabled") if $dryrun;
+
 my $base_records = slurp_json($base_file);
 my $edited_records = slurp_json($edited_file);
 
@@ -140,7 +145,12 @@ for my $type (@record_types) {
     my $collection_class = "RT::" . ($class_type{$type} || $type);
     my $record_class = $collection_class->RecordClass;
 
-    $new_for_insertdata{$type} = $new_records;
+    if ($dryrun) {
+        RT->Logger->debug("Skipping create of " . scalar(@$new_records) . "x $type because of dry run.") if @$new_records;
+    }
+    else {
+        $new_for_insertdata{$type} = $new_records;
+    }
 
     for (@$updated_records) {
         my ($base, $edited) = @$_;
@@ -193,6 +203,11 @@ for my $type (@record_types) {
                         }
                     }
 
+                    if ($dryrun) {
+                        RT->Logger->debug("Skipping update $record_class #$id $column from '$current_value' to '$new_value' because of dry run.");
+                        next;
+                    }
+
                     my ($ok, $msg) = $record->$method($new_value);
                     if ($ok) {
                         RT->Logger->debug("Updated $record_class #$id $column: $msg");
@@ -218,9 +233,17 @@ for my $type (@record_types) {
 
         my ($ok, $msg);
         if ($record->can('SetDisabled') || $record->_Accessible('Disabled', 'write')) {
+            if ($dryrun) {
+                RT->Logger->debug("Skipping disabling of $record_class #$id because of dry run.");
+                next;
+            }
             ($ok, $msg) = $record->SetDisabled(1);
         }
         elsif ($record->can('Delete')) {
+            if ($dryrun) {
+                RT->Logger->debug("Skipping delete of $record_class #$id because of dry run.");
+                next;
+            }
             ($ok, $msg) = $record->Delete;
         }
         else {
@@ -236,8 +259,10 @@ for my $type (@record_types) {
     }
 }
 
-my $new_json = $JSON->encode(\%new_for_insertdata);
-$RT::Handle->InsertData(\$new_json, undef, disconnect_after => 0);
+if (grep { scalar(@$_) } values %new_for_insertdata) {
+    my $new_json = $JSON->encode(\%new_for_insertdata);
+    $RT::Handle->InsertData(\$new_json, undef, disconnect_after => 0);
+}
 
 sub find_differences {
     my $base_records = shift;
@@ -440,6 +465,12 @@ Avoid nonessential output.
 
 Adjust LogToSTDERR config option.
 
+=item B<--dryrun>
+
+Attempt to process changes but skip making any actual changes to the
+database. Changes that would have been made are logged at level C<debug>
+so you may wish to pass C<--log=debug> to see them.
+
 =back
 
 =cut

-----------------------------------------------------------------------


More information about the rt-commit mailing list