File Coverage

blib/lib/Git/PurePerl.pm
Criterion Covered Total %
statement 288 308 93.5
branch 46 76 60.5
condition 18 48 37.5
subroutine 64 66 96.9
pod 7 23 30.4
total 423 521 81.1


line stmt bran cond sub pod time code
1             package Git::PurePerl;
2 4     4   41736 use Moose;
  4         1207460  
  4         24  
3 4     4   21017 use MooseX::StrictConstructor;
  4         54861  
  4         12  
4 4     4   25334 use MooseX::Types::Path::Class;
  4         355272  
  4         25  
5 4     4   5237 use Compress::Zlib qw(uncompress);
  4         155266  
  4         285  
6 4     4   1866 use Data::Stream::Bulk;
  4         328377  
  4         131  
7 4     4   2207 use Data::Stream::Bulk::Array;
  4         58352  
  4         147  
8 4     4   2314 use Data::Stream::Bulk::Path::Class;
  4         113377  
  4         142  
9 4     4   3533 use DateTime;
  4         259718  
  4         145  
10 4     4   1839 use Digest::SHA;
  4         8029  
  4         161  
11 4     4   1777 use File::Find::Rule;
  4         21807  
  4         25  
12 4     4   1653 use Git::PurePerl::Actor;
  4         12  
  4         210  
13 4     4   2100 use Git::PurePerl::Config;
  4         12  
  4         129  
14 4     4   1358 use Git::PurePerl::DirectoryEntry;
  4         1086  
  4         131  
15 4     4   1860 use Git::PurePerl::Loose;
  4         899  
  4         118  
16 4     4   1745 use Git::PurePerl::Object;
  4         9  
  4         130  
17 4     4   1862 use Git::PurePerl::NewDirectoryEntry;
  4         11  
  4         134  
18 4     4   2146 use Git::PurePerl::NewObject;
  4         987  
  4         136  
19 4     4   2013 use Git::PurePerl::NewObject::Blob;
  4         9  
  4         139  
20 4     4   2129 use Git::PurePerl::NewObject::Commit;
  4         1240  
  4         142  
21 4     4   1986 use Git::PurePerl::NewObject::Tag;
  4         10  
  4         158  
22 4     4   2061 use Git::PurePerl::NewObject::Tree;
  4         1143  
  4         144  
23 4     4   2282 use Git::PurePerl::Object::Tree;
  4         1096  
  4         149  
24 4     4   1946 use Git::PurePerl::Object::Blob;
  4         9  
  4         148  
25 4     4   2195 use Git::PurePerl::Object::Commit;
  4         1207  
  4         148  
26 4     4   2127 use Git::PurePerl::Object::Tag;
  4         1143  
  4         158  
27 4     4   28 use Git::PurePerl::Object::Tree;
  4         6  
  4         85  
28 4     4   2046 use Git::PurePerl::Pack;
  4         1022  
  4         136  
29 4     4   1955 use Git::PurePerl::Pack::WithIndex;
  4         1079  
  4         136  
30 4     4   2050 use Git::PurePerl::Pack::WithoutIndex;
  4         997  
  4         142  
31 4     4   1812 use Git::PurePerl::PackIndex;
  4         1151  
  4         141  
32 4     4   2123 use Git::PurePerl::PackIndex::Version1;
  4         1078  
  4         131  
33 4     4   2017 use Git::PurePerl::PackIndex::Version2;
  4         1016  
  4         125  
34 4     4   1854 use Git::PurePerl::Protocol;
  4         975  
  4         119  
35 4     4   2071 use IO::Digest;
  4         8847  
  4         94  
36 4     4   1722 use IO::Socket::INET;
  4         36989  
  4         22  
37 4     4   1354 use Path::Class;
  4         8  
  4         191  
38 4     4   17 use namespace::autoclean;
  4         7  
  4         28  
39              
40             our $VERSION = '0.52';
41             $VERSION = eval $VERSION;
42              
43             has 'directory' => (
44             is => 'ro',
45             isa => 'Path::Class::Dir',
46             required => 0,
47             coerce => 1
48             );
49              
50             has 'gitdir' => (
51             is => 'ro',
52             isa => 'Path::Class::Dir',
53             required => 1,
54             coerce => 1
55             );
56              
57             has 'loose' => (
58             is => 'rw',
59             isa => 'Git::PurePerl::Loose',
60             required => 0,
61             lazy_build => 1,
62             );
63              
64             has 'packs' => (
65             is => 'rw',
66             isa => 'ArrayRef[Git::PurePerl::Pack]',
67             required => 0,
68             auto_deref => 1,
69             lazy_build => 1,
70             );
71              
72             has 'description' => (
73             is => 'rw',
74             isa => 'Str',
75             lazy => 1,
76             default => sub {
77             my $self = shift;
78             file( $self->gitdir, 'description' )->slurp( chomp => 1 );
79             }
80             );
81              
82             has 'config' => (
83             is => 'ro',
84             isa => 'Git::PurePerl::Config',
85             lazy => 1,
86             default => sub {
87             my $self = shift;
88             Git::PurePerl::Config->new(git => $self);
89             }
90             );
91              
92             __PACKAGE__->meta->make_immutable;
93              
94             sub BUILDARGS {
95 9     9 1 16 my $class = shift;
96 9         94 my $params = $class->SUPER::BUILDARGS(@_);
97              
98 9   66     134 $params->{'gitdir'} ||= dir( $params->{'directory'}, '.git' );
99 9         574 return $params;
100             }
101              
102             sub BUILD {
103 9     9 0 17 my $self = shift;
104              
105 9 50       220 unless ( -d $self->gitdir ) {
106 0         0 confess $self->gitdir . ' is not a directory';
107             }
108 9 50 66     535 unless ( not defined $self->directory or -d $self->directory ) {
109 0         0 confess $self->directory . ' is not a directory';
110             }
111             }
112              
113             sub _build_loose {
114 9     9   16 my $self = shift;
115 9         185 my $loose_dir = dir( $self->gitdir, 'objects' );
116 9         525 return Git::PurePerl::Loose->new( directory => $loose_dir );
117             }
118              
119             sub _build_packs {
120 9     9   15 my $self = shift;
121 9         193 my $pack_dir = dir( $self->gitdir, 'objects', 'pack' );
122 9         291 my @packs;
123 9         204 foreach my $filename ( $pack_dir->children ) {
124 6 100       1077 next unless $filename =~ /\.pack$/;
125 3         175 push @packs,
126             Git::PurePerl::Pack::WithIndex->new( filename => $filename );
127             }
128 9         1263 return \@packs;
129             }
130              
131             sub _ref_names_recursive {
132 22     22   28 my ( $dir, $base, $names ) = @_;
133              
134 22         56 foreach my $file ( $dir->children ) {
135 5 50       1089 if ( -d $file ) {
136 0         0 my $reldir = $file->relative($dir);
137 0         0 my $subbase = $base . $reldir . "/";
138 0         0 _ref_names_recursive( $file, $subbase, $names );
139             } else {
140 5         177 push @$names, $base . $file->basename;
141             }
142             }
143             }
144              
145             sub ref_names {
146 11     11 0 8638 my $self = shift;
147 11         15 my @names;
148 11         22 foreach my $type (qw(heads remotes tags)) {
149 33         1856 my $dir = dir( $self->gitdir, 'refs', $type );
150 33 100       1281 next unless -d $dir;
151 22         559 my $base = "refs/$type/";
152 22         57 _ref_names_recursive( $dir, $base, \@names );
153             }
154 11         1283 my $packed_refs = file( $self->gitdir, 'packed-refs' );
155 11 100       564 if ( -f $packed_refs ) {
156 6         190 foreach my $line ( $packed_refs->slurp( chomp => 1 ) ) {
157 12 100       817 next if $line =~ /^#/;
158 6 50       12 next if $line =~ /^\^/;
159 6         21 my ( $sha1, $name ) = split ' ', $line;
160 6         17 push @names, $name;
161             }
162             }
163 11         220 return @names;
164             }
165              
166             sub refs_sha1 {
167 3     3 0 5 my $self = shift;
168 3         8 return map { $self->ref_sha1($_) } $self->ref_names;
  3         8  
169             }
170              
171             sub refs {
172 3     3 0 3 my $self = shift;
173 3         9 return map { $self->ref($_) } $self->ref_names;
  3         10  
174             }
175              
176             sub ref_sha1 {
177 30     30 0 44 my ( $self, $wantref ) = @_;
178 30         795 my $dir = dir( $self->gitdir, 'refs' );
179 30 50       1117 return unless -d $dir;
180              
181 30 50       759 if ($wantref eq "HEAD") {
182 0         0 my $file = file($self->gitdir, 'HEAD');
183 0   0     0 my $sha1 = file($file)->slurp
184             || confess("Error reading $file: $!");
185 0         0 chomp $sha1;
186 0         0 return _ensure_sha1_is_sha1( $self, $sha1 );
187             }
188              
189 30         238 foreach my $file ( File::Find::Rule->new->file->in($dir) ) {
190 16         15284 my $ref = 'refs/' . file($file)->relative($dir)->as_foreign('Unix');
191 16 50       6043 if ( $ref eq $wantref ) {
192 16   33     62 my $sha1 = file($file)->slurp
193             || confess("Error reading $file: $!");
194 16         3204 chomp $sha1;
195 16         53 return _ensure_sha1_is_sha1( $self, $sha1 );
196             }
197             }
198              
199 14         12622 my $packed_refs = file( $self->gitdir, 'packed-refs' );
200 14 50       770 if ( -f $packed_refs ) {
201 14         441 my $last_name;
202             my $last_sha1;
203 14         45 foreach my $line ( $packed_refs->slurp( chomp => 1 ) ) {
204 28 100       2042 next if $line =~ /^#/;
205 14         69 my ( $sha1, $name ) = split ' ', $line;
206 14         33 $sha1 =~ s/^\^//;
207 14   33     30 $name ||= $last_name;
208              
209 14 0 33     38 return _ensure_sha1_is_sha1( $self, $last_sha1 ) if $last_name and $last_name eq $wantref and $name ne $wantref;
      33        
210              
211 14         18 $last_name = $name;
212 14         27 $last_sha1 = $sha1;
213             }
214 14 50       44 return _ensure_sha1_is_sha1( $self, $last_sha1 ) if $last_name eq $wantref;
215             }
216 0         0 return undef;
217             }
218              
219             sub _ensure_sha1_is_sha1 {
220 30     30   48 my ( $self, $sha1 ) = @_;
221 30 50       102 return $self->ref_sha1($1) if $sha1 =~ /^ref: (.*)/;
222 30         183 return $sha1;
223             }
224              
225             sub ref {
226 21     21 0 42 my ( $self, $wantref ) = @_;
227 21         62 return $self->get_object( $self->ref_sha1($wantref) );
228             }
229              
230             sub master_sha1 {
231 3     3 0 16 my $self = shift;
232 3         10 return $self->ref_sha1('refs/heads/master');
233             }
234              
235             sub master {
236 9     9 1 23 my $self = shift;
237 9         36 return $self->ref('refs/heads/master');
238             }
239              
240             sub head_sha1 {
241 0     0 0 0 my $self = shift;
242 0         0 return $self->ref_sha1('HEAD');
243             }
244              
245             sub head {
246 0     0 0 0 my $self = shift;
247 0         0 return $self->ref('HEAD');
248             }
249              
250             sub get_object {
251 869     869 1 1020 my ( $self, $sha1 ) = @_;
252 869 100       1515 return unless $sha1;
253 866   66     1414 return $self->get_object_packed($sha1) || $self->get_object_loose($sha1);
254             }
255              
256             sub get_objects {
257 11     11 0 63 my ( $self, @sha1s ) = @_;
258 11         29 return map { $self->get_object($_) } @sha1s;
  801         1418  
259             }
260              
261             sub get_object_packed {
262 866     866 1 756 my ( $self, $sha1 ) = @_;
263              
264 866         19315 foreach my $pack ( $self->packs ) {
265 798         2002 my ( $kind, $size, $content ) = $pack->get_object($sha1);
266 798 50 33     4237 if ( defined($kind) && defined($size) && defined($content) ) {
      33        
267 798         1684 return $self->create_object( $sha1, $kind, $size, $content );
268             }
269             }
270             }
271              
272             sub get_object_loose {
273 68     68 1 84 my ( $self, $sha1 ) = @_;
274              
275 68         1449 my ( $kind, $size, $content ) = $self->loose->get_object($sha1);
276 68 50 33     486 if ( defined($kind) && defined($size) && defined($content) ) {
      33        
277 68         157 return $self->create_object( $sha1, $kind, $size, $content );
278             }
279             }
280              
281             sub create_object {
282 866     866 1 1055 my ( $self, $sha1, $kind, $size, $content ) = @_;
283 866 100       2165 if ( $kind eq 'commit' ) {
    100          
    50          
    0          
284 155         5331 return Git::PurePerl::Object::Commit->new(
285             sha1 => $sha1,
286             kind => $kind,
287             size => $size,
288             content => $content,
289             git => $self,
290             );
291             } elsif ( $kind eq 'tree' ) {
292 403         12953 return Git::PurePerl::Object::Tree->new(
293             sha1 => $sha1,
294             kind => $kind,
295             size => $size,
296             content => $content,
297             git => $self,
298             );
299             } elsif ( $kind eq 'blob' ) {
300 308         9951 return Git::PurePerl::Object::Blob->new(
301             sha1 => $sha1,
302             kind => $kind,
303             size => $size,
304             content => $content,
305             git => $self,
306             );
307             } elsif ( $kind eq 'tag' ) {
308 0         0 return Git::PurePerl::Object::Tag->new(
309             sha1 => $sha1,
310             kind => $kind,
311             size => $size,
312             content => $content,
313             git => $self,
314             );
315             } else {
316 0         0 confess "unknown kind $kind: $content";
317             }
318             }
319              
320             sub all_sha1s {
321 20     20 1 317 my $self = shift;
322 20         476 my $dir = dir( $self->gitdir, 'objects' );
323              
324 20         758 my @streams;
325 20         471 push @streams, $self->loose->all_sha1s;
326              
327 20         4350 foreach my $pack ( $self->packs ) {
328 6         32 push @streams, $pack->all_sha1s;
329             }
330              
331 20         781 return Data::Stream::Bulk::Cat->new( streams => \@streams );
332             }
333              
334             sub all_objects {
335 10     10 0 8695 my $self = shift;
336 10         30 my $stream = $self->all_sha1s;
337             return Data::Stream::Bulk::Filter->new(
338 11     11   1424 filter => sub { return [ $self->get_objects(@$_) ] },
339 10         1533 stream => $stream,
340             );
341             }
342              
343             sub put_object {
344 14     14 0 41 my ( $self, $object, $ref ) = @_;
345 14         330 $self->loose->put_object($object);
346              
347 14 100       1002 if ( $object->kind eq 'commit' ) {
348 4 50       14 $ref = 'master' unless $ref;
349 4         95 $self->update_ref( $ref, $object->sha1 );
350             }
351             }
352              
353             sub update_ref {
354 5     5 0 10 my ( $self, $refname, $sha1 ) = @_;
355 5         118 my $ref = file( $self->gitdir, 'refs', 'heads', $refname );
356 5         339 $ref->parent->mkpath;
357 5         554 my $ref_fh = $ref->openw;
358 5 50       737 $ref_fh->print($sha1) || die "Error writing to $ref";
359              
360             # FIXME is this always what we want?
361 5         176 my $head = file( $self->gitdir, 'HEAD' );
362 5         246 my $head_fh = $head->openw;
363 5 50       619 $head_fh->print("ref: refs/heads/$refname")
364             || die "Error writing to $head";
365             }
366              
367             sub init {
368 3     3 0 95362 my ( $class, %arguments ) = @_;
369              
370 3         9 my $directory = $arguments{directory};
371 3         6 my $git_dir;
372              
373 3 100       11 unless ( defined $directory ) {
374             $git_dir = $arguments{gitdir}
375 1   33     5 || confess
376             "init() needs either a 'directory' or a 'gitdir' argument";
377             } else {
378 2 50       9 if ( not defined $arguments{gitdir} ) {
379 2         8 $git_dir = $arguments{gitdir} = dir( $directory, '.git' );
380             }
381 2         73 dir($directory)->mkpath;
382             }
383              
384 3         477 dir($git_dir)->mkpath;
385 3         466 dir( $git_dir, 'refs', 'tags' )->mkpath;
386 3         556 dir( $git_dir, 'objects', 'info' )->mkpath;
387 3         534 dir( $git_dir, 'objects', 'pack' )->mkpath;
388 3         376 dir( $git_dir, 'branches' )->mkpath;
389 3         392 dir( $git_dir, 'hooks' )->mkpath;
390              
391 3 100       367 my $bare = defined($directory) ? 'false' : 'true';
392 3         15 $class->_add_file(
393             file( $git_dir, 'config' ),
394             "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = $bare\n\tlogallrefupdates = true\n"
395             );
396 3         156 $class->_add_file( file( $git_dir, 'description' ),
397             "Unnamed repository; edit this file to name it for gitweb.\n" );
398 3         95 $class->_add_file(
399             file( $git_dir, 'hooks', 'applypatch-msg' ),
400             "# add shell script and make executable to enable\n"
401             );
402 3         92 $class->_add_file( file( $git_dir, 'hooks', 'post-commit' ),
403             "# add shell script and make executable to enable\n" );
404 3         101 $class->_add_file(
405             file( $git_dir, 'hooks', 'post-receive' ),
406             "# add shell script and make executable to enable\n"
407             );
408 3         90 $class->_add_file( file( $git_dir, 'hooks', 'post-update' ),
409             "# add shell script and make executable to enable\n" );
410 3         93 $class->_add_file(
411             file( $git_dir, 'hooks', 'pre-applypatch' ),
412             "# add shell script and make executable to enable\n"
413             );
414 3         89 $class->_add_file( file( $git_dir, 'hooks', 'pre-commit' ),
415             "# add shell script and make executable to enable\n" );
416 3         88 $class->_add_file( file( $git_dir, 'hooks', 'pre-rebase' ),
417             "# add shell script and make executable to enable\n" );
418 3         83 $class->_add_file( file( $git_dir, 'hooks', 'update' ),
419             "# add shell script and make executable to enable\n" );
420              
421 3         85 dir( $git_dir, 'info' )->mkpath;
422 3         383 $class->_add_file( file( $git_dir, 'info', 'exclude' ),
423             "# *.[oa]\n# *~\n" );
424              
425 3         204 return $class->new(%arguments);
426             }
427              
428             sub checkout {
429 4     4 0 4147 my ( $self, $directory, $tree ) = @_;
430 4   33     26 $directory ||= $self->directory;
431 4   33     53 $tree ||= $self->master->tree;
432 4 50       123 confess("Missing tree") unless $tree;
433 4         121 foreach my $directory_entry ( $tree->directory_entries ) {
434 5         138 my $filename = file( $directory, $directory_entry->filename );
435 5         493 my $sha1 = $directory_entry->sha1;
436 5         130 my $mode = $directory_entry->mode;
437 5         18 my $object = $self->get_object($sha1);
438 5 50       138 if ( $object->kind eq 'blob' ) {
    0          
439 5         116 $self->_add_file( $filename, $object->content );
440 5 50       281 chmod( oct( '0' . $mode ), $filename )
441             || die "Error chmoding $filename to $mode: $!";
442             } elsif ( $object->kind eq 'tree' ) {
443 0         0 dir($filename)->mkpath;
444 0         0 $self->checkout( $filename, $object );
445             } else {
446 0         0 die $object->kind;
447             }
448             }
449             }
450              
451             sub clone {
452 1     1 0 851 my $self = shift;
453              
454 1         2 my $remote;
455 1 50       4 if (@_ == 2) {
456             # For backwards compatibility
457 1         2 $remote = "git://$_[0]";
458 1 50       6 $remote .= "/" unless $_[1] =~ m{^/};
459 1         2 $remote .= $_[1];
460             } else {
461 0         0 $remote = shift;
462             }
463              
464 1         36 my $protocol = Git::PurePerl::Protocol->new(
465             remote => $remote,
466             );
467              
468 1         5 my $sha1s = $protocol->connect;
469 1         5 my $head = $sha1s->{HEAD};
470 1         15 my $data = $protocol->fetch_pack($head);
471              
472 1         29 my $filename
473             = file( $self->gitdir, 'objects', 'pack', 'pack-' . $head . '.pack' );
474 1         141 $self->_add_file( $filename, $data );
475              
476 1         95 my $pack
477             = Git::PurePerl::Pack::WithoutIndex->new( filename => $filename );
478 1         5 $pack->create_index();
479              
480 1         194 $self->update_ref( master => $head );
481             }
482              
483             sub _add_file {
484 39     39   1939 my ( $class, $filename, $contents ) = @_;
485 39   33     90 my $fh = $filename->openw || confess "Error opening to $filename: $!";
486 39         5591 binmode($fh); #important for Win32
487 39 50       130 $fh->print($contents) || confess "Error writing to $filename: $!";
488 39 50       686 $fh->close || confess "Error closing $filename: $!";
489             }
490              
491             1;
492              
493             __END__
494              
495             =head1 NAME
496              
497             Git::PurePerl - A Pure Perl interface to Git repositories
498              
499             =head1 SYNOPSIS
500              
501             my $git = Git::PurePerl->new(
502             directory => '/path/to/git/'
503             );
504             $git->master->committer;
505             $git->master->comment;
506             $git->get_object($git->master->tree);
507              
508             =head1 DESCRIPTION
509              
510             This module is a Pure Perl interface to Git repositories.
511              
512             It was mostly based on Grit L<http://grit.rubyforge.org/>.
513              
514             =head1 METHODS
515              
516             =over 4
517              
518             =item master
519              
520             =item get_object
521              
522             =item get_object_packed
523              
524             =item get_object_loose
525              
526             =item create_object
527              
528             =item all_sha1s
529              
530             =back
531              
532             =head1 MAINTAINANCE
533              
534             This module is maintained in git at L<http://github.com/broquaint/git-pureperl/>.
535              
536             Patches are welcome, please come speak to one of the L<Gitalist> team
537             on C<< #gitalist >>.
538              
539             =head1 AUTHOR
540              
541             Leon Brocard <acme@astray.com>
542              
543             =head1 CONTRIBUTORS
544              
545             =over 4
546              
547             =item Chris Reinhardt
548              
549             =item Tomas (t0m) Doran
550              
551             =item Dan (broquaint) Brook
552              
553             =item Alex Vandiver
554              
555             =item Dagfinn Ilmari MannsE<aring>ker
556              
557             =back
558              
559             =head1 COPYRIGHT
560              
561             Copyright (C) 2008, Leon Brocard and the above mentioned contributors.
562              
563             =head1 LICENSE
564              
565             This module is free software; you can redistribute it or
566             modify it under the same terms as Perl itself.
567              
568             =cut