our
$VERSION
= version->new(1.1.20);
our
$workflow
= App::Git::Workflow->new;
our
(
$name
) =
$PROGRAM_NAME
=~ m{^.*/(.*?)$}mxs;
our
%option
;
our
%p2u_extra
;
sub
run {
my
$self
=
shift
;
%option
= (
format
=>
'test'
,
max_history
=>
$workflow
->config(
'workflow.max-history'
) || 1,
branches
=> 0,
);
get_options(
\
%option
,
'tag|t=s'
,
'branch|b=s'
,
'local|l!'
,
'remote|r!'
,
'format|f=s'
,
'quick|q!'
,
'include|i=s'
,
'exclude|e=s'
,
'all'
,
'max_history|max-history|m=i'
,
'fetch|F'
,
'fix|x'
,
) or
return
;
my
$action
=
shift
@ARGV
||
'am_i'
;
my
$format
=
'format_'
.
$option
{
format
};
$action
=~ s/-/_/g;
$action
=
"do_$action"
;
if
( !
$self
->can(
$action
) ) {
$action
=~ s/^do_//;
$action
=~ s/_/-/;
warn
"Unknown action '$action'!\n"
;
Pod::Usage::pod2usage(
%p2u_extra
,
-verbose
=> 1 );
return
1;
}
elsif
(
$action
eq
'do_show'
&& !
$self
->can(
$format
) ) {
warn
"Unknown format '$option{format}'!\n"
;
Pod::Usage::pod2usage(
%p2u_extra
,
-verbose
=> 1 );
return
1;
}
$workflow
->{VERBOSE} =
$option
{verbose};
$workflow
->{TEST } =
$option
{test};
$workflow
->git->fetch
if
$option
{fetch};
if
(
$option
{branch_age}) {
return
branch_age();
}
if
(
$action
eq
'do_show'
) {
$option
{branches} = 1;
}
my
@releases
=
$workflow
->releases(
%option
);
if
(
$option
{verbose}) {
my
$local
=
localtime
(
$releases
[-1]{
time
});
my
$now
=
localtime
;
my
$time
=
time
;
warn
<<"DETAILS";
Branch : $releases[-1]{name}
SHA : $releases[-1]{sha}
Time : $local ($releases[-1]{time})
Now : $now ($time)
DETAILS
}
$option
{all} = 1
if
$action
eq
'do_show'
&&
$option
{
format
} eq
'test'
;
$self
->
$action
(
@releases
);
return
;
}
sub
do_show {
my
(
$self
,
@releases
) =
@_
;
my
$csv
= branches_contain(
@releases
);
if
(
$option
{verbose}) {
warn
@$csv
.
" branches found\n"
;
}
my
$format
=
'format_'
.
$option
{
format
};
$self
->
$format
(
$csv
,
@releases
);
return
;
}
sub
do_am_i {
my
(
undef
,
@releases
) =
@_
;
my
$format
=
q/--format=format:%H %at <%an>%ae/
;
my
$bad
= 0;
for
my
$release
(
reverse
@releases
) {
my
(
$ans
) =
grep
{/
$release
->{sha}/}
$workflow
->git->
log
(
$format
);
chomp
$ans
if
$ans
;
next
if
$ans
;
$bad
++;
warn
"Missing release $release->{name}!\n"
;
}
if
(
$bad
) {
if
(
$option
{fix} ) {
$workflow
->git->merge(
$releases
[-1]{name});
return
;
}
return
$bad
;
}
else
{
print
"Up to date\n"
;
}
return
;
}
sub
do_current {
my
(
undef
,
@releases
) =
@_
;
print
"Current prod \"$releases[0]{name}\"\n"
;
return
;
}
sub
do_update_me {
my
(
undef
,
@releases
) =
@_
;
print
"Merging \"$releases[0]{name}\"\n"
;
$workflow
->git->merge(
$releases
[0]{name});
return
;
}
sub
branches_contain {
my
@releases
=
@_
;
my
@branches
=
$workflow
->branches(
$option
{remote} ?
'remote'
:
'both'
);
my
$format
=
q/--format=format:%at <%an>%ae/
;
my
@csv
;
BRANCH:
for
my
$branch
(
@branches
) {
next
BRANCH
if
$option
{include} &&
$branch
!~ /
$option
{include}/;
next
BRANCH
if
$option
{exclude} &&
$branch
=~ /
$option
{exclude}/;
my
(
$first
,
$author
,
$found
,
$release
);
my
(
$log
) =
$workflow
->git->
log
(
$format
,
qw/-n 1/
,
$branch
);
next
if
!
$log
;
my
(
$time
,
$user
) =
split
/\s+/,
$log
, 2;
$first
=
$time
;
$author
=
$user
;
if
(
$time
<
$releases
[-1]{
time
} ) {
warn
"skipping $branch\n"
if
$option
{verbose} &&
$option
{verbose} > 1;
next
BRANCH;
}
my
$age
=
time
-
$releases
[0]{
time
} + 10 * 60 * 60 * 24;
my
$ago
= -1;
RELEASE:
for
my
$released
(
reverse
@releases
) {
$ago
++;
next
RELEASE
if
!
$released
->{branches}{
$branch
};
$release
=
$released
->{name};
$age
=
$released
==
$releases
[-1] ? 0 :
time
-
$released
->{
time
};
last
RELEASE;
}
next
BRANCH
if
!
$option
{all} && !
$option
{verbose} &&
$found
;
push
@csv
, [
$release
||
"Out of date"
,
$branch
,
$author
,
int
$age
/ 60 / 60 / 24,
$ago
];
warn
+(
$found
?
'up to date'
:
"missing $releases[-1]{name}"
) .
"\t$branch\t$author\n"
if
$option
{quick};
}
return
\
@csv
;
}
sub
format_text {
my
(
undef
,
$csv
,
@releases
) =
@_
;
my
@max
= (0,0,0);
for
my
$row
(
@$csv
) {
$max
[0] =
length
$row
->[0]
if
$max
[0] <
length
$row
->[0];
$max
[1] =
length
$row
->[1]
if
$max
[1] <
length
$row
->[1];
$max
[2] =
length
$row
->[2]
if
$max
[2] <
length
$row
->[2];
}
for
my
$row
(
@$csv
) {
printf
"%$max[0]s %-$max[1]s %-$max[2]s (%2.0f days old)\n"
,
@$row
[0..3];
}
return
;
}
sub
format_csv {
my
(
undef
,
$csv
,
@releases
) =
@_
;
my
$sepperator
=
$option
{
format
} eq
'tab'
?
"\t"
:
','
;
for
my
$row
(
@$csv
) {
print
+(
join
$sepperator
,
@$row
),
"\n"
;
}
return
;
}
{
no
warnings
qw/once/
;
*format_tab
=
*format_csv
;
}
sub
format_json {
my
(
undef
,
$csv
,
@releases
) =
@_
;
my
$repo
=
$workflow
->config(
'remote.origin.url'
);
my
(
$name
) =
$repo
=~ m{[/:](.*?)(?:[.]git)?$}xms;
print
JSON::encode_json({
repository
=>
$repo
,
name
=>
$name
,
release
=>
$releases
[-1]{name},
release_date
=>
''
.
localtime
(
$releases
[-1]{
time
}),
branches
=> [
map
{{
status
=>
$_
->[0],
name
=>
$_
->[1],
last_author
=>
$_
->[2],
release_age
=>
$_
->[3] }}
@$csv
]
});
return
;
}
sub
format_html {
my
(
undef
,
$csv
,
@releases
) =
@_
;
my
$sepperator
=
"</td><td>"
;
my
$date
=
localtime
;
my
$repo
=
$workflow
->config(
'remote.origin.url'
);
print
<<"HTML";
<table>
<caption>Branch statuses for <i>$repo</i> ($date)</caption>
<thead>
<tr>
<th>Production Branch/Tag Status</th>
<th>Branch Name</th>
<th>Last commit owner</th>
<th>Included release age (days)</th>
</tr>
</thead>
HTML
for
my
$row
(
@$csv
) {
next
if
!
$row
&& !
$row
->[2];
my
(
$name
,
$email
) =
$row
->[2] =~ /^<([^>]+)>(.*)$/;
$row
->[0] =
$row
->[0] eq
$releases
[-1]{name} ?
$row
->[0] :
qq{<span class="old">$row->[0]</span>}
;
$row
->[2] =
$row
->[0] eq
$releases
[-1]{name} ?
$name
:
qq{<a href="mailto:$email?subject=$row->[1]%20is%20out%20of%20date">$name</a>}
;
print
"<tr class=\"age_$row->[4]\"><td>"
. (
join
$sepperator
,
@$row
[0..3]),
"</td></tr>\n"
;
}
print
"</table>\n"
;
return
;
}
sub
format_test {
my
(
undef
,
$csv
,
@releases
) =
@_
;
Test::More->
import
();
for
my
$row
(
@$csv
) {
is(
$row
->[0],
$releases
[-1]{name},
$row
->[1] .
' is upto date'
)
or note(
"Release is $row->[3] days old"
);
}
return
;
}
1;