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         6  
  4         20  
3 4     4   15835 use MooseX::StrictConstructor;
  4         6  
  4         25  
4 4     4   7238 use MooseX::Types::Path::Class;
  4         7  
  4         38  
5 4     4   2441 use Compress::Raw::Zlib;
  4         6  
  4         697  
6 4     4   20 use IO::File;
  4         5  
  4         453  
7 4     4   17 use namespace::autoclean;
  4         4  
  4         29  
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         370 $fh->binmode();
30 4         122 return $fh;
31             }
32              
33             sub all_sha1s {
34 6     6 0 7 my ( $self, $want_sha1 ) = @_;
35 6         149 return Data::Stream::Bulk::Array->new(
36             array => [ $self->index->all_sha1s ] );
37             }
38              
39             sub unpack_object {
40 3878     3878 0 4062 my ( $self, $offset ) = @_;
41 3878         2614 my $obj_offset = $offset;
42 3878         89124 my $fh = $self->fh;
43              
44 3878 50       7188 $fh->seek( $offset, 0 ) || die "Error seeking in pack: $!";
45 3878 50       22205 $fh->read( my $c, 1 ) || die "Error reading from pack: $!";
46 3878   50     29827 $c = unpack( 'C', $c ) || die $!;
47              
48 3878         3721 my $size = ( $c & 0xf );
49 3878         3580 my $type_number = ( $c >> 4 ) & 7;
50 3878   33     6078 my $type = $TYPES[$type_number] || confess "invalid type $type_number";
51              
52 3878         3092 my $shift = 4;
53 3878         3065 $offset++;
54              
55 3878         6516 while ( ( $c & 0x80 ) != 0 ) {
56 3852 50       5669 $fh->read( $c, 1 ) || die $!;
57 3852   50     17285 $c = unpack( 'C', $c ) || die $!;
58 3852         4236 $size |= ( ( $c & 0x7f ) << $shift );
59 3852         2889 $shift += 7;
60 3852         5796 $offset += 1;
61             }
62              
63 3878 100 100     16701 if ( $type eq 'ofs_delta' || $type eq 'ref_delta' ) {
    50 100        
      66        
      33        
64 2700         4170 ( $type, $size, my $content )
65             = $self->unpack_deltified( $type, $offset, $obj_offset, $size );
66 2700         7289 return ( $type, $size, $content );
67              
68             } elsif ( $type eq 'commit'
69             || $type eq 'tree'
70             || $type eq 'blob'
71             || $type eq 'tag' )
72             {
73 1178         1714 my $content = $self->read_compressed( $offset, $size );
74 1178         3819 return ( $type, $size, $content );
75             } else {
76 0         0 confess "invalid type $type";
77             }
78             }
79              
80             sub read_compressed {
81 4630     4630 0 4709 my ( $self, $offset, $size ) = @_;
82 4630         122721 my $fh = $self->fh;
83              
84 4630 50       9536 $fh->seek( $offset, 0 ) || die $!;
85 4630         33026 my ( $deflate, $status ) = Compress::Raw::Zlib::Inflate->new(
86             -AppendOutput => 1,
87             -ConsumeInput => 0
88             );
89              
90 4630         861492 my $out = "";
91 4630         8692 while ( length($out) < $size ) {
92 4642 50       11981 $fh->read( my $block, 4096 ) || die $!;
93 4642         86840 my $status = $deflate->inflate( $block, $out );
94             }
95 4630 50       7338 confess length($out)." is not $size" unless length($out) == $size;
96              
97 4630 50       15334 $fh->seek( $offset + $deflate->total_in, 0 ) || die $!;
98 4630         35185 return $out;
99             }
100              
101             sub unpack_deltified {
102 3080     3080 0 8922 my ( $self, $type, $offset, $obj_offset, $size ) = @_;
103 3080         73923 my $fh = $self->fh;
104              
105 3080         2431 my $base;
106              
107 3080 50       5448 $fh->seek( $offset, 0 ) || die $!;
108 3080 50       17403 $fh->read( my $data, $SHA1Size ) || die $!;
109 3080         22313 my $sha1 = unpack( 'H*', $data );
110              
111 3080 100       4629 if ( $type eq 'ofs_delta' ) {
112 2         7 my $i = 0;
113 2         5 my $c = unpack( 'C', substr( $data, $i, 1 ) );
114 2         5 my $base_offset = $c & 0x7f;
115              
116 2         7 while ( ( $c & 0x80 ) != 0 ) {
117 2         6 $c = unpack( 'C', substr( $data, ++$i, 1 ) );
118 2         2 $base_offset++;
119 2         5 $base_offset <<= 7;
120 2         7 $base_offset |= $c & 0x7f;
121             }
122 2         3 $base_offset = $obj_offset - $base_offset;
123 2         3 $offset += $i + 1;
124              
125 2         6 ( $type, undef, $base ) = $self->unpack_object($base_offset);
126             } else {
127 3078         6459 ( $type, undef, $base ) = $self->get_object($sha1);
128 3078         3503 $offset += $SHA1Size;
129              
130             }
131              
132 3080         5106 my $delta = $self->read_compressed( $offset, $size );
133 3080         5560 my $new = $self->patch_delta( $base, $delta );
134              
135 3080         7082 return ( $type, length($new), $new );
136             }
137              
138             sub patch_delta {
139 3080     3080 0 3997 my ( $self, $base, $delta ) = @_;
140              
141 3080         4115 my ( $src_size, $pos ) = $self->patch_delta_header_size( $delta, 0 );
142 3080 50       4331 if ( $src_size != length($base) ) {
143 0         0 confess "invalid delta data";
144             }
145              
146 3080         4276 ( my $dest_size, $pos ) = $self->patch_delta_header_size( $delta, $pos );
147 3080         2733 my $dest = "";
148              
149 3080         4789 while ( $pos < length($delta) ) {
150 36078         26353 my $c = substr( $delta, $pos, 1 );
151 36078         28006 $c = unpack( 'C', $c );
152 36078         23981 $pos++;
153 36078 100       39970 if ( ( $c & 0x80 ) != 0 ) {
    50          
154              
155 24294         14337 my $cp_off = 0;
156 24294         14575 my $cp_size = 0;
157 24294 100       34730 $cp_off = unpack( 'C', substr( $delta, $pos++, 1 ) )
158             if ( $c & 0x01 ) != 0;
159 24294 100       34012 $cp_off |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 8
160             if ( $c & 0x02 ) != 0;
161 24294 50       29635 $cp_off |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 16
162             if ( $c & 0x04 ) != 0;
163 24294 50       26325 $cp_off |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 24
164             if ( $c & 0x08 ) != 0;
165 24294 100       34396 $cp_size = unpack( 'C', substr( $delta, $pos++, 1 ) )
166             if ( $c & 0x10 ) != 0;
167 24294 100       29857 $cp_size |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 8
168             if ( $c & 0x20 ) != 0;
169 24294 50       26442 $cp_size |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 16
170             if ( $c & 0x40 ) != 0;
171 24294 50       27228 $cp_size = 0x10000 if $cp_size == 0;
172              
173 24294         42413 $dest .= substr( $base, $cp_off, $cp_size );
174             } elsif ( $c != 0 ) {
175 11784         10015 $dest .= substr( $delta, $pos, $c );
176 11784         14656 $pos += $c;
177             } else {
178 0         0 confess 'invalid delta data';
179             }
180             }
181              
182 3080 50       4392 if ( length($dest) != $dest_size ) {
183 0         0 confess 'invalid delta data';
184             }
185 3080         8993 return $dest;
186             }
187              
188             sub patch_delta_header_size {
189 6160     6160 0 5302 my ( $self, $delta, $pos ) = @_;
190              
191 6160         4113 my $size = 0;
192 6160         3932 my $shift = 0;
193 6160         4111 while (1) {
194              
195 12302         10077 my $c = substr( $delta, $pos, 1 );
196 12302 50       14556 unless ( defined $c ) {
197 0         0 confess 'invalid delta header';
198             }
199 12302         12130 $c = unpack( 'C', $c );
200              
201 12302         8919 $pos++;
202 12302         9067 $size |= ( $c & 0x7f ) << $shift;
203 12302         7962 $shift += 7;
204 12302 100       17049 last if ( $c & 0x80 ) == 0;
205             }
206 6160         6994 return ( $size, $pos );
207             }
208              
209             __PACKAGE__->meta->make_immutable;
210