This updates a number of things: - Move the cache directory under the .local directory - Reformat & clean up with perltidy. Add the perltidy command line. - Add username and email aliases to clean up duplicates and unknown email addresses. - Use full length commit IDs - Collect patch commenters - Check variables before using them as key values - Ignore patch submit time, just collect the date - Get stats about large patches - Format the output better Example output: Statistics from commitc35f281934to commitf8fbf0917cPatch, Date, Owner, Author, Submitter, Inserted lines, Deleted lines, Subject, Reviewers, Commenters "f8fbf0917c722378454b07c2e8ec1a3f87b324ae", 2022/12/10, Frank Chu, Frank Chu, Martin Roth, 22, 1, "mb/google/brya/var/marasov: Change FSP board type to Type3", "Frank Chu, Eric Lai" , "-" "5778e06771627a5541ca2b137e783f47257f05ec", 2022/12/10, Dinesh Gehlot, Dinesh Gehlot, Subrata Banik, 30, 1, "soc/intel/meteorlake: Drop casts around `soc_read_pmc_base()`", "Kapil Porwal, Elyes Haouas" , "Subrata Banik" "ed8bdefcdf6c19258febb9931d1e8eb12b958bcc", 2022/12/10, Jamie Ryu, Jamie Ryu, Felix Held, 76, 3, "mb/intel/mtlrvp: Add MTL-P RVP board ids", "Usha P, Sridhar Siricilla, Eric Lai, Subrata Banik" , "Eric Lai, Subrata Banik, Harsha B R, Angel Pons" - Total Commits: 985 - Average Commits per day: 17.85 - Total lines added: 61475 - Average lines added per commit: 62.41 - Number of patches adding more than 100 lines: 49 - Average lines added per small commit: 37.82 - Total lines removed: 758022 - Average lines removed per commit: 769.57 - Total difference between added and removed: -696547 === Authors - Number of commits === Author ,Ptchs ,Revws , Cmnts , Sbmts , Email , Prcnt, Last commit , Earliest_commit Elyes Haouas , 126 , 90 , 28 , 0 , ehaouas@noos.fr ,12.79%, 2022/12/10 , 2022/10/17 Arthur Heymans , 107 , 99 , 28 , 40 , arthur@aheymans.xyz ,10.86%, 2022/12/10 , 2022/10/17 === Authors - Lines added === Martin Roth , 10103, 16.434% Kyösti Mälkki , 6044, 9.832% Arthur Heymans , 3314, 5.391% === Authors - Lines removed === Arthur Heymans , -741944, 97.879% Felix Held , -3031, 0.400% Kyösti Mälkki , -1680, 0.222% === Reviewers - Number of patches reviewed === Angel Pons , 272, 27.614% Eric Lai , 201, 20.406% Felix Held , 106, 10.761% === Submitters - Number of patches submitted === Name , #, total%, Own, own%, Other, other% Felix Held , 482, 48.934%, 56, 11.62%, 426, 88.38% Martin Roth , 179, 18.173%, 42, 23.46%, 137, 76.54% Subrata Banik , 54, 5.482%, 31, 57.41%, 23, 42.59% Signed-off-by: Martin Roth <gaumless@gmail.com> Change-Id: Ie1694116ab36ca4db25d13935adadca10e50068f Reviewed-on: https://review.coreboot.org/c/coreboot/+/70572 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Angel Pons <th3fanbus@gmail.com> Reviewed-by: Felix Singer <felixsinger@posteo.net>
		
			
				
	
	
		
			717 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			717 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env perl
 | |
| #
 | |
| # SPDX-License-Identifier: GPL-2.0-only
 | |
| 
 | |
| package gerrit_stats;
 | |
| 
 | |
| # To install any needed modules install the cpanm app, and use it to install the required modules:
 | |
| #  sudo cpan App::cpanminus
 | |
| #  sudo /usr/local/bin/cpanm JSON::Util Net::OpenSSH DateTime Devel::Size
 | |
| 
 | |
| # perltidy -l=200 -bt=2 -ce
 | |
| 
 | |
| use strict;
 | |
| use warnings;
 | |
| use English qw( -no_match_vars );
 | |
| use File::Find;
 | |
| use File::Path;
 | |
| use Getopt::Long;
 | |
| use Getopt::Std;
 | |
| use JSON::Util;
 | |
| use Net::OpenSSH;
 | |
| use Data::Dumper qw(Dumper);
 | |
| use DateTime;
 | |
| use Devel::Size qw(size total_size);
 | |
| 
 | |
| my $old_version;
 | |
| my $new_version;
 | |
| my $infodir = "$ENV{'HOME'}/.local/commit_info/" . `git config -l | grep remote.origin.url | sed 's|.*@||' | sed 's|:.*||'`;
 | |
| chomp($infodir);
 | |
| my $URL_WITH_USER;
 | |
| my $SKIP_GERRIT_CHECK;
 | |
| my $print_commit_list = 1;
 | |
| 
 | |
| #disable print buffering
 | |
| $OUTPUT_AUTOFLUSH = 1;
 | |
| binmode STDOUT, ":utf8";
 | |
| 
 | |
| Main();
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # Main
 | |
| #-------------------------------------------------------------------------------
 | |
| sub Main {
 | |
|     check_arguments();
 | |
| 
 | |
|     my %submitters                = ();
 | |
|     my %authors                   = ();
 | |
|     my %owners                    = ();
 | |
|     my %reviewers                 = ();
 | |
|     my %commenters                = ();
 | |
|     my %author_added              = ();
 | |
|     my %author_removed            = ();
 | |
|     my $total_added               = 0;
 | |
|     my $total_removed             = 0;
 | |
|     my $number_of_commits         = 0;
 | |
|     my $number_of_submitters      = 0;
 | |
|     my $submit_epoch              = "";
 | |
|     my $first_submit_epoch        = "";
 | |
|     my $patches_over_100_lines    = 0;
 | |
|     my $total_lines_large_patches = 0;
 | |
|     my %email_addresses           = (
 | |
|         'Kacper Stojek' => 'kacper.stojek@3mdeb.com',
 | |
|         'Damien Zammit' => 'damien@zamaudio.com',
 | |
|         'Pavel Sayekat' => 'pavelsayekat@gmail.com',
 | |
|         'Lance Zhao'    => 'lance.zhao@gmail.com',
 | |
|     );
 | |
| 
 | |
|     my %aliases = (
 | |
|         ' Felix Singer'                        => 'Felix Singer',
 | |
|         'Abhay kumar'                          => 'Abhay Kumar',
 | |
|         'AlexandruX Gagniuc'                   => 'Alexandru Gagniuc',
 | |
|         'Anish K. Patel'                       => 'Anish K Patel',
 | |
|         'Bao Zheng'                            => 'Zheng Bao',
 | |
|         'Bernhard M. Wiedemann'                => 'Bernhard M. Wiedermann',
 | |
|         'Björn Busse'                         => 'Bjarn Busse',
 | |
|         'BryantOu'                             => 'Bryant Ou',
 | |
|         'Chen Wisley'                          => 'Wisley Chen',
 | |
|         'Cheng-Yi Chiang'                      => 'Jimmy Cheng-Yi Chiang',
 | |
|         'Chris Ching (using chromium account)' => 'Chris Ching,',
 | |
|         'ChromeOS Developer'                   => 'Dave Parker',
 | |
|         'Cristi M'                             => 'Cristian Magherusan-Stanciu',
 | |
|         'Cristian M?gheru?an-Stanciu'          => 'Cristian Magherusan-Stanciu',
 | |
|         'Cristian MÄgheruÈan-Stanciu'          => 'Cristian Magherusan-Stanciu',
 | |
|         'Cristian MÄgheruÈan-Stanciu'          => 'Cristian Magherusan-Stanciu',
 | |
|         'DAWEI CHIEN'                          => 'Dawei Chien',
 | |
|         'efdesign98'                           => 'Frank Vibrans',
 | |
|         'Eugene D. Myers'                      => 'Eugene Myers',
 | |
|         'Frank Vibrans III'                    => 'Frank Vibrans',
 | |
|         'frank vibrans'                        => 'Frank Vibrans',
 | |
|         'Frank.Vibrans'                        => 'Frank Vibrans',
 | |
|         'FrankChu'                             => 'Frank Chu',
 | |
|         'garmin chang'                         => 'Garmin Chang',
 | |
|         'Garmin.Chang'                         => 'Garmin Chang',
 | |
|         'hannahwilliams2'                      => 'Hannah Williams',
 | |
|         'HAOUAS Elyes'                         => 'Elyes Haouas',
 | |
|         'Harshapriya N'                        => 'Harsha Priya',
 | |
|         'Hsuan-ting Chen'                      => 'Hsuan Ting Chen',
 | |
|         'Iru Cai (vimacs)'                     => 'Iru Cai',
 | |
|         'Jérémy Compostella'                   => 'Jeremy Compostella',
 | |
|         'Jérémy Compostella'                   => 'Jeremy Compostella',
 | |
|         'JG Poxu'                              => 'Po Xu',
 | |
|         'JonathonHall-Purism'                  => 'Jonathon Hall',
 | |
|         'Karthikeyan Ramasubramanian'          => 'Karthik Ramasubramanian',
 | |
|         'Kerry She'                            => 'Kerry Sheh',
 | |
|         'kewei.xu'                             => 'kewei xu',
 | |
|         'Kumar, Gomathi'                       => 'Gomathi Kumar',
 | |
|         'Kyösti Mälkki'                      => 'Kyösti Mälkki',
 | |
|         'Kyösti Mälkki'                        => 'Kyösti Mälkki',
 | |
|         'Marcello Sylvester Bauer'             => 'Marcello Sylvester Bauer',
 | |
|         'Martin L Roth'                        => 'Martin Roth',
 | |
|         'Martin Roth - Personal'               => 'Martin Roth',
 | |
|         'Matt Ziegelbaum'                      => 'Matthew Ziegelbaum',
 | |
|         'mengqi.zhang'                         => 'Mengqi Zhang',
 | |
|         'mrnuke'                               => 'Alexandru Gagniuc',
 | |
|         'Nina-CM Wu'                           => 'Nina Wu',
 | |
|         'ot_zhenguo.li'                        => 'Zhenguo Li',
 | |
|         'Patrick Georgi patrick.georgi'        => 'Patrick Georgi',
 | |
|         'Patrick Georgi patrick'               => 'Patrick Georgi',
 | |
|         'Pavlushka'                            => 'Pavel Sayekat',
 | |
|         'Ravi Kumar Bokka'                     => 'Ravi Kumar',
 | |
|         'Ravi kumar'                           => 'Ravi Kumar',
 | |
|         'Ravi Sarawadi'                        => 'Ravishankar Sarawadi',
 | |
|         'ravindr1'                             => 'Ravindra',
 | |
|         'Ravindra N'                           => 'Ravindra',
 | |
|         'Ricardo Ribalda Delgado'              => 'Ricardo Ribalda',
 | |
|         'ron minnich'                          => 'Ron Minnich',
 | |
|         'Ronald G. Minnich'                    => 'Ron Minnich',
 | |
|         'samrab'                               => 'Sudheer Amrabadi',
 | |
|         'SANTHOSH JANARDHANA HASSAN'           => 'Santhosh Janardhana Hassan',
 | |
|         'semihalf-czapiga-jakub'               => 'Jakub Czapiga',
 | |
|         'Seunghwan Kim'                        => 'SH Kim',
 | |
|         'Sooi, Li Cheng'                       => 'Li Cheng Sooi',
 | |
|         'Stefan Reinauerstepan'                => 'Stefan Reinauer',
 | |
|         'stepan'                               => 'Stefan Reinauer',
 | |
|         'Swift Geek (Sebastian Grzywna)'       => 'Sebastian "Swift Geek" Grzywna',
 | |
|         'Sylvain "ythier" Hitier'              => 'Sylvain Hitier',
 | |
|         'Thomas Gstädtner'                    => 'Thomas Gstaedtner',
 | |
|         'UwePoeche'                            => 'Uwe Poeche',
 | |
|         'UwePoeche'                            => 'Uwe Poeche',
 | |
|         'Varshit Pandya'                       => 'Varshit B Pandya',
 | |
|         'Wayne3 Wang'                          => 'Wayne Wang',
 | |
|         'Wayne3_Wang'                          => 'Wayne Wang',
 | |
|         'Xi Chen'                              => 'Xixi Chen',
 | |
|         'Yu-Hsuan Hsu'                         => 'Yu-hsuan Hsu',
 | |
|         'zbao'                                 => 'Zheng Bao',
 | |
|         'Zheng Bao zheng.bao'                  => 'Zheng Bao',
 | |
|         'zhiyong tao'                          => 'Zhiyong Tao',
 | |
|         'Дмитрий Понаморев'                    => 'Dmitry Ponamorev',
 | |
|     );
 | |
| 
 | |
|     if ( !$URL_WITH_USER ) {
 | |
|         get_user();
 | |
|     }
 | |
| 
 | |
|     print "Saving data to $infodir\n";
 | |
| 
 | |
|     # Make sure the versions exist
 | |
|     check_versions();
 | |
| 
 | |
|     # Fetch patches if needed.  Get ids of first and last commits
 | |
|     my @commits = `git log --pretty=%H "$old_version..$new_version" 2>/dev/null`;
 | |
|     get_commits(@commits);
 | |
|     my $last_commit_id  = $commits[0];
 | |
|     my $first_commit_id = $commits[ @commits - 1 ];
 | |
|     chomp $last_commit_id;
 | |
|     chomp $first_commit_id;
 | |
| 
 | |
|     print "Statistics from commit $first_commit_id to commit $last_commit_id\n";
 | |
|     print "Patch, Date, Owner, Author, Submitter, Inserted lines, Deleted lines, Subject, Reviewers, Commenters\n";
 | |
| 
 | |
|     # Loop through all commits
 | |
|     for my $commit_id (@commits) {
 | |
|         $commit_id =~ s/^\s+|\s+$//g;
 | |
| 
 | |
|         my $submitter        = "";
 | |
|         my %patch_reviewers  = ();
 | |
|         my %patch_commenters = ();
 | |
|         my $info;
 | |
|         my $owner;
 | |
|         my $author;
 | |
|         my $author_email;
 | |
|         my $inserted_lines = 0;
 | |
|         my $deleted_lines  = 0;
 | |
|         my $subject;
 | |
| 
 | |
|         $number_of_commits++;
 | |
|         print "\"$commit_id\", ";
 | |
| 
 | |
|         # Read the data file for the current commit
 | |
|         if ( -f "$infodir/$commit_id" && -s "$infodir/$commit_id" > 20 ) {
 | |
|             open( my $HANDLE, "<", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
 | |
|             $info = <$HANDLE>;
 | |
|             close $HANDLE;
 | |
| 
 | |
|             my $commit_info = JSON::Util->decode($info);
 | |
| 
 | |
|             # Get the easy data
 | |
|             $owner = $commit_info->{'owner'}{'name'};
 | |
|             if ( !$owner ) {
 | |
|                 $owner = $commit_info->{'owner'}{'username'};
 | |
|             }
 | |
|             if ( !$owner ) {
 | |
|                 $owner = "-";
 | |
|             }
 | |
|             if ( $owner && exists( $aliases{$owner} ) ) {
 | |
|                 $owner = $aliases{$owner};
 | |
|             }
 | |
| 
 | |
|             $author = $commit_info->{'currentPatchSet'}{'author'}{'name'};
 | |
|             if ( $author && exists( $aliases{$author} ) ) {
 | |
|                 $author = $aliases{$author};
 | |
|             }
 | |
|             if ( !$author ) {
 | |
|                 $author = $commit_info->{'currentPatchSet'}{'author'}{'username'};
 | |
|             }
 | |
|             $author_email   = $commit_info->{'currentPatchSet'}{'author'}{'email'};
 | |
|             $inserted_lines = $commit_info->{'currentPatchSet'}{'sizeInsertions'};
 | |
|             $deleted_lines  = $commit_info->{'currentPatchSet'}{'sizeDeletions'};
 | |
|             $subject        = $commit_info->{'subject'};
 | |
| 
 | |
|             #get the patch's submitter
 | |
|             my $approvals = $commit_info->{'currentPatchSet'}{'approvals'};
 | |
|             for my $approval (@$approvals) {
 | |
|                 if ( $approval->{'type'} eq "SUBM" ) {
 | |
|                     $submit_epoch = $approval->{'grantedOn'};
 | |
|                     $submitter    = $approval->{'by'}{'name'};
 | |
|                     if ( exists( $aliases{$submitter} ) ) {
 | |
|                         $submitter = $aliases{$submitter};
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             # Get all the commenters for all patch revisions
 | |
|             my $comments = $commit_info->{'comments'};
 | |
|             for my $comment (@$comments) {
 | |
|                 my $commenter;
 | |
|                 if ( $comment->{'reviewer'}{'username'} ) {
 | |
|                     if ( $comment->{'reviewer'}{'username'} eq "jenkins" ) {
 | |
|                         next;
 | |
|                     }
 | |
|                     if ( $comment->{'reviewer'}{'username'} eq "hardwaretestrobot" ) {
 | |
|                         next;
 | |
|                     }
 | |
|                     if ( $comment->{'reviewer'}{'username'} eq "raptor-automated-test" ) {
 | |
|                         next;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if ( $comment->{'reviewer'}{'name'} ) {
 | |
|                     if ( $comment->{'reviewer'}{'name'} eq "Gerrit Code Review" ) {
 | |
|                         next;
 | |
|                     }
 | |
|                 }
 | |
|                 if ( $comment->{'message'} ) {
 | |
|                     if ( $comment->{'message'} =~ "successfully cherry-picked" ) {
 | |
|                         next;
 | |
|                     }
 | |
|                     if ( $comment->{'message'} =~ ": Code-Review" ) {
 | |
|                         next;
 | |
|                     }
 | |
|                     if ( $comment->{'message'} =~ "Uploaded patch set" ) {
 | |
|                         next;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if ( !$commenter ) {
 | |
|                     $commenter = $comment->{'reviewer'}{'name'};
 | |
|                     if ( $commenter && exists( $aliases{$commenter} ) ) {
 | |
|                         $commenter = $aliases{$commenter};
 | |
|                     }
 | |
|                 }
 | |
|                 if ( !$commenter ) {
 | |
|                     $commenter = $comment->{'reviewer'}{'username'};
 | |
|                     if ( $commenter && exists( $aliases{$commenter} ) ) {
 | |
|                         $commenter = $aliases{$commenter};
 | |
|                     }
 | |
|                 }
 | |
|                 if ( $commenter && $author && $commenter eq $author ) {
 | |
|                     next;
 | |
|                 }
 | |
|                 if ($commenter) {
 | |
|                     if ( $commenter && exists $patch_commenters{$commenter} ) {
 | |
|                         $patch_commenters{$commenter}++;
 | |
|                     } else {
 | |
|                         $patch_commenters{$commenter} = 1;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             # Get all the reviewers for all patch revisions
 | |
|             my $patchsets = $commit_info->{'patchSets'};
 | |
|             for my $patch (@$patchsets) {
 | |
|                 if ( !$author ) {
 | |
|                     $author = $patch->{'author'}{'name'};
 | |
|                     if ( $author && exists( $aliases{$author} ) ) {
 | |
|                         $author = $aliases{$author};
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 my $approvals = $patch->{'approvals'};
 | |
|                 for my $approval (@$approvals) {
 | |
| 
 | |
|                     if ( ( !$submitter ) && ( $approval->{'type'} eq "SUBM" ) ) {
 | |
|                         $submit_epoch = $approval->{'grantedOn'};
 | |
|                         $submitter    = $approval->{'by'}{'name'};
 | |
|                         if ( $submitter && exists( $aliases{$submitter} ) ) {
 | |
|                             $submitter = $aliases{$submitter};
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     if ( $approval->{'type'} eq "Code-Review" ) {
 | |
|                         my $patch_reviewer = $approval->{'by'}{'name'};
 | |
|                         if ($patch_reviewer) {
 | |
|                             if ( exists $patch_reviewers{$patch_reviewer} ) {
 | |
|                                 $patch_reviewers{$patch_reviewer}++;
 | |
|                             } else {
 | |
|                                 $patch_reviewers{$patch_reviewer} = 1;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         } else {
 | |
| 
 | |
|             # Get the info from git
 | |
|             my $logline = `git log --pretty="%ct@@@%s@@@%an@@@%aE@@@%cn" $commit_id^..$commit_id --`;
 | |
|             $logline =~ m/^(.*)@@@(.*)@@@(.*)@@@(.*)@@@(.*)\n/;
 | |
|             ( $submit_epoch, $subject, $author, $author_email, $submitter ) = ( $1, $2, $3, $4, $5 );
 | |
|             if ( exists( $aliases{$author} ) ) {
 | |
|                 $author = $aliases{$author};
 | |
|             }
 | |
|             $owner = $author;
 | |
| 
 | |
|             if ( $submitter && exists( $aliases{$submitter} ) ) {
 | |
|                 $submitter = $aliases{$submitter};
 | |
|             }
 | |
| 
 | |
|             $logline = `git log --pretty= --shortstat $commit_id^..$commit_id --`;
 | |
|             if ( $logline =~ m/\s+(\d+)\s+insertion/ ) {
 | |
|                 $inserted_lines = $1;
 | |
|             }
 | |
|             if ( $logline =~ m/\s+(\d+)\s+deletion/ ) {
 | |
|                 $deleted_lines = $1 * -1;
 | |
|             }
 | |
|             my @loglines = `git log $commit_id^..$commit_id -- | grep '\\sReviewed-by:'`;
 | |
|             for my $line (@loglines) {
 | |
|                 if ( $line =~ m/.*:\s+(.*)\s</ ) {
 | |
|                     my $patch_reviewer = $1;
 | |
|                     if ( exists( $aliases{$patch_reviewer} ) ) {
 | |
|                         $patch_reviewer = $aliases{$patch_reviewer};
 | |
|                     }
 | |
|                     if ($patch_reviewer) {
 | |
|                         if ( exists $patch_reviewers{$patch_reviewer} ) {
 | |
|                             $patch_reviewers{$patch_reviewer}++;
 | |
|                         } else {
 | |
|                             $patch_reviewers{$patch_reviewer} = 1;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         # Not entirely certain why this is needed, but for a number of patches have been submitted
 | |
|         # the submit time in gerrit is set to April 9, 2015.
 | |
|         if ( $submit_epoch == 1428586219 ) {
 | |
|             my $logline = `git log --pretty="%ct" $commit_id^..$commit_id --`;
 | |
|             $logline =~ m/^(.*)\n/;
 | |
|             $submit_epoch = $1;
 | |
|         }
 | |
| 
 | |
|         # Add the count and owner to the submitter hash
 | |
|         if ( $submitter && exists $submitters{$submitter} && exists $submitters{$submitter}{count} ) {
 | |
|             $submitters{$submitter}{count}++;
 | |
|         } else {
 | |
|             $submitters{$submitter}{count} = 1;
 | |
|             $number_of_submitters++;
 | |
|             $submitters{$submitter}{"self"} = 0;
 | |
|             $submitters{$submitter}{others} = 0;
 | |
|             $submitters{$submitter}{name}   = $submitter;
 | |
|         }
 | |
| 
 | |
|         if ( $submitter eq $author ) {
 | |
|             $submitters{$submitter}{"self"}++;
 | |
|         } else {
 | |
|             $submitters{$submitter}{others}++;
 | |
|         }
 | |
| 
 | |
|         # Create a readable date
 | |
|         my $dt = DateTime->from_epoch( epoch => $submit_epoch );
 | |
|         $dt->set_time_zone('Europe/Paris');
 | |
|         my $submit_time = $dt->strftime('%Y/%m/%d');
 | |
|         if ( !$first_submit_epoch ) {
 | |
|             $first_submit_epoch = $submit_epoch;
 | |
|         }
 | |
| 
 | |
|         # Create the list of commenters to print
 | |
|         my $commenterlist = "";
 | |
|         foreach my $commenter ( keys %patch_commenters ) {
 | |
|             if ( $commenter && exists( $aliases{$commenter} ) ) {
 | |
|                 $commenter = $aliases{$commenter};
 | |
|             }
 | |
| 
 | |
|             if ( $commenterlist eq "" ) {
 | |
|                 $commenterlist = $commenter;
 | |
|             } else {
 | |
|                 $commenterlist .= ", $commenter";
 | |
|             }
 | |
| 
 | |
|             if ( $commenter && exists $commenters{$commenter} ) {
 | |
|                 $commenters{$commenter}++;
 | |
|             } else {
 | |
|                 $commenters{$commenter} = 1;
 | |
|             }
 | |
|         }
 | |
|         if ( !$commenterlist ) {
 | |
|             $commenterlist = "-";
 | |
|         }
 | |
| 
 | |
|         # Create the list of reviewers to print
 | |
|         my $reviewerlist = "";
 | |
|         foreach my $reviewer ( keys %patch_reviewers ) {
 | |
|             if ( exists( $aliases{$reviewer} ) ) {
 | |
|                 $reviewer = $aliases{$reviewer};
 | |
|             }
 | |
| 
 | |
|             if ( $reviewerlist eq "" ) {
 | |
|                 $reviewerlist = $reviewer;
 | |
|             } else {
 | |
|                 $reviewerlist .= ", $reviewer";
 | |
|             }
 | |
| 
 | |
|             if ( $reviewer && exists $reviewers{$reviewer} ) {
 | |
|                 $reviewers{$reviewer}++;
 | |
|             } else {
 | |
|                 $reviewers{$reviewer} = 1;
 | |
|             }
 | |
|         }
 | |
|         if ( !$reviewerlist ) {
 | |
|             $reviewerlist = "-";
 | |
|         }
 | |
| 
 | |
|         if ($print_commit_list) {
 | |
|             print "$submit_time, $owner, $author, $submitter, $inserted_lines, $deleted_lines, \"$subject\", \"$reviewerlist\" , \"$commenterlist\"\n";
 | |
|         } else {
 | |
|             print "$number_of_commits\n";
 | |
|         }
 | |
|         $total_added += $inserted_lines;
 | |
|         if ( $inserted_lines - $deleted_lines > 100 ) {
 | |
|             $patches_over_100_lines++;
 | |
|             $total_lines_large_patches += $inserted_lines;
 | |
|         }
 | |
|         $total_removed += $deleted_lines;
 | |
|         if ( exists $owners{$owner} ) {
 | |
|             $owners{$owner}++;
 | |
|         } else {
 | |
|             $owners{$owner} = 1;
 | |
|         }
 | |
| 
 | |
|         if ( $author && exists $authors{$author}{"num"} ) {
 | |
|             $authors{$author}{"num"}++;
 | |
|             $author_added{$author}   += $inserted_lines;
 | |
|             $author_removed{$author} += $deleted_lines;
 | |
|             $authors{$author}{"earliest_commit"} = $submit_time;
 | |
|         } else {
 | |
|             $authors{$author}{"num"}             = 1;
 | |
|             $authors{$author}{"latest_commit"}   = $submit_time;
 | |
|             $authors{$author}{"earliest_commit"} = $submit_time;
 | |
|             $author_added{$author}               = $inserted_lines;
 | |
|             $author_removed{$author}             = $deleted_lines;
 | |
|         }
 | |
|         if ( $author && ( !exists $authors{$author}{email} || $authors{$author}{email} eq "-" ) ) {
 | |
|             if ($author_email) {
 | |
|                 $authors{$author}{email} = "$author_email";
 | |
|             } elsif ( exists $email_addresses{$author} ) {
 | |
|                 $authors{$author}{email} = $email_addresses{$author};
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     my $Days = ( $first_submit_epoch - $submit_epoch ) / 86400;
 | |
|     if ( ( $first_submit_epoch - $submit_epoch ) % 86400 ) {
 | |
|         $Days += 1;
 | |
|     }
 | |
| 
 | |
|     print "\n- Total Commits: $number_of_commits\n";
 | |
|     printf "- Average Commits per day: %.2f\n", $number_of_commits / $Days;
 | |
|     print "- Total lines added: $total_added\n";
 | |
|     printf "- Average lines added per commit: %.2f\n", $total_added / $number_of_commits;
 | |
|     print "- Number of patches adding more than 100 lines: $patches_over_100_lines\n";
 | |
|     printf "- Average lines added per small commit: %.2f\n", ( $total_added - $total_lines_large_patches ) / ( $number_of_commits - $patches_over_100_lines );
 | |
| 
 | |
|     print "- Total lines removed: $total_removed\n";
 | |
|     printf "- Average lines removed per commit: %.2f\n", $total_removed / $number_of_commits;
 | |
|     print "- Total difference between added and removed: " . ( $total_added - $total_removed ) . "\n\n";
 | |
| 
 | |
|     print "=== Authors - Number of commits ===\n";
 | |
|     printf "%-30s ,%5s ,%5s ,%6s ,%6s , %-52s ,%6s, %-19s , %s\n", "Author", "Ptchs", "Revws", "Cmnts", "Sbmts", "Email", "Prcnt", "Last commit", "Earliest_commit";
 | |
| 
 | |
|     my $number_of_authors = 0;
 | |
|     foreach my $author ( sort { $authors{$b}{num} <=> $authors{$a}{num} } ( keys %authors ) ) {
 | |
|         my $submissions = 0;
 | |
|         if ( $author && exists $submitters{$author} ) {
 | |
|             $submissions = $submitters{$author}{count};
 | |
|         }
 | |
|         my $review_count = 0;
 | |
|         if ( $author && exists $reviewers{$author} ) {
 | |
|             $review_count = $reviewers{$author};
 | |
|         }
 | |
| 
 | |
|         my $comment_count = 0;
 | |
|         if ( $author && exists $commenters{$author} ) {
 | |
|             $comment_count = $commenters{$author};
 | |
|         }
 | |
| 
 | |
|         if ( $author && !exists $authors{$author}{"email"} ) {
 | |
|             $authors{$author}{"email"} = "-";
 | |
|         }
 | |
| 
 | |
|         printf "%-30s ,%5d ,%5d ,%6d ,%6d , %-52s ,%5.2f%%, %s , %s\n", $author, $authors{$author}{"num"}, $review_count,
 | |
|           $comment_count, $submissions, $authors{$author}{"email"}, $authors{$author}{"num"} / $number_of_commits * 100,
 | |
|           $authors{$author}{"latest_commit"}, $authors{$author}{"earliest_commit"};
 | |
|         $number_of_authors++;
 | |
|     }
 | |
|     print "Total Authors: $number_of_authors\n\n";
 | |
| 
 | |
|     print "=== Authors - Lines added ===\n";
 | |
|     foreach my $author ( sort { $author_added{$b} <=> $author_added{$a} } ( keys %author_added ) ) {
 | |
|         if ( $author_added{$author} ) {
 | |
|             printf "%-30s, %10d, %2.3f%%\n", $author, $author_added{$author}, $author_added{$author} / $total_added * 100;
 | |
|         }
 | |
|     }
 | |
|     print "\n";
 | |
| 
 | |
|     print "=== Authors - Lines removed ===\n";
 | |
|     foreach my $author ( sort { $author_removed{$b} <=> $author_removed{$a} } ( keys %author_removed ) ) {
 | |
|         if ( $author_removed{$author} ) {
 | |
|             printf "%-30s, %10d, %6.3f%%\n", $author, $author_removed{$author} * -1, $author_removed{$author} / $total_removed * 100;
 | |
|         }
 | |
|     }
 | |
|     print "\n";
 | |
| 
 | |
|     print "=== Reviewers - Number of patches reviewed ===\n";
 | |
|     my $number_of_reviewers = 0;
 | |
|     foreach my $reviewer ( sort { $reviewers{$b} <=> $reviewers{$a} } ( keys %reviewers ) ) {
 | |
|         printf "%-30s, %6d, %6.3f%%\n", $reviewer, $reviewers{$reviewer}, $reviewers{$reviewer} / $number_of_commits * 100;
 | |
|         $number_of_reviewers++;
 | |
|     }
 | |
|     print "Total Reviewers: $number_of_reviewers\n\n";
 | |
| 
 | |
|     print "=== Submitters - Number of patches submitted ===\n";
 | |
|     printf "%-30s, %6s, %7s, %6s, %7s, %6s, %7s\n", "Name", "#", "total%", "Own", "own%", "Other", "other%";
 | |
|     foreach my $submitter ( sort { $submitters{$b}{count} <=> $submitters{$a}{count} } ( keys %submitters ) ) {
 | |
|         printf "%-30s, % 6d, %6.3f%%, %6d, %6.2f%%, %6d, %6.2f%%\n",
 | |
|           $submitter,
 | |
|           $submitters{$submitter}{count},
 | |
|           $submitters{$submitter}{count} / $number_of_commits * 100,
 | |
|           $submitters{$submitter}{"self"},
 | |
|           $submitters{$submitter}{"self"} / $submitters{$submitter}{count} * 100,
 | |
|           $submitters{$submitter}{others}, $submitters{$submitter}{others} / $submitters{$submitter}{count} * 100;
 | |
|     }
 | |
|     print "Total Submitters: $number_of_submitters\n\n";
 | |
| 
 | |
|     print "Commits, Ave, Added, Removed, Diff, Authors, Reviewers, Submitters\n";
 | |
|     printf "$number_of_commits, %.2f, $total_added, $total_removed, " . ( $total_added + $total_removed ) . ", $number_of_authors, $number_of_reviewers, $number_of_submitters\n",
 | |
|       $number_of_commits / $Days;
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| #-------------------------------------------------------------------------------
 | |
| sub check_versions {
 | |
|     `git cat-file -e $old_version^{commit} 2>/dev/null`;
 | |
|     if ( ${^CHILD_ERROR_NATIVE} ) {
 | |
|         print "Error: Old version ($old_version) does not exist.\n";
 | |
|         exit 1;
 | |
|     }
 | |
| 
 | |
|     `git cat-file -e $new_version^{commit} 2>/dev/null`;
 | |
|     if ( ${^CHILD_ERROR_NATIVE} ) {
 | |
|         print "Error: New version ($new_version) does not exist.\n";
 | |
|         exit 1;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| sub get_user {
 | |
| 
 | |
|     my $url = `git config -l | grep remote.origin.url`;
 | |
| 
 | |
|     if ( $url =~ /.*url=ssh:\/\/(\w+@[a-zA-Z][a-zA-Z0-9\.]+:\d+)(\/\w+)*/ ) {
 | |
|         $URL_WITH_USER = $1;
 | |
|     } else {
 | |
|         print "Error: Could not get a ssh url with a username from gitconfig.\n";
 | |
|         print "       use the -u option to set a url.\n";
 | |
|         exit 1;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| #-------------------------------------------------------------------------------
 | |
| sub get_commits {
 | |
|     my @commits     = @_;
 | |
|     my $submit_time = "";
 | |
|     if ( defined $SKIP_GERRIT_CHECK && $SKIP_GERRIT_CHECK ) {
 | |
|         return;
 | |
|     }
 | |
|     my $ssh = Net::OpenSSH->new( "$URL_WITH_USER", );
 | |
|     $ssh->error and die "Couldn't establish SSH connection to $URL_WITH_USER:" . $ssh->error;
 | |
| 
 | |
|     print "Using URL: ssh://$URL_WITH_USER\n";
 | |
| 
 | |
|     if ( !-d $infodir ) {
 | |
|         mkpath($infodir);
 | |
|     }
 | |
| 
 | |
|     for my $commit_id (@commits) {
 | |
|         $commit_id =~ s/^\s+|\s+$//g;
 | |
|         $submit_time = "";
 | |
|         my $gerrit_review;
 | |
| 
 | |
|         # Look for last coreboot commit
 | |
|         if ( $commit_id eq "7309709742" ) {
 | |
|             last;
 | |
|         }
 | |
| 
 | |
|         if ( -f "$infodir/$commit_id" ) {
 | |
|             $gerrit_review = 1;
 | |
|         } else {
 | |
|             $gerrit_review = `git log $commit_id^..$commit_id | grep '\\sReviewed-on:\\s'`;
 | |
|         }
 | |
| 
 | |
|         if ( $gerrit_review && $commit_id && ( !-f "$infodir/$commit_id" ) ) {
 | |
|             print "Downloading $commit_id";
 | |
|             my @info = $ssh->capture("gerrit query --format=JSON --comments --files --current-patch-set --all-approvals --submit-records --dependencies commit:$commit_id");
 | |
|             $ssh->error and die "remote ls command failed: " . $ssh->error;
 | |
| 
 | |
|             my $commit_info = JSON::Util->decode( $info[0] );
 | |
|             my $rowcount    = $commit_info->{'rowCount'};
 | |
|             if ( defined $rowcount && ( $rowcount eq "0" ) ) {
 | |
|                 print " - no gerrit commit for that id.\n";
 | |
|                 open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
 | |
|                 print $HANDLE "No gerrit commit";
 | |
|                 close $HANDLE;
 | |
|                 next;
 | |
|             }
 | |
|             my $approvals = $commit_info->{'currentPatchSet'}{'approvals'};
 | |
| 
 | |
|             for my $approval (@$approvals) {
 | |
|                 if ( $approval->{'type'} eq "SUBM" ) {
 | |
|                     $submit_time = $approval->{'grantedOn'};
 | |
|                 }
 | |
|             }
 | |
|             my $dt = "";
 | |
|             if ($submit_time) {
 | |
|                 $dt = DateTime->from_epoch( epoch => $submit_time );
 | |
|             } else {
 | |
|                 print " - no submit time for that id.\n";
 | |
|                 open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
 | |
|                 print $HANDLE "No submit time";
 | |
|                 close $HANDLE;
 | |
| 
 | |
|                 next;
 | |
|             }
 | |
| 
 | |
|             open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
 | |
|             print $HANDLE $info[0];
 | |
|             close $HANDLE;
 | |
| 
 | |
|             $dt->set_time_zone('Europe/Paris');
 | |
|             print " - submit time: " . $dt->strftime('%Y/%m/%d %H:%M:%S') . "\n";
 | |
|         } elsif ( $commit_id && ( !-f "$infodir/$commit_id" ) ) {
 | |
|             print "No gerrit commit for $commit_id\n";
 | |
|             open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
 | |
|             print $HANDLE "No gerrit commit";
 | |
|             close $HANDLE;
 | |
|         }
 | |
|     }
 | |
|     print "\n";
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # check_arguments parse the command line arguments
 | |
| #-------------------------------------------------------------------------------
 | |
| sub check_arguments {
 | |
|     my $show_usage = 0;
 | |
|     GetOptions(
 | |
|         'help|?'  => sub { usage() },
 | |
|         'url|u=s' => \$URL_WITH_USER,
 | |
|         'skip|s'  => \$SKIP_GERRIT_CHECK,
 | |
|     );
 | |
| 
 | |
|     # strip ssh:// from url if passed in.
 | |
|     if ( defined $URL_WITH_USER ) {
 | |
|         $URL_WITH_USER =~ s|ssh://||;
 | |
|     }
 | |
|     if (@ARGV) {
 | |
|         ( $old_version, $new_version ) = @ARGV;
 | |
|     } else {
 | |
|         usage();
 | |
|     }
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # usage - Print the arguments for the user
 | |
| #-------------------------------------------------------------------------------
 | |
| sub usage {
 | |
|     print "gerrit_stats <options> [Old version] [New version]\n";
 | |
|     print "Old version should be a tag (4.1), a branch (origin/4.1), or a commit id\n";
 | |
|     print "New version can be 'HEAD' a branch (origin/master) a tag (4.2), or a commit id\n";
 | |
|     print " Options:\n";
 | |
|     print "    u | url [url]           url with username.\n";
 | |
|     print "Example: \"$0 -u Gaumless\@review.coreboot.org:29418 origin/4.1 4.2\"\n";
 | |
|     exit(0);
 | |
| }
 | |
| 
 | |
| 1;
 |