File Coverage

blib/lib/App/GitHooks/Utils.pm
Criterion Covered Total %
statement 53 54 98.1
branch 12 16 75.0
condition 10 16 62.5
subroutine 10 11 90.9
pod 4 4 100.0
total 89 101 88.1


line stmt bran cond sub pod time code
1             package App::GitHooks::Utils;
2              
3 21     21   26503 use strict;
  21         32  
  21         612  
4 21     21   82 use warnings;
  21         33  
  21         572  
5              
6             # External dependencies.
7 21     21   83 use Carp;
  21         31  
  21         1175  
8 21     21   839 use File::Spec;
  21         230  
  21         476  
9 21     21   5330 use Try::Tiny;
  21         20102  
  21         15396  
10              
11             # No internal dependencies - keep this as a leaf module in the graph so that we
12             # can include it everywhere.
13              
14              
15             =head1 NAME
16              
17             App::GitHooks::Utils - Support functions for App::GitHooks and its plugins.
18              
19              
20             =head1 VERSION
21              
22             Version 1.9.0
23              
24             =cut
25              
26             our $VERSION = '1.9.0';
27              
28              
29             =head1 FUNCTIONS
30              
31             =head2 get_project_prefixes()
32              
33             Get an arrayref of valid project prefixes.
34              
35             my $project_prefixes = App::GitHooks::Utils::get_project_prefixes( $app );
36              
37             Arguments:
38              
39             =over 4
40              
41             =item * $app
42              
43             An C<App::GitHooks> instance.
44              
45             =back
46              
47             =cut
48              
49             sub get_project_prefixes
50             {
51 13     13 1 177 my ( $app ) = @_;
52 13   100     66 my $config_line = $app->get_config()->{'_'}->{'project_prefixes'} // '';
53              
54             # Strip leading/trailing whitespace.
55 13         95 $config_line =~ s/(?:^\s+|\s+$)//g;
56              
57 13         122 return [ split( /\s*[, ]\s*/, $config_line ) ];
58             }
59              
60              
61             =head2 get_project_prefix_regex()
62              
63             Return a non-capturing regex that will match all the valid project prefixes.
64              
65             my $project_prefix_regex = App::GitHooks::Utils::get_project_prefix_regex( $app );
66              
67             Arguments:
68              
69             =over 4
70              
71             =item * $app
72              
73             An C<App::GitHooks> instance.
74              
75             =back
76              
77             =cut
78              
79             sub get_project_prefix_regex
80             {
81 9     9 1 223 my ( $app ) = @_;
82              
83 9         40 my $prefixes = get_project_prefixes( $app );
84              
85 9 100       55 if ( scalar( @$prefixes ) == 0 )
    100          
86             {
87 3         14 return '';
88             }
89             elsif ( scalar( @$prefixes ) == 1 )
90             {
91 1         9 return $prefixes->[0];
92             }
93             else
94             {
95 5         39 return '(?:' . join( '|', @$prefixes ) . ')';
96             }
97             }
98              
99              
100             =head2 get_ticket_id_from_commit_regex()
101              
102             Return a regex that will extract a ticket ID from a commit message, if it
103             exists.
104              
105             my $ticket_id_regex = App::GitHooks::Utils::get_ticket_id_from_commit_regex( $app );
106              
107             Arguments:
108              
109             =over 4
110              
111             =item * $app
112              
113             An C<App::GitHooks> instance.
114              
115             =back
116              
117             =cut
118              
119             sub get_ticket_id_from_commit_regex
120             {
121 2     2 1 84 my ( $app ) = @_;
122 2         9 my $config = $app->get_config();
123              
124             # Retrieve the regular expression from the config or use a default.
125 2   100     6 my $ticket_regex = $app->get_config()->get_regex( '_', 'extract_ticket_id_from_commit' )
126             // '^($project_prefixes-\d+|--)\: ?';
127              
128             # Replace the list of project prefixes if it is mentioned in the regex.
129 2         7 my $project_prefix_regex = get_project_prefix_regex( $app );
130 2         12 $ticket_regex =~ s/\$project_prefixes/$project_prefix_regex/g;
131              
132 2         12 return $ticket_regex;
133             }
134              
135              
136             =head2 get_ticket_id_from_branch_name()
137              
138             Return the ticket ID derived from the name of the current branch for this
139             repository.
140              
141             my $ticket_id = App::GitHooks::Utils::get_ticket_id_from_branch_name( $app );
142              
143             Arguments:
144              
145             =over 4
146              
147             =item * $app
148              
149             An C<App::GitHooks> instance.
150              
151             =back
152              
153             =cut
154              
155             sub get_ticket_id_from_branch_name
156             {
157 4     4 1 148 my ( $app ) = @_;
158 4         20 my $repository = $app->get_repository();
159 4         34 my $config = $app->get_config();
160              
161             # If the config doesn't specify a way to extract the ticket ID from the
162             # branch, there's nothing we can do here.
163 4         27 my $ticket_regex = $config->get_regex( '_', 'extract_ticket_id_from_branch' );
164             return undef
165 4 100       31 if !defined( $ticket_regex );
166              
167             # Check if we're in a rebase. During a rebase (regardless of whether it's
168             # interractive or not), the HEAD goes in a detached state, and we won't be
169             # able to call symbolic-ref on it to get a branch name.
170 3         22 my $git_directory = $repository->git_dir();
171             return undef
172 3 50 33     228 if ( -d File::Spec->catfile( $git_directory, 'rebase-merge' ) ) # detect rebase -i
173             || ( -d File::Spec->catfile( $git_directory, 'rebase-apply' ) ); # detect rebase
174              
175 3         11 my $ticket_id;
176             try
177             {
178              
179             # Retrieve the branch name.
180 3     3   294 my $branch_name = $repository->run('symbolic-ref', 'HEAD');
181 3         41034 my ( $branch_name_without_prefixes ) = $branch_name =~ /([^\/]+)$/;
182              
183             # Extract the ticket ID from the branch name.
184 3         22 my $project_prefix_regex = get_project_prefix_regex( $app );
185 3         9 $ticket_regex =~ s/\$project_prefixes/$project_prefix_regex/g;
186 3         80 ( $ticket_id ) = $branch_name_without_prefixes =~ /$ticket_regex/i;
187              
188 3         13 my $normalize = $config->get( '_', 'normalize_branch_ticket_id' );
189 3 100 66     32 if ( defined( $ticket_id ) && defined( $normalize ) && ( $normalize =~ /\S/ ) )
      66        
190             {
191 1         13 my ( $match, $replacement ) = $normalize =~ m|^\s*s/(.*?)(?<!\\)/(.*)/\s*|x;
192 1 50 33     13 croak "Invalid format for 'normalize_branch_ticket_id' in configuration file."
193             if !defined( $match ) || !defined( $replacement );
194 1 50       7 croak "Unsafe matching pattern in 'normalize_branch_ticket_id', escape your slashes"
195             if $match =~ /(?<!\\)\//;
196 1 50       8 croak "Unsafe replacement pattern in 'normalize_branch_ticket_id', escape your slashes"
197             if $replacement =~ /(?<!\\)\//;
198 1         141 eval( "\$ticket_id =~ s/$match/$replacement/i" ); ## no critic (BuiltinFunctions::ProhibitStringyEval, ErrorHandling::RequireCheckingReturnValueOfEval)
199             }
200             }
201             catch
202             {
203 0     0   0 carp "ERROR: $_";
204 3         67 };
205              
206 3         183 return $ticket_id;
207             }
208              
209              
210             =head1 BUGS
211              
212             Please report any bugs or feature requests through the web interface at
213             L<https://github.com/guillaumeaubert/App-GitHooks/issues/new>.
214             I will be notified, and then you'll automatically be notified of progress on
215             your bug as I make changes.
216              
217              
218             =head1 SUPPORT
219              
220             You can find documentation for this module with the perldoc command.
221              
222             perldoc App::GitHooks::Utils
223              
224              
225             You can also look for information at:
226              
227             =over
228              
229             =item * GitHub's request tracker
230              
231             L<https://github.com/guillaumeaubert/App-GitHooks/issues>
232              
233             =item * AnnoCPAN: Annotated CPAN documentation
234              
235             L<http://annocpan.org/dist/app-githooks>
236              
237             =item * CPAN Ratings
238              
239             L<http://cpanratings.perl.org/d/app-githooks>
240              
241             =item * MetaCPAN
242              
243             L<https://metacpan.org/release/App-GitHooks>
244              
245             =back
246              
247              
248             =head1 AUTHOR
249              
250             L<Guillaume Aubert|https://metacpan.org/author/AUBERTG>,
251             C<< <aubertg at cpan.org> >>.
252              
253              
254             =head1 COPYRIGHT & LICENSE
255              
256             Copyright 2013-2017 Guillaume Aubert.
257              
258             This code is free software; you can redistribute it and/or modify it under the
259             same terms as Perl 5 itself.
260              
261             This program is distributed in the hope that it will be useful, but WITHOUT ANY
262             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
263             PARTICULAR PURPOSE. See the LICENSE file for more details.
264              
265             =cut
266              
267             1;