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   21 use Moose;
  4         7  
  4         33  
3 4     4   23367 use MooseX::StrictConstructor;
  4         9  
  4         40  
4 4     4   9619 use MooseX::Types::Path::Class;
  4         7  
  4         44  
5 4     4   3046 use Compress::Raw::Zlib;
  4         7  
  4         771  
6 4     4   23 use IO::File;
  4         5  
  4         531  
7 4     4   20 use namespace::autoclean;
  4         10  
  4         40  
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   7 my $self = shift;
28 4   33     110 my $fh = IO::File->new( $self->filename ) || confess($!);
29 4         487 $fh->binmode();
30 4         148 return $fh;
31             }
32              
33             sub all_sha1s {
34 6     6 0 10 my ( $self, $want_sha1 ) = @_;
35 6         175 return Data::Stream::Bulk::Array->new(
36             array => [ $self->index->all_sha1s ] );
37             }
38              
39             sub unpack_object {
40 3838     3838 0 4416 my ( $self, $offset ) = @_;
41 3838         3678 my $obj_offset = $offset;
42 3838         98486 my $fh = $self->fh;
43              
44 3838 50       8270 $fh->seek( $offset, 0 ) || die "Error seeking in pack: $!";
45 3838 50       23734 $fh->read( my $c, 1 ) || die "Error reading from pack: $!";
46 3838   50     37486 $c = unpack( 'C', $c ) || die $!;
47              
48 3838         4209 my $size = ( $c & 0xf );
49 3838         4732 my $type_number = ( $c >> 4 ) & 7;
50 3838   33     7249 my $type = $TYPES[$type_number] || confess "invalid type $type_number";
51              
52 3838         3267 my $shift = 4;
53 3838         3286 $offset++;
54              
55 3838         6973 while ( ( $c & 0x80 ) != 0 ) {
56 3805 50       6177 $fh->read( $c, 1 ) || die $!;
57 3805   50     18843 $c = unpack( 'C', $c ) || die $!;
58 3805         4307 $size |= ( ( $c & 0x7f ) << $shift );
59 3805         4374 $shift += 7;
60 3805         6043 $offset += 1;
61             }
62              
63 3838 100 100     19715 if ( $type eq 'ofs_delta' || $type eq 'ref_delta' ) {
    50 100        
      66        
      33        
64 2662         5031 ( $type, $size, my $content )
65             = $self->unpack_deltified( $type, $offset, $obj_offset, $size );
66 2662         10023 return ( $type, $size, $content );
67              
68             } elsif ( $type eq 'commit'
69             || $type eq 'tree'
70             || $type eq 'blob'
71             || $type eq 'tag' )
72             {
73 1176         2122 my $content = $self->read_compressed( $offset, $size );
74 1176         4803 return ( $type, $size, $content );
75             } else {
76 0         0 confess "invalid type $type";
77             }
78             }
79              
80             sub read_compressed {
81 4590     4590 0 5253 my ( $self, $offset, $size ) = @_;
82 4590         139069 my $fh = $self->fh;
83              
84 4590 50       9682 $fh->seek( $offset, 0 ) || die $!;
85 4590         37589 my ( $deflate, $status ) = Compress::Raw::Zlib::Inflate->new(
86             -AppendOutput => 1,
87             -ConsumeInput => 0
88             );
89              
90 4590         1010765 my $out = "";
91 4590         10637 while ( length($out) < $size ) {
92 4602 50       10827 $fh->read( my $block, 4096 ) || die $!;
93 4602         102896 my $status = $deflate->inflate( $block, $out );
94             }
95 4590 50       8094 confess length($out)." is not $size" unless length($out) == $size;
96              
97 4590 50       15407 $fh->seek( $offset + $deflate->total_in, 0 ) || die $!;
98 4590         41184 return $out;
99             }
100              
101             sub unpack_deltified {
102 3040     3040 0 4403 my ( $self, $type, $offset, $obj_offset, $size ) = @_;
103 3040         81372 my $fh = $self->fh;
104              
105 3040         2889 my $base;
106              
107 3040 50       6226 $fh->seek( $offset, 0 ) || die $!;
108 3040 50       18551 $fh->read( my $data, $SHA1Size ) || die $!;
109 3040         27640 my $sha1 = unpack( 'H*', $data );
110              
111 3040 100       6025 if ( $type eq 'ofs_delta' ) {
112 2         10 my $i = 0;
113 2         7 my $c = unpack( 'C', substr( $data, $i, 1 ) );
114 2         6 my $base_offset = $c & 0x7f;
115              
116 2         9 while ( ( $c & 0x80 ) != 0 ) {
117 2         7 $c = unpack( 'C', substr( $data, ++$i, 1 ) );
118 2         5 $base_offset++;
119 2         8 $base_offset <<= 7;
120 2         7 $base_offset |= $c & 0x7f;
121             }
122 2         3 $base_offset = $obj_offset - $base_offset;
123 2         5 $offset += $i + 1;
124              
125 2         9 ( $type, undef, $base ) = $self->unpack_object($base_offset);
126             } else {
127 3038         7711 ( $type, undef, $base ) = $self->get_object($sha1);
128 3038         3915 $offset += $SHA1Size;
129              
130             }
131              
132 3040         5286 my $delta = $self->read_compressed( $offset, $size );
133 3040         7127 my $new = $self->patch_delta( $base, $delta );
134              
135 3040         9500 return ( $type, length($new), $new );
136             }
137              
138             sub patch_delta {
139 3040     3040 0 4898 my ( $self, $base, $delta ) = @_;
140              
141 3040         5261 my ( $src_size, $pos ) = $self->patch_delta_header_size( $delta, 0 );
142 3040 50       4804 if ( $src_size != length($base) ) {
143 0         0 confess "invalid delta data";
144             }
145              
146 3040         4112 ( my $dest_size, $pos ) = $self->patch_delta_header_size( $delta, $pos );
147 3040         3125 my $dest = "";
148              
149 3040         5115 while ( $pos < length($delta) ) {
150 31626         26306 my $c = substr( $delta, $pos, 1 );
151 31626         26980 $c = unpack( 'C', $c );
152 31626         21781 $pos++;
153 31626 100       37811 if ( ( $c & 0x80 ) != 0 ) {
    50          
154              
155 21654         15052 my $cp_off = 0;
156 21654         14457 my $cp_size = 0;
157 21654 100       34287 $cp_off = unpack( 'C', substr( $delta, $pos++, 1 ) )
158             if ( $c & 0x01 ) != 0;
159 21654 100       33484 $cp_off |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 8
160             if ( $c & 0x02 ) != 0;
161 21654 50       29753 $cp_off |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 16
162             if ( $c & 0x04 ) != 0;
163 21654 50       28030 $cp_off |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 24
164             if ( $c & 0x08 ) != 0;
165 21654 100       34376 $cp_size = unpack( 'C', substr( $delta, $pos++, 1 ) )
166             if ( $c & 0x10 ) != 0;
167 21654 100       30828 $cp_size |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 8
168             if ( $c & 0x20 ) != 0;
169 21654 50       26669 $cp_size |= unpack( 'C', substr( $delta, $pos++, 1 ) ) << 16
170             if ( $c & 0x40 ) != 0;
171 21654 50       26147 $cp_size = 0x10000 if $cp_size == 0;
172              
173 21654         40917 $dest .= substr( $base, $cp_off, $cp_size );
174             } elsif ( $c != 0 ) {
175 9972         9414 $dest .= substr( $delta, $pos, $c );
176 9972         14487 $pos += $c;
177             } else {
178 0         0 confess 'invalid delta data';
179             }
180             }
181              
182 3040 50       5091 if ( length($dest) != $dest_size ) {
183 0         0 confess 'invalid delta data';
184             }
185 3040         10645 return $dest;
186             }
187              
188             sub patch_delta_header_size {
189 6080     6080 0 5889 my ( $self, $delta, $pos ) = @_;
190              
191 6080         4816 my $size = 0;
192 6080         4488 my $shift = 0;
193 6080         4727 while (1) {
194              
195 12142         11677 my $c = substr( $delta, $pos, 1 );
196 12142 50       15909 unless ( defined $c ) {
197 0         0 confess 'invalid delta header';
198             }
199 12142         13956 $c = unpack( 'C', $c );
200              
201 12142         8765 $pos++;
202 12142         10464 $size |= ( $c & 0x7f ) << $shift;
203 12142         8973 $shift += 7;
204 12142 100       18642 last if ( $c & 0x80 ) == 0;
205             }
206 6080         7596 return ( $size, $pos );
207             }
208              
209             __PACKAGE__->meta->make_immutable;
210