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