File Coverage

blib/lib/Git/MoreHooks/CheckCommitAuthorFromMailmap.pm
Criterion Covered Total %
statement 24 123 19.5
branch 0 38 0.0
condition 0 9 0.0
subroutine 8 18 44.4
pod 4 5 80.0
total 36 193 18.6


line stmt bran cond sub pod time code
1             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
2             ## no critic (ControlStructures::ProhibitPostfixControls)
3              
4             package Git::MoreHooks::CheckCommitAuthorFromMailmap;
5              
6 1     1   230467 use strict;
  1         7  
  1         27  
7 1     1   6 use warnings;
  1         2  
  1         22  
8 1     1   5 use utf8;
  1         2  
  1         7  
9              
10             # ABSTRACT: Check Git commit author by using the mailmap file.
11              
12             our $VERSION = '0.018'; # VERSION: generated by DZP::OurPkgVersion
13              
14 1     1   682 use Git::Hooks 3.000000;
  1         84487  
  1         138  
15 1     1   12 use Path::Tiny;
  1         2  
  1         45  
16 1     1   11 use Try::Tiny;
  1         2  
  1         54  
17 1     1   8 use Log::Any qw{$log};
  1         2  
  1         7  
18 1     1   778 use Module::Load qw{ load };
  1         1127  
  1         8  
19              
20             my $PKG = __PACKAGE__;
21             my ($CFG) = __PACKAGE__ =~ /::([^:]+)$/msx;
22             $CFG = 'githooks.' . $CFG;
23              
24             #############
25             # Grok hook configuration, check it and set defaults.
26              
27             sub _setup_config {
28 0     0     my ($git) = @_;
29 0           $log->debugf( __PACKAGE__ . '::_setup_config(%s):', '$git' );
30              
31 0           my $config = $git->get_config();
32 0           $log->tracef( __PACKAGE__ . '::_setup_config(): Current Git config:\n%s.', $config );
33              
34 0   0       $config->{ lc $CFG } //= {};
35              
36 0           my $default = $config->{ lc $CFG };
37 0   0       $default->{'match-mailmap-name'} //= ['1'];
38 0   0       $default->{'allow-mailmap-aliases'} //= ['1'];
39              
40 0           return;
41             }
42              
43             ##########
44              
45             sub check_commit_at_client {
46 0     0 1   my ($git) = @_;
47              
48 0           my $current_branch = $git->get_current_branch();
49 0 0         return 1 unless $git->is_reference_enabled($current_branch);
50              
51 0           my $author_name = $ENV{'GIT_AUTHOR_NAME'};
52 0           my $author_email = '<' . $ENV{'GIT_AUTHOR_EMAIL'} . '>';
53              
54 0           my $commit = ':0';
55 0           return _check_author( $git, $commit, $author_name, $author_email );
56             }
57              
58             sub check_commit_at_server {
59 0     0 1   my ( $git, $commit ) = @_;
60              
61 0           my $author_name = $commit->{'author_name'};
62 0           my $author_email = '<' . $commit->{'author_email'} . '>';
63              
64 0           return _check_author( $git, $commit, $author_name, $author_email );
65             }
66              
67             sub _check_author {
68 0     0     my ( $git, $commit, $author_name, $author_email ) = @_;
69              
70 0           _setup_config($git);
71              
72 0 0         return 1 if $git->im_admin();
73              
74 0           my $errors = 0;
75 0 0         _check_mailmap( $git, $commit, $author_name, $author_email ) or ++$errors;
76              
77 0           return $errors == 0;
78             }
79              
80             sub _check_mailmap {
81 0     0     my ( $git, $commit, $author_name, $author_email ) = @_;
82              
83 0           my $errors = 0;
84             eval {
85 0           load 'Git::Mailmap';
86 0           1; # To cover the fact that operation correctly returns a false value.
87 0 0         } or do {
88 0           $log->errorf( __PACKAGE__ . q{::} . '_set_critic():Cannot load Git::Mailmap' );
89 0           $git->fault( 'Cannot load Git::Mailmap', { prefix => $PKG, commit => $commit } );
90 0           ++$errors;
91 0           return $errors;
92             };
93              
94             # TODO Move config checking to _setup_config() or elsewhere so it can break early on.
95 0           my $author = $author_name . q{ } . $author_email;
96 0           my $mailmap = Git::Mailmap->new();
97 0           my $mailmap_as_string;
98             try {
99 0     0     $mailmap_as_string = $git->run( 'cat-file', '-p', 'HEAD:.mailmap' );
100 0           };
101 0 0         if ( defined $mailmap_as_string ) {
102 0           $mailmap->from_string( 'mailmap' => $mailmap_as_string );
103 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): HEAD:.mailmap read in.' . ' Content from Git::Mailmap:\n%s',
104             $mailmap->to_string() );
105             }
106              
107             # 2) Config variable mailmap.file
108 0           my $mapfile_location = $git->get_config( 'mailmap' => 'file' );
109 0 0         if ( defined $mapfile_location ) {
110 0 0         if ( -e $mapfile_location ) {
111 0           my $file_as_str = path($mapfile_location)->slurp_utf8;
112 0           $mailmap->from_string( 'mailmap' => $file_as_str );
113 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): mailmap.file (%s) read in.' . ' Content from Git::Mailmap:\n%s',
114             $mapfile_location, $mailmap->to_string() );
115             }
116             else {
117 0           $git->fault( ( sprintf 'Config variable \'mailmap.file\' (value: %s) does not point to a file.', $mapfile_location ),
118             { prefix => $PKG, commit => $commit } );
119 0           ++$errors;
120             }
121             }
122              
123             # 3) Config variable mailmap.blob
124 0           my $mapfile_blob = $git->get_config( 'mailmap' => 'blob' );
125 0 0         if ( defined $mapfile_blob ) {
126 0           my $blob_as_str;
127             try {
128 0     0     $blob_as_str = $git->run( 'cat-file', '-p', $mapfile_blob );
129 0           };
130 0 0         if ( defined $blob_as_str ) {
131 0           $mailmap->from_string( 'mailmap' => $blob_as_str );
132 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): mailmap.blob (%s) read in.' . ' Content from Git::Mailmap:\n%s',
133             $mapfile_blob, $mailmap->to_string() );
134             }
135             else {
136 0           $git->fault( ( sprintf 'Config variable \'mailmap.blob\' (value: %s) does not point to a file.', $mapfile_blob ),
137             { prefix => $PKG, commit => $commit } );
138 0           ++$errors;
139             }
140             }
141              
142 0           my $verified = 0;
143              
144             # Always search (first) among proper emails (and names if wanted).
145 0           my %search_params = ( 'proper-email' => $author_email );
146 0 0         if ( $git->get_config( $CFG => 'match-mailmap-name' ) eq '1' ) {
147 0           $search_params{'proper-name'} = $author_name;
148             }
149 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): search_params=%s.', \%search_params );
150 0           $verified = $mailmap->verify(%search_params);
151 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): verified=%s.', $verified );
152              
153             # If was not found among proper-*, and user wants, search aliases.
154 0 0 0       if ( !$verified && $git->get_config( $CFG => 'allow-mailmap-aliases' ) eq '1' ) {
155 0           my %c_search_params = ( 'commit-email' => $author_email );
156 0 0         if ( $git->get_config( $CFG => 'match-mailmap-name' ) eq '1' ) {
157 0           $c_search_params{'commit-name'} = $author_name;
158             }
159 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): c_search_params=%s.', \%c_search_params );
160 0           $verified = $mailmap->verify(%c_search_params);
161             }
162 0 0         if ( $verified == 0 ) {
163 0           $git->fault( "Commit author '$author' has no match in mailmap file.", { prefix => $PKG, commit => $commit } );
164 0           ++$errors;
165             }
166              
167 0           return $errors == 0;
168             }
169              
170             sub check_ref {
171 0     0 0   my ( $git, $ref ) = @_;
172              
173 0 0         return 1 unless $git->is_reference_enabled($ref);
174              
175 0           my $errors = 0;
176 0           foreach my $commit ( $git->get_affected_ref_commits($ref) ) {
177 0 0         check_commit_at_server( $git, $commit )
178             or ++$errors;
179             }
180              
181 0           return $errors == 0;
182             }
183              
184             # This routine can act both as an update or a pre-receive hook.
185             sub check_affected_refs {
186 0     0 1   my ($git) = @_;
187              
188 0           _setup_config($git);
189              
190 0 0         return 1 if $git->im_admin($git);
191              
192 0           my $errors = 0;
193              
194 0           foreach my $ref ( $git->get_affected_refs() ) {
195 0 0         check_ref( $git, $ref )
196             or ++$errors;
197             }
198              
199 0           return $errors == 0;
200             }
201              
202             sub check_patchset {
203 0     0 1   my ( $git, $opts ) = @_;
204              
205 0           _setup_config($git);
206              
207 0           my $sha1 = $opts->{'--commit'};
208 0           my $commit = $git->get_commit($sha1);
209              
210 0           my $branch = $opts->{'--branch'};
211 0 0         $branch = "refs/heads/$branch"
212             unless $branch =~ m{^refs/}msx;
213 0 0         return 1 unless $git->is_reference_enabled($branch);
214              
215 0           return check_commit_at_server( $git, $commit );
216             }
217              
218             # Install hooks
219             my $options = { config => \&_setup_config };
220              
221             GITHOOKS_CHECK_PRE_COMMIT( \&check_commit_at_client, $options );
222             GITHOOKS_CHECK_AFFECTED_REFS( \&check_affected_refs, $options );
223             GITHOOKS_CHECK_PATCHSET( \&check_patchset, $options );
224              
225             1;
226              
227             __END__