fix and clean up numeric version bumping
[p5sagit/Distar.git] / helpers / bump-version
index a958689..e0c1fa8 100755 (executable)
@@ -3,27 +3,48 @@
 use strict;
 use warnings FATAL => 'all';
 use File::Find;
-use Getopt::Long qw(:config gnu_compat);
+use Getopt::Long qw(:config gnu_getopt);
 use File::Temp ();
 
 GetOptions(
   "git"     => \my $git,
+  "force"   => \my $force,
+  'n|dry-run' => \my $dry_run,
 ) or die("Error in command line arguments\n");
 
-my ($old_version, $bump) = @ARGV;
+my $old_version = shift
+  or die "no old version provided!\n";
+my $bump = shift;
 my ($new_decimal, $new_vstring) = bump_version($old_version, $bump);
 
-warn "Bumping $old_version -> $new_decimal\n";
+warn "Bumping $old_version -> $new_decimal" . ($new_decimal ne $new_vstring ? " ($new_vstring)" : '') . "\n";
+
+my $file_match = qr{
+  Makefile\.PL
+  |lib[/\\].*\.(?:pod|pm)
+  |bin[/\\].*
+  |script[/\\].*
+}x;
+
+my $dir_match = qr{
+  (?:
+    .
+    |lib
+    |bin
+    |script
+  )
+  (?:[/\\]|$)
+}x;
 
 my %files;
 if ($git) {
-  if (system "git diff --cached HEAD") {
+  if (system "git diff --quiet --cached HEAD") {
     die "Staged changes!\n";
   }
   for (`git ls-files`) {
     chomp;
     next
-      unless /^lib\/.*\.(?:pod|pm)$/ || /^Makefile\.PL$/;
+      unless /^$file_match$/;
     $files{$_} = `git show HEAD:"$_"`;
   }
 }
@@ -31,16 +52,21 @@ else {
   find({
     no_chdir => 1,
     wanted => sub {
-      next
+      my $fn = File::Spec->abs2rel($_, '.');
+      if (-d && $fn !~ /^$dir_match$/) {
+        $File::Find::prune = 1;
+        return;
+      }
+      return
         unless -f;
-      next
-        unless /^lib\/.*\.(?:pod|pm)$/ || /^Makefile\.PL$/;
-      open my $fh, '<', $_
-        or die "can't open $_: $!";
-      $files{$_} = do { local $/; <$fh> };
+      return
+        unless $fn =~ /^$file_match$/;
+      open my $fh, '<', $fn
+        or die "can't open $fn: $!";
+      $files{$fn} = do { local $/; <$fh> };
       close $fh;
     },
-  }, 'lib');
+  }, '.');
 }
 
 my $FILE_RE = qr{
@@ -54,7 +80,7 @@ my $FILE_RE = qr{
   (.*)$
 }x;
 my $MAKE_RE = qr{
-  (^.* version \s* => \s* )
+  (^.* ['"]?version['"] \s* => \s* )
   (['"]?) v?([0-9]+(?:[._][0-9]+)*) \2
   ( \s*, )
   (?:
@@ -66,38 +92,45 @@ my $MAKE_RE = qr{
 
 my $patch = '';
 for my $file (sort keys %files) {
-  my $content = $files{$file};
-  my $file_diff = '';
-  my $re = $file eq 'Makefile.PL' ? $MAKE_RE : $FILE_RE;
-  my @lines = split /\r?\n/, $content;
-  for my $ln (0 .. $#lines) {
-    my $line = $lines[$ln];
-    if ($lines[$ln] =~ $re) {
-      die "unable to bump version number in $file from $old_version, found $3\n"
-        if $3 ne $old_version;
-      my $comment = ($5 ? $5 . $new_vstring : '');
-      my $new_line = "$1'$new_decimal'$4$comment$6";
-      $file_diff .= <<"END_DIFF";
+  eval {
+    my $content = $files{$file};
+    my $file_diff = '';
+    my $re = $file eq 'Makefile.PL' ? $MAKE_RE : $FILE_RE;
+    my @lines = split /\r?\n/, $content;
+    for my $ln (0 .. $#lines) {
+      my $line = $lines[$ln];
+      if ($lines[$ln] =~ $re) {
+        die "unable to bump version number in $file from $old_version, found $3\n"
+          if !$force && $3 ne $old_version;
+        my $comment = ($5 ? $5 . $new_vstring : '');
+        my $new_line = "$1'$new_decimal'$4$comment$6";
+        $file_diff .= <<"END_DIFF";
 @@ -@{[ $ln ]},3 +@{[ $ln ]},3 @@
  $lines[$ln-1]
 -$lines[$ln]
 +$new_line
  $lines[$ln+1]
 END_DIFF
+      }
     }
-  }
-  if ($file_diff) {
-    $patch .= <<"END_HEADER" . $file_diff;
+    if ($file_diff) {
+      $patch .= <<"END_HEADER" . $file_diff;
 --- a/$file
 +++ b/$file
 END_HEADER
-  }
+    }
+    1;
+  } or $dry_run ? warn($@) : die($@);
 }
 
+if ($dry_run) {
+  print $patch;
+  exit;
+}
 my ($fh, $file) = File::Temp::tempfile( "bump-version-XXXXXX", TMPDIR => 1 );
 print { $fh } $patch;
 close $fh;
-system qw(git apply --apply --stat), $file
+system qw(git --no-pager apply --apply --stat), $file
   and exit 1;
 
 if ($git) {
@@ -113,36 +146,71 @@ sub version_parts {
   my $version = shift;
   my $dotted = $version =~ s/^v//;
   my @parts = split /\./, $version;
-  if (!$dotted && @parts == 2) {
-    my $dec = pop @parts;
-    $dec =~ s/_//g;
-    push @parts, $dec =~ /(\d{1,3})/g;
+  if (!$dotted && @parts <= 2) {
+    tr/_//d for @parts;
+    if (@parts == 2) {
+      my $dec = pop @parts;
+      $dec .= "0" x ((- length $dec) % 3);
+      push @parts, $dec =~ /(\d{1,3})/g;
+    }
+  }
+  elsif ($version =~ tr/_//) {
+    die "don't know how to handle underscores in dotted-decimal versions!\n";
   }
   $_ += 0 for @parts;
-  push @parts, 0
-    until @parts >= 3;
   return @parts;
 }
 
 sub bump_version {
-  my ($old_version, $new) = @_;
+  my ($version, $new) = @_;
 
-  my %bump_part = (major => 0, minor => 1, bugfix => 2);
-  my $bump_this = $bump_part{$new||'bugfix'};
+  my %bump_part = (major => 0, minor => 1, bugfix => 2, last => -1);
+  my $bump_this = $bump_part{$new||'last'};
 
   my $new_vstring;
   my $new_decimal;
 
   if (defined $bump_this) {
-    my @new_parts = version_parts($old_version);
-    $new_parts[$bump_this]++;
-    $new_parts[$_] = 0 for ($bump_this+1 .. $#new_parts);
-    $new_vstring = join('.', @new_parts);
-    my $alpha_pos = index($old_version, '_');
-    my $format = '%i.' . ( '%03i' x (@new_parts - 1) );
-    $new_decimal = sprintf($format, @new_parts);
-    substr $new_decimal, $alpha_pos, 0, '_'
-      if $alpha_pos != -1;
+    if ($version =~ /^v/ || ($version =~ tr/.//) > 1) {
+      my $v = $version =~ /^(v)/ ? $1 : '';
+      my @parts = version_parts($version);
+      $bump_this += @parts
+        if $bump_this < 0;
+      $parts[$_] = 0 for $bump_this+1 .. $#parts;
+      $parts[$_] = 0 for $#parts+1 .. $bump_this;
+      $parts[$bump_this]++;
+      $_ += 0
+        for @parts;
+      $new_decimal = $new_vstring = $v . join '.', @parts;
+    }
+    else {
+      my $alpha_pos = index($version, '_');
+      if ($alpha_pos == -1) {
+        undef $alpha_pos;
+      }
+      else {
+        my $dot_pos = index($version, '.');
+        $alpha_pos = $dot_pos == -1 ? -$alpha_pos : $alpha_pos - $dot_pos;
+      }
+      $new_decimal = $version;
+      $new_decimal =~ tr/_//d;
+      my $dec_len = $new_decimal =~ /(\.\d+)/ ? length($1) - 1 : 0;
+      if ($bump_this != -1) {
+        my $cut_len = $bump_this * 3;
+        $dec_len = $cut_len
+          if $dec_len < $cut_len;
+        $new_decimal =~ s/(\..{1,$cut_len}).*/$1/;
+      }
+      $new_decimal += 10 ** -($bump_this == -1 ? $dec_len : ($bump_this * 3));
+      $new_decimal = sprintf "%.${dec_len}f", $new_decimal;
+      if (defined $alpha_pos) {
+        my $dot_pos = index($new_decimal, '.');
+        $dot_pos = length $new_decimal
+          if $dot_pos == -1;
+        substr $new_decimal, $dot_pos + $alpha_pos, 0, '_';
+      }
+      $new_vstring = 'v' . join '.', version_parts($new_decimal);
+    }
   }
   elsif ($new =~ /^v?[0-9]+(?:[._][0-9]+)*$/) {
     $new_decimal = $new;