File Coverage

blib/lib/Module/Release/Git.pm
Criterion Covered Total %
statement 67 93 72.0
branch 13 22 59.0
condition 3 11 27.2
subroutine 14 18 77.7
pod 10 10 100.0
total 107 154 69.4


line stmt bran cond sub pod time code
1 6     6   5372526 use v5.10;
  6         41  
2              
3             package Module::Release::Git;
4              
5 6     6   36 use strict;
  6         28  
  6         189  
6 6     6   58 use warnings;
  6         21  
  6         548  
7 6     6   43 use Exporter qw(import);
  6         19  
  6         1739  
8              
9             our @EXPORT = qw(
10             check_vcs
11             get_recent_contributors
12             get_vcs_tag_format
13             is_allowed_branch
14             make_vcs_tag
15             vcs_branch
16             vcs_commit_message
17             vcs_commit_message_template
18             vcs_exit
19             vcs_tag
20             );
21              
22             our $VERSION = '1.019';
23              
24             =encoding utf8
25              
26             =head1 NAME
27              
28             Module::Release::Git - Use Git with Module::Release
29              
30             =head1 SYNOPSIS
31              
32             The release script automatically loads this module if it sees a
33             F<.git> directory. The module exports C, C, and
34             C.
35              
36             For git status checks, you can ignore untracked files by setting
37             C in F<.releaserc>:
38              
39             # .releaserc
40             ignore_untracked 1
41              
42             =head1 DESCRIPTION
43              
44             Module::Release::Git subclasses Module::Release, and provides
45             its own implementations of the C and C methods
46             that are suitable for use with a Git repository.
47              
48             These methods are B exported in to the callers namespace
49             using Exporter.
50              
51             This module depends on the external git binary (so far).
52              
53             =over 4
54              
55             =item check_vcs()
56              
57             Check the state of the Git repository. If you set the C
58             config to a true value, B will not complain about untracked files.
59              
60             =cut
61              
62             sub _get_time {
63 0     0   0 my( $self ) = @_;
64 0         0 require POSIX;
65 0         0 POSIX::strftime( '%Y%m%d%H%M%S', localtime );
66             }
67              
68             sub _git_status_command {
69 6     6   11 my $self = shift;
70 6 50       20 my $opt = $self->config->ignore_untracked ? '-uno' : '';
71 6         264 return "git status -s $opt 2>&1";
72             }
73              
74             sub check_vcs {
75 6     6 1 1180117 my $self = shift;
76              
77 6         33 $self->_print( "Checking state of Git... " );
78              
79 6         39 my $command = _git_status_command($self);
80 6         22 my $git_status = $self->run( $command );
81              
82 6     6   1144 no warnings 'uninitialized';
  6         13  
  6         7338  
83              
84 6         36 my $branch = $self->vcs_branch;
85              
86 6         14 my $up_to_date = ($git_status eq '');
87              
88 6 100       25 $self->_die( "\nERROR: Git is not up-to-date: Can't release files\n\n$git_status\n" )
89             unless $up_to_date;
90              
91 2         15 $self->_print( "Git up-to-date on branch $branch\n" );
92              
93 2         8 return 1;
94             }
95              
96             =item get_recent_contributors()
97              
98             Return a list of contributors since last release.
99              
100             =cut
101              
102             sub get_recent_contributors {
103 0     0 1 0 my $self = shift;
104              
105 0         0 chomp( my $last_tagged_commit = $self->run("git rev-list --tags --max-count=1") );
106 0         0 chomp( my @commits_from_last_tag = split /\R/, $self->run("git rev-list $last_tagged_commit..HEAD") );
107              
108             my @authors_since_last_tag =
109 0         0 map { qx{git show --no-patch --pretty=format:'%an <%ae>' $_} }
  0         0  
110             @commits_from_last_tag;
111 0         0 my %authors = map { $_, 1 } @authors_since_last_tag;
  0         0  
112 0         0 my @authors = sort keys %authors;
113              
114 0         0 return @authors;
115             }
116              
117             =item is_allowed_branch
118              
119             Returns true if the current branch is allowed to release.
120              
121             1. Look at the config for C. That's a comma-separated
122             list of allowed branch names. If the current branch is exactly any of
123             those, return true. Or, keep trying.
124              
125             2. Look at the config for C. If the current
126             branch matches that Perl pattern, return true. Or, keep trying.
127              
128             3. If the current branch is exactly C or C
, return true.
129              
130             4. Or, return false.
131              
132             =cut
133              
134             sub is_allowed_branch {
135 17     17 1 83492 my( $self ) = @_;
136 17         55 my $branch = $self->vcs_branch;
137              
138 17         68 return do {
139 17 100 0     85 if( $self->config->allowed_branches ) {
    50          
    0          
140 9         171 my $s = $self->config->allowed_branches;
141 9         432 scalar grep { $_ eq $branch } split /\s*,\s*/, $s;
  27         93  
142             }
143             elsif( $self->config->allowed_branches_regex ) {
144 8         372 my $re = eval { my $r = $self->config->allowed_branches_regex; qr/$r/ };
  8         28  
  8         231  
145 8         129 $branch =~ m/$re/;
146             }
147 0         0 elsif( $branch eq 'master' or $branch eq 'main' ) { 1 }
148 0         0 else { 0 }
149             };
150             }
151              
152             =item get_vcs_tag_format
153              
154             Return the tag format. It's a sprintf-like syntax, but with one format:
155              
156             %v replace with the full version
157              
158             If you've set C<> in the configuration, it uses that. Otherwise it
159             returns C.
160              
161             =cut
162              
163             sub get_vcs_tag_format {
164 9     9 1 46 my( $self ) = @_;
165 9 50       36 $self->config->git_default_tag || 'release-%v'
166             }
167              
168             =item make_vcs_tag
169              
170             By default, examines the name of the remote file
171             (i.e. F) and constructs a tag string like
172             C from it. Override this method if you want to use a
173             different tagging scheme, or don't even call it.
174              
175             =cut
176              
177             sub make_vcs_tag {
178 9     9 1 10242 my( $self, $tag_format ) = @_;
179 9 50       43 $tag_format = defined $tag_format ? $tag_format : $self->get_vcs_tag_format;
180              
181 9         272 my $version = eval { $self->dist_version };
  9         96  
182 9         45 my $err = $@;
183 9 100       25 unless( defined $version ) {
184 3         20 $self->_warn( "Could not get version [$err]" );
185 3         81 $version = $self->_get_time;
186             }
187              
188 9         62 $tag_format =~ s/%v/$version/e;
  9         27  
189              
190 9         61 return $tag_format;
191             }
192              
193             =item vcs_branch()
194              
195             Return the current branch name.
196              
197             =cut
198              
199             sub vcs_branch {
200 8     8 1 12947 state $branch;
201 8 100       32 return $branch if $branch;
202              
203 5         23 my( $self ) = @_;
204 5         39 ( $branch ) = $self->run('git rev-parse --abbrev-ref HEAD');
205 6     6   51 no warnings qw(uninitialized);
  6         13  
  6         4578  
206 5         24 chomp( $branch );
207 5         25 $branch;
208             }
209              
210             =item vcs_commit_message_template()
211              
212             Returns the config for C, or the default C<'* for version %s'>.
213             This is a C ready string. The first argument to C
214             is the release version.
215              
216             =cut
217              
218             sub vcs_commit_message_template {
219 1     1 1 6342 my( $self ) = @_;
220 1   50     6 $self->config->commit_message_format // '* for version %s'
221             }
222              
223             =item vcs_commit_message()
224              
225             Returns the commit message, using C as the
226             format.
227              
228             =cut
229              
230             sub vcs_commit_message {
231 0     0 1 0 my( $self, $args ) = @_;
232 0         0 my $template = $self->vcs_commit_message_template;
233 0         0 sprintf $template, $args->{version};
234             }
235              
236             =item vcs_exit
237              
238             Perform repo tasks post-release. This one pushes origin to master
239             and pushes tags.
240              
241             =cut
242              
243             sub vcs_exit {
244 0     0 1 0 my( $self, $tag ) = @_;
245              
246 0   0     0 $tag ||= $self->make_vcs_tag;
247              
248 0         0 $self->_print( "Cleaning up git\n" );
249              
250 0 0       0 return 0 unless defined $tag;
251              
252 0         0 $self->_print( "Pushing to origin\n" );
253 0         0 $self->run( "git push origin master" );
254              
255 0         0 $self->_print( "Pushing tags\n" );
256 0         0 $self->run( "git push --tags" );
257              
258 0         0 return 1;
259             }
260              
261             =item vcs_tag(TAG)
262              
263             Tag the release in local Git, using the value from C.
264              
265             =cut
266              
267             sub vcs_tag {
268 5     5 1 17 my( $self, $tag ) = @_;
269              
270 5   66     28 $tag ||= $self->make_vcs_tag;
271              
272 5         24 $self->_print( "Tagging release with $tag\n" );
273              
274 5 50       21 return 0 unless defined $tag;
275              
276 5         21 $self->run( "git tag $tag" );
277              
278 5         36 return 1;
279             }
280              
281             =back
282              
283             =head1 TO DO
284              
285             =over 4
286              
287             =item Use Gitlib.pm whenever it exists
288              
289             =item More options for tagging
290              
291             =back
292              
293             =head1 SEE ALSO
294              
295             L, L
296              
297             =head1 SOURCE AVAILABILITY
298              
299             This module is in Github:
300              
301             https://github.com/briandfoy/module-release-git
302              
303             =head1 AUTHOR
304              
305             brian d foy,
306              
307             =head1 COPYRIGHT AND LICENSE
308              
309             Copyright © 2007-2025, brian d foy . All rights reserved.
310              
311             You may redistribute this under the same terms as the Artistic License 2.0.
312              
313             =cut
314              
315             1;