File Coverage

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