b474e3fb0cb18ccdd51bc09c133f30b2b747445c
[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   'n|dry-run' => \my $dry_run,
13   'stable'  => \my $stable,
14   'alpha'   => \my $alpha,
15 ) or die("Error in command line arguments\n");
16
17 my $old_version = shift
18   or die "no old version provided!\n";
19 my $bump = shift;
20 my ($new_decimal, $new_vstring) = bump_version($old_version, $bump);
21 die "--stable and --alpha are incompatible!\n"
22   if $stable and $alpha;
23
24 warn "Bumping $old_version -> $new_decimal" . ($new_decimal ne $new_vstring ? " ($new_vstring)" : '') . "\n";
25
26 my $file_match = qr{
27   Makefile\.PL
28   |lib[/\\].*\.(?:pod|pm)
29   |bin[/\\].*
30   |script[/\\].*
31 }x;
32
33 my $dir_match = qr{
34   (?:
35     .
36     |lib
37     |bin
38     |script
39   )
40   (?:[/\\]|$)
41 }x;
42
43 my %files;
44 if ($git) {
45   if (system "git diff --quiet --cached HEAD") {
46     die "Staged changes!\n";
47   }
48   for (`git ls-files`) {
49     chomp;
50     next
51       unless /^$file_match$/;
52     $files{$_} = `git show HEAD:"$_"`;
53   }
54 }
55 else {
56   find({
57     no_chdir => 1,
58     wanted => sub {
59       my $fn = File::Spec->abs2rel($_, '.');
60       if (-d && $fn !~ /^$dir_match$/) {
61         $File::Find::prune = 1;
62         return;
63       }
64       return
65         unless -f;
66       return
67         unless $fn =~ /^$file_match$/;
68       open my $fh, '<', $fn
69         or die "can't open $fn: $!";
70       $files{$fn} = do { local $/; <$fh> };
71       close $fh;
72     },
73   }, '.');
74 }
75
76 my $FILE_RE = qr{
77   (^.* \$VERSION \s* = \s* )
78   (['"]?) v?([0-9]+(?:[._][0-9]+)*) \2
79   ( \s*; )
80   (?:
81     (\s*\#\s*)
82     v?[.0-9]+
83   )?
84   (.*)$
85 }x;
86 my $MAKE_RE = qr{
87   (^.* ['"]?version['"] \s* => \s* )
88   (['"]?) v?([0-9]+(?:[._][0-9]+)*) \2
89   ( \s*, )
90   (?:
91     (\s*\#\s*)
92     v?[.0-9]+
93   )?
94   (.*)$
95 }x;
96
97 my $patch = '';
98 for my $file (sort keys %files) {
99   eval {
100     my $content = $files{$file};
101     my $file_diff = '';
102     my $re = $file eq 'Makefile.PL' ? $MAKE_RE : $FILE_RE;
103     my @lines = split /\r?\n/, $content;
104     for my $ln (0 .. $#lines) {
105       my $line = $lines[$ln];
106       if ($lines[$ln] =~ $re) {
107         die "unable to bump version number in $file from $old_version, found $3\n"
108           if !$force && $3 ne $old_version;
109         my $comment = ($5 ? $5 . $new_vstring : '');
110         my $new_line = "$1'$new_decimal'$4$comment$6";
111         $file_diff .= <<"END_DIFF";
112 @@ -@{[ $ln ]},3 +@{[ $ln ]},3 @@
113  $lines[$ln-1]
114 -$lines[$ln]
115 +$new_line
116  $lines[$ln+1]
117 END_DIFF
118       }
119     }
120     if ($file_diff) {
121       $patch .= <<"END_HEADER" . $file_diff;
122 --- a/$file
123 +++ b/$file
124 END_HEADER
125     }
126     1;
127   } or $dry_run ? warn($@) : die($@);
128 }
129
130 if ($dry_run) {
131   print $patch;
132   exit;
133 }
134 my ($fh, $file) = File::Temp::tempfile( "bump-version-XXXXXX", TMPDIR => 1 );
135 print { $fh } $patch;
136 close $fh;
137 system qw(git --no-pager apply --apply --stat), $file
138   and exit 1;
139
140 if ($git) {
141   system qw(git apply --cached), $file
142     and exit 1;
143
144   my $message = "Bumping version to $new_decimal";
145   system qw(git commit -m), $message
146     and exit 1;
147 }
148
149 sub version_parts {
150   my $version = shift;
151   my $dotted = $version =~ s/^v//;
152   my @parts = split /\./, $version;
153   if (!$dotted && @parts <= 2) {
154     tr/_//d for @parts;
155     if (@parts == 2) {
156       my $dec = pop @parts;
157       $dec .= "0" x ((- length $dec) % 3);
158       push @parts, $dec =~ /(\d{1,3})/g;
159     }
160   }
161   elsif ($version =~ tr/_//) {
162     die "don't know how to handle underscores in dotted-decimal versions!\n";
163   }
164   $_ += 0 for @parts;
165   return @parts;
166 }
167
168 sub bump_version {
169   my ($version, $new) = @_;
170
171   my %bump_part = (major => 0, minor => 1, bugfix => 2, last => -1);
172   my $bump_this = $bump_part{$new||'last'};
173
174   my $new_vstring;
175   my $new_decimal;
176
177   if (defined $bump_this) {
178     if ($version =~ /^v/ || ($version =~ tr/.//) > 1) {
179       my $v = $version =~ /^(v)/ ? $1 : '';
180       if ($version =~ tr/_//d && !$stable || $alpha) {
181         die "can't bump dotted decimal versions with alpha components!\n";
182       }
183       my @parts = version_parts($version);
184       $bump_this += @parts
185         if $bump_this < 0;
186       $parts[$_] = 0 for $bump_this+1 .. $#parts;
187       $parts[$_] = 0 for $#parts+1 .. $bump_this;
188       $parts[$bump_this]++;
189       $_ += 0
190         for @parts;
191       $new_decimal = $new_vstring = $v . join '.', @parts;
192     }
193     else {
194       my $alpha_pos;
195       if (!$stable) {
196         $alpha_pos = index($version, '_');
197         if ($alpha_pos == -1) {
198           undef $alpha_pos;
199         }
200         else {
201           my $dot_pos = index($version, '.');
202           $alpha_pos = $dot_pos == -1 ? -$alpha_pos : $alpha_pos - $dot_pos;
203         }
204       }
205       $new_decimal = $version;
206       $new_decimal =~ tr/_//d;
207       my $dec_len = $new_decimal =~ /(\.\d+)/ ? length($1) - 1 : 0;
208       if ($bump_this != -1) {
209         my $cut_len = $bump_this * 3;
210         $dec_len = $cut_len
211           if $dec_len < $cut_len;
212         $new_decimal =~ s/(\..{1,$cut_len}).*/$1/;
213       }
214       $new_decimal += 10 ** -($bump_this == -1 ? $dec_len : ($bump_this * 3));
215       $new_decimal = sprintf "%.${dec_len}f", $new_decimal;
216       if ($alpha) {
217         $alpha_pos ||= $dec_len >= 2 ? int($dec_len / 2) + 1 :
218           die "don't know how to make $new_decimal into an alpha version";
219       }
220       if (defined $alpha_pos) {
221         my $dot_pos = index($new_decimal, '.');
222         $dot_pos = length $new_decimal
223           if $dot_pos == -1;
224         substr $new_decimal, $dot_pos + $alpha_pos, 0, '_';
225       }
226       $new_vstring = 'v' . join '.', version_parts($new_decimal);
227     }
228   }
229   elsif ($new =~ /^v?[0-9]+(?:[._][0-9]+)*$/) {
230     $new_decimal = $new;
231     $new_vstring = join('.', version_parts($new_decimal));
232   }
233   else {
234     die "no idea which part to bump - $new means nothing to me"
235   }
236   return ($new_decimal, $new_vstring);
237 }
238