File Coverage

blib/lib/App/GitHooks.pm
Criterion Covered Total %
statement 55 57 96.4
branch n/a
condition n/a
subroutine 19 19 100.0
pod n/a
total 74 76 97.3


line stmt bran cond sub pod time code
1             package App::GitHooks;
2              
3 22     22   29637 use strict;
  22         36  
  22         732  
4 22     22   98 use warnings;
  22         37  
  22         867  
5              
6             # Unbuffer output to display it as quickly as possible.
7             local $| = 1;
8              
9             # External dependencies.
10 22     22   95 use Carp qw( carp croak );
  22         30  
  22         1373  
11 22     22   11387 use Class::Load qw();
  22         540474  
  22         545  
12 22     22   10222 use Config::Tiny qw();
  22         17883  
  22         416  
13 22     22   11110 use Data::Validate::Type qw();
  22         142978  
  22         571  
14 22     22   15630 use Data::Dumper qw( Dumper );
  22         130240  
  22         1652  
15 22     22   178 use File::Basename qw();
  22         33  
  22         349  
16 22     22   11353 use Git::Repository qw();
  22         451303  
  22         644  
17             use Module::Pluggable
18 22         146 require => 1,
19 22     22   12703 sub_name => '_search_plugins';
  22         169124  
20 22     22   16501 use Term::ANSIColor qw();
  22         128472  
  22         985  
21 22     22   12076 use Text::Wrap qw();
  22         47345  
  22         630  
22 22     22   122 use Try::Tiny qw( try catch finally );
  22         27  
  22         1269  
23 22     22   13824 use Storable qw();
  22         58524  
  22         566  
24              
25             # Internal dependencies.
26 22     22   8764 use App::GitHooks::Config;
  22         42  
  22         687  
27 22     22   7156 use App::GitHooks::Constants qw( :HOOK_EXIT_CODES );
  22         47  
  22         3132  
28 22     22   7947 use App::GitHooks::Plugin;
  22         42  
  22         569  
29 22     22   8216 use App::GitHooks::StagedChanges;
  22         54  
  22         647  
30 22     22   8226 use App::GitHooks::Terminal;
  0            
  0            
31              
32              
33             =head1 NAME
34              
35             App::GitHooks - Extensible plugins system for git hooks.
36              
37              
38             =head1 VERSION
39              
40             Version 1.7.3
41              
42             =cut
43              
44             our $VERSION = '1.7.3';
45              
46              
47             =head1 DESCRIPTION
48              
49             C is an extensible and easy to configure git hooks framework that supports many plugins.
50              
51             Here's an example of it in action, running the C hook checks before
52             the commit message can be entered:
53              
54             =begin html
55              
56            
57              
58             =end html
59              
60             Here is another example, with a Perl file that fails compilation this time:
61              
62             =begin html
63              
64            
65              
66             =end html
67              
68              
69             =head1 SYNOPSIS
70              
71             =over 4
72              
73             =item 1.
74              
75             Install this distribution (with cpanm or your preferred CPAN client):
76              
77             cpanm App::GitHooks
78              
79             =item 2.
80              
81             Install the plugins you are interested in (with cpanmor your prefered CPAN
82             client), as C does not bundle them. See the list of plugins
83             below, but for example:
84              
85             cpanm App::GitHooks::Plugin::BlockNOCOMMIT
86             cpanm App::GitHooks::Plugin::DetectCommitNoVerify
87             ...
88              
89             =item 3.
90              
91             Go to the git repository for which you want to set up git hooks, and run:
92              
93             githooks install
94              
95             =item 4.
96              
97             Enjoy!
98              
99             =back
100              
101              
102             =head1 GIT REQUIREMENTS
103              
104             L requires git v1.7.4.1 or above.
105              
106              
107             =head1 VALID GIT HOOK NAMES
108              
109             =over 4
110              
111             =item * applypatch-msg
112              
113             =item * pre-applypatch
114              
115             =item * post-applypatch
116              
117             =item * pre-commit
118              
119             =item * prepare-commit-msg
120              
121             =item * commit-msg
122              
123             =item * post-commit
124              
125             =item * pre-rebase
126              
127             =item * post-checkout
128              
129             =item * post-merge
130              
131             =item * pre-receive
132              
133             =item * update
134              
135             =item * post-receive
136              
137             =item * post-update
138              
139             =item * pre-auto-gc
140              
141             =item * post-rewrite
142              
143             =back
144              
145             =cut
146              
147             # List of valid git hooks.
148             # From https://www.kernel.org/pub/software/scm/git/docs/githooks.html
149             our $HOOK_NAMES =
150             [
151             qw(
152             applypatch-msg
153             commit-msg
154             post-applypatch
155             post-checkout
156             post-commit
157             post-merge
158             post-receive
159             post-rewrite
160             post-update
161             pre-applypatch
162             pre-auto-gc
163             pre-commit
164             pre-push
165             pre-rebase
166             pre-receive
167             prepare-commit-msg
168             update
169             )
170             ];
171              
172              
173             =head1 OFFICIALLY SUPPORTED PLUGINS
174              
175             =over 4
176              
177             =item * L
178              
179             Prevent committing code with #NOCOMMIT mentions.
180              
181             =item * L
182              
183             Prevent commits in a production environment.
184              
185             =item * L
186              
187             Find out when someone uses --no-verify and append the pre-commit checks to the
188             commit message.
189              
190             =item * L
191              
192             Force running a specific tool at regular intervals.
193              
194             =item * L
195              
196             Detect discrepancies between the ticket ID specified by the branch name and the
197             one in the commit message.
198              
199             =item * L
200              
201             Verify that Perl files compile without errors.
202              
203             =item * L
204              
205             Verify that all changes and addition to the Perl files pass PerlCritic checks.
206              
207             =item * L
208              
209             Enforce a specific Perl interpreter on the first line of Perl files.
210              
211             =item * L
212              
213             Verify that the syntax of PgBouncer auth files is correct.
214              
215             =item * L
216              
217             Derive a ticket ID from the branch name and prepend it to the commit-message.
218              
219             =item * L
220              
221             Require a commit message.
222              
223             =item * L
224              
225             Verify that staged Ruby files compile.
226              
227             =item * L
228              
229             Validate POD format in Perl and POD files.
230              
231             =back
232              
233              
234             =head1 CONTRIBUTED PLUGINS
235              
236             =over 4
237              
238             =item * L
239              
240             Verify that staged Ruby files compile.
241              
242             =item * L
243              
244             Prevent trailing whitespace from being committed.
245              
246             =back
247              
248              
249             =head1 CONFIGURATION OPTIONS
250              
251             =head2 Configuration format
252              
253             L uses L, so the configuration should follow the
254             following format:
255              
256             general_key_1 = value
257             general_key_2 = value
258              
259             [section_1]
260             section_1_key 1 = value
261              
262             The file is divided between the global configuration options at the beginning
263             of the file (such as C above) and plugin specific configuration
264             options which are located in distinct sections (such as C in the
265             C<[section_1]> section).
266              
267              
268             =head2 Configuration file locations
269              
270             L supports setting custom options by creating one of the
271             following files, which are searched in descending order of preference:
272              
273             =over 4
274              
275             =item *
276              
277             A file of any name anywhere on your system, if you set the environment variable
278             C to its path.
279              
280             Note that you should normally use C. This option is provided mostly
281             for testing purposes, when configuration options for testing in a reliable
282             manner are of the utmost importance and take precedence over any
283             repository-specific settings.
284              
285             =item *
286              
287             A C<.githooksrc> file at the root of the git repository.
288              
289             The settings will then only apply to that repository.
290              
291             =item *
292              
293             A file of any name anywhere on your system, if you set the environment variable
294             C to its path.
295              
296             Note that C<.githooksrc> files at the top of a repository or in a user's home
297             directory will take precedence over a file specified by the C
298             environment variable.
299              
300             =item *
301              
302             A C<.githooksrc> file in the home directory of the current user.
303              
304             The settings will then apply to all the repositories that have hooks set up.
305             Note that if C<.githooksrc> file is defined at the root of a repository, that
306             configuration file will take precedence over the one defined in the home
307             directory of the current user (as it is presumably more specific). Auto-merge
308             of options across multiple C<.githooksrc> files in an inheritance fashion is
309             not currently supported.
310              
311             =back
312              
313              
314             =head2 General configuration options
315              
316             =over 4
317              
318             =item * project_prefixes
319              
320             A comma-separated list of project prefixes, in case you want to use this in
321             C or C.
322              
323             project_prefixes = OPS, DEV
324              
325             =item * extract_ticket_id_from_commit
326              
327             A regular expression with _one_ capturing group that will be applied to the
328             first line of a commit message to extract the ticket ID referenced, if there is
329             one.
330              
331             extract_ticket_id_from_commit = /^($project_prefixes-\d+|--): /
332              
333             =item * extract_ticket_id_from_branch
334              
335             A regular expression with _one_ capturing group that will be applied to branch
336             names to extract a ticket ID. This allows creating one branch per ticket and
337             having the hooks check that the commit messages and the branch names are in
338             sync.
339              
340             extract_ticket_id_from_branch = /^($project_prefixes-?\d+)/
341              
342             =item * normalize_branch_ticket_id
343              
344             A replacement expression that normalizes the ticket ID captured with
345             C.
346              
347             normalize_branch_ticket_id = s/^(.*?)-?(\d+)$/\U$1-$2/
348              
349             =item * skip_directories
350              
351             A regular expression to filter the directory names that should be skipped when
352             analyzing files as part of file-level checks.
353              
354             skip_directories = /^cpan(?:-[^\/]+)?\//
355              
356             =item * force_plugins
357              
358             A comma-separated list of the plugins that must be present on the system and
359             will be executed. If any plugins from this list are missing, the action will
360             error out. If any other plugins not in this list are installed on the system,
361             they will be ignored.
362              
363             force_plugins = App::GitHooks::Plugin::ValidatePODFormat, App::GitHooks::Plugin::RequireCommitMessage
364              
365             =back
366              
367              
368             =head2 Testing-specific options
369              
370             =over 4
371              
372             =item * limit_plugins
373              
374             Deprecated. Use C instead.
375              
376             =item * force_interactive
377              
378             Force the application to consider that the terminal is interactive (`1`) or
379             non-interactive (`0`) independently of whether the actual STDOUT is interactive
380             or not.
381              
382             =item * force_use_colors
383              
384             Force the output to use colors (`1`) or to not use colors (`0`) independently
385             of the ability of STDOUT to display colors.
386              
387             =item * force_is_utf8
388              
389             Allows the output to use utf-8 characters (`1`) or not (`0`), independently of
390             whether the output declares supporting utf-8.
391              
392             =item * commit_msg_no_edit
393              
394             Allows skipping the loop to edit the message when the commit message checks
395             failed.
396              
397             =back
398              
399              
400             =head1 FUNCTIONS
401              
402             =head2 run()
403              
404             Run the specified hook.
405              
406             App::GitHooks::run(
407             name => $name,
408             arguments => \@arguments,
409             );
410              
411             Arguments:
412              
413             =over 4
414              
415             =item * name I<(mandatory)>
416              
417             The name of the git hook calling this class. See the "VALID GIT HOOK NAMES"
418             section for acceptable values.
419              
420             =item * arguments I<(optional)>
421              
422             An arrayref of arguments passed originally to the git hook.
423              
424             =item * exit I<(optional, default 1)>
425              
426             Indicate whether the method should exit (1) or simply return the exit status
427             without actually exiting (0).
428              
429             =back
430              
431             =cut
432              
433             sub run
434             {
435             my ( $class, %args ) = @_;
436             my $name = delete( $args{'name'} );
437             my $arguments = delete( $args{'arguments'} );
438             my $exit = delete( $args{'exit'} ) // 1;
439              
440             my $exit_code =
441             try
442             {
443             croak 'Invalid argument(s): ' . join( ', ', keys %args )
444             if scalar( keys %args ) != 0;
445              
446             # Clean up hook name in case we were passed a file path.
447             $name = File::Basename::fileparse( $name );
448              
449             # Validate hook name.
450             croak 'A hook name must be passed'
451             if !defined( $name );
452             croak "Invalid hook name $name"
453             if scalar( grep { $_ eq $name } @$HOOK_NAMES ) == 0;
454              
455             # Validate arguments.
456             croak 'Unknown argument(s): ' . join( ', ', keys %args )
457             if scalar( keys %args ) != 0;
458              
459             # Load the hook class.
460             my $hook_class = "App::GitHooks::Hook::" . _to_camelcase( $name );
461             Class::Load::load_class( $hook_class );
462              
463             # Create a new App instance to hold the various data.
464             my $self = $class->new(
465             arguments => $arguments,
466             name => $name,
467             );
468              
469             # Force the output to match the terminal encoding.
470             my $terminal = $self->get_terminal();
471             my $terminal_encoding = $terminal->get_encoding();
472             binmode( STDOUT, "encoding($terminal_encoding)" )
473             if $terminal->is_utf8();
474              
475             # Run the hook.
476             my $hook_exit_code = $hook_class->run(
477             app => $self,
478             );
479             croak "$hook_class ran successfully but did not return an exit code."
480             if !defined( $hook_exit_code );
481              
482             return $hook_exit_code;
483             }
484             catch
485             {
486             chomp( $_ );
487             print STDERR "Error detected in hook: >$_<.\n";
488             return $HOOK_EXIT_FAILURE;
489             };
490              
491             if ( $exit )
492             {
493             exit( $exit_code );
494             }
495             else
496             {
497             return $exit_code;
498             }
499             }
500              
501              
502             =head1 METHODS
503              
504             =head2 new()
505              
506             Create a new C object.
507              
508             my $app = App::GitHooks->new(
509             name => $name,
510             arguments => \@arguments,
511             );
512              
513             Arguments:
514              
515             =over 4
516              
517             =item * name I<(mandatory)>
518              
519             The name of the git hook calling this class. See the "VALID GIT HOOK NAMES"
520             section for acceptable values.
521              
522             =item * arguments I<(optional)>
523              
524             An arrayref of arguments passed originally to the git hook.
525              
526             =back
527              
528             =cut
529              
530             sub new
531             {
532             my ( $class, %args ) = @_;
533             my $name = delete( $args{'name'});
534             my $arguments = delete( $args{'arguments'} );
535              
536             # Defaults.
537             $arguments = []
538             if !defined( $arguments );
539              
540             # Check arguments.
541             croak "The 'argument' parameter must be an arrayref"
542             if !Data::Validate::Type::is_arrayref( $arguments );
543             croak "The argument 'name' is mandatory"
544             if !defined( $name );
545             croak "Invalid hook name $name"
546             if scalar( grep { $_ eq $name } @$HOOK_NAMES ) == 0;
547             croak 'The following argument(s) are not valid: ' . join( ', ', keys %args )
548             if scalar( keys %args ) != 0;
549              
550             # Create object.
551             my $self = bless(
552             {
553             plugins => undef,
554             force_non_interactive => 0,
555             terminal => App::GitHooks::Terminal->new(),
556             arguments => $arguments,
557             hook_name => $name,
558             repository => undef,
559             use_colors => 1,
560             },
561             $class,
562             );
563              
564             # Look up testing overrides.
565             my $config = $self->get_config();
566              
567             my $force_use_color = $config->get( 'testing', 'force_use_colors' );
568             $self->use_colors( $force_use_color )
569             if defined( $force_use_color );
570              
571             my $force_is_utf8 = $config->get( 'testing', 'force_is_utf8' );
572             $self->get_terminal()->is_utf8( $force_is_utf8 )
573             if defined( $force_is_utf8 );
574              
575             return $self;
576             }
577              
578              
579             =head2 clone()
580              
581             Clone the current object and override its properties with the arguments
582             specified.
583              
584             my $cloned_app = $app->clone(
585             name => $hook_name, # optional
586             );
587              
588             =over 4
589              
590             =item * name I<(optional)>
591              
592             The name of the invoking hook.
593              
594             =back
595              
596             =cut
597              
598             sub clone
599             {
600             my ( $self, %args ) = @_;
601             my $name = delete( $args{'name'} );
602             croak 'Invalid argument(s): ' . join( ', ', keys %args )
603             if scalar( keys %args ) != 0;
604              
605             # Clone the object.
606             my $cloned_app = Storable::dclone( $self );
607              
608             # Overrides.
609             if ( defined( $name ) )
610             {
611             croak "Invalid hook name $name"
612             if scalar( grep { $_ eq $name } @$HOOK_NAMES ) == 0;
613              
614             $cloned_app->{'hook_name'} = $name;
615             }
616              
617             return $cloned_app;
618             }
619              
620              
621             =head2 get_hook_plugins()
622              
623             Return an arrayref of all the plugins installed and available for a specific
624             git hook on the current system.
625              
626             my $plugins = $app->get_hook_plugins(
627             $hook_name
628             );
629              
630             Arguments:
631              
632             =over 4
633              
634             =item * $hook_name
635              
636             The name of the git hook for which to find available plugins.
637              
638             =back
639              
640             =cut
641              
642             sub get_hook_plugins
643             {
644             my ( $self, $hook_name ) = @_;
645              
646             # Check parameters.
647             croak "A git hook name is required"
648             if !defined( $hook_name );
649              
650             # Handle both - and _ in the hook name.
651             $hook_name =~ s/-/_/g;
652              
653             # Searching for plugins is expensive, so we cache it here.
654             $self->{'plugins'} = $self->get_all_plugins()
655             if !defined( $self->{'plugins'} );
656              
657             return $self->{'plugins'}->{ $hook_name } // [];
658             }
659              
660              
661             =head2 get_all_plugins()
662              
663             Return a hashref of the plugins available for every git hook.
664              
665             my $all_plugins = $self->get_all_plugins();
666              
667             =cut
668              
669             sub get_all_plugins
670             {
671             my ( $self ) = @_;
672             my $config = $self->get_config();
673              
674             # Find all available plugins regardless of the desired target hook, using
675             # Module::Pluggable.
676             my @discovered_plugins = __PACKAGE__->_search_plugins();
677              
678             # Warn about deprecated 'limit_plugins' config option.
679             my $limit_plugins = $config->get( 'testing', 'limit_plugins' );
680             if ( defined( $limit_plugins ) )
681             {
682             carp "The configuration option 'limit_plugins' under the [testing] section "
683             . "is deprecated, please switch to using 'force_plugins' under the general "
684             . "configuration section as soon as possible";
685             }
686              
687             # If the environment restricts the list of plugins to run, we use that.
688             # Otherwise, we exclude test plugins.
689             my $force_plugins = $config->get( '_', 'force_plugins' )
690             // $limit_plugins
691             // '';
692             my @plugins = ();
693             if ( $force_plugins =~ /\w/ )
694             {
695             my %forced_plugins =
696             map { $_ => 1 }
697             # Prepend App::GitHooks::Plugin:: to the plugin name if omitted.
698             map { $_ =~ /^App/ ? $_ : "App::GitHooks::Plugin::$_" }
699             # Split the comma-separated list.
700             split( /(?:\s+|\s*,\s*)/, $force_plugins );
701              
702             foreach my $plugin ( @discovered_plugins )
703             {
704             # Only add plugins listed in the config file.
705             next if !$forced_plugins{ $plugin };
706              
707             push( @plugins, $plugin );
708             delete( $forced_plugins{ $plugin } );
709             }
710              
711             # If plugins listed in the config file are not found on the system, don't
712             # continue.
713             if ( scalar( keys %forced_plugins ) != 0 )
714             {
715             croak sprintf(
716             "The following plugins must be installed on your system, per the "
717             . "'force_plugins' directive in your githooksrc config file: %s",
718             join( ', ', keys %forced_plugins ),
719             );
720             }
721             }
722             else
723             {
724             foreach my $plugin ( @discovered_plugins )
725             {
726             next if $plugin =~ /^\QApp::GitHooks::Plugin::Test::\E/x;
727             push( @plugins, $plugin );
728             }
729             }
730             #print STDERR Dumper( \@plugins );
731              
732             # Parse each plugin to find out which hook(s) they apply to.
733             my $all_plugins = {};
734             foreach my $plugin ( @plugins )
735             {
736             # Load the plugin class.
737             Class::Load::load_class( $plugin );
738              
739             # Store the list of plugins available for each hook.
740             my $hooks_declared;
741             foreach my $hook ( @{ $App::GitHooks::Plugin::SUPPORTED_SUBS } )
742             {
743             next if !$plugin->can( 'run_' . $hook );
744             $hooks_declared = 1;
745              
746             $all_plugins->{ $hook } //= [];
747             push( @{ $all_plugins->{ $hook } }, $plugin );
748             }
749              
750             # Alert if the plugin didn't declare any hook handling subroutines -
751             # that's probably the sign of a typo in a subroutine name.
752             carp "The plugin $plugin does not declare any hook handling subroutines, check for typos in sub names?"
753             if !$hooks_declared;
754             }
755              
756             return $all_plugins;
757             }
758              
759              
760             =head2 get_config()
761              
762             Retrieve the configuration information for the current project.
763              
764             my $config = $app->get_config();
765              
766             =cut
767              
768             sub get_config
769             {
770             my ( $self ) = @_;
771              
772             if ( !defined( $self->{'config'} ) )
773             {
774             my $config_file;
775             my $config_source;
776             # For testing purposes, provide a way to enforce a specific .githooksrc
777             # file regardless of how anything else is set up on the machine.
778             if ( defined( $ENV{'GITHOOKSRC_FORCE'} ) && ( -e $ENV{'GITHOOKSRC_FORCE'} ) )
779             {
780             $config_source = 'GITHOOKSRC_FORCE environment variable';
781             $config_file = $ENV{'GITHOOKSRC_FORCE'};
782             }
783             # First, use repository-specific githooksrc files.
784             elsif ( -e '.githooksrc' )
785             {
786             $config_source = '.githooksrc at the root of the repository';
787             $config_file = '.githooksrc';
788             }
789             # Fall back on the GITHOOKSRC variable.
790             elsif ( defined( $ENV{'GITHOOKSRC'} ) && ( -e $ENV{'GITHOOKSRC'} ) )
791             {
792             $config_source = 'GITHOOKSRC environment variable';
793             $config_file = $ENV{'GITHOOKSRC'};
794             }
795             # Fall back on the home directory of the user.
796             elsif ( defined( $ENV{'HOME'} ) && ( -e $ENV{'HOME'} . '/.githooksrc' ) )
797             {
798             $config_source = '.githooksrc in the home directory';
799             $config_file = $ENV{'HOME'} . '/.githooksrc';
800             }
801              
802             $self->{'config'} = App::GitHooks::Config->new(
803             defined( $config_file )
804             ? ( file => $config_file, source => $config_source )
805             : (),
806             );
807             }
808              
809             return $self->{'config'};
810             }
811              
812              
813             =head2 force_non_interactive()
814              
815             By default C detects whether it is running in interactive mode,
816             but this allows forcing it to run in non-interactive mode.
817              
818             # Retrieve the current setting.
819             my $force_non_interactive = $app->force_non_interactive();
820              
821             # Force non-interactive mode.
822             $app->force_non_interactive( 1 );
823              
824             # Go back to the default behavior of detecting the current mode.
825             $app->force_non_interactive( 0 );
826              
827             =cut
828              
829             sub force_non_interactive
830             {
831             my ( $self, $value ) = @_;
832              
833             if ( defined( $value ) )
834             {
835             if ( $value =~ /^(?:0|1)$/ )
836             {
837             $self->{'force_non_interactive'} = $value;
838             }
839             else
840             {
841             croak 'Invalid argument';
842             }
843             }
844              
845             return $self->{'force_non_interactive'};
846             }
847              
848              
849             =head2 get_failure_character()
850              
851             Return a character to use to indicate a failure.
852              
853             my $failure_character = $app->get_failure_character()
854              
855             =cut
856              
857             sub get_failure_character
858             {
859             my ( $self ) = @_;
860              
861             return $self->get_terminal()->is_utf8()
862             ? "\x{00D7}"
863             : "x";
864             }
865              
866              
867             =head2 get_success_character()
868              
869             Return a character to use to indicate a success.
870              
871             my $success_character = $app->get_success_character()
872              
873             =cut
874              
875             sub get_success_character
876             {
877             my ( $self ) = @_;
878              
879             return $self->get_terminal()->is_utf8()
880             ? "\x{2713}"
881             : "o";
882             }
883              
884              
885             =head2 get_warning_character()
886              
887             Return a character to use to indicate a warning.
888              
889             my $warning_character = $app->get_warning_character()
890              
891             =cut
892              
893             sub get_warning_character
894             {
895             my ( $self ) = @_;
896              
897             return $self->get_terminal()->is_utf8()
898             ? "\x{26A0}"
899             : "!";
900             }
901              
902              
903             =head2 get_staged_changes()
904              
905             Return a C object corresponding to the changes
906             staged in the current project.
907              
908             my $staged_changes = $app->get_staged_changes();
909              
910             =cut
911              
912             sub get_staged_changes
913             {
914             my ( $self ) = @_;
915              
916             if ( !defined( $self->{'staged_changes'} ) )
917             {
918             $self->{'staged_changes'} = App::GitHooks::StagedChanges->new(
919             app => $self,
920             );
921             }
922              
923             return $self->{'staged_changes'};
924             }
925              
926              
927             =head2 use_colors()
928              
929             Allows disabling the use of colors in C's output.
930              
931             # Retrieve the current setting.
932             my $use_colors = $app->use_colors();
933              
934             # Disable colors in the output.
935             $app->use_colors( 0 );
936              
937             =cut
938              
939             sub use_colors
940             {
941             my ( $self, $value ) = @_;
942              
943             if ( defined( $value ) )
944             {
945             $self->{'use_colors'} = $value;
946             }
947              
948             return $self->{'use_colors'};
949             }
950              
951              
952             =head1 ACCESSORS
953              
954             =head2 get_repository()
955              
956             Return the underlying C object for the current project.
957              
958             my $repository = $app->get_repository();
959              
960             =cut
961              
962             sub get_repository
963             {
964             my ( $self ) = @_;
965              
966             $self->{'repository'} //= Git::Repository->new();
967              
968             return $self->{'repository'};
969             }
970              
971              
972             =head2 get_hook_name
973              
974             Return the name of the git hook that called the current instance.
975              
976             my $hook_name = $app->get_hook_name();
977              
978             =cut
979              
980             sub get_hook_name
981             {
982             my ( $self ) = @_;
983              
984             return $self->{'hook_name'};
985             }
986              
987              
988             =head2 get_command_line_arguments()
989              
990             Return the arguments passed originally to the git hook.
991              
992             my $command_line_arguments = $app->get_command_line_arguments();
993              
994             =cut
995              
996             sub get_command_line_arguments
997             {
998             my ( $self ) = @_;
999              
1000             return $self->{'arguments'} // [];
1001             }
1002              
1003              
1004             =head2 get_terminal()
1005              
1006             Return the C object associated with the current
1007             instance.
1008              
1009             my $terminal = $app->get_terminal();
1010              
1011             =cut
1012              
1013             sub get_terminal
1014             {
1015             my ( $self ) = @_;
1016              
1017             return $self->{'terminal'};
1018             }
1019              
1020              
1021             =head1 DISPLAY METHODS
1022              
1023             =head2 wrap()
1024              
1025             Format information while respecting the format width and indentation.
1026              
1027             my $string = $app->wrap( $information, $indent );
1028              
1029             =cut
1030              
1031             sub wrap
1032             {
1033             my ( $self, $information, $indent ) = @_;
1034             $indent //= '';
1035              
1036             return
1037             if !defined( $information );
1038              
1039             my $terminal_width = $self->get_terminal()->get_width();
1040             if ( defined( $terminal_width ) )
1041             {
1042             local $Text::Wrap::columns = $terminal_width; ## no critic (Variables::ProhibitPackageVars)
1043              
1044             return Text::Wrap::wrap(
1045             $indent,
1046             $indent,
1047             $information,
1048             );
1049             }
1050             else
1051             {
1052              
1053             return join(
1054             "\n",
1055             map
1056             { defined( $_ ) && $_ ne '' ? $indent . $_ : $_ } # Don't indent blank lines.
1057             split( /\n/, $information, -1 ) # Keep trailing \n's.
1058             );
1059             }
1060             }
1061              
1062              
1063             =head2 color()
1064              
1065             Print text with colors.
1066              
1067             $app->color( $color, $text );
1068              
1069             =cut
1070              
1071             sub color
1072             {
1073             my ( $self, $color, $string ) = @_;
1074              
1075             return $self->use_colors()
1076             ? Term::ANSIColor::colored( [ $color ], $string )
1077             : $string;
1078             }
1079              
1080              
1081             =head1 PRIVATE FUNCTIONS
1082              
1083             =head2 _to_camelcase()
1084              
1085             Convert a dash-separated string to camelcase.
1086              
1087             my $camelcase_string = App::GitHooks::_to_camelcase( $string );
1088              
1089             This function is useful to convert git hook names (commit-msg) to module names
1090             (CommitMsg).
1091              
1092             =cut
1093              
1094             sub _to_camelcase
1095             {
1096             my ( $name ) = @_;
1097              
1098             $name =~ s/-(.)/\U$1/g;
1099             $name = ucfirst( $name );
1100              
1101             return $name;
1102             }
1103              
1104              
1105             =head1 NOTES
1106              
1107             =head2 Manual installation
1108              
1109             Symlink your git hooks under .git/hooks to a file with the following content:
1110              
1111             #!/usr/bin/env perl
1112              
1113             use strict;
1114             use warnings;
1115              
1116             use App::GitHooks;
1117              
1118             App::GitHooks->run(
1119             name => $0,
1120             arguments => \@ARGV,
1121             );
1122              
1123             All you need to do then is install the plugins you are interested in!
1124              
1125             This distribution also includes a C directory that you can symlink /
1126             copy to C<.git/hooks/> instead , to get all the hooks set up properly in one
1127             swoop.
1128              
1129             Important: adjust C as needed, if that line is not a valid
1130             interpreter, your git actions will fail with C
1131             .git/hooks/[hook name]: No such file or directory>.
1132              
1133              
1134             =head1 BUGS
1135              
1136             Please report any bugs or feature requests through the web interface at
1137             L.
1138             I will be notified, and then you'll automatically be notified of progress on
1139             your bug as I make changes.
1140              
1141              
1142             =head1 SUPPORT
1143              
1144             You can find documentation for this module with the perldoc command.
1145              
1146             perldoc App::GitHooks
1147              
1148              
1149             You can also look for information at:
1150              
1151             =over
1152              
1153             =item * GitHub's request tracker
1154              
1155             L
1156              
1157             =item * AnnoCPAN: Annotated CPAN documentation
1158              
1159             L
1160              
1161             =item * CPAN Ratings
1162              
1163             L
1164              
1165             =item * MetaCPAN
1166              
1167             L
1168              
1169             =back
1170              
1171              
1172             =head1 AUTHOR
1173              
1174             L,
1175             C<< >>.
1176              
1177              
1178             =head1 COPYRIGHT & LICENSE
1179              
1180             Copyright 2013-2015 Guillaume Aubert.
1181              
1182             This program is free software: you can redistribute it and/or modify it under
1183             the terms of the GNU General Public License version 3 as published by the Free
1184             Software Foundation.
1185              
1186             This program is distributed in the hope that it will be useful, but WITHOUT ANY
1187             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
1188             PARTICULAR PURPOSE. See the GNU General Public License for more details.
1189              
1190             You should have received a copy of the GNU General Public License along with
1191             this program. If not, see http://www.gnu.org/licenses/
1192              
1193             =cut
1194              
1195             1;