File Coverage

blib/lib/App/GitHooks/Plugin/BlockNOCOMMIT.pm
Criterion Covered Total %
statement 51 57 89.4
branch 12 22 54.5
condition n/a
subroutine 10 10 100.0
pod 3 3 100.0
total 76 92 82.6


line stmt bran cond sub pod time code
1             package App::GitHooks::Plugin::BlockNOCOMMIT;
2              
3 17     17   2657506 use strict;
  17         25  
  17         380  
4 17     17   54 use warnings;
  17         15  
  17         320  
5              
6 17     17   47 use base 'App::GitHooks::Plugin';
  17         22  
  17         2082  
7              
8              
9             # External dependencies.
10 17     17   980 use Carp;
  17         17  
  17         705  
11 17     17   937 use Data::Validate::Type;
  17         14151  
  17         452  
12 17     17   7813 use File::Slurp ();
  17         73338  
  17         368  
13              
14             # Internal dependencies.
15 17     17   1368 use App::GitHooks::Constants qw( :PLUGIN_RETURN_CODES );
  17         10484  
  17         8354  
16              
17              
18             =head1 NAME
19              
20             App::GitHooks::Plugin::BlockNOCOMMIT - Prevent committing code with #NOCOMMIT mentions.
21              
22              
23             =head1 DESCRIPTION
24              
25             Sometimes you want to experiment with code, but you want to make sure that test
26             code doesn't get committed by accident.
27              
28             This plugin allows you to use C<#NOCOMMIT> to indicate such code blocks, and
29             will prevent you from committing those blocks.
30              
31             For example:
32              
33             # This is a test that will not work once deployed.
34             # NOCOMMIT
35             ...
36              
37             Note that the following variations on the tag are correctly picked up:
38              
39             =over 4
40              
41             =item * C<#NOCOMMIT>
42              
43             =item * C<# NOCOMMIT>
44              
45             =item * C<# NO COMMIT>
46              
47             =back
48              
49              
50             =head1 VERSION
51              
52             Version 1.1.1
53              
54             =cut
55              
56             our $VERSION = '1.1.1';
57              
58              
59             =head1 METHODS
60              
61             =head2 get_file_pattern()
62              
63             Return a pattern to filter the files this plugin should analyze.
64              
65             my $file_pattern = App::GitHooks::Plugin::BlockNOCOMMIT->get_file_pattern(
66             app => $app,
67             );
68              
69             =cut
70              
71             sub get_file_pattern
72             {
73             # Check all files.
74 15     15 1 547936 return qr//;
75             }
76              
77              
78             =head2 get_file_check_description()
79              
80             Return a description of the check performed on files by the plugin and that
81             will be displayed to the user, if applicable, along with an indication of the
82             success or failure of the plugin.
83              
84             my $description = App::GitHooks::Plugin::BlockNOCOMMIT->get_file_check_description();
85              
86             =cut
87              
88             sub get_file_check_description
89             {
90 15     15 1 9297 return 'The file has no #NOCOMMIT tags.';
91             }
92              
93              
94             =head2 run_pre_commit_file()
95              
96             Code to execute for each file as part of the pre-commit hook.
97              
98             my $success = App::GitHooks::Plugin::BlockNOCOMMIT->run_pre_commit_file();
99              
100             =cut
101              
102             sub run_pre_commit_file
103             {
104 7     7 1 5779 my ( $class, %args ) = @_;
105 7         65 my $app = delete( $args{'app'} );
106 7         489 my $file = delete( $args{'file'} );
107 7         33 my $git_action = delete( $args{'git_action'} );
108 7 50       130 croak 'Unknown argument(s): ' . join( ', ', keys %args )
109             if scalar( keys %args ) != 0;
110              
111             # Check parameters.
112 7 50       193 croak "The 'app' argument is mandatory"
113             if !Data::Validate::Type::is_instance( $app, class => 'App::GitHooks' );
114 7 50       458 croak "The 'file' argument is mandatory"
115             if !defined( $file );
116 7 50       62 croak "The 'git_action' argument is mandatory"
117             if !defined( $git_action );
118              
119             # Ignore deleted files.
120 7 50       80 return $PLUGIN_RETURN_SKIPPED
121             if $git_action eq 'D';
122              
123             # Ignore merges, since they correspond mostly to code written by other people.
124 7         150 my $staged_changes = $app->get_staged_changes();
125 7 50       177 return $PLUGIN_RETURN_SKIPPED
126             if $staged_changes->is_merge();
127              
128             # Ignore revert commits.
129 7 50       101 return $PLUGIN_RETURN_SKIPPED
130             if $staged_changes->is_revert();
131              
132             # Determine what lines were written by the current user.
133 7         73 my @review_lines = ();
134 7 50       38 if ( $git_action eq 'A' )
135             {
136             # "git blame" fails on new files, so we need to add the entire file
137             # separately.
138 7         144 my @lines = File::Slurp::read_file( $file );
139 7         2563 foreach my $i ( 0 .. scalar( @lines ) - 1 ) {
140 28         42 chomp( $lines[$i] );
141 28         103 push(
142             @review_lines,
143             {
144             line_number => $i,
145             code => $lines[$i],
146             }
147             );
148             }
149             }
150             else
151             {
152             # Find uncommitted lines only.
153 0         0 my $repository = $app->get_repository();
154 0         0 my $blame_lines = $repository->blame(
155             $file,
156             use_cache => 1,
157             );
158              
159 0         0 foreach my $blame_line ( @$blame_lines )
160             {
161 0         0 my $commit_attributes = $blame_line->get_commit_attributes();
162 0 0       0 next unless $commit_attributes->{'author-mail'} eq 'not.committed.yet';
163 0         0 push(
164             @review_lines,
165             {
166             line_number => $blame_line->get_line_number(),
167             code => $blame_line->get_line(),
168             }
169             );
170             }
171             }
172              
173             # Inspect uncommitted lines for NOCOMMIT mentions.
174 7         21 my @violations = ();
175 7         16 foreach my $line ( @review_lines )
176             {
177 28         37 my $code = $line->{'code'};
178 28 100       184 next if $code !~ /
179             (?:\/\/|\#) # Support Perl and Javascript comments.
180             .* # Support NOCOMMIT anywhere in the comment, not just at the beginning.
181             \bNO\s*COMMIT\b # Support NOCOMMIT and NO COMMIT.
182             /ix;
183              
184 6         56 my $bad_line = $code;
185 6         56 $bad_line =~ s/\b(NO\s*COMMIT)\b/$app->color('red', $1)/iegs;
  6         103  
186             push(
187             @violations,
188 6         180 sprintf( "line #%d: %s\n", $line->{'line_number'}, $bad_line )
189             );
190             }
191              
192 7 100       145 die "#NOCOMMIT tags found:\n" . join( '', @violations ) . "\n"
193             if scalar( @violations ) != 0;
194              
195 1         9 return $PLUGIN_RETURN_PASSED;
196             }
197              
198              
199             =head1 BUGS
200              
201             Please report any bugs or feature requests through the web interface at
202             L.
203             I will be notified, and then you'll automatically be notified of progress on
204             your bug as I make changes.
205              
206              
207             =head1 SUPPORT
208              
209             You can find documentation for this module with the perldoc command.
210              
211             perldoc App::GitHooks::Plugin::BlockNOCOMMIT
212              
213              
214             You can also look for information at:
215              
216             =over
217              
218             =item * GitHub's request tracker
219              
220             L
221              
222             =item * AnnoCPAN: Annotated CPAN documentation
223              
224             L
225              
226             =item * CPAN Ratings
227              
228             L
229              
230             =item * MetaCPAN
231              
232             L
233              
234             =back
235              
236              
237             =head1 AUTHOR
238              
239             L,
240             C<< >>.
241              
242              
243             =head1 COPYRIGHT & LICENSE
244              
245             Copyright 2013-2016 Guillaume Aubert.
246              
247             This code is free software; you can redistribute it and/or modify it under the
248             same terms as Perl 5 itself.
249              
250             This program is distributed in the hope that it will be useful, but WITHOUT ANY
251             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
252             PARTICULAR PURPOSE. See the LICENSE file for more details.
253              
254             =cut
255              
256             1;