xref: /aosp_15_r20/external/coreboot/util/release/gerrit_stats.pl (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1#!/usr/bin/env perl
2#
3# SPDX-License-Identifier: GPL-2.0-only
4
5package gerrit_stats;
6
7# To install any needed modules install the cpanm app, and use it to install the required modules:
8#  sudo cpan App::cpanminus
9#  sudo /usr/local/bin/cpanm JSON::Util Net::OpenSSH DateTime Devel::Size
10
11# perltidy -l=200 -bt=2 -ce
12
13use strict;
14use warnings;
15use English qw( -no_match_vars );
16use File::Find;
17use File::Path;
18use Getopt::Long;
19use Getopt::Std;
20use JSON::Util;
21use Net::OpenSSH;
22use Data::Dumper qw(Dumper);
23use DateTime;
24use Devel::Size qw(size total_size);
25
26my $old_version;
27my $new_version;
28my $infodir = "$ENV{'HOME'}/.local/commit_info/" . `git config -l | grep remote.origin.url | sed 's|.*@||' | sed 's|:.*||'`;
29chomp($infodir);
30my $URL_WITH_USER;
31my $SKIP_GERRIT_CHECK;
32my $print_commit_list = 1;
33
34#disable print buffering
35$OUTPUT_AUTOFLUSH = 1;
36binmode STDOUT, ":utf8";
37
38Main();
39
40#-------------------------------------------------------------------------------
41# Main
42#-------------------------------------------------------------------------------
43sub Main {
44    check_arguments();
45
46    my %submitters                = ();
47    my %authors                   = ();
48    my %owners                    = ();
49    my %reviewers                 = ();
50    my %commenters                = ();
51    my %author_added              = ();
52    my %author_removed            = ();
53    my $total_added               = 0;
54    my $total_removed             = 0;
55    my $number_of_commits         = 0;
56    my $number_of_submitters      = 0;
57    my $submit_epoch              = "";
58    my $first_submit_epoch        = "";
59    my $patches_over_100_lines    = 0;
60    my $total_lines_large_patches = 0;
61    my %email_addresses           = (
62        'Kacper Stojek' => '[email protected]',
63        'Damien Zammit' => '[email protected]',
64        'Pavel Sayekat' => '[email protected]',
65        'Lance Zhao'    => '[email protected]',
66    );
67
68    my %aliases = (
69        ' Felix Singer'                        => 'Felix Singer',
70        'Abhay kumar'                          => 'Abhay Kumar',
71        'AlexandruX Gagniuc'                   => 'Alexandru Gagniuc',
72        'Anish K. Patel'                       => 'Anish K Patel',
73        'Bao Zheng'                            => 'Zheng Bao',
74        'Bernhard M. Wiedemann'                => 'Bernhard M. Wiedermann',
75        'Björn Busse'                         => 'Bjarn Busse',
76        'BryantOu'                             => 'Bryant Ou',
77        'Chen Wisley'                          => 'Wisley Chen',
78        'Cheng-Yi Chiang'                      => 'Jimmy Cheng-Yi Chiang',
79        'Chris Ching (using chromium account)' => 'Chris Ching,',
80        'ChromeOS Developer'                   => 'Dave Parker',
81        'Cristi M'                             => 'Cristian Magherusan-Stanciu',
82        'Cristian M?gheru?an-Stanciu'          => 'Cristian Magherusan-Stanciu',
83        'Cristian MÄgheruÈan-Stanciu'          => 'Cristian Magherusan-Stanciu',
84        'Cristian MÄgheruÈan-Stanciu'          => 'Cristian Magherusan-Stanciu',
85        'DAWEI CHIEN'                          => 'Dawei Chien',
86        'efdesign98'                           => 'Frank Vibrans',
87        'Eugene D. Myers'                      => 'Eugene Myers',
88        'Frank Vibrans III'                    => 'Frank Vibrans',
89        'frank vibrans'                        => 'Frank Vibrans',
90        'Frank.Vibrans'                        => 'Frank Vibrans',
91        'FrankChu'                             => 'Frank Chu',
92        'garmin chang'                         => 'Garmin Chang',
93        'Garmin.Chang'                         => 'Garmin Chang',
94        'hannahwilliams2'                      => 'Hannah Williams',
95        'HAOUAS Elyes'                         => 'Elyes Haouas',
96        'Harshapriya N'                        => 'Harsha Priya',
97        'Hsuan-ting Chen'                      => 'Hsuan Ting Chen',
98        'Iru Cai (vimacs)'                     => 'Iru Cai',
99        'Jérémy Compostella'                   => 'Jeremy Compostella',
100        'Jérémy Compostella'                   => 'Jeremy Compostella',
101        'JG Poxu'                              => 'Po Xu',
102        'JonathonHall-Purism'                  => 'Jonathon Hall',
103        'Karthikeyan Ramasubramanian'          => 'Karthik Ramasubramanian',
104        'Kerry She'                            => 'Kerry Sheh',
105        'kewei.xu'                             => 'kewei xu',
106        'Kumar, Gomathi'                       => 'Gomathi Kumar',
107        'Kyösti Mälkki'                      => 'Kyösti Mälkki',
108        'Kyösti Mälkki'                        => 'Kyösti Mälkki',
109        'Marcello Sylvester Bauer'             => 'Marcello Sylvester Bauer',
110        'Martin L Roth'                        => 'Martin Roth',
111        'Martin Roth - Personal'               => 'Martin Roth',
112        'Matt Ziegelbaum'                      => 'Matthew Ziegelbaum',
113        'mengqi.zhang'                         => 'Mengqi Zhang',
114        'mrnuke'                               => 'Alexandru Gagniuc',
115        'Nina-CM Wu'                           => 'Nina Wu',
116        'ot_zhenguo.li'                        => 'Zhenguo Li',
117        'Patrick Georgi patrick.georgi'        => 'Patrick Georgi',
118        'Patrick Georgi patrick'               => 'Patrick Georgi',
119        'Pavlushka'                            => 'Pavel Sayekat',
120        'Ravi Kumar Bokka'                     => 'Ravi Kumar',
121        'Ravi kumar'                           => 'Ravi Kumar',
122        'Ravi Sarawadi'                        => 'Ravishankar Sarawadi',
123        'ravindr1'                             => 'Ravindra',
124        'Ravindra N'                           => 'Ravindra',
125        'Ricardo Ribalda Delgado'              => 'Ricardo Ribalda',
126        'ron minnich'                          => 'Ron Minnich',
127        'Ronald G. Minnich'                    => 'Ron Minnich',
128        'samrab'                               => 'Sudheer Amrabadi',
129        'SANTHOSH JANARDHANA HASSAN'           => 'Santhosh Janardhana Hassan',
130        'semihalf-czapiga-jakub'               => 'Jakub Czapiga',
131        'Seunghwan Kim'                        => 'SH Kim',
132        'Sooi, Li Cheng'                       => 'Li Cheng Sooi',
133        'Stefan Reinauerstepan'                => 'Stefan Reinauer',
134        'stepan'                               => 'Stefan Reinauer',
135        'Swift Geek (Sebastian Grzywna)'       => 'Sebastian "Swift Geek" Grzywna',
136        'Sylvain "ythier" Hitier'              => 'Sylvain Hitier',
137        'Thomas Gstädtner'                    => 'Thomas Gstaedtner',
138        'UwePoeche'                            => 'Uwe Poeche',
139        'UwePoeche'                            => 'Uwe Poeche',
140        'Varshit Pandya'                       => 'Varshit B Pandya',
141        'Wayne3 Wang'                          => 'Wayne Wang',
142        'Wayne3_Wang'                          => 'Wayne Wang',
143        'Xi Chen'                              => 'Xixi Chen',
144        'Yu-Hsuan Hsu'                         => 'Yu-hsuan Hsu',
145        'zbao'                                 => 'Zheng Bao',
146        'Zheng Bao zheng.bao'                  => 'Zheng Bao',
147        'zhiyong tao'                          => 'Zhiyong Tao',
148        'Дмитрий Понаморев'                    => 'Dmitry Ponamorev',
149    );
150
151    if ( !$URL_WITH_USER ) {
152        get_user();
153    }
154
155    print "Saving data to $infodir\n";
156
157    # Make sure the versions exist
158    check_versions();
159
160    # Fetch patches if needed.  Get ids of first and last commits
161    my @commits = `git log --pretty=%H "$old_version..$new_version" 2>/dev/null`;
162    get_commits(@commits);
163    my $last_commit_id  = $commits[0];
164    my $first_commit_id = $commits[ @commits - 1 ];
165    chomp $last_commit_id;
166    chomp $first_commit_id;
167
168    print "Statistics from commit $first_commit_id to commit $last_commit_id\n";
169    print "Patch, Date, Owner, Author, Submitter, Inserted lines, Deleted lines, Subject, Reviewers, Commenters\n";
170
171    # Loop through all commits
172    for my $commit_id (@commits) {
173        $commit_id =~ s/^\s+|\s+$//g;
174
175        my $submitter        = "";
176        my %patch_reviewers  = ();
177        my %patch_commenters = ();
178        my $info;
179        my $owner;
180        my $author;
181        my $author_email;
182        my $inserted_lines = 0;
183        my $deleted_lines  = 0;
184        my $subject;
185
186        $number_of_commits++;
187        print "\"$commit_id\", ";
188
189        # Read the data file for the current commit
190        if ( -f "$infodir/$commit_id" && -s "$infodir/$commit_id" > 20 ) {
191            open( my $HANDLE, "<", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
192            $info = <$HANDLE>;
193            close $HANDLE;
194
195            my $commit_info = JSON::Util->decode($info);
196
197            # Get the easy data
198            $owner = $commit_info->{'owner'}{'name'};
199            if ( !$owner ) {
200                $owner = $commit_info->{'owner'}{'username'};
201            }
202            if ( !$owner ) {
203                $owner = "-";
204            }
205            if ( $owner && exists( $aliases{$owner} ) ) {
206                $owner = $aliases{$owner};
207            }
208            $owner =~ s/"/'/g;
209            $owner =~ s/,/./g;
210
211            $author = $commit_info->{'currentPatchSet'}{'author'}{'name'};
212            if ( $author && exists( $aliases{$author} ) ) {
213                $author = $aliases{$author};
214            }
215            if ( !$author ) {
216                $author = $commit_info->{'currentPatchSet'}{'author'}{'username'};
217            }
218            $author =~ s/"/'/g;
219            $author =~ s/,/./g;
220            $author_email   = $commit_info->{'currentPatchSet'}{'author'}{'email'};
221            $inserted_lines = $commit_info->{'currentPatchSet'}{'sizeInsertions'};
222            $deleted_lines  = $commit_info->{'currentPatchSet'}{'sizeDeletions'};
223            $subject        = $commit_info->{'subject'};
224            $subject =~ s/"/'/g;
225
226            #get the patch's submitter
227            my $approvals = $commit_info->{'currentPatchSet'}{'approvals'};
228            for my $approval (@$approvals) {
229                if ( $approval->{'type'} eq "SUBM" ) {
230                    $submit_epoch = $approval->{'grantedOn'};
231                    $submitter    = $approval->{'by'}{'name'};
232                    if ( exists( $aliases{$submitter} ) ) {
233                        $submitter = $aliases{$submitter};
234                    }
235                }
236            }
237
238            # Get all the commenters for all patch revisions
239            my $comments = $commit_info->{'comments'};
240            for my $comment (@$comments) {
241                my $commenter;
242                if ( $comment->{'reviewer'}{'username'} ) {
243                    if ( $comment->{'reviewer'}{'username'} eq "jenkins" ) {
244                        next;
245                    }
246                    if ( $comment->{'reviewer'}{'username'} eq "hardwaretestrobot" ) {
247                        next;
248                    }
249                    if ( $comment->{'reviewer'}{'username'} eq "raptor-automated-test" ) {
250                        next;
251                    }
252                }
253
254                if ( $comment->{'reviewer'}{'name'} ) {
255                    if ( $comment->{'reviewer'}{'name'} eq "Gerrit Code Review" ) {
256                        next;
257                    }
258                }
259                if ( $comment->{'message'} ) {
260                    if ( $comment->{'message'} =~ "successfully cherry-picked" ) {
261                        next;
262                    }
263                    if ( $comment->{'message'} =~ ": Code-Review" ) {
264                        next;
265                    }
266                    if ( $comment->{'message'} =~ "Uploaded patch set" ) {
267                        next;
268                    }
269                }
270
271                if ( !$commenter ) {
272                    $commenter = $comment->{'reviewer'}{'name'};
273                    if ( $commenter && exists( $aliases{$commenter} ) ) {
274                        $commenter = $aliases{$commenter};
275                    }
276                }
277                if ( !$commenter ) {
278                    $commenter = $comment->{'reviewer'}{'username'};
279                    if ( $commenter && exists( $aliases{$commenter} ) ) {
280                        $commenter = $aliases{$commenter};
281                    }
282                }
283                $commenter =~ s/"/'/g;
284                $commenter =~ s/,/./g;
285                if ( $commenter && $author && $commenter eq $author ) {
286                    next;
287                }
288                if ($commenter) {
289                    if ( $commenter && exists $patch_commenters{$commenter} ) {
290                        $patch_commenters{$commenter}++;
291                    } else {
292                        $patch_commenters{$commenter} = 1;
293                    }
294                }
295            }
296
297            # Get all the reviewers for all patch revisions
298            my $patchsets = $commit_info->{'patchSets'};
299            for my $patch (@$patchsets) {
300                if ( !$author ) {
301                    $author = $patch->{'author'}{'name'};
302                    if ( $author && exists( $aliases{$author} ) ) {
303                        $author = $aliases{$author};
304                    }
305                }
306
307                my $approvals = $patch->{'approvals'};
308                for my $approval (@$approvals) {
309
310                    if ( ( !$submitter ) && ( $approval->{'type'} eq "SUBM" ) ) {
311                        $submit_epoch = $approval->{'grantedOn'};
312                        $submitter    = $approval->{'by'}{'name'};
313                        if ( $submitter && exists( $aliases{$submitter} ) ) {
314                            $submitter = $aliases{$submitter};
315                        }
316                    }
317                    $submitter =~ s/"/'/g;
318                    $submitter =~ s/,/./g;
319
320                    if ( $approval->{'type'} eq "Code-Review" ) {
321                        my $patch_reviewer = $approval->{'by'}{'name'};
322                        if ($patch_reviewer) {
323                            if ( exists $patch_reviewers{$patch_reviewer} ) {
324                                $patch_reviewers{$patch_reviewer}++;
325                            } else {
326                                $patch_reviewers{$patch_reviewer} = 1;
327                            }
328                        }
329                    }
330                }
331            }
332
333        } else {
334
335            # Get the info from git
336            my $logline = `git log --pretty="%ct@@@%s@@@%an@@@%aE@@@%cn" $commit_id^..$commit_id --`;
337            $logline =~ m/^(.*)@@@(.*)@@@(.*)@@@(.*)@@@(.*)\n/;
338            ( $submit_epoch, $subject, $author, $author_email, $submitter ) = ( $1, $2, $3, $4, $5 );
339            if ( exists( $aliases{$author} ) ) {
340                $author = $aliases{$author};
341            }
342            $owner = $author;
343
344            if ( $submitter && exists( $aliases{$submitter} ) ) {
345                $submitter = $aliases{$submitter};
346            }
347
348            $logline = `git log --pretty= --shortstat $commit_id^..$commit_id --`;
349            if ( $logline =~ m/\s+(\d+)\s+insertion/ ) {
350                $inserted_lines = $1;
351            }
352            if ( $logline =~ m/\s+(\d+)\s+deletion/ ) {
353                $deleted_lines = $1 * -1;
354            }
355            my @loglines = `git log $commit_id^..$commit_id -- | grep '\\sReviewed-by:'`;
356            for my $line (@loglines) {
357                if ( $line =~ m/.*:\s+(.*)\s</ ) {
358                    my $patch_reviewer = $1;
359                    if ( exists( $aliases{$patch_reviewer} ) ) {
360                        $patch_reviewer = $aliases{$patch_reviewer};
361                    }
362                    if ($patch_reviewer) {
363                        if ( exists $patch_reviewers{$patch_reviewer} ) {
364                            $patch_reviewers{$patch_reviewer}++;
365                        } else {
366                            $patch_reviewers{$patch_reviewer} = 1;
367                        }
368                    }
369                }
370            }
371
372        }
373
374        # Not entirely certain why this is needed, but for a number of patches have been submitted
375        # the submit time in gerrit is set to April 9, 2015.
376        if ( $submit_epoch == 1428586219 ) {
377            my $logline = `git log --pretty="%ct" $commit_id^..$commit_id --`;
378            $logline =~ m/^(.*)\n/;
379            $submit_epoch = $1;
380        }
381
382        # Add the count and owner to the submitter hash
383        if ( $submitter && exists $submitters{$submitter} && exists $submitters{$submitter}{count} ) {
384            $submitters{$submitter}{count}++;
385        } else {
386            $submitters{$submitter}{count} = 1;
387            $number_of_submitters++;
388            $submitters{$submitter}{"self"} = 0;
389            $submitters{$submitter}{others} = 0;
390            $submitters{$submitter}{name}   = $submitter;
391        }
392
393        if ( $submitter eq $author ) {
394            $submitters{$submitter}{"self"}++;
395        } else {
396            $submitters{$submitter}{others}++;
397        }
398
399        # Create a readable date
400        my $dt = DateTime->from_epoch( epoch => $submit_epoch );
401        $dt->set_time_zone('Europe/Paris');
402        my $submit_time = $dt->strftime('%Y/%m/%d');
403        if ( !$first_submit_epoch ) {
404            $first_submit_epoch = $submit_epoch;
405        }
406
407        # Create the list of commenters to print
408        my $commenterlist = "";
409        foreach my $commenter ( keys %patch_commenters ) {
410            if ( $commenter && exists( $aliases{$commenter} ) ) {
411                $commenter = $aliases{$commenter};
412            }
413
414            if ( $commenterlist eq "" ) {
415                $commenterlist = $commenter;
416            } else {
417                $commenterlist .= ", $commenter";
418            }
419
420            if ( $commenter && exists $commenters{$commenter} ) {
421                $commenters{$commenter}++;
422            } else {
423                $commenters{$commenter} = 1;
424            }
425        }
426        if ( !$commenterlist ) {
427            $commenterlist = "-";
428        }
429
430        # Create the list of reviewers to print
431        my $reviewerlist = "";
432        foreach my $reviewer ( keys %patch_reviewers ) {
433            if ( exists( $aliases{$reviewer} ) ) {
434                $reviewer = $aliases{$reviewer};
435            }
436
437            if ( $reviewerlist eq "" ) {
438                $reviewerlist = $reviewer;
439            } else {
440                $reviewerlist .= ", $reviewer";
441            }
442
443            if ( $reviewer && exists $reviewers{$reviewer} ) {
444                $reviewers{$reviewer}++;
445            } else {
446                $reviewers{$reviewer} = 1;
447            }
448        }
449        if ( !$reviewerlist ) {
450            $reviewerlist = "-";
451        }
452
453        if ($print_commit_list) {
454            print "$submit_time, $owner, $author, $submitter, $inserted_lines, $deleted_lines, \"$subject\", \"$reviewerlist\" , \"$commenterlist\"\n";
455        } else {
456            print "$number_of_commits\n";
457        }
458        $total_added += $inserted_lines;
459        if ( $inserted_lines - $deleted_lines > 100 ) {
460            $patches_over_100_lines++;
461            $total_lines_large_patches += $inserted_lines;
462        }
463        $total_removed += $deleted_lines;
464        if ( exists $owners{$owner} ) {
465            $owners{$owner}++;
466        } else {
467            $owners{$owner} = 1;
468        }
469
470        if ( $author && exists $authors{$author}{"num"} ) {
471            $authors{$author}{"num"}++;
472            $author_added{$author}   += $inserted_lines;
473            $author_removed{$author} += $deleted_lines;
474            $authors{$author}{"earliest_commit"} = $submit_time;
475        } else {
476            $authors{$author}{"num"}             = 1;
477            $authors{$author}{"latest_commit"}   = $submit_time;
478            $authors{$author}{"earliest_commit"} = $submit_time;
479            $author_added{$author}               = $inserted_lines;
480            $author_removed{$author}             = $deleted_lines;
481        }
482        if ( $author && ( !exists $authors{$author}{email} || $authors{$author}{email} eq "-" ) ) {
483            if ($author_email) {
484                $authors{$author}{email} = "$author_email";
485            } elsif ( exists $email_addresses{$author} ) {
486                $authors{$author}{email} = $email_addresses{$author};
487            }
488        }
489    }
490    my $Days = ( $first_submit_epoch - $submit_epoch ) / 86400;
491    if ( ( $first_submit_epoch - $submit_epoch ) % 86400 ) {
492        $Days += 1;
493    }
494
495    print "\n* Total Commits: $number_of_commits\n";
496    printf "* Average Commits per day: %.2f\n", $number_of_commits / $Days;
497    print "* Total lines added: $total_added\n";
498    printf "* Average lines added per commit: %.2f\n", $total_added / $number_of_commits;
499    print "* Number of patches adding more than 100 lines: $patches_over_100_lines\n";
500    printf "* Average lines added per small commit: %.2f\n", ( $total_added - $total_lines_large_patches ) / ( $number_of_commits - $patches_over_100_lines );
501
502    print "* Total lines removed: $total_removed\n";
503    printf "* Average lines removed per commit: %.2f\n", $total_removed / $number_of_commits;
504    print "* Total difference between added and removed: " . ( $total_added - $total_removed ) . "\n\n";
505
506    print "=== Authors - Number of commits ===\n";
507    printf "%-30s ,%5s ,%5s ,%6s ,%6s , %-52s ,%6s, %-19s , %s\n", "Author", "Ptchs", "Revws", "Cmnts", "Sbmts", "Email", "Prcnt", "Last commit", "Earliest_commit";
508
509    my $number_of_authors = 0;
510    foreach my $author ( sort { $authors{$b}{num} <=> $authors{$a}{num} } ( keys %authors ) ) {
511        my $submissions = 0;
512        if ( $author && exists $submitters{$author} ) {
513            $submissions = $submitters{$author}{count};
514        }
515        my $review_count = 0;
516        if ( $author && exists $reviewers{$author} ) {
517            $review_count = $reviewers{$author};
518        }
519
520        my $comment_count = 0;
521        if ( $author && exists $commenters{$author} ) {
522            $comment_count = $commenters{$author};
523        }
524
525        if ( $author && !exists $authors{$author}{"email"} ) {
526            $authors{$author}{"email"} = "-";
527        }
528
529        printf "%-30s ,%5d ,%5d ,%6d ,%6d , %-52s ,%5.2f%%, %s , %s\n", $author, $authors{$author}{"num"}, $review_count,
530          $comment_count, $submissions, $authors{$author}{"email"}, $authors{$author}{"num"} / $number_of_commits * 100,
531          $authors{$author}{"latest_commit"}, $authors{$author}{"earliest_commit"};
532        $number_of_authors++;
533    }
534    print "Total Authors: $number_of_authors\n\n";
535
536    print "=== Authors - Lines added ===\n";
537    foreach my $author ( sort { $author_added{$b} <=> $author_added{$a} } ( keys %author_added ) ) {
538        if ( $author_added{$author} ) {
539            printf "%-30s, %10d, %2.3f%%\n", $author, $author_added{$author}, $author_added{$author} / $total_added * 100;
540        }
541    }
542    print "\n";
543
544    print "=== Authors - Lines removed ===\n";
545    foreach my $author ( sort { $author_removed{$b} <=> $author_removed{$a} } ( keys %author_removed ) ) {
546        if ( $author_removed{$author} ) {
547            printf "%-30s, %10d, %6.3f%%\n", $author, $author_removed{$author} * -1, $author_removed{$author} / $total_removed * 100;
548        }
549    }
550    print "\n";
551
552    print "=== Reviewers - Number of patches reviewed ===\n";
553    my $number_of_reviewers = 0;
554    foreach my $reviewer ( sort { $reviewers{$b} <=> $reviewers{$a} } ( keys %reviewers ) ) {
555        printf "%-30s, %6d, %6.3f%%\n", $reviewer, $reviewers{$reviewer}, $reviewers{$reviewer} / $number_of_commits * 100;
556        $number_of_reviewers++;
557    }
558    print "Total Reviewers: $number_of_reviewers\n\n";
559
560    print "=== Submitters - Number of patches submitted ===\n";
561    printf "%-30s, %6s, %7s, %6s, %7s, %6s, %7s\n", "Name", "#", "total%", "Own", "own%", "Other", "other%";
562    foreach my $submitter ( sort { $submitters{$b}{count} <=> $submitters{$a}{count} } ( keys %submitters ) ) {
563        printf "%-30s, % 6d, %6.3f%%, %6d, %6.2f%%, %6d, %6.2f%%\n",
564          $submitter,
565          $submitters{$submitter}{count},
566          $submitters{$submitter}{count} / $number_of_commits * 100,
567          $submitters{$submitter}{"self"},
568          $submitters{$submitter}{"self"} / $submitters{$submitter}{count} * 100,
569          $submitters{$submitter}{others}, $submitters{$submitter}{others} / $submitters{$submitter}{count} * 100;
570    }
571    print "Total Submitters: $number_of_submitters\n\n";
572
573    print "Commits, Ave, Added, Removed, Diff, Authors, Reviewers, Submitters\n";
574    printf "$number_of_commits, %.2f, $total_added, $total_removed, " . ( $total_added + $total_removed ) . ", $number_of_authors, $number_of_reviewers, $number_of_submitters\n",
575      $number_of_commits / $Days;
576}
577
578#-------------------------------------------------------------------------------
579#-------------------------------------------------------------------------------
580sub check_versions {
581    `git cat-file -e $old_version^{commit} 2>/dev/null`;
582    if ( ${^CHILD_ERROR_NATIVE} ) {
583        print "Error: Old version ($old_version) does not exist.\n";
584        exit 1;
585    }
586
587    `git cat-file -e $new_version^{commit} 2>/dev/null`;
588    if ( ${^CHILD_ERROR_NATIVE} ) {
589        print "Error: New version ($new_version) does not exist.\n";
590        exit 1;
591    }
592}
593
594#-------------------------------------------------------------------------------
595sub get_user {
596
597    my $url = `git config -l | grep remote.origin.url`;
598
599    if ( $url =~ /.*url=ssh:\/\/(\w+@[a-zA-Z][a-zA-Z0-9\.]+:\d+)(\/\w+)*/ ) {
600        $URL_WITH_USER = $1;
601    } else {
602        print "Error: Could not get a ssh url with a username from gitconfig.\n";
603        print "       use the -u option to set a url.\n";
604        exit 1;
605    }
606}
607
608#-------------------------------------------------------------------------------
609#-------------------------------------------------------------------------------
610sub get_commits {
611    my @commits     = @_;
612    my $submit_time = "";
613    if ( defined $SKIP_GERRIT_CHECK && $SKIP_GERRIT_CHECK ) {
614        return;
615    }
616    my $ssh = Net::OpenSSH->new( "$URL_WITH_USER", );
617    $ssh->error and die "Couldn't establish SSH connection to $URL_WITH_USER:" . $ssh->error;
618
619    print "Using URL: ssh://$URL_WITH_USER\n";
620
621    if ( !-d $infodir ) {
622        mkpath($infodir);
623    }
624
625    for my $commit_id (@commits) {
626        $commit_id =~ s/^\s+|\s+$//g;
627        $submit_time = "";
628        my $gerrit_review;
629
630        # Look for last coreboot commit
631        if ( $commit_id eq "7309709742" ) {
632            last;
633        }
634
635        if ( -f "$infodir/$commit_id" ) {
636            $gerrit_review = 1;
637        } else {
638            $gerrit_review = `git log $commit_id^..$commit_id | grep '\\sReviewed-on:\\s'`;
639        }
640
641        if ( $gerrit_review && $commit_id && ( !-f "$infodir/$commit_id" ) ) {
642            print "Downloading $commit_id";
643            my @info = $ssh->capture("gerrit query --format=JSON --comments --files --current-patch-set --all-approvals --submit-records --dependencies commit:$commit_id");
644            $ssh->error and die "remote ls command failed: " . $ssh->error;
645
646            my $commit_info = JSON::Util->decode( $info[0] );
647            my $rowcount    = $commit_info->{'rowCount'};
648            if ( defined $rowcount && ( $rowcount eq "0" ) ) {
649                print " - no gerrit commit for that id.\n";
650                open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
651                print $HANDLE "No gerrit commit";
652                close $HANDLE;
653                next;
654            }
655            my $approvals = $commit_info->{'currentPatchSet'}{'approvals'};
656
657            for my $approval (@$approvals) {
658                if ( $approval->{'type'} eq "SUBM" ) {
659                    $submit_time = $approval->{'grantedOn'};
660                }
661            }
662            my $dt = "";
663            if ($submit_time) {
664                $dt = DateTime->from_epoch( epoch => $submit_time );
665            } else {
666                print " - no submit time for that id.\n";
667                open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
668                print $HANDLE "No submit time";
669                close $HANDLE;
670
671                next;
672            }
673
674            open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
675            print $HANDLE $info[0];
676            close $HANDLE;
677
678            $dt->set_time_zone('Europe/Paris');
679            print " - submit time: " . $dt->strftime('%Y/%m/%d %H:%M:%S') . "\n";
680        } elsif ( $commit_id && ( !-f "$infodir/$commit_id" ) ) {
681            print "No gerrit commit for $commit_id\n";
682            open( my $HANDLE, ">", "$infodir/$commit_id" ) or die "Error: could not open file '$infodir/$commit_id'\n";
683            print $HANDLE "No gerrit commit";
684            close $HANDLE;
685        }
686    }
687    print "\n";
688}
689
690#-------------------------------------------------------------------------------
691# check_arguments parse the command line arguments
692#-------------------------------------------------------------------------------
693sub check_arguments {
694    my $show_usage = 0;
695    GetOptions(
696        'help|?'  => sub { usage() },
697        'url|u=s' => \$URL_WITH_USER,
698        'skip|s'  => \$SKIP_GERRIT_CHECK,
699    );
700
701    # strip ssh:// from url if passed in.
702    if ( defined $URL_WITH_USER ) {
703        $URL_WITH_USER =~ s|ssh://||;
704    }
705    if (@ARGV) {
706        ( $old_version, $new_version ) = @ARGV;
707    } else {
708        usage();
709    }
710}
711
712#-------------------------------------------------------------------------------
713# usage - Print the arguments for the user
714#-------------------------------------------------------------------------------
715sub usage {
716    print "gerrit_stats <options> [Old version] [New version]\n";
717    print "Old version should be a tag (4.1), a branch (origin/4.1), or a commit id\n";
718    print "New version can be 'HEAD' a branch (origin/main) a tag (4.2), or a commit id\n";
719    print " Options:\n";
720    print "    u | url [url]           url with username.\n";
721    print "Example: \"$0 -u Gaumless\@review.coreboot.org:29418 origin/4.1 4.2\"\n";
722    exit(0);
723}
724
7251;
726