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