File Coverage

blib/lib/App/GitHooks/Hook/PreCommit.pm
Criterion Covered Total %
statement 52 55 94.5
branch 18 22 86.3
condition 1 3 33.3
subroutine 10 10 100.0
pod 2 2 100.0
total 83 92 91.3


line stmt bran cond sub pod time code
1             package App::GitHooks::Hook::PreCommit;
2              
3 15     15   25537 use strict;
  15         22  
  15         456  
4 15     15   60 use warnings;
  15         23  
  15         518  
5              
6             # Inherit from the base Hook class.
7 15     15   54 use base 'App::GitHooks::Hook';
  15         24  
  15         5754  
8              
9             # External dependencies.
10 15     15   77 use Carp;
  15         21  
  15         751  
11 15     15   917 use Data::Dumper;
  15         10900  
  15         697  
12 15     15   767 use Data::Validate::Type;
  15         8479  
  15         513  
13 15     15   795 use Path::Tiny qw();
  15         8137  
  15         290  
14              
15             # Internal dependencies.
16 15     15   61 use App::GitHooks::Constants qw( :HOOK_EXIT_CODES :PLUGIN_RETURN_CODES );
  15         22  
  15         7140  
17              
18              
19             =head1 NAME
20              
21             App::GitHooks::Hook::PreCommit - Handler for pre-commit hook.
22              
23              
24             =head1 VERSION
25              
26             Version 1.9.0
27              
28             =cut
29              
30             our $VERSION = '1.9.0';
31              
32              
33             =head1 METHODS
34              
35             =head2 run()
36              
37             Run the hook handler and return an exit status to pass to git.
38              
39             my $exit_status = App::GitHooks::Hook::PreCommit->run(
40             app => $app,
41             );
42              
43             Arguments:
44              
45             =over 4
46              
47             =item * app I<(mandatory)>
48              
49             An App::GitHooks object.
50              
51             =back
52              
53             =cut
54              
55             sub run
56             {
57 14     14 1 38 my ( $class, %args ) = @_;
58 14         30 my $app = delete( $args{'app'} );
59 14 50       54 croak 'Unknown argument(s): ' . join( ', ', keys %args )
60             if scalar( keys %args ) != 0;
61              
62             # Check parameters.
63 14 50       64 croak "The 'app' argument is mandatory"
64             if !Data::Validate::Type::is_instance( $app, class => 'App::GitHooks' );
65              
66             # Remove the file that we use to indicate that pre-commit checks have been
67             # run for the set of staged files, in case it exists.
68 14         712 unlink( '.git/COMMIT-MSG-CHECKS' );
69              
70             # If the terminal isn't interactive, we won't have a human available to fix the
71             # problems so we just let the commit go as is.
72 14         54 my $config = $app->get_config();
73 14         42 my $force_interactive = $config->get( 'testing', 'force_interactive' );
74 14 50 33     44 return $HOOK_EXIT_SUCCESS
75             if !$app->get_terminal()->is_interactive() && !$force_interactive;
76              
77             # Run the checks on the staged changes.
78 14         32 my $checks_pass = run_all_tests( $app );
79              
80             # If the checks passed, write a file for the prepare-commit-msg hook to know
81             # that we've already run the checks and there's no need to do it a second time.
82             # This is what allows it to detect when --no-verify was used.
83 7 100       26 if ( $checks_pass )
84             {
85 4         39 Path::Tiny::path( '.git', 'COMMIT-MSG-CHECKS' )
86             ->spew( $checks_pass );
87             }
88              
89             # Indicate if we should allow continuing to the commit message or not.
90 7 100       2096 return $checks_pass
91             ? $HOOK_EXIT_SUCCESS
92             : $HOOK_EXIT_FAILURE;
93             }
94              
95              
96             =head2 run_all_tests()
97              
98             Run all the tests available for the pre-commit hook and return whether issues
99             were detected.
100              
101             my $tests_success = run_all_tests( $app );
102              
103             This is a two step operation:
104              
105             =over 4
106              
107             =item 1. We load all the plugins that support "pre-commit", and run them to
108             analyze the overall pre-commit operation.
109              
110             =item 2. Each staged file is loaded and we run plugins that support
111             "pre-commit-file" on each one.
112              
113             =back
114              
115             =cut
116              
117             sub run_all_tests
118             {
119 14     14 1 16 my ( $app ) = @_;
120              
121             # Find all the plugins that support the pre-commit hook.
122 14         58 my $plugins = $app->get_hook_plugins( 'pre-commit' );
123              
124             # Run the plugins.
125 14         24 my $tests_success = 1;
126 14         24 my $has_warnings = 0;
127 14         32 foreach my $plugin ( @$plugins )
128             {
129 14         56 my $check_result = $plugin->run_pre_commit(
130             app => $app,
131             );
132              
133 14 100       50 $tests_success = 0
134             if $check_result == $PLUGIN_RETURN_FAILED;
135              
136 14 100       46 $has_warnings = 1
137             if $check_result == $PLUGIN_RETURN_WARNED;
138             }
139              
140             # Check the changed files individually with plugins that support
141             # "pre-commit-file".
142             {
143 14         24 my ( $file_checks, $file_warnings ) = $app
  14         62  
144             ->get_staged_changes()
145             ->verify();
146 7 100       33 $tests_success = 0
147             if !$file_checks;
148 7 100       31 $has_warnings = 1
149             if $file_warnings;
150             }
151              
152             # If warnings were found, notify users.
153 7 100       24 if ( $has_warnings )
154             {
155             # If we have a user, stop and ask if we should continue with the commit.
156             # uncoverable branch true
157 2 50       5 if ( $app->get_terminal()->is_interactive() )
158             {
159 0         0 print "Some warnings were found. Press <Enter> to continue committing or Ctrl-C to abort the commit.\n";
160 0         0 my $input = <STDIN>; ## no critic (InputOutput::ProhibitExplicitStdin)
161 0         0 print "\n";
162             }
163             # If we don't have a user, just warn and continue.
164             else
165             {
166 2         5 print "Some warnings were found, please review.\n";
167             }
168             }
169              
170 7         38 return $tests_success;
171             }
172              
173              
174             =head1 BUGS
175              
176             Please report any bugs or feature requests through the web interface at
177             L<https://github.com/guillaumeaubert/App-GitHooks/issues/new>.
178             I will be notified, and then you'll automatically be notified of progress on
179             your bug as I make changes.
180              
181              
182             =head1 SUPPORT
183              
184             You can find documentation for this module with the perldoc command.
185              
186             perldoc App::GitHooks::Hook::PreCommit
187              
188              
189             You can also look for information at:
190              
191             =over
192              
193             =item * GitHub's request tracker
194              
195             L<https://github.com/guillaumeaubert/App-GitHooks/issues>
196              
197             =item * AnnoCPAN: Annotated CPAN documentation
198              
199             L<http://annocpan.org/dist/app-githooks>
200              
201             =item * CPAN Ratings
202              
203             L<http://cpanratings.perl.org/d/app-githooks>
204              
205             =item * MetaCPAN
206              
207             L<https://metacpan.org/release/App-GitHooks>
208              
209             =back
210              
211              
212             =head1 AUTHOR
213              
214             L<Guillaume Aubert|https://metacpan.org/author/AUBERTG>,
215             C<< <aubertg at cpan.org> >>.
216              
217              
218             =head1 COPYRIGHT & LICENSE
219              
220             Copyright 2013-2017 Guillaume Aubert.
221              
222             This code is free software; you can redistribute it and/or modify it under the
223             same terms as Perl 5 itself.
224              
225             This program is distributed in the hope that it will be useful, but WITHOUT ANY
226             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
227             PARTICULAR PURPOSE. See the LICENSE file for more details.
228              
229             =cut
230              
231             1;