File Coverage

blib/lib/App/GitHooks/Plugin/VersionTagsRequireChangelog.pm
Criterion Covered Total %
statement 21 67 31.3
branch 0 12 0.0
condition 0 2 0.0
subroutine 7 12 58.3
pod 3 3 100.0
total 31 96 32.2


line stmt bran cond sub pod time code
1             package App::GitHooks::Plugin::VersionTagsRequireChangelog;
2              
3 1     1   25032 use strict;
  1         2  
  1         38  
4 1     1   6 use warnings;
  1         2  
  1         47  
5              
6 1     1   6 use base 'App::GitHooks::Plugin';
  1         8  
  1         732  
7              
8             # External dependencies.
9 1     1   1191 use CPAN::Changes;
  1         25502  
  1         53  
10 1     1   721 use Log::Any qw($log);
  1         12524  
  1         8  
11 1     1   3552 use Try::Tiny;
  1         2628  
  1         84  
12              
13             # Internal dependencies.
14 1     1   706 use App::GitHooks::Constants qw( :PLUGIN_RETURN_CODES );
  1         6329  
  1         1052  
15              
16             # Uncomment to see debug information.
17             #use Log::Any::Adapter ('Stderr');
18              
19              
20             =head1 NAME
21              
22             App::GitHooks::Plugin::VersionTagsRequireChangelog - Require git version tags to have a matching changelog entry.
23              
24              
25             =head1 DESCRIPTION
26              
27             This is a companion plugin for L.
28             C simply skips git version tags without a matching entry
29             in the changelog file, and this plugin allows you to force git version tags to
30             have a matching entry in the changelog file.
31              
32             For example, you cannot do this:
33              
34             git tag v1.0.0
35             git push origin v1.0.0
36              
37             Unless your changelog file has a release entry for C.
38              
39              
40             =head1 VERSION
41              
42             Version 1.1.0
43              
44             =cut
45              
46             our $VERSION = '1.1.0';
47              
48              
49             =head1 CONFIGURATION OPTIONS
50              
51             This plugin supports the following options in the
52             C<[VersionTagsRequireChangelog]> section of your C<.githooksrc> file.
53              
54             [VersionTagsRequireChangelog]
55             changelog_path = Changes
56              
57              
58             =head2 changelog_path
59              
60             The path to the changelog file, relative to the root of the repository.
61              
62             For example, if the changelog file is named C and lives at the root of
63             your repository:
64              
65             changelog_path = Changes
66              
67              
68             =head1 METHODS
69              
70             =head2 run_pre_push()
71              
72             Code to execute as part of the pre-push hook.
73              
74             my $plugin_return_code = App::GitHooks::Plugin::VersionTagsRequireChangelog->run_pre_push(
75             app => $app,
76             stdin => $stdin,
77             );
78              
79             Arguments:
80              
81             =over 4
82              
83             =item * $app I<(mandatory)>
84              
85             An C object.
86              
87             =item * $stdin I<(mandatory)>
88              
89             The content provided by git on stdin, corresponding to a list of references
90             being pushed.
91              
92             =back
93              
94             =cut
95              
96             sub run_pre_push
97             {
98 0     0 1   my ( $class, %args ) = @_;
99 0           my $app = delete( $args{'app'} );
100 0           my $stdin = delete( $args{'stdin'} );
101              
102 0           $log->info( 'Entering VersionTagsRequireChangelog.' );
103              
104 0           my $config = $app->get_config();
105 0           my $repository = $app->get_repository();
106              
107             # Check if we are pushing any tags.
108 0           my @tags = get_pushed_tags( $app, $stdin );
109 0           $log->infof( "Found %s tag(s) to push.", scalar( @tags ) );
110 0 0         if ( scalar( @tags ) == 0 )
111             {
112 0           $log->info( "No tags were found in the list of references to push." );
113 0           return $PLUGIN_RETURN_SKIPPED;
114             }
115              
116             # Get the list of releases in the changelog.
117 0           my $releases = get_changelog_releases( $app );
118              
119             # Find tags without release notes.
120             my @missing_changelog_tags =
121 0           grep { !defined( $releases->{ $_ } ) }
  0            
122             @tags;
123              
124 0 0         if ( scalar( @missing_changelog_tags ) != 0 )
125             {
126 0 0         die sprintf(
    0          
127             'You are trying to push the following %s, but %s missing from the changelog: %s.',
128             scalar( @missing_changelog_tags ) == 1 ? 'tag' : 'tags',
129             scalar( @missing_changelog_tags ) == 1 ? 'it is' : 'they are',
130             join( ', ', @missing_changelog_tags ),
131             ) . "\n";
132             }
133              
134 0           return $PLUGIN_RETURN_PASSED;
135             }
136              
137              
138             =head1 FUNCTIONS
139              
140             =head2 get_pushed_tags()
141              
142             Retrieve a list of the tags being pushed with C.
143              
144             my $tags = App::GitHooks::Plugin::VersionTagsRequireChangelog::get_pushed_tags(
145             $app,
146             $stdin,
147             );
148              
149             Arguments:
150              
151             =over 4
152              
153             =item * $app I<(mandatory)>
154              
155             An C object.
156              
157             =item * $stdin I<(mandatory)>
158              
159             The content provided by git on stdin, corresponding to a list of references
160             being pushed.
161              
162             =back
163              
164             =cut
165              
166             sub get_pushed_tags
167             {
168 0     0 1   my ( $app, $stdin ) = @_;
169 0           my $config = $app->get_config();
170              
171             # Tag pattern.
172 0   0       my $version_tag_regex = $config->get_regex( 'VersionTagsRequireChangelog', 'version_tag_regex' )
173             // '(v\d+\.\d+\.\d+)';
174 0           $log->infof( "Using git tag regex '%s'.", $version_tag_regex );
175              
176             # Analyze each reference being pushed.
177 0           my $tags = {};
178 0           foreach my $line ( @$stdin )
179             {
180 0           chomp( $line );
181 0           $log->debugf( 'Parse STDIN line >%s<.', $line );
182              
183             # Extract the tag information.
184 0           my ( $tag ) = ( $line =~ /^refs\/tags\/$version_tag_regex\b/x );
185 0 0         next if !defined( $tag );
186 0           $log->infof( "Found tag '%s'.", $tag );
187 0           $tags->{ $tag } = 1;
188             }
189              
190 0           return keys %$tags;
191             }
192              
193              
194             =head2 get_changelog_releases()
195              
196             Retrieve a hashref of all the releases in the changelog file.
197              
198             my $releases = App::GitHooks::Plugin::VersionTagsRequireChangelog::get_changelog_releases(
199             $app,
200             );
201              
202             Arguments:
203              
204             =over 4
205              
206             =item * $app I<(mandatory)>
207              
208             An C object.
209              
210             =back
211              
212             =cut
213              
214             sub get_changelog_releases
215             {
216 0     0 1   my ( $app ) = @_;
217 0           my $repository = $app->get_repository();
218 0           my $config = $app->get_config();
219              
220             # Make sure the changelog file exists.
221 0           my $changelog_path = $config->get( 'VersionTagsRequireChangelog', 'changelog_path' );
222 0           $changelog_path = $repository->work_tree() . '/' . $changelog_path;
223 0 0         die "The changelog '$changelog_path' specified in your .githooksrc config does not exist in the repository\n"
224             if ! -e $changelog_path;
225 0           $log->infof( "Using changelog '%s'.", $changelog_path );
226              
227             # Read the changelog.
228             my $changes =
229             try
230             {
231 0     0     return CPAN::Changes->load( $changelog_path );
232             }
233             catch
234             {
235 0     0     $log->error( "Unable to parse the change log" );
236 0           die "Unable to parse the change log\n";
237 0           };
238 0           $log->info( 'Successfully parsed the change log file.' );
239              
240             # Organize the releases into a hash for easy lookup.
241             my $releases =
242             {
243 0           map { $_->version() => $_ }
  0            
244             $changes->releases()
245             };
246 0           $log->infof( "Found %s release(s) in the changelog file.", scalar( keys %$releases ) );
247              
248 0           return $releases;
249             }
250              
251              
252             =head1 BUGS
253              
254             Please report any bugs or feature requests through the web interface at
255             L.
256             I will be notified, and then you'll automatically be notified of progress on
257             your bug as I make changes.
258              
259              
260             =head1 SUPPORT
261              
262             You can find documentation for this module with the perldoc command.
263              
264             perldoc App::GitHooks::Plugin::VersionTagsRequireChangelog
265              
266              
267             You can also look for information at:
268              
269             =over
270              
271             =item * GitHub's request tracker
272              
273             L
274              
275             =item * AnnoCPAN: Annotated CPAN documentation
276              
277             L
278              
279             =item * CPAN Ratings
280              
281             L
282              
283             =item * MetaCPAN
284              
285             L
286              
287             =back
288              
289              
290             =head1 AUTHOR
291              
292             L,
293             C<< >>.
294              
295              
296             =head1 COPYRIGHT & LICENSE
297              
298             Copyright 2015-2017 Guillaume Aubert.
299              
300             This code is free software; you can redistribute it and/or modify it under the
301             same terms as Perl 5 itself.
302              
303             This program is distributed in the hope that it will be useful, but WITHOUT ANY
304             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
305             PARTICULAR PURPOSE. See the LICENSE file for more details.
306              
307             =cut
308              
309             1;