#!/usr/bin/perl eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if $running_under_some_shell; #!/usr/bin/perl use strict; my $main_repo="bootstrap"; my @pieces=("artwork", "base", "calc", "components", "extensions", "extras", "filters", "help", "impress", "libs-core", "libs-extern", "libs-extern-sys", "libs-gui", "l10n", "postprocess", "sdk", "testing", "ure", "writer"); sub search_bugs($$$$) { my ($pdata, $piece, $commit_id, $line) = @_; my $bug = ""; my $bug_orig; while (defined $bug) { # match fdo#123, rhz#123, i#123 if ( $line =~ m/(\w*\#+\d+)/ ) { $bug_orig = $1; $bug = $1; # match #i123# } elsif ( $line =~ m/(\#i)(\d+)(\#)/ ) { $bug_orig = $1 . $2 . $3; $bug = "i#$2"; } else { $bug = undef; next; } # print " found $bug\n"; $bug_orig =~ s/\#/\\#/; $line =~ s/[Rr]esolves:\s*$bug_orig\s*//; $line =~ s/\s*-\s*$bug_orig\s*//; $line =~ s/\(?$bug_orig\)?[:,]?\s*//; %{$pdata->{$piece}{$commit_id}{'bugs'}} = () if (! defined %{$pdata->{$piece}{$commit_id}{'bugs'}}); $pdata->{$piece}{$commit_id}{'bugs'}{$bug} = 1; $pdata->{$piece}{$commit_id}{'flags'}{'bug'} = 1; } return $line; } sub standardize_summary($) { my $line = shift; $line =~ s/^\s*//; $line =~ s/\s*$//; # lower first letter $line =~ m/(^.)/; my $first_char = lc($1); $line =~ s/^./$first_char/; # FIXME: remove do at the end of line # remove bug numbers return $line; } sub load_git_log($$$$) { my ($pdata, $repo_dir, $piece, $pgit_args) = @_; my $cmd = "cd $repo_dir && git log " . join ' ', @{$pgit_args}; my $commit_id; my $summary; print STDERR "Analyzing log from the git repo: $piece...\n"; open (GIT, "$cmd 2>&1|") || die "Can't run $cmd: $!"; %{$pdata->{$piece}} = (); while (my $line = ) { chomp $line; if ( $line =~ m/^commit ([0-9a-z]{20})/ ) { $commit_id = "$1"; $summary=undef; %{$pdata->{$piece}{"$commit_id"}} = (); %{$pdata->{$piece}{"$commit_id"}{'flags'}} = (); next; } if ( $line =~ /^Author:\s*([^\<]*)\<([^\>]*)>/ ) { # get rid of extra empty spaces; my $name = "$1"; $name =~ s/\s+$//; die "Error: Author already defined for the commit {$commit_id}\n" if defined ($pdata->{$piece}{$commit_id}{'author'}); %{$pdata->{$piece}{$commit_id}{'author'}} = (); $pdata->{$piece}{$commit_id}{'author'}{'name'} = "$name"; $pdata->{$piece}{$commit_id}{'author'}{'email'} = "$2"; next; } if ( $line =~ /^Date:\s+/ ) { # ignore date line next; } if ( $line =~ /^\s*$/ ) { # ignore empty line next; } $line = search_bugs($pdata, $piece, $commit_id, $line); # FIXME: need to be implemeted # search_keywords($pdata, $line); unless (defined $pdata->{$piece}{$commit_id}{'summary'}) { $summary = standardize_summary($line); $pdata->{$piece}{$commit_id}{'summary'} = $summary; } } close GIT; } sub get_repo_name($) { my $repo_dir = shift; open (GIT_CONFIG, "$repo_dir/.git/config") || die "can't open \"$$repo_dir/.git/config\" for reading: $!\n"; while (my $line = ) { chomp $line; if ( $line =~ /^\s*url\s*=\s*(\S+)$/ ) { my $repo_name = "$1"; $repo_name = s/.*\///g; return "$repo_name"; } } die "Error: can't find repo name in \"$$repo_dir/.git/config\"\n"; } sub load_data($$$$) { my ($pdata, $top_dir, $piece, $pgit_args) = @_; if (defined $piece) { my $piece_dir; if ("$piece" eq "$main_repo") { $piece_dir = "$top_dir"; } else { $piece_dir = "$top_dir/clone/$piece"; } load_git_log($pdata, $piece_dir, $piece, $pgit_args); } else { load_git_log($pdata, $top_dir, $main_repo, $pgit_args); foreach my $piece (@pieces) { load_git_log($pdata, "$top_dir/clone/$piece", $piece, $pgit_args); } } } sub print_summary_in_stat($$$$$$$) { my ($summary, $pprint_filters, $ppiece_title, $pflags, $pbugs, $pauthors, $prefix) = @_; return if ( $summary eq "" ); # do we want to print this summary at all? my $print; if (%{$pprint_filters}) { foreach my $flag (keys %{$pprint_filters}) { $print = 1 if (defined $pflags->{$flag}); } } else { $print = 1; } return unless (defined $print); # print piece title if not done yet if (defined ${$ppiece_title}) { print "${$ppiece_title}\n"; ${$ppiece_title} = undef; } # finally print the summary line my $bugs = ""; if ( %{$pbugs} ) { $bugs = " (" . join (", ", keys %{$pbugs}) . ")"; } my $authors = ""; if ( %{$pauthors} ) { $authors = " [" . join (", ", keys %{$pauthors}) . "]"; } print $prefix . $summary . $bugs . $authors . "\n"; } sub print_weekly_stat($$) { my ($pdata, $pprint_filters) = @_; foreach my $piece ( sort { $a cmp $b } keys %{$pdata}) { # check if this peice has any entries at all my $piece_title = "+ $piece"; if ( %{$pdata->{$piece}} ) { my $old_summary=""; my %authors = (); my %bugs = (); my %flags = (); foreach my $id ( sort { $pdata->{$piece}{$a}{'summary'} cmp $pdata->{$piece}{$b}{'summary'} } keys %{$pdata->{$piece}}) { my $summary = $pdata->{$piece}{$id}{'summary'}; if ($summary ne $old_summary) { print_summary_in_stat($old_summary, $pprint_filters, \$piece_title, \%flags, \%bugs, \%authors, " + "); $old_summary = $summary; %authors = (); %bugs = (); %flags = (); } # collect bug numbers if (defined $pdata->{$piece}{$id}{'bugs'}) { foreach my $bug (keys %{$pdata->{$piece}{$id}{'bugs'}}) { $bugs{$bug} = 1; } } # collect author names my $author = $pdata->{$piece}{$id}{'author'}{'name'}; $authors{$author} = 1; # collect flags foreach my $flag ( keys %{$pdata->{$piece}{$id}{'flags'}} ) { $flags{$flag} = 1; } } print_summary_in_stat($old_summary, $pprint_filters, \$piece_title, \%flags, \%bugs, \%authors, " + "); } } } ######################################################################## # help sub usage() { print "This script generates LO git commit summary\n\n" . "Usage: lo-commit-stat [--help] [--no-pieces] [--piece=] topdir [git_log_param...]\n\n" . "Options:\n" . " --help print this help\n" . " --no-pieces read changes just from the main repository, ignore other cloned repos\n" . " --piece= summarize just chnages from the given piece\n" . " --bugs print just bug fixes\n" . " topdir directory with the libreoffice/bootstrap clone; the piece repos\n" . " must be cloned in the main-repo-root/clone/ subdirectories\n" . " git_log_param extra parameters passed to the git log command to define\n" . " the area of interest , e.g. --after=\"2010-09-27\" or\n" . " TAG..HEAD"; } ####################################################################### ####################################################################### # MAIN ####################################################################### ####################################################################### my $piece; my $top_dir; my @git_args; my %data; my %print_filters = (); foreach my $arg (@ARGV) { if ($arg eq '--help') { usage(); exit; } elsif ($arg eq '--no-pieces') { $piece = "bootstrap"; } elsif ($arg =~ m/--piece=(.*)/) { $piece = $1; } elsif ($arg eq '--bugs') { $print_filters{'bug'} = 1; } else { if (! defined $top_dir) { $top_dir=$arg; } else { push @git_args, $arg; } } } (defined $top_dir) || die "Error: top direcotry is not defined\n"; (-d "$top_dir") || die "Error: not a directory: $top_dir\n"; (-f "$top_dir/.git/config") || die "Error: can't find $top_dir/.git/config\n"; load_data(\%data, $top_dir,$piece, \@git_args); print_weekly_stat(\%data, \%print_filters);