#!/usr/bin/env perl use strict; use warnings FATAL => 'all'; use File::Find; use Getopt::Long qw(:config gnu_getopt); use File::Temp (); GetOptions( "git" => \my $git, "force" => \my $force, ) or die("Error in command line arguments\n"); my ($old_version, $bump) = @ARGV; my ($new_decimal, $new_vstring) = bump_version($old_version, $bump); warn "Bumping $old_version -> $new_decimal\n"; my $file_match = qr{^(?: Makefile\.PL |lib[/\\].*\.(?:pod|pm) |bin[/\\].* |script[/\\].* )$}x; my %files; if ($git) { if (system "git diff --quiet --cached HEAD") { die "Staged changes!\n"; } for (`git ls-files`) { chomp; next unless /$file_match/; $files{$_} = `git show HEAD:"$_"`; } } else { find({ no_chdir => 1, wanted => sub { next unless -f; next unless /(?:\.[\/\\])?$file_match/; open my $fh, '<', $_ or die "can't open $_: $!"; $files{$_} = do { local $/; <$fh> }; close $fh; }, }, '.'); } my $FILE_RE = qr{ (^.* \$VERSION \s* = \s* ) (['"]?) v?([0-9]+(?:[._][0-9]+)*) \2 ( \s*; ) (?: (\s*\#\s*) v?[.0-9]+ )? (.*)$ }x; my $MAKE_RE = qr{ (^.* ['"]?version['"] \s* => \s* ) (['"]?) v?([0-9]+(?:[._][0-9]+)*) \2 ( \s*, ) (?: (\s*\#\s*) v?[.0-9]+ )? (.*)$ }x; my $patch = ''; for my $file (sort keys %files) { my $content = $files{$file}; my $file_diff = ''; my $re = $file eq 'Makefile.PL' ? $MAKE_RE : $FILE_RE; my @lines = split /\r?\n/, $content; for my $ln (0 .. $#lines) { my $line = $lines[$ln]; if ($lines[$ln] =~ $re) { die "unable to bump version number in $file from $old_version, found $3\n" if !$force && $3 ne $old_version; my $comment = ($5 ? $5 . $new_vstring : ''); my $new_line = "$1'$new_decimal'$4$comment$6"; $file_diff .= <<"END_DIFF"; @@ -@{[ $ln ]},3 +@{[ $ln ]},3 @@ $lines[$ln-1] -$lines[$ln] +$new_line $lines[$ln+1] END_DIFF } } if ($file_diff) { $patch .= <<"END_HEADER" . $file_diff; --- a/$file +++ b/$file END_HEADER } } my ($fh, $file) = File::Temp::tempfile( "bump-version-XXXXXX", TMPDIR => 1 ); print { $fh } $patch; close $fh; system qw(git --no-pager apply --apply --stat), $file and exit 1; if ($git) { system qw(git apply --cached), $file and exit 1; my $message = "Bumping version to $new_decimal"; system qw(git commit -m), $message and exit 1; } sub version_parts { my $version = shift; my $dotted = $version =~ s/^v//; my @parts = split /\./, $version; if (!$dotted && @parts == 2) { my $dec = pop @parts; $dec =~ s/_//g; $dec .= "0" x ((- length $dec) % 3); push @parts, $dec =~ /(\d{1,3})/g; } $_ += 0 for @parts; return @parts; } sub bump_version { my ($version, $new) = @_; my %bump_part = (major => 0, minor => 1, bugfix => 2, last => -1); my $bump_this = $bump_part{$new||'last'}; my $new_vstring; my $new_decimal; if (defined $bump_this) { if ($version =~ /^v/ || ($version =~ tr/.//) > 1) { my @parts = version_parts($version); $parts[$bump_this]++; $parts[$_] = 0 for (($bump_this < 0 ? @parts : 0)+$bump_this+1 .. $#parts); $_ += 0 for @parts; $new_vstring = join '.', @parts; my $format = '%i.'. join '', map { '%03i' } @parts[1 .. $#parts]; $new_decimal = sprintf $format, @parts; } else { my $alpha_pos = index($version, '_'); $version =~ s/_//g; $version =~ s/^(\d+)\.//; my @parts = $1; push @parts, $version =~ /(\d{1,3})/g; my $format = '%i.'.join '', map { '%0'.length($_).'i' } @parts[1 .. $#parts]; $parts[$bump_this]++; $parts[$_] = 0 for (($bump_this < 0 ? @parts : 0)+$bump_this+1 .. $#parts); $new_decimal = sprintf $format, @parts; substr $new_decimal, $alpha_pos, 0, '_' if $alpha_pos != -1; $new_vstring = join '.', version_parts($new_decimal); } } elsif ($new =~ /^v?[0-9]+(?:[._][0-9]+)*$/) { $new_decimal = $new; $new_vstring = join('.', version_parts($new_decimal)); } else { die "no idea which part to bump - $new means nothing to me" } return ($new_decimal, $new_vstring); }