| 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 |  |  |  |  |  |  |  |