File Coverage

blib/lib/Git/Database/Backend/Git.pm
Criterion Covered Total %
statement 73 88 82.9
branch 16 22 72.7
condition n/a
subroutine 15 15 100.0
pod 0 9 0.0
total 104 134 77.6


line stmt bran cond sub pod time code
1             package Git::Database::Backend::Git;
2             $Git::Database::Backend::Git::VERSION = '0.012';
3 7     7   192451 use Git::Version::Compare qw( ge_git );
  7         57  
  7         1159  
4 7     7   96 use Sub::Quote;
  7         48  
  7         988  
5              
6 7     7   92 use Moo;
  7         43  
  7         206  
7 7     7   25558 use namespace::clean;
  7         43  
  7         155  
8              
9             with
10             'Git::Database::Role::Backend',
11             'Git::Database::Role::ObjectReader',
12             'Git::Database::Role::ObjectWriter',
13             'Git::Database::Role::RefReader',
14             'Git::Database::Role::RefWriter',
15             ;
16              
17             has '+store' => (
18             isa => quote_sub( q{
19             die 'store is not a Git object'
20             if !eval { $_[0]->isa('Git') }
21             } ),
22             );
23              
24             has object_factory => (
25             is => 'lazy',
26             init_arg => undef,
27 37     37   1145 builder => sub { [ $_[0]->store->command_bidi_pipe( 'cat-file', '--batch' ) ] },
28             predicate => 1,
29             clearer => 1,
30             );
31              
32             has object_checker => (
33             is => 'lazy',
34             init_arg => undef,
35 11     11   373 builder => sub { [ $_[0]->store->command_bidi_pipe( 'cat-file', '--batch-check' ) ] },
36             predicate => 1,
37             clearer => 1,
38             );
39              
40             # Git::Database::Role::Backend
41             sub hash_object {
42 95     95 0 27425 my ( $self, $object ) = @_;
43 95         791 my ( $pid, $in, $out, $ctx ) =
44             $self->store->command_bidi_pipe( 'hash-object', '-t', $object->kind,
45             '--stdin' );
46 95         1051905 print {$out} $object->content;
  95         11069  
47 95         3989 close $out;
48 95         32963 chomp( my $digest = <$in> );
49 95         1885 $self->store->command_close_bidi_pipe( $pid, $in, undef, $ctx ); # $out closed
50 95         15712 return $digest;
51             }
52              
53             # Git::Database::Role::ObjectReader
54             sub get_object_meta {
55 57     57 0 642 my ( $self, $digest ) = @_;
56 57         1870 my $checker = $self->object_checker;
57              
58             # request the object
59 57         129264 print { $checker->[2] } $digest, "\n";
  57         2692  
60              
61             # process the reply
62 57         723 local $/ = "\012";
63 57         2810 chomp( my $reply = $checker->[1]->getline );
64              
65             # protect against weird cases like if $digest contains a space
66 57         12790 my @parts = split / /, $reply;
67 57 100       1196 return ( $digest, 'missing', undef ) if $parts[-1] eq 'missing';
68              
69 24         165 my ( $kind, $size ) = splice @parts, -2;
70 24         541 return join( ' ', @parts ), $kind, $size;
71             }
72              
73             sub get_object_attributes {
74 156     156 0 1437 my ( $self, $digest ) = @_;
75 156         5106 my $factory = $self->object_factory;
76              
77             # request the object
78 156         456594 print { $factory->[2]} $digest, "\n";
  156         6356  
79              
80             # process the reply
81 156         934 my $out = $factory->[1];
82 156         1721 local $/ = "\012";
83 156         58950 chomp( my $reply = <$out> );
84              
85             # protect against weird cases like if $sha1 contains a space
86 156         2766 my ( $sha1, $kind, $size ) = my @parts = split / /, $reply;
87              
88             # git versions >= 2.11.0.rc0 throw more verbose errors
89 156 50       1761 if ( $parts[0] =~ /^(?:symlink|dangling|loop|notdir)$/ ) {
90 0         0 <$out>; # eat the next line
91 0         0 return undef;
92             }
93              
94             # object does not exist in the git object database
95 156 100       1452 return undef if $parts[-1] eq 'missing';
96              
97             # git versions >= 2.21.0.rc0 explicitely say if a sha1 is ambiguous
98 96 50       495 return undef if $kind eq 'ambiguous';
99              
100             # read the whole content in memory at once
101 96         1062 my $res = read $out, (my $content), $size;
102 96 50       486 if( $res != $size ) {
103 0         0 $factory->close; # in case the exception is trapped
104 0         0 $self->clear_object_factory;
105 0         0 die "Read $res/$size of content from git";
106             }
107              
108             # read the last byte
109 96         445 $res = read $out, (my $junk), 1;
110 96 50       389 if( $res != 1 ) {
111 0         0 $factory->close; # in case the exception is trapped
112 0         0 $self->clear_object_factory;
113 0         0 die "Unable to finish reading content from git";
114             }
115              
116             # careful with utf-8!
117             # create a new hash with kind, digest, content and size
118             return {
119 96         2279 kind => $kind,
120             size => $size,
121             content => $content,
122             digest => $sha1
123             };
124             }
125              
126             sub all_digests {
127 11     11 0 285196 my ( $self, $kind ) = @_;
128 11         141 my $store = $self->store;
129 11 100       605 my $re = $kind ? qr/ \Q$kind\E / : qr/ /;
130              
131             # the --batch-all-objects option appeared in v2.6.0-rc0
132 11 50       166 if ( ge_git $store->version, '2.6.0.rc0' ) {
133 11         84121 return map +( split / / )[0],
134             grep /$re/,
135             $store->command(qw( cat-file --batch-check --batch-all-objects ));
136             }
137             else { # this won't return unreachable objects
138 0         0 my ( $pid, $in, $out, $ctx ) =
139             $store->command_bidi_pipe(qw( cat-file --batch-check ));
140              
141             my @digests =
142             map +( split / / )[0], grep /$re/,
143 0         0 map { print {$out} ( split / / )[0], "\n"; $in->getline }
  0         0  
  0         0  
  0         0  
144             sort $store->command(qw( rev-list --all --objects ));
145 0         0 $store->command_close_bidi_pipe( $pid, $in, $out, $ctx );
146 0         0 return @digests;
147             }
148             }
149              
150             # Git::Database::Role::ObjectWriter
151             sub put_object {
152 13     13 0 87 my ( $self, $object ) = @_;
153 13         176 my ( $pid, $in, $out, $ctx ) =
154             $self->store->command_bidi_pipe( 'hash-object', '-t', $object->kind,
155             '-w', '--stdin' );
156 13         133406 print {$out} $object->content;
  13         1498  
157 13         667 close $out;
158 13         12785 chomp( my $digest = <$in> );
159 13         332 $self->store->command_close_bidi_pipe( $pid, $in, undef, $ctx ); # $out closed
160 13         2057 return $digest;
161             }
162              
163             # Git::Database::Role::RefReader
164             sub refs {
165 20     20 0 110023 my ($self) = @_;
166             return {
167 20         234 reverse map +( split / / ),
168             $self->store->command(qw( show-ref --head ))
169             };
170             }
171              
172             # Git::Database::Role::RefWriter
173             sub put_ref {
174 2     2 0 22528 my ($self, $refname, $digest ) = @_;
175 2         63 $self->store->command( 'update-ref', $refname, $digest );
176             }
177              
178             sub delete_ref {
179 2     2 0 23606 my ($self, $refname ) = @_;
180 2         56 $self->store->command( 'update-ref', '-d', $refname );
181             }
182              
183             sub DEMOLISH {
184 46     46 0 266227 my ( $self, $in_global_destruction ) = @_;
185 46 50       372 return if $in_global_destruction; # why bother?
186              
187 46 100       575 $self->store->command_close_bidi_pipe( @{ $self->object_factory } )
  37         897  
188             if $self->has_object_factory;
189 46 100       19124 $self->store->command_close_bidi_pipe( @{ $self->object_checker } )
  11         380  
190             if $self->has_object_checker;
191             }
192              
193             1;
194              
195             __END__
196              
197             =pod
198              
199             =for Pod::Coverage
200             has_object_checker
201             has_object_factory
202             DEMOLISH
203             hash_object
204             get_object_attributes
205             get_object_meta
206             all_digests
207             put_object
208             refs
209             put_ref
210             delete_ref
211              
212             =head1 NAME
213              
214             Git::Database::Backend::Git - A Git::Database backend based on Git
215              
216             =head1 VERSION
217              
218             version 0.012
219              
220             =head1 SYNOPSIS
221              
222             # get a store
223             my $r = Git->new();
224              
225             # let Git::Database produce the backend
226             my $db = Git::Database->new( store => $r );
227              
228             =head1 DESCRIPTION
229              
230             This backend reads and writes data from a Git repository using the
231             L<Git> Git wrapper.
232              
233             =head2 Git Database Roles
234              
235             This backend does the following roles
236             (check their documentation for a list of supported methods):
237             L<Git::Database::Role::Backend>,
238             L<Git::Database::Role::ObjectReader>,
239             L<Git::Database::Role::ObjectWriter>,
240             L<Git::Database::Role::RefReader>,
241             L<Git::Database::Role::RefWriter>.
242              
243             =head1 AUTHOR
244              
245             Philippe Bruhat (BooK) <book@cpan.org>
246              
247             =head1 COPYRIGHT
248              
249             Copyright 2016-2019 Philippe Bruhat (BooK), all rights reserved.
250              
251             =head1 LICENSE
252              
253             This program is free software; you can redistribute it and/or modify it
254             under the same terms as Perl itself.
255              
256             =cut