File Coverage

blib/lib/App/GitHooks/Hook/CommitMsg.pm
Criterion Covered Total %
statement 50 57 87.7
branch 8 18 44.4
condition 2 7 28.5
subroutine 10 10 100.0
pod 1 1 100.0
total 71 93 76.3


line stmt bran cond sub pod time code
1             package App::GitHooks::Hook::CommitMsg;
2              
3 2     2   27184 use strict;
  2         5  
  2         79  
4 2     2   13 use warnings;
  2         2  
  2         92  
5              
6             # Inherit from the base Hook class.
7 2     2   12 use base 'App::GitHooks::Hook';
  2         5  
  2         1081  
8              
9             # External dependencies.
10 2     2   16 use Carp;
  2         4  
  2         130  
11 2     2   850 use Data::Dumper;
  2         11685  
  2         152  
12 2     2   966 use Path::Tiny qw();
  2         14546  
  2         63  
13              
14             # Internal dependencies.
15 2     2   1159 use App::GitHooks::CommitMessage;
  2         7  
  2         99  
16 2     2   17 use App::GitHooks::Constants qw( :HOOK_EXIT_CODES :PLUGIN_RETURN_CODES );
  2         4  
  2         467  
17 2     2   558 use App::GitHooks::StagedChanges;
  2         5  
  2         1011  
18              
19              
20             =head1 NAME
21              
22             App::GitHooks::Hook::CommitMsg - Handle the commit-msg hook.
23              
24              
25             =head1 VERSION
26              
27             Version 1.9.0
28              
29             =cut
30              
31             our $VERSION = '1.9.0';
32              
33              
34             =head1 METHODS
35              
36             =head2 run()
37              
38             Run the hook handler and return an exit status to pass to git.
39              
40             my $exit_status = App::GitHooks::Hook::CommitMsg->run(
41             app => $app,
42             );
43              
44             Arguments:
45              
46             =over 4
47              
48             =item * app I<(mandatory)>
49              
50             An App::GitHooks object.
51              
52             =back
53              
54             =cut
55              
56             sub run
57             {
58 1     1 1 4 my ( $class, %args ) = @_;
59 1         3 my $app = delete( $args{'app'} );
60 1 50       4 croak 'Unknown argument(s): ' . join( ', ', keys %args )
61             if scalar( keys %args ) != 0;
62              
63             # Check parameters.
64 1 50       5 croak "The 'app' argument is mandatory"
65             if !Data::Validate::Type::is_instance( $app, class => 'App::GitHooks' );
66              
67             # Reassigns standard input back to the keyboard.
68             # Note: this will silently fail in some non-interactive shells where /dev/tty
69             # can't be referenced, which is fine since there will be no user typing anyway.
70 1 50       37 my $console = $^O eq 'MSWin32'
71             ? 'CON:'
72             : '/dev/tty';
73 1         28 open( STDIN, '<', $console ); ## no critic (InputOutput::RequireCheckedOpen)
74              
75             # If the terminal isn't interactive, we won't have a human available to fix the
76             # problems so we just let the commit go as is.
77 1         6 my $config = $app->get_config();
78 1         5 my $force_interactive = $config->get( 'testing', 'force_interactive' );
79 1 50 33     4 return $HOOK_EXIT_SUCCESS
80             if !$app->get_terminal()->is_interactive() && !$force_interactive;
81              
82             # Analyze the commit message and prompt the user to fix it if needed until it
83             # passes the checks.
84 1         3 my $has_errors = 0;
85 1         2 while ( 1 )
86             {
87             # Retrieve the commit message.
88 1         4 my $command_line_arguments = $app->get_command_line_arguments();
89 1         3 my $commit_message_file = $command_line_arguments->[0];
90 1   50     6 my $commit_message = App::GitHooks::CommitMessage->new(
91             message => Path::Tiny::path( $commit_message_file )->slurp_utf8() // '',
92             app => $app,
93             );
94              
95             # If the commit message is empty, don't bother running any checks - git will
96             # abort the commit.
97             last
98 1 50       11 if $commit_message->is_empty();
99              
100             # Find all the tests we will need to run.
101 1         6 my $plugins = $app->get_hook_plugins( $app->get_hook_name() );
102 1         8 $has_errors = 0;
103 1         3 foreach my $plugin ( @$plugins )
104             {
105 1         10 my $return_code = $plugin->run_commit_msg(
106             app => $app,
107             commit_message => $commit_message,
108             );
109 1 50       5 $has_errors = 1
110             if $return_code == $PLUGIN_RETURN_FAILED;
111             }
112              
113             # If errors were found, let the user try to fix the commit message,
114             # otherwise finish and let git complete.
115 1 50       4 if ( $has_errors )
116             {
117 0         0 print "Press <Enter> to edit the commit message or Ctrl-C to abort the commit.\n";
118 0 0       0 if ( $app->get_terminal()->is_interactive() )
119             {
120 0         0 my $input = <STDIN>; ## no critic (InputOutput::ProhibitExplicitStdin)
121              
122 0   0     0 my $editor = $ENV{'EDITOR'} // 'vim';
123 0         0 system("$editor $commit_message_file");
124 0         0 print "\n";
125             }
126             else
127             {
128             # $has_errors is set to 1, but we're not in interactive mode so we
129             # can't wait for STDIN.
130 0         0 last;
131             }
132             }
133             else
134             {
135             # No errors, $has_errors is set to 0, finish.
136 1         236 last;
137             }
138             }
139              
140             # Success.
141 1 50       12 return $has_errors
142             ? $HOOK_EXIT_FAILURE
143             : $HOOK_EXIT_SUCCESS;
144             }
145              
146              
147             =head1 BUGS
148              
149             Please report any bugs or feature requests through the web interface at
150             L<https://github.com/guillaumeaubert/App-GitHooks/issues/new>.
151             I will be notified, and then you'll automatically be notified of progress on
152             your bug as I make changes.
153              
154              
155             =head1 SUPPORT
156              
157             You can find documentation for this module with the perldoc command.
158              
159             perldoc App::GitHooks::Hook::CommitMsg
160              
161              
162             You can also look for information at:
163              
164             =over
165              
166             =item * GitHub's request tracker
167              
168             L<https://github.com/guillaumeaubert/App-GitHooks/issues>
169              
170             =item * AnnoCPAN: Annotated CPAN documentation
171              
172             L<http://annocpan.org/dist/app-githooks>
173              
174             =item * CPAN Ratings
175              
176             L<http://cpanratings.perl.org/d/app-githooks>
177              
178             =item * MetaCPAN
179              
180             L<https://metacpan.org/release/App-GitHooks>
181              
182             =back
183              
184              
185             =head1 AUTHOR
186              
187             L<Guillaume Aubert|https://metacpan.org/author/AUBERTG>,
188             C<< <aubertg at cpan.org> >>.
189              
190              
191             =head1 COPYRIGHT & LICENSE
192              
193             Copyright 2013-2017 Guillaume Aubert.
194              
195             This code is free software; you can redistribute it and/or modify it under the
196             same terms as Perl 5 itself.
197              
198             This program is distributed in the hope that it will be useful, but WITHOUT ANY
199             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
200             PARTICULAR PURPOSE. See the LICENSE file for more details.
201              
202             =cut
203              
204             1;