File Coverage

blib/lib/Git/Repository/Plugin/Blame.pm
Criterion Covered Total %
statement 51 51 100.0
branch 15 20 75.0
condition 4 4 100.0
subroutine 10 10 100.0
pod 1 1 100.0
total 81 86 94.1


line stmt bran cond sub pod time code
1             package Git::Repository::Plugin::Blame;
2              
3 3     3   111245 use warnings;
  3         6  
  3         133  
4 3     3   18 use strict;
  3         7  
  3         86  
5 3     3   77 use 5.006;
  3         19  
6              
7 3     3   1836 use Git::Repository::Plugin;
  3         1506  
  3         260  
8             our @ISA = qw( Git::Repository::Plugin );
9 2     2   96 sub _keywords { return qw( blame ) } ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
10              
11 3     3   19 use Carp;
  3         5  
  3         198  
12 3     3   1878 use Class::Load qw();
  3         46589  
  3         133  
13 3     3   1665 use Perl6::Slurp qw();
  3         4793  
  3         90  
14 3     3   1516 use Git::Repository::Plugin::Blame::Line;
  3         7  
  3         1773  
15              
16              
17             =head1 NAME
18              
19             Git::Repository::Plugin::Blame - Add a blame() method to L.
20              
21              
22             =head1 VERSION
23              
24             Version 1.4.0
25              
26             =cut
27              
28             our $VERSION = '1.4.0';
29              
30              
31             =head1 SYNOPSIS
32              
33             # Load the plugin.
34             use Git::Repository 'Blame';
35              
36             my $repository = Git::Repository->new();
37              
38             # Get the git blame information.
39             my $blame_lines = $repository->blame( $file );
40              
41              
42             =head1 DESCRIPTION
43              
44             This module adds a new C method to L, which can be
45             used to determine what the last change for each line in a file is.
46              
47              
48             =head1 METHODS
49              
50             =head2 blame()
51              
52             Return the git blame information for a given file as an arrayref of
53             L objects.
54              
55             my $blame_lines = $repository->blame(
56             $file,
57             use_cache => $boolean, # default 0
58             );
59              
60             Arguments:
61              
62             =over 4
63              
64             =item * use_cache I<(default: 0)>
65              
66             Cache the git blame output.
67              
68             =item * ignore_whitespace I<(default: 0)>
69              
70             Ignore whitespace when comparing the parent's version and the child's to find
71             where the lines came from.
72              
73             =back
74              
75             =cut
76              
77             sub blame
78             {
79 6     6 1 438601 my ( $repository, $file, %args ) = @_;
80 6   100     48 my $use_cache = delete( $args{'use_cache'} ) || 0;
81 6   100     118 my $ignore_whitespace = delete( $args{'ignore_whitespace'} ) || 0;
82 6 50       29 croak 'The following arguments are not valid: ' . join( ', ' , keys %args )
83             if scalar( keys %args ) != 0;
84              
85             # Check if the cache is enabled and if the file has already been parsed.
86 6         14 my $cache;
87 6 100       16 if ( $use_cache )
88             {
89 2         16 my $class = Class::Load::load_class( 'Git::Repository::Plugin::Blame::Cache' );
90 2         190 $cache = $class->new(
91             repository => $repository->work_tree(),
92             blame_args =>
93             {
94             ignore_whitespace => $ignore_whitespace,
95             },
96             );
97 2 50       8 croak 'Failed to initialize cache for repository ' . $repository->work_tree()
98             if !defined( $cache );
99              
100 2         5 my $blame_lines = $cache->get_blame_lines( file => $file );
101 2 50       6 return $blame_lines
102             if defined( $blame_lines );
103             }
104              
105             # Run the command.
106 6         19 my @commandline_options = ( '--porcelain' );
107 6 100       19 push( @commandline_options, '-w' ) if $ignore_whitespace;
108 6         37 my $command = $repository->command( 'blame', @commandline_options, $file );
109 6         65247 my @output = $command->final_output();
110              
111             # Parse the output.
112 6         23080 my ( $commit_id, $original_line_number, $final_line_number, $lines_count_in_group );
113 6         19 my $commit_attributes = {};
114 6         16 my $lines = [];
115 6         26 foreach my $line ( @output )
116             {
117 163 100       269 if ( $line =~ /^\t(.*)$/x )
118             {
119             # It's a line from the file we git blamed.
120             push(
121             @$lines,
122             Git::Repository::Plugin::Blame::Line->new(
123             line_number => $final_line_number,
124             line => defined( $1 ) ? $1 : '',
125 21 50       217 commit_attributes => $commit_attributes->{ $commit_id },
126             commit_id => $commit_id,
127             )
128             );
129             }
130             else
131             {
132             # It's a git header line.
133 142 100       567 if ( $line =~ /^([0-9a-f]+)\s(\d+)\s(\d+)\s*(\d*)$/x )
    50          
134             {
135 21         130 ( $commit_id, $original_line_number, $final_line_number, $lines_count_in_group ) = ( $1, $2, $3, $4 );
136             }
137             elsif ( $line =~ m/^([\w\-]+)\s*(.*)$/x )
138             {
139 121         388 $commit_attributes->{ $commit_id }->{ $1 } = $2;
140             }
141             }
142             }
143              
144             # If we have a cache object, cache the output.
145 6 100       21 if ( defined( $cache ) )
146             {
147 2         23 $cache->set_blame_lines(
148             file => $file,
149             blame_lines => $lines,
150             );
151             }
152              
153 6         122 return $lines;
154             }
155              
156              
157             =head1 BUGS
158              
159             Please report any bugs or feature requests through the web interface at
160             L.
161             I will be notified, and then you'll automatically be notified of progress on
162             your bug as I make changes.
163              
164              
165             =head1 SUPPORT
166              
167             You can find documentation for this module with the perldoc command.
168              
169             perldoc Git::Repository::Plugin::Blame
170              
171              
172             You can also look for information at:
173              
174             =over 4
175              
176             =item * GitHub (report bugs there)
177              
178             L
179              
180             =item * AnnoCPAN: Annotated CPAN documentation
181              
182             L
183              
184             =item * CPAN Ratings
185              
186             L
187              
188             =item * MetaCPAN
189              
190             L
191              
192             =back
193              
194              
195             =head1 AUTHOR
196              
197             L,
198             C<< >>.
199              
200              
201             =head1 COPYRIGHT & LICENSE
202              
203             Copyright 2012-2017 Guillaume Aubert.
204              
205             This code is free software; you can redistribute it and/or modify it under the
206             same terms as Perl 5 itself.
207              
208             This program is distributed in the hope that it will be useful, but WITHOUT ANY
209             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
210             PARTICULAR PURPOSE. See the LICENSE file for more details.
211              
212             =cut
213              
214             1;