File Coverage

blib/lib/Git/MoreHooks/CheckCommitAuthorFromMailmap.pm
Criterion Covered Total %
statement 18 104 17.3
branch 0 36 0.0
condition 0 9 0.0
subroutine 6 14 42.8
pod 4 5 80.0
total 28 168 16.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   191425 use strict;
  1         7  
  1         23  
7 1     1   4 use warnings;
  1         2  
  1         20  
8 1     1   4 use utf8;
  1         2  
  1         7  
9              
10             # ABSTRACT: Check Git commit author by using the mailmap file.
11              
12             our $VERSION = '0.016'; # VERSION: generated by DZP::OurPkgVersion
13              
14 1     1   488 use Git::Hooks 3.000000;
  1         66093  
  1         140  
15 1     1   9 use Path::Tiny;
  1         3  
  1         38  
16 1     1   6 use Log::Any qw{$log};
  1         3  
  1         9  
17             require Git::Mailmap;
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 0           my $author = $author_name . q{ } . $author_email;
84 0           my $mailmap = Git::Mailmap->new();
85 0           my $mailmap_as_string = $git->run( 'cat-file', '-p', 'HEAD:.mailmap' );
86              
87             # TODO Move config checking to _setup_config() or elsewhere so it can break early on.
88 0 0         if ( defined $mailmap_as_string ) {
89 0           $mailmap->from_string( 'mailmap' => $mailmap_as_string );
90 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): HEAD:.mailmap read in.' . ' Content from Git::Mailmap:\n%s',
91             $mailmap->to_string() );
92             }
93              
94             # 2) Config variable mailmap.file
95 0           my $mapfile_location = $git->get_config( 'mailmap.' => 'file' );
96 0 0         if ( defined $mapfile_location ) {
97 0 0         if ( -e $mapfile_location ) {
98 0           my $file_as_str = Path::Tiny->file($mapfile_location)->slurp_utf8;
99 0           $mailmap->from_string( 'mailmap' => $file_as_str );
100 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): mailmap.file (%s) read in.' . ' Content from Git::Mailmap:\n%s',
101             $mapfile_location, $mailmap->to_string() );
102             }
103             else {
104 0           $git->fault( 'Config variable \'mailmap.file\' does not point to a file.', { prefix => $PKG, commit => $commit } );
105             }
106             }
107              
108             # 3) Config variable mailmap.blob
109 0           my $mapfile_blob = $git->get_config( 'mailmap.' => 'blob' );
110 0 0         if ( defined $mapfile_blob ) {
111 0 0         if ( my $blob_as_str = $git->command( 'cat-file', '-p', $mapfile_blob ) ) {
112 0           $mailmap->from_string( 'mailmap' => $blob_as_str );
113 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): mailmap.blob (%s) read in.' . ' Content from Git::Mailmap:\n%s',
114             $mapfile_blob, $mailmap->to_string() );
115             }
116             else {
117 0           $git->fault( 'Config variable \'mailmap.blob\' does not point to a file.', { prefix => $PKG, commit => $commit } );
118 0           ++$errors;
119             }
120             }
121              
122 0           my $verified = 0;
123              
124             # Always search (first) among proper emails (and names if wanted).
125 0           my %search_params = ( 'proper-email' => $author_email );
126 0 0         if ( $git->get_config( $CFG => 'match-mailmap-name' ) eq '1' ) {
127 0           $search_params{'proper-name'} = $author_name;
128             }
129 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): search_params=%s.', \%search_params );
130 0           $verified = $mailmap->verify(%search_params);
131 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): verified=%s.', $verified );
132              
133             # If was not found among proper-*, and user wants, search aliases.
134 0 0 0       if ( !$verified && $git->get_config( $CFG => 'allow-mailmap-aliases' ) eq '1' ) {
135 0           my %c_search_params = ( 'commit-email' => $author_email );
136 0 0         if ( $git->get_config( $CFG => 'match-mailmap-name' ) eq '1' ) {
137 0           $c_search_params{'commit-name'} = $author_name;
138             }
139 0           $log->debugf( __PACKAGE__ . q{::} . '_check_mailmap(): c_search_params=%s.', \%c_search_params );
140 0           $verified = $mailmap->verify(%c_search_params);
141             }
142 0 0         if ( $verified == 0 ) {
143 0           $git->fault( "Commit author '$author' has no match in mailmap file.", { prefix => $PKG, commit => $commit } );
144 0           ++$errors;
145             }
146              
147 0           return $errors == 0;
148             }
149              
150             sub check_ref {
151 0     0 0   my ( $git, $ref ) = @_;
152              
153 0 0         return 1 unless $git->is_reference_enabled($ref);
154              
155 0           my $errors = 0;
156 0           foreach my $commit ( $git->get_affected_ref_commits($ref) ) {
157 0 0         check_commit_at_server( $git, $commit )
158             or ++$errors;
159             }
160              
161 0           return $errors == 0;
162             }
163              
164             # This routine can act both as an update or a pre-receive hook.
165             sub check_affected_refs {
166 0     0 1   my ($git) = @_;
167              
168 0           _setup_config($git);
169              
170 0 0         return 1 if $git->im_admin($git);
171              
172 0           my $errors = 0;
173              
174 0           foreach my $ref ( $git->get_affected_refs() ) {
175 0 0         check_ref( $git, $ref )
176             or ++$errors;
177             }
178              
179 0           return $errors == 0;
180             }
181              
182             sub check_patchset {
183 0     0 1   my ( $git, $opts ) = @_;
184              
185 0           _setup_config($git);
186              
187 0           my $sha1 = $opts->{'--commit'};
188 0           my $commit = $git->get_commit($sha1);
189              
190 0           my $branch = $opts->{'--branch'};
191 0 0         $branch = "refs/heads/$branch"
192             unless $branch =~ m{^refs/}msx;
193 0 0         return 1 unless $git->is_reference_enabled($branch);
194              
195 0           return check_commit_at_server( $git, $commit );
196             }
197              
198             # Install hooks
199             my $options = { config => \&_setup_config };
200              
201             GITHOOKS_CHECK_PRE_COMMIT( \&check_commit_at_client, $options );
202             GITHOOKS_CHECK_AFFECTED_REFS( \&check_affected_refs, $options );
203             GITHOOKS_CHECK_PATCHSET( \&check_patchset, $options );
204              
205             1;
206              
207             __END__