gnu style args
[p5sagit/Distar.git] / helpers / bump-version
1 #!/usr/bin/env perl
2
3 use strict;
4 use warnings FATAL => 'all';
5 use File::Find;
6 use Getopt::Long qw(:config gnu_getopt);
7 use File::Temp ();
8
9 GetOptions(
10   "git"     => \my $git,
11   "force"   => \my $force,
12 ) or die("Error in command line arguments\n");
13
14 my ($old_version, $bump) = @ARGV;
15 my ($new_decimal, $new_vstring) = bump_version($old_version, $bump);
16
17 warn "Bumping $old_version -> $new_decimal\n";
18
19 my $file_match = qr{^(?:
20   Makefile\.PL
21   |lib[/\\].*\.(?:pod|pm)
22   |bin[/\\].*
23   |script[/\\].*
24 )$}x;
25
26 my %files;
27 if ($git) {
28   if (system "git diff --quiet --cached HEAD") {
29     die "Staged changes!\n";
30   }
31   for (`git ls-files`) {
32     chomp;
33     next
34       unless /$file_match/;
35     $files{$_} = `git show HEAD:"$_"`;
36   }
37 }
38 else {
39   find({
40     no_chdir => 1,
41     wanted => sub {
42       next
43         unless -f;
44       next
45         unless /(?:\.[\/\\])?$file_match/;
46       open my $fh, '<', $_
47         or die "can't open $_: $!";
48       $files{$_} = do { local $/; <$fh> };
49       close $fh;
50     },
51   }, '.');
52 }
53
54 my $FILE_RE = qr{
55   (^.* \$VERSION \s* = \s* )
56   (['"]?) v?([0-9]+(?:[._][0-9]+)*) \2
57   ( \s*; )
58   (?:
59     (\s*\#\s*)
60     v?[.0-9]+
61   )?
62   (.*)$
63 }x;
64 my $MAKE_RE = qr{
65   (^.* ['"]?version['"] \s* => \s* )
66   (['"]?) v?([0-9]+(?:[._][0-9]+)*) \2
67   ( \s*, )
68   (?:
69     (\s*\#\s*)
70     v?[.0-9]+
71   )?
72   (.*)$
73 }x;
74
75 my $patch = '';
76 for my $file (sort keys %files) {
77   my $content = $files{$file};
78   my $file_diff = '';
79   my $re = $file eq 'Makefile.PL' ? $MAKE_RE : $FILE_RE;
80   my @lines = split /\r?\n/, $content;
81   for my $ln (0 .. $#lines) {
82     my $line = $lines[$ln];
83     if ($lines[$ln] =~ $re) {
84       die "unable to bump version number in $file from $old_version, found $3\n"
85         if !$force && $3 ne $old_version;
86       my $comment = ($5 ? $5 . $new_vstring : '');
87       my $new_line = "$1'$new_decimal'$4$comment$6";
88       $file_diff .= <<"END_DIFF";
89 @@ -@{[ $ln ]},3 +@{[ $ln ]},3 @@
90  $lines[$ln-1]
91 -$lines[$ln]
92 +$new_line
93  $lines[$ln+1]
94 END_DIFF
95     }
96   }
97   if ($file_diff) {
98     $patch .= <<"END_HEADER" . $file_diff;
99 --- a/$file
100 +++ b/$file
101 END_HEADER
102   }
103 }
104
105 my ($fh, $file) = File::Temp::tempfile( "bump-version-XXXXXX", TMPDIR => 1 );
106 print { $fh } $patch;
107 close $fh;
108 system qw(git --no-pager apply --apply --stat), $file
109   and exit 1;
110
111 if ($git) {
112   system qw(git apply --cached), $file
113     and exit 1;
114
115   my $message = "Bumping version to $new_decimal";
116   system qw(git commit -m), $message
117     and exit 1;
118 }
119
120 sub version_parts {
121   my $version = shift;
122   my $dotted = $version =~ s/^v//;
123   my @parts = split /\./, $version;
124   if (!$dotted && @parts == 2) {
125     my $dec = pop @parts;
126     $dec =~ s/_//g;
127     $dec .= "0" x ((- length $dec) % 3);
128     push @parts, $dec =~ /(\d{1,3})/g;
129   }
130   $_ += 0 for @parts;
131   return @parts;
132 }
133
134 sub bump_version {
135   my ($version, $new) = @_;
136
137   my %bump_part = (major => 0, minor => 1, bugfix => 2, last => -1);
138   my $bump_this = $bump_part{$new||'last'};
139
140   my $new_vstring;
141   my $new_decimal;
142
143   if (defined $bump_this) {
144     if ($version =~ /^v/ || ($version =~ tr/.//) > 1) {
145       my @parts = version_parts($version);
146       $parts[$bump_this]++;
147       $parts[$_] = 0 for (($bump_this < 0 ? @parts : 0)+$bump_this+1 .. $#parts);
148       $_ += 0
149         for @parts;
150       $new_vstring = join '.', @parts;
151       my $format = '%i.'. join '', map { '%03i' } @parts[1 .. $#parts];
152       $new_decimal = sprintf $format, @parts;
153     }
154     else {
155       my $alpha_pos = index($version, '_');
156       $version =~ s/_//g;
157       $version =~ s/^(\d+)\.//;
158       my @parts = $1;
159       push @parts, $version =~ /(\d{1,3})/g;
160       my $format = '%i.'.join '', map { '%0'.length($_).'i' } @parts[1 .. $#parts];
161       $parts[$bump_this]++;
162       $parts[$_] = 0 for (($bump_this < 0 ? @parts : 0)+$bump_this+1 .. $#parts);
163       $new_decimal = sprintf $format, @parts;
164       substr $new_decimal, $alpha_pos, 0, '_'
165         if $alpha_pos != -1;
166       $new_vstring = join '.', version_parts($new_decimal);
167     }
168   }
169   elsif ($new =~ /^v?[0-9]+(?:[._][0-9]+)*$/) {
170     $new_decimal = $new;
171     $new_vstring = join('.', version_parts($new_decimal));
172   }
173   else {
174     die "no idea which part to bump - $new means nothing to me"
175   }
176   return ($new_decimal, $new_vstring);
177 }
178