summaryrefslogtreecommitdiff
path: root/mu-aux/gitinfo
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2017-04-05 21:09:55 +0300
committerSergey Poznyakoff <gray@gnu.org>2017-04-06 07:33:36 +0300
commit9c6c1cd005bb037c40f3e1a2f0ce1492a70ec33f (patch)
tree2f5a4080e156c4080497ed01774398f3a6989188 /mu-aux/gitinfo
parent612f9a557fc97f23711e3c6c55f3e601ac4ac32d (diff)
downloadmailutils-9c6c1cd005bb037c40f3e1a2f0ce1492a70ec33f.tar.gz
mailutils-9c6c1cd005bb037c40f3e1a2f0ce1492a70ec33f.tar.bz2
Minor improvement in gitinfo.h production
* Makefile.am: Minor change. * include/mailutils/Makefile.am (gitinfo.h): Build only if $(srcdir) and $(builddir) are the same. * mu-aux/gitinfo.pl: Rename to mu-aux/gitinfo * configure.ac (GITINFO): Reflect this change * mu-aux/Makefile.am: Likewise.
Diffstat (limited to 'mu-aux/gitinfo')
-rwxr-xr-xmu-aux/gitinfo524
1 files changed, 524 insertions, 0 deletions
diff --git a/mu-aux/gitinfo b/mu-aux/gitinfo
new file mode 100755
index 000000000..a932f2a66
--- /dev/null
+++ b/mu-aux/gitinfo
@@ -0,0 +1,524 @@
+eval '(exit $?0)' && eval 'exec perl -wS "$0" "$@"'
+ & eval 'exec perl -wS "$0" $argv:q'
+ if 0;
+
+# This file is part of GNU Mailutils.
+# Copyright (C) 2017 Free Software Foundation, Inc.
+#
+# GNU Mailutils is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 3, or (at
+# your option) any later version.
+#
+# GNU Mailutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use Getopt::Long qw(:config gnu_getopt no_ignore_case);
+use Pod::Usage;
+use Pod::Man;
+use Pod::Find qw(pod_where);
+
+=head1 NAME
+
+gitinfo.pl - build version tag for mailutils
+
+=head1 SYNOPSIS
+
+B<perl gitinfo.pl>
+[B<-C> I<DIR>]
+[B<-H> I<FORMAT>]
+[B<-o> I<FILE>]
+[B<-r> B<recent> | B<released> | B<stable>]
+[B<--directory=>I<DIR>]
+[B<--format=>I<FORMAT>]
+[B<--output=>I<FILE>]
+[B<--reference=>B<recent> | B<released> | B<stable>]
+
+B<perl gitinfo.pl> B<-h> | B<--help> | B<--usage>
+
+=head1 DESCRIPTION
+
+Outputs Git information for the version of GNU mailutils in
+the local source tree. The information is printed according to
+the supplied I<FORMAT>. The format is an arbitrary string, containing
+variable references in the form B<$I<VARIABLE>> or B<${I<VARIABLE>}>.
+If the variable reference occurs within a quoted string, any occurrences
+of double quotes and backslashes in the expansion will be escaped by
+backslashes.
+
+The following variables are defined:
+
+=over 4
+
+=item B<package_name>
+
+Package name, obtained from the B<AC_INIT> line in F<configure.ac>.
+
+=item B<package_version>
+
+Package version, from the same source.
+
+=item B<recent_version>
+
+Most recent version as listed in the B<NEWS> file.
+
+=item B<recent_date>
+
+Date when the most recent version was released (if it was).
+
+=item B<recent_commit>
+
+Hash of the commit corresponding to the resent version.
+
+=item B<recent_distance>
+
+Distance (number of commits) between B<recent_commit> and B<HEAD>.
+
+=item B<released_version>
+
+The most recent released version as per B<NEWS> file.
+
+=item B<released_date>
+
+Date when it was released.
+
+=item B<released_commit>
+
+Hash of the commit corresponding to B<released_version>.
+
+=item B<released_distance>
+
+Distance (number of commits) between B<released_commit> and B<HEAD>.
+
+=item B<stable_version>
+
+The most recent stable version, i.e. most recently released version
+which number doesn't have patchlevel part. Most often is the same
+as B<released_version>.
+
+=item B<stable_date>
+
+Date of the stable release.
+
+=item B<stable_commit>
+
+Hash of the commit corresponding to B<stable_version>.
+
+=item B<stable_distance>
+
+Distance between B<stable_commit> and B<HEAD>.
+
+=item B<refversion>
+
+Reference version, selected by the B<--reference> command line option.
+By default, it is the same as B<recent_version> above.
+
+=item B<refdate>
+
+Date when B<refversion> was released. May be absent, if the reference
+version was never released.
+
+=item B<refcommit>
+
+Hash of the commit corresponding to the B<refversion>.
+
+=item B<refdist>
+
+Number of commits between B<refcommit> and B<HEAD>.
+
+=item B<describe>
+
+Result of the B<git describe> command.
+
+=item B<dirty>
+
+If the source tree has uncommitted modifications, this variable is set
+to 1. Otherwise, it is undefined.
+
+=item B<commit_hash>
+
+The hash of the topmost commit.
+
+=item B<commit_time>
+
+Time of the topmost commit (UNIX timestamp).
+
+=item B<commit_subject>
+
+Subject of the topmost commit.
+
+=back
+
+The construct
+
+B<{?I<EXPR>??I<REPL-IF-TRUE>>[B<?|I<REPL-IF-FALSE>>]B<?}>
+
+within a format string provides a rudimental control flow facility. The
+I<EXPR> is evaluated as a Perl expression in an environment when the variales
+disucussed above are defined. If the result is true (in Perl sense), the
+construct expands to I<REPL-IF-TRUE>. Othervise, it expands to
+I<REPL-IF-FALSE> or, if it is not defined, to the empty string.
+
+Notice, that conditional expressions cannot be nested.
+
+In addition to the format strings, the argument to the B<--format> option
+can be any of the following predefined format names:
+
+=over 4
+
+=item B<h>
+
+=item B<c>
+
+Produces a set of B<C> defines. It is equivalent to the following multi-line
+format:
+
+ #define MU_GIT_COMMIT_HASH "$commit_hash"
+ #define MU_GIT_COMMIT_TIME "$commit_time"
+ #define MU_GIT_COMMIT_SUBJECT "$commit_subject"
+ {?$refdist>0??#define MU_GIT_COMMIT_DISTANCE $refdist
+ ?}#define MU_GIT_DESCRIBE_STRING "$describe{?$dirty??-dirty?}"
+
+=item B<all>
+
+This is the default format. It outputs all variables as B<I<NAME>="I<VALUE>">
+pairs, each pair on a separate line. Occurrences of B<"> and B<\> within
+values are escaped by additional backslashes.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-C>, B<--directory=>I<DIR>
+
+Use I<DIR> as the top-level source directory. By default it is determined
+automatically, which works for as long as B<gitinfo.pl> is run from a
+subdirectory of the git-controlled working tree.
+
+=item B<-H>, B<--format>=I<FORMAT>
+
+Select output format. See B<DESCRIPTION> for the details.
+
+=item B<-o>, B<--output=>I<FILE>
+
+Output results to the I<FILE>, instead of the standard output.
+
+=item B<-r>, B<--reference=>I<VERSION>
+
+Select the reference version. Argument is one of: B<recent> (the default),
+B<released>, or B<stable>.
+
+=item B<-h>
+
+Display short help summary.
+
+=item B<--help>
+
+Display the manpage.
+
+=item B<--usage>
+
+Display command line usage summary.
+
+=back
+
+=cut
+
+# find_commit(VERSION)
+# Finds commit corresponding to the version number VERSION.
+sub find_commit($) {
+ my $v = quotemeta(shift);
+ my $cmd = q{git log -S'^AC_INIT\(\[[^]]+\][[:space:]]*,[[:space:]]*\[}
+ . $v
+ . q{\]' --pickaxe-regex --reverse --pretty=format:%H -- configure.ac};
+ open(my $fd, '-|', $cmd)
+ or die "$cmd: $!";
+ my $s = <$fd>;
+ close $fd;
+ chomp $s;
+ return $s;
+}
+
+# find_count(VERSION)
+# Returns number of commits between VERSION and the current commit.
+sub find_count($) {
+ my $v = shift;
+ my $cmd = "git rev-list --count $v..HEAD";
+ open(my $fd, '-|', $cmd) or die "$cmd: $!";
+ my $s = <$fd>;
+ close $fd;
+ chomp $s;
+ return $s;
+}
+
+sub scan_news($) {
+ my ($hashref) = @_;
+ my $file = 'NEWS';
+ open(my $fd, '<', $file) or die "can't open $file: $!";
+ while (<$fd>) {
+ chomp;
+ if (/^(?:\*[[:space:]]+)?
+ [vV]ersion [[:space:]]+
+ ([[:digit:]](?:[.,][[:digit:]]+){1,2}(?:[[:digit:]._-])*)
+ (?:(?:.*)[[:punct:]][[:space:]]*
+ ([[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}))?/x) {
+ my ($ver, $date) = ($1, $2);
+ unless (exists($hashref->{recent_version})) {
+ $hashref->{recent_version} = $ver;
+ $hashref->{recent_date} = $date if $date;
+ }
+ if ($date) {
+ if (!exists($hashref->{released_version})) {
+ $hashref->{released_version} = $ver;
+ $hashref->{released_date} = $date;
+ }
+ if ($ver =~ /^\d+\.\d+$/) {
+ $hashref->{stable_version} = $ver;
+ $hashref->{stable_date} = $date;
+ last;
+ }
+ }
+ }
+ }
+ close $fd;
+}
+
+# this_version()
+# Returns a list (package, version).
+sub this_version() {
+ my $file = 'configure.ac';
+ open(my $fd, '<', $file) or die "can't open $file: $!";
+ while (<$fd>) {
+ chomp;
+ return ($1, $2) if (/^AC_INIT\(\[(.+?)\]\s*,\s*\[(.+?)\].*/);
+ }
+}
+
+# git_describe($HASHREF)
+# Define 'describe' and 'dirty' keys in $HASHREF
+sub git_describe($) {
+ my ($href) = @_;
+ my $descr = `git describe`;
+ chomp $descr;
+ $href->{describe} = $descr;
+ if (`git diff-index --name-only HEAD 2>/dev/null`) {
+ $href->{dirty} = 1;
+ }
+ return $descr;
+}
+
+# last_commit_info($HASHREF)
+# Populates %$HASHREF with entries describing the current commit.
+sub last_commit_info {
+ my ($hashref) = @_;
+ my @names = qw(commit_hash commit_time commit_subject);
+ open(my $fd,'-|',
+ "git log --max-count=1 --pretty=format:'%H%n%ai%n%s' HEAD")
+ or die;
+ while (<$fd>) {
+ chomp;
+ my $name = shift @names;
+ last unless $name;
+ $hashref->{$name} = $_;
+ }
+ close $fd;
+}
+
+# Convert POD markup to the usage message suitable for --help and --usage
+# output.
+sub pod_usage_msg() {
+ my %args;
+ open my $fd, '>', \my $msg;
+
+ $args{-input} = pod_where({-inc => 1}, __PACKAGE__);
+ pod2usage(-verbose => 99,
+ -sections => 'NAME',
+ -output => $fd,
+ -exitval => 'NOEXIT',
+ %args);
+ my @a = split /\n/, $msg;
+ $msg = $a[1];
+ $msg =~ s/^\s+//;
+ $msg =~ s/ - /: /;
+ return $msg;
+}
+
+my %gitinfo;
+
+sub eval_format {
+ my ($format) = @_;
+ my @res;
+ while ($format) {
+ if ($format =~ /^(?<pfx>.*?)"(?<sfx>.*)$/s) {
+ push @res, eval_format($+{pfx});
+ my $acc;
+ $format = $+{sfx};
+ my $s = $format;
+ while ($s) {
+ if ($s =~ /^([^\\"]*?)\\"(.*)$/s) {
+ $acc .= $1 . '"';
+ $s = $2;
+ } elsif ($s =~ /^(.*?)"(.*)$/s) {
+ $acc .= $1;
+ $format = $2;
+ last;
+ } else {
+ $acc = undef;
+ last;
+ }
+ }
+ if (defined($acc)) {
+ my $x = eval_format($acc);
+ $x =~ s/(["\\])/\\$1/g;
+ push @res, '"', $x, '"';
+ }
+ } elsif ($format =~ /^(?<pfx>.*?)
+ \{\?
+ (?<cond>.+?)
+ \?\?
+ (?<iftrue>.+?)
+ (?:\?\|(?<iffalse>.*?))?
+ \?\}(?<sfx>.*)$/sx) {
+
+ my ($pfx, $cond, $iftrue, $iffalse, $sfx) =
+ ($+{pfx}, $+{cond}, $+{iftrue}, $+{iffalse}, $+{sfx});
+
+ use Safe;
+ my $s = new Safe;
+ while (my ($k,$v) = each %gitinfo) {
+ ${$s->varglob($k)} = $v;
+ }
+ my $x = $s->reval($cond);
+ push @res, eval_format($pfx);
+ if ($x) {
+ push @res, eval_format($iftrue);
+ } else {
+ push @res, eval_format($iffalse);
+ }
+ $format = $sfx;
+ } elsif ($format =~ /^(?<pfx>.*?)
+ \$(?<ocb>{?)
+ (?<var>[a-zA-Z_][a-zA-Z_0-9]*)
+ (?<ccb>}?)
+ (?<sfx>.*)$/sx) {
+ my ($pfx, $ocb, $var, $ccb, $sfx) =
+ ($+{pfx}, $+{ocb}, $+{var}, $+{ccb}, $+{sfx});
+ if ("$ocb$ccb" =~ /^(?:{})?$/) {
+ push @res, $pfx;
+ my $val = $gitinfo{$var} if defined $gitinfo{$var};
+ #$val =~ s/(["\\])/\\$1/g if $odq;
+ push @res, $val;
+ $format = $sfx;
+ } else {
+ last;
+ }
+ } else {
+ last;
+ }
+ }
+
+ push @res, $format if $format;
+
+ return join('', @res);
+}
+
+my $refpoint = 'recent';
+my $output;
+my $format = 'all';
+
+my %fmtab = (
+ c => <<'EOT'
+#define MU_GIT_COMMIT_HASH "$commit_hash"
+#define MU_GIT_COMMIT_TIME "$commit_time"
+#define MU_GIT_COMMIT_SUBJECT "$commit_subject"
+{?$refdist>0??#define MU_GIT_COMMIT_DISTANCE $refdist
+?}#define MU_GIT_DESCRIBE_STRING "$describe{?$dirty??-dirty?}"
+EOT
+ ,
+ 'h' => 'c',
+ 'all' => sub {
+ foreach my $name (sort keys %gitinfo) {
+ my $val = $gitinfo{$name};
+ next unless defined $val;
+ $val =~ s/(["\\])/\\$1/g;
+ print "$name=\"$val\"\n";
+ }
+ }
+);
+
+my $dir;
+
+GetOptions("help" => sub {
+ pod2usage(-exitstatus => 0, -verbose => 2);
+ },
+ "h" => sub {
+ pod2usage(-message => pod_usage_msg(),
+ -exitstatus => 0);
+ },
+ "usage" => sub {
+ pod2usage(-exitstatus => 0, -verbose => 0);
+ },
+ "directory|C=s" => \$dir,
+ "reference|r=s" => \$refpoint,
+ "format|H=s" => \$format,
+ "output|o=s" => \$output
+ ) or exit(1);
+
+if ($output) {
+ open(STDOUT, '>', $output) or die "can't open $output: $!"
+}
+
+if ($dir) {
+ chdir($dir) or die "can't change to $dir: $!";
+} elsif (! -d '.git') {
+ $dir = `git rev-parse --show-toplevel 2>/dev/null`;
+ chomp $dir;
+ chdir($dir) or die "can't change to $dir: $!";
+}
+
+if (-d '.git') {
+ scan_news(\%gitinfo);
+ foreach my $pfx (qw(ref recent_ stable_ released_)) {
+ my $name = $pfx . 'version';
+ if (exists($gitinfo{$name})) {
+ my $com = $gitinfo{$pfx . 'commit'} = find_commit($gitinfo{$name});
+ my $n = find_count($com);
+ if ($n =~ /^\d+$/) {
+ $gitinfo{$pfx . 'dist'} = $n;
+ }
+ }
+ }
+
+ unless (exists($gitinfo{$refpoint . '_version'})) {
+ die "reference point '$refpoint' doesn't exist";
+ }
+
+ @gitinfo{qw(refversion refdate refcommit refdist)} =
+ @gitinfo{map { "${refpoint}_$_" } qw(version date commit dist)};
+
+ last_commit_info(\%gitinfo);
+ git_describe(\%gitinfo);
+ ($gitinfo{package_name}, $gitinfo{package_version}) = this_version;
+ if ($gitinfo{recent_version} =~ /^\d+\.\d+$/) {
+ $gitinfo{upload_dest} = 'ftp';
+ } else {
+ $gitinfo{upload_dest} = 'alpha';
+ }
+}
+
+$format = $fmtab{$format} while exists $fmtab{$format};
+if (ref($format) eq 'CODE') {
+ &{$format}
+} else {
+ $format = "$format\n" unless $format =~ /\n$/s;
+ print eval_format($format);
+}
+

Return to:

Send suggestions and report system problems to the System administrator.