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