File Coverage

blib/lib/App/GitHooks/Hook/CommitMsg.pm
Criterion Covered Total %
statement 27 57 47.3
branch 0 18 0.0
condition 0 7 0.0
subroutine 9 10 90.0
pod 1 1 100.0
total 37 93 39.7


line stmt bran cond sub pod time code
1             package App::GitHooks::Hook::CommitMsg;
2              
3 1     1   17374 use strict;
  1         2  
  1         34  
4 1     1   3 use warnings;
  1         1  
  1         25  
5              
6             # Inherit from the base Hook class.
7 1     1   3 use base 'App::GitHooks::Hook';
  1         1  
  1         352  
8              
9             # External dependencies.
10 1     1   7 use Carp;
  1         1  
  1         47  
11 1     1   753 use Data::Dumper;
  1         7334  
  1         54  
12 1     1   758 use Path::Tiny qw();
  1         10933  
  1         25  
13              
14             # Internal dependencies.
15 1     1   351 use App::GitHooks::CommitMessage;
  1         3  
  1         32  
16 1     1   7 use App::GitHooks::Constants qw( :HOOK_EXIT_CODES :PLUGIN_RETURN_CODES );
  1         1  
  1         163  
17 1     1   456 use App::GitHooks::StagedChanges;
  1         2  
  1         320  
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.7.3
28              
29             =cut
30              
31             our $VERSION = '1.7.3';
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 0     0 1   my ( $class, %args ) = @_;
59 0           my $app = delete( $args{'app'} );
60 0 0         croak 'Unknown argument(s): ' . join( ', ', keys %args )
61             if scalar( keys %args ) != 0;
62              
63             # Check parameters.
64 0 0         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 0 0         my $console = $^O eq 'MSWin32'
71             ? 'CON:'
72             : '/dev/tty';
73 0           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 0           my $config = $app->get_config();
78 0           my $force_interactive = $config->get( 'testing', 'force_interactive' );
79 0 0 0       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 0           my $has_errors = 0;
85 0           while ( 1 )
86             {
87             # Retrieve the commit message.
88 0           my $command_line_arguments = $app->get_command_line_arguments();
89 0           my $commit_message_file = $command_line_arguments->[0];
90 0   0       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 0 0         if $commit_message->is_empty();
99              
100             # Find all the tests we will need to run.
101 0           my $plugins = $app->get_hook_plugins( $app->get_hook_name() );
102 0           $has_errors = 0;
103 0           foreach my $plugin ( @$plugins )
104             {
105 0           my $return_code = $plugin->run_commit_msg(
106             app => $app,
107             commit_message => $commit_message,
108             );
109 0 0         $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 0 0         if ( $has_errors )
116             {
117 0           print "Press to edit again the commit message or Ctrl-C to abort the commit.\n";
118 0 0         if ( $app->get_terminal()->is_interactive() )
119             {
120 0           my $input = ; ## no critic (InputOutput::ProhibitExplicitStdin)
121              
122 0   0       my $editor = $ENV{'EDITOR'} // 'vim';
123 0           system("$editor $commit_message_file");
124 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           last;
131             }
132             }
133             else
134             {
135             # No errors, $has_errors is set to 0, finish.
136 0           last;
137             }
138             }
139              
140             # Success.
141 0 0         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.
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
169              
170             =item * AnnoCPAN: Annotated CPAN documentation
171              
172             L
173              
174             =item * CPAN Ratings
175              
176             L
177              
178             =item * MetaCPAN
179              
180             L
181              
182             =back
183              
184              
185             =head1 AUTHOR
186              
187             L,
188             C<< >>.
189              
190              
191             =head1 COPYRIGHT & LICENSE
192              
193             Copyright 2013-2015 Guillaume Aubert.
194              
195             This program is free software: you can redistribute it and/or modify it under
196             the terms of the GNU General Public License version 3 as published by the Free
197             Software Foundation.
198              
199             This program is distributed in the hope that it will be useful, but WITHOUT ANY
200             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
201             PARTICULAR PURPOSE. See the GNU General Public License for more details.
202              
203             You should have received a copy of the GNU General Public License along with
204             this program. If not, see http://www.gnu.org/licenses/
205              
206             =cut
207              
208             1;