File Coverage

blib/lib/Git/PurePerl/Pack.pm
Criterion Covered Total %
statement 117 122 95.9
branch 34 52 65.3
condition 13 22 59.0
subroutine 13 13 100.0
pod 0 6 0.0
total 177 215 82.3


line stmt bran cond sub pod time code
1             package Git::PurePerl::Pack;
2 4     4   15 use Moose;
  4         7  
  4         24  
3 4     4   18048 use MooseX::StrictConstructor;
  4         6  
  4         31  
4 4     4   8309 use MooseX::Types::Path::Class;
  4         7  
  4         41  
5 4     4   2693 use Compress::Raw::Zlib;
  4         8  
  4         867  
6 4     4   18 use IO::File;
  4         6  
  4         525  
7 4     4   19 use namespace::autoclean;
  4         8  
  4         42  
8              
9             has 'filename' =>
10             ( is => 'ro', isa => 'Path::Class::File', required => 1, coerce => 1 );
11             has 'fh' =>
12             ( is => 'rw', isa => 'IO::File', required => 0, lazy_build => 1 );
13              
14             my @TYPES = ( 'none', 'commit', 'tree', 'blob', 'tag', '', 'ofs_delta',
15             'ref_delta' );
16             my $OBJ_NONE = 0;
17             my $OBJ_COMMIT = 1;
18             my $OBJ_TREE = 2;
19             my $OBJ_BLOB = 3;
20             my $OBJ_TAG = 4;
21             my $OBJ_OFS_DELTA = 6;
22             my $OBJ_REF_DELTA = 7;
23              
24             my $SHA1Size = 20;
25              
26             sub _build_fh {
27 4     4   6 my $self = shift;
28 4   33     94 my $fh = IO::File->new( $self->filename ) || confess($!);
29 4         656 $fh->binmode();
30 4         128 return $fh;
31             }
32              
33             sub all_sha1s {
34 6     6 0 15 my ( $self, $want_sha1 ) = @_;
35 6         160 return Data::Stream::Bulk::Array->new(
36             array => [ $self->index->all_sha1s ] );
37             }
38              
39             sub unpack_object {
40 3770     3770 0 3455 my ( $self, $offset ) = @_;
41 3770         2680 my $obj_offset = $offset;
42 3770         85244 my $fh = $self->fh;
43              
44 3770 50       6706 $fh->seek( $offset, 0 ) || die "Error seeking in pack: $!";
45 3770 50       20768 $fh->read( my $c, 1 ) || die "Error reading from pack: $!";
46 3770   50     29580 $c = unpack( 'C', $c ) || die $!;
47              
48 3770         4205 my $size = ( $c & 0xf );
49 3770         3229 my $type_number = ( $c >> 4 ) & 7;
50 3770   33     6536 my $type = $TYPES[$type_number] || confess "invalid type $type_number";
51              
52 3770         2457 my $shift = 4;
53 3770         2692 $offset++;
54              
55 3770         5511 while ( ( $c & 0x80 ) != 0 ) {
56 3766 50       5454 $fh->read( $c, 1 ) || die $!;
57 3766   50     16585 $c = unpack( 'C', $c ) || die $!;
58 3766         3828 $size |= ( ( $c & 0x7f ) << $shift );
59 3766         2684 $shift += 7;
60 3766         5488 $offset += 1;
61             }
62              
63 3770 100 100     16122 if ( $type eq 'ofs_delta' || $type eq 'ref_delta' ) {
    50 100        
      66        
      33        
64 2597         4439 ( $type, $size, my $content )
65             = $self->unpack_deltified( $type, $offset, $obj_offset, $size );
66 2597         7727 return ( $type, $size, $content );
67              
68             } elsif ( $type eq 'commit'
69             || $type eq 'tree'
70             || $type eq 'blob'
71             || $type eq 'tag' )
72             {
73 1173         1728 my $content = $self->read_compressed( $offset, $size );
74 1173         3892 return ( $type, $size, $content );
75             } else {
76 0         0 confess "invalid type $type";
77             }
78             }
79              
80             sub read_compressed {
81 4522     4522 0 4004 my ( $self, $offset, $size ) = @_;
82 4522         116938 my $fh = $self->fh;
83              
84 4522 50       9250 $fh->seek( $offset, 0 ) || die $!;
85 4522         32052 my ( $deflate, $status ) = Compress::Raw::Zlib::Inflate->new(
86             -AppendOutput => 1,
87             -ConsumeInput => 0
88             );
89              
90 4522         828564 my $out = "";
91 4522         8856 while ( length($out) < $size ) {
92 4534 50       9786 $fh->read( my $block, 4096 ) || die $!;
93 4534         83629 my $status = $deflate->inflate( $block, $out );
94             }
95 4522 50       6317 confess length($out)." is not $size" unless length($out) == $size;
96              
97 4522 50       13227 $fh->seek( $offset + $deflate->total_in, 0 ) || die $!;
98 4522         34594 return $out;
99             }
100              
101             sub unpack_deltified {
102 2972     2972 0 3501 my ( $self, $type, $offset, $obj_offset, $size ) = @_;
103 2972         69222 my $fh = $self->fh;
104              
105 2972         2131 my $base;
106              
107 2972 50       5085 $fh->seek( $offset, 0 ) || die $!;
108 2972 50       15899 $fh->read( my $data, $SHA1Size ) || die $!;
109 2972         22554 my $sha1 = unpack( 'H*', $data );
110              
111 2972 100       4433 if ( $type eq 'ofs_delta' ) {
112 2         7 my $i = 0;
113 2         6 my $c = unpack( 'C', substr( $data, $i, 1 ) );
114 2         6 my $base_offset = $c & 0x7f;
115              
116 2         8 while ( ( $c & 0x80 ) != 0 ) {
117 2         5 $c = unpack( 'C', substr( $data, ++$i, 1 ) );
118 2         4 $base_offset++;
119 2         4 $base_offset <<= 7;
120 2         46 $base_offset |= $c & 0x7f;
121             }
122 2         4 $base_offset = $obj_offset - $base_offset;
123 2         4 $offset += $i + 1;
124              
125 2         10 ( $type, undef, $base ) = $self->unpack_object($base_offset);
126             } else {
127 2970         6205 ( $type, undef, $base ) = $self->get_object($sha1);
128 2970         3634 $offset += $SHA1Size;
129              
130             }
131              
132 2972         5001 my $delta = $self->read_compressed( $offset, $size );
133 2972         5265 my $new = $self->patch_delta( $base, $delta );
134              
135 2972         7080 return ( $type, length($new), $new );
136             }
137              
138             sub patch_delta {
139 2972     2972 0 3600 my ( $self, $base, $delta ) = @_;
140              
141 2972         3855 my ( $src_size, $pos ) = $self->patch_delta_header_size( $delta, 0 );
142 2972 50       4906 if ( $src_size != length($base) ) {
143 0         0 confess "invalid delta data";
144             }
145              
146 2972         3927 ( my $dest_size, $pos ) = $self->patch_delta_header_size( $delta, $pos );
147 2972         2882 my $dest = "";
148              
149 2972         4083 while ( $pos < length($delta) ) {
150 30928         23054 my $c = substr( $delta, $pos, 1 );
151 30928         23766 $c = unpack( 'C', $c );
152 30928         17618 $pos++;
153 30928 100       33544 if ( ( $c & 0x80 ) != 0 ) {
    50          
154              
155 21126         13023 my $cp_off = 0;
156 21126         12411 my $cp_size = 0;
157 21126 100       30291 $cp_off = unpack( 'C', substr( $delta, $pos++, 1 ) )
158             if ( $c & 0x01 ) != 0;
159 21126 100       29740 $cp_off |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 8
160             if ( $c & 0x02 ) != 0;
161 21126 50       23362 $cp_off |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 16
162             if ( $c & 0x04 ) != 0;
163 21126 50       23154 $cp_off |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 24
164             if ( $c & 0x08 ) != 0;
165 21126 100       28736 $cp_size = unpack( 'C', substr( $delta, $pos++, 1 ) )
166             if ( $c & 0x10 ) != 0;
167 21126 100       25234 $cp_size |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 8
168             if ( $c & 0x20 ) != 0;
169 21126 50       23134 $cp_size |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 16
170             if ( $c & 0x40 ) != 0;
171 21126 50       22818 $cp_size = 0x10000 if $cp_size == 0;
172              
173 21126         35939 $dest .= substr( $base, $cp_off, $cp_size );
174             } elsif ( $c != 0 ) {
175 9802         8094 $dest .= substr( $delta, $pos, $c );
176 9802         12916 $pos += $c;
177             } else {
178 0         0 confess 'invalid delta data';
179             }
180             }
181              
182 2972 50       3844 if ( length($dest) != $dest_size ) {
183 0         0 confess 'invalid delta data';
184             }
185 2972         8940 return $dest;
186             }
187              
188             sub patch_delta_header_size {
189 5944     5944 0 4995 my ( $self, $delta, $pos ) = @_;
190              
191 5944         4026 my $size = 0;
192 5944         3908 my $shift = 0;
193 5944         3501 while (1) {
194              
195 11870         10124 my $c = substr( $delta, $pos, 1 );
196 11870 50       13596 unless ( defined $c ) {
197 0         0 confess 'invalid delta header';
198             }
199 11870         10916 $c = unpack( 'C', $c );
200              
201 11870         7466 $pos++;
202 11870         8843 $size |= ( $c & 0x7f ) << $shift;
203 11870         7486 $shift += 7;
204 11870 100       15828 last if ( $c & 0x80 ) == 0;
205             }
206 5944         6400 return ( $size, $pos );
207             }
208              
209             __PACKAGE__->meta->make_immutable;
210