I was curious about something like this earlier this week, and I wrote this little hack. Got anything better? ---- >8 ---- #!/usr/bin/perl -w use lib (split(/:/, $ENV{GITPERLLIB} || "/your/git/installation/lib/perl5/site_perl/5.8.8")); use strict; use Git; use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION = 1; $main::VERSION = 0.1; sub usage { my $name; eval { require File::Basename; $name = File::Basename::basename($0); } or do { $name = substr $0, rindex($0, '/') + 1; }; print "Usage: $name [--help] [-flstv] <tree-ish> [paths...]\n"; } sub main::HELP_MESSAGE { usage; print "\nOPTIONS\n\n", " -f use file centric view\n", " -l use longer format\n", " -s use short format\n", " Both -l and -s produce an even longer format\n", " -t print total lines at the end\n", " -v print more information while processing\n", "\n"; } sub new_parser { my $fh = shift; my $ctx = shift; return { FH => $fh, CTX => $ctx, buf => ''}; } sub get_next_line { my $obj = shift; my $fh = $obj->{'FH'}; defined($obj->{'buf'} = <$fh>); } sub parse_blame_entry { my $obj = shift; return () unless get_next_line $obj; $_ = $obj->{'buf'}; chomp; my ($sha1, $sourceline, $resultline, $num_lines) = split ' ', $_, 4; return () unless defined $num_lines; my %h = ( sha1 => $sha1, sourceline => $sourceline, resultline => $resultline, lines => $num_lines ); while (get_next_line $obj) { $_ = $obj->{'buf'}; chomp; my ($key, $val) = split ' ', $_, 2; $h{$key} = $val; last if m/^filename /; } return %h; } sub parse_blame { my $obj = shift; my $authors = shift; my %commits; while (my %h = parse_blame_entry $obj) { if (! exists $commits{$h{'sha1'}}) { if (! exists $authors->{$h{'author'}}->{$obj->{'filename'}}) { $authors->{$h{'author'}}->{$obj->{'filename'}} = 0; } $commits{$h{'sha1'}} = \$authors->{$h{'author'}}->{$obj->{'filename'}}; } ${$commits{$h{'sha1'}}} += $h{'lines'}; } } sub count_total_lines { my $authors = shift; my $lines = 0; for (values %{$authors}) { for (values %{$_}) { $lines += $_; } } return $lines; } sub count_author_lines { my $authors = shift; my %alines; foreach my $author (keys %{$authors}) { my $lines = 0; for (values %{$authors->{$author}}) { $lines += $_; } $alines{$author} = $lines; } return %alines; } sub count_file_lines { my $authors = shift; my %flines; for (values %{$authors}) { foreach my $file (keys %{$_}) { $flines{$file} += $_->{$file}; } } return %flines; } sub print_short { my $authors = shift; my %alines = count_author_lines $authors; foreach my $author (sort {$alines{$b} <=> $alines{$a}} keys %alines) { printf "%6d %s\n", $alines{$author}, $author; } } sub print_long { my $authors = shift; my %alines = count_author_lines $authors; foreach my $author (sort {$alines{$b} <=> $alines{$a}} keys %alines) { print "$author (", $alines{$author}, "):\n"; foreach my $file (sort {$authors->{$author}->{$b} <=> $authors->{$author}->{$a}} keys %{$authors->{$author}}) { printf " %10d %s\n", $authors->{$author}->{$file}, $file; } } } sub print_longer { my $authors = shift; my %alines = count_author_lines $authors; my $total_lines = count_total_lines $authors; foreach my $author (sort {$alines{$b} <=> $alines{$a}} keys %alines) { printf "%s (%d, %.2f%%):\n", $author, $alines{$author}, 100. * $alines{$author} / $total_lines; foreach my $file (sort {$authors->{$author}->{$b} <=> $authors->{$author}->{$a}} keys %{$authors->{$author}}) { printf " %10d (%5.2f%%) %s\n", $authors->{$author}->{$file}, 100. * $authors->{$author}->{$file} / $alines{$author}, $file; } } } sub print_with_file_percentage { my $authors = shift; my %alines = count_author_lines $authors; my %flines = count_file_lines $authors; my $total_lines = count_total_lines $authors; my $total_files = scalar(keys %flines); foreach my $author (sort {$alines{$b} <=> $alines{$a}} keys %alines) { printf "%s (%d lines in %d files, " . "%.2f%% of all lines, %.2f%% of all files):\n", $author, $alines{$author}, scalar(keys %{$authors->{$author}}), 100. * $alines{$author} / $total_lines, 100. * scalar(keys %{$authors->{$author}})/$total_files; foreach my $file (sort {$authors->{$author}->{$b} <=> $authors->{$author}->{$a}} keys %{$authors->{$author}}) { printf " %10d (%6.2f%%) of %6d (%6.2f%%) %s\n", $authors->{$author}->{$file}, 100. * $authors->{$author}->{$file} / $flines{$file}, $flines{$file}, 100. * $authors->{$author}->{$file} / $alines{$author}, $file; } } } sub print_with_file_perspective { my $authors = shift; my %flines = count_file_lines $authors; foreach my $file (sort keys %flines) { my @auths = grep {exists $authors->{$_}->{$file}} keys %{$authors}; print "$file ($flines{$file}):\n"; foreach my $author (sort {$authors->{$b}->{$file} <=> $authors->{$a}->{$file}} @auths) { printf " %10d %s\n", $authors->{$author}->{$file}, $author; } } } my $verbose = 0; my $output_format = 0; my $show_total = 0; our ($opt_f, $opt_l, $opt_s, $opt_t, $opt_v); getopts("flstv") or die "Invalid options specified"; if ($opt_f) { $output_format = 4; } elsif ($opt_l && $opt_s) { $output_format = 3; } elsif ($opt_l) { $output_format = 2; } elsif ($opt_s) { $output_format = 1; } if ($opt_t) { $show_total = 1; } if ($opt_v) { $verbose = 1; } eval {select STDERR; usage; exit 1;} unless $#ARGV >= 0; my %authors; my $repo = Git->repository(); my ($fh, $ctx) = $repo->command_output_pipe('ls-tree', '-r', '--name-only', '--', @ARGV); my $ls_tree = new_parser $fh, $ctx; while (get_next_line $ls_tree) { chomp $ls_tree->{'buf'}; my $is_text = `file "$ls_tree->{'buf'}"` =~ m/text/; if ($is_text) { if ($verbose) { print STDERR "Processing file: ", $ls_tree->{'buf'}, "\n"; } } else { if ($verbose) { print STDERR "Skipping non-text file: ", $ls_tree->{'buf'}, "\n"; } next; } ($fh, $ctx) = $repo->command_output_pipe('blame', '-C', '-C', '-w', '--incremental', '--', $ls_tree->{'buf'}); my $blame_obj = new_parser $fh, $ctx; $blame_obj->{'filename'} = $ls_tree->{'buf'}; parse_blame $blame_obj, \%authors; $repo->command_close_pipe($blame_obj->{'FH'}, $blame_obj->{'CTX'}); } $repo->command_close_pipe($ls_tree->{'FH'}, $ls_tree->{'CTX'}); if ($output_format == 0) { print_long \%authors; } elsif ($output_format == 1) { print_short \%authors; } elsif ($output_format == 2) { print_longer \%authors; } elsif ($output_format == 3) { print_with_file_percentage \%authors; } elsif ($output_format == 4) { print_with_file_perspective \%authors; } print count_total_lines(\%authors), " total lines.\n" if ($show_total); exit; -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html