3 use MooseX::StrictConstructor;
4 use MooseX::Types::Path::Class;
5 use Compress::Zlib qw(uncompress);
6 use Data::Stream::Bulk;
7 use Data::Stream::Bulk::Array;
8 use Data::Stream::Bulk::Path::Class;
12 use Git::PurePerl::Actor;
13 use Git::PurePerl::Config;
14 use Git::PurePerl::DirectoryEntry;
15 use Git::PurePerl::Loose;
16 use Git::PurePerl::Object;
17 use Git::PurePerl::NewDirectoryEntry;
18 use Git::PurePerl::NewObject;
19 use Git::PurePerl::NewObject::Blob;
20 use Git::PurePerl::NewObject::Commit;
21 use Git::PurePerl::NewObject::Tag;
22 use Git::PurePerl::NewObject::Tree;
23 use Git::PurePerl::Object::Tree;
24 use Git::PurePerl::Object::Blob;
25 use Git::PurePerl::Object::Commit;
26 use Git::PurePerl::Object::Tag;
27 use Git::PurePerl::Object::Tree;
28 use Git::PurePerl::Pack;
29 use Git::PurePerl::Pack::WithIndex;
30 use Git::PurePerl::Pack::WithoutIndex;
31 use Git::PurePerl::PackIndex;
32 use Git::PurePerl::PackIndex::Version1;
33 use Git::PurePerl::PackIndex::Version2;
34 use Git::PurePerl::Protocol;
38 use namespace::autoclean;
40 our $VERSION = '0.46';
41 $VERSION = eval $VERSION;
45 isa => 'Path::Class::Dir',
52 isa => 'Path::Class::Dir',
59 isa => 'Git::PurePerl::Loose',
66 isa => 'ArrayRef[Git::PurePerl::Pack]',
72 has 'description' => (
78 file( $self->gitdir, 'description' )->slurp( chomp => 1 );
84 isa => 'Git::PurePerl::Config',
88 Git::PurePerl::Config->new(git => $self);
92 __PACKAGE__->meta->make_immutable;
96 my $params = $class->SUPER::BUILDARGS(@_);
98 $params->{'gitdir'} ||= dir( $params->{'directory'}, '.git' );
105 unless ( -d $self->gitdir ) {
106 confess $self->gitdir . ' is not a directory';
108 unless ( not defined $self->directory or -d $self->directory ) {
109 confess $self->directory . ' is not a directory';
115 my $loose_dir = dir( $self->gitdir, 'objects' );
116 return Git::PurePerl::Loose->new( directory => $loose_dir );
121 my $pack_dir = dir( $self->gitdir, 'objects', 'pack' );
123 foreach my $filename ( $pack_dir->children ) {
124 next unless $filename =~ /\.pack$/;
126 Git::PurePerl::Pack::WithIndex->new( filename => $filename );
131 sub _ref_names_recursive {
132 my ( $dir, $base, $names ) = @_;
134 foreach my $file ( $dir->children ) {
136 my $reldir = $file->relative($dir);
137 my $subbase = $base . $reldir . "/";
138 _ref_names_recursive( $file, $subbase, $names );
140 push @$names, $base . $file->basename;
148 foreach my $type (qw(heads remotes tags)) {
149 my $dir = dir( $self->gitdir, 'refs', $type );
151 my $base = "refs/$type/";
152 _ref_names_recursive( $dir, $base, \@names );
154 my $packed_refs = file( $self->gitdir, 'packed-refs' );
155 if ( -f $packed_refs ) {
156 foreach my $line ( $packed_refs->slurp( chomp => 1 ) ) {
157 next if $line =~ /^#/;
158 my ( $sha1, my $name ) = split ' ', $line;
167 return map { $self->ref_sha1($_) } $self->ref_names;
172 return map { $self->ref($_) } $self->ref_names;
176 my ( $self, $wantref ) = @_;
178 my $dir = dir( $self->gitdir, 'refs' );
179 return unless -d $dir;
181 if ($wantref eq "HEAD") {
182 my $file = file($self->gitdir, 'HEAD');
183 my $sha1 = file($file)->slurp
184 || confess("Error reading $file: $!");
186 return $self->ref_sha1($1) if $sha1 =~ /^ref: (.*)/;
190 foreach my $file ( File::Find::Rule->new->file->in($dir) ) {
191 my $ref = 'refs/' . file($file)->relative($dir)->as_foreign('Unix');
192 if ( $ref eq $wantref ) {
193 my $sha1 = file($file)->slurp
194 || confess("Error reading $file: $!");
196 return $self->ref_sha1($1) if $sha1 =~ /^ref: (.*)/;
201 my $packed_refs = file( $self->gitdir, 'packed-refs' );
202 if ( -f $packed_refs ) {
203 foreach my $line ( $packed_refs->slurp( chomp => 1 ) ) {
204 next if $line =~ /^#/;
205 my ( $sha1, my $name ) = split ' ', $line;
206 if ( $name eq $wantref ) {
207 return $self->ref_sha1($1) if $sha1 =~ /^ref: (.*)/;
216 my ( $self, $wantref ) = @_;
217 return $self->get_object( $self->ref_sha1($wantref) );
222 return $self->ref_sha1('refs/heads/master');
227 return $self->ref('refs/heads/master');
232 return $self->ref_sha1('HEAD');
237 return $self->ref('HEAD');
241 my ( $self, $sha1 ) = @_;
243 return $self->get_object_packed($sha1) || $self->get_object_loose($sha1);
247 my ( $self, @sha1s ) = @_;
248 return map { $self->get_object($_) } @sha1s;
251 sub get_object_packed {
252 my ( $self, $sha1 ) = @_;
254 foreach my $pack ( $self->packs ) {
255 my ( $kind, $size, $content ) = $pack->get_object($sha1);
256 if ( defined($kind) && defined($size) && defined($content) ) {
257 return $self->create_object( $sha1, $kind, $size, $content );
262 sub get_object_loose {
263 my ( $self, $sha1 ) = @_;
265 my ( $kind, $size, $content ) = $self->loose->get_object($sha1);
266 if ( defined($kind) && defined($size) && defined($content) ) {
267 return $self->create_object( $sha1, $kind, $size, $content );
272 my ( $self, $sha1, $kind, $size, $content ) = @_;
273 if ( $kind eq 'commit' ) {
274 return Git::PurePerl::Object::Commit->new(
281 } elsif ( $kind eq 'tree' ) {
282 return Git::PurePerl::Object::Tree->new(
289 } elsif ( $kind eq 'blob' ) {
290 return Git::PurePerl::Object::Blob->new(
297 } elsif ( $kind eq 'tag' ) {
298 return Git::PurePerl::Object::Tag->new(
306 confess "unknown kind $kind: $content";
312 my $dir = dir( $self->gitdir, 'objects' );
315 push @streams, $self->loose->all_sha1s;
317 foreach my $pack ( $self->packs ) {
318 push @streams, $pack->all_sha1s;
321 return Data::Stream::Bulk::Cat->new( streams => \@streams );
326 my $stream = $self->all_sha1s;
327 return Data::Stream::Bulk::Filter->new(
328 filter => sub { return [ $self->get_objects(@$_) ] },
334 my ( $self, $object, $ref ) = @_;
335 $self->loose->put_object($object);
337 if ( $object->kind eq 'commit' ) {
338 $ref = 'master' unless $ref;
339 $self->update_ref( $ref, $object->sha1 );
344 my ( $self, $refname, $sha1 ) = @_;
345 my $ref = file( $self->gitdir, 'refs', 'heads', $refname );
346 $ref->parent->mkpath;
347 my $ref_fh = $ref->openw;
348 $ref_fh->print($sha1) || die "Error writing to $ref";
350 # FIXME is this always what we want?
351 my $head = file( $self->gitdir, 'HEAD' );
352 my $head_fh = $head->openw;
353 $head_fh->print("ref: refs/heads/$refname")
354 || die "Error writing to $head";
358 my ( $class, %arguments ) = @_;
360 my $directory = $arguments{directory};
363 unless ( defined $directory ) {
364 $git_dir = $arguments{gitdir}
366 "init() needs either a 'directory' or a 'gitdir' argument";
368 if ( not defined $arguments{gitdir} ) {
369 $git_dir = $arguments{gitdir} = dir( $directory, '.git' );
371 dir($directory)->mkpath;
374 dir($git_dir)->mkpath;
375 dir( $git_dir, 'refs', 'tags' )->mkpath;
376 dir( $git_dir, 'objects', 'info' )->mkpath;
377 dir( $git_dir, 'objects', 'pack' )->mkpath;
378 dir( $git_dir, 'branches' )->mkpath;
379 dir( $git_dir, 'hooks' )->mkpath;
381 my $bare = defined($directory) ? 'false' : 'true';
383 file( $git_dir, 'config' ),
384 "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = $bare\n\tlogallrefupdates = true\n"
386 $class->_add_file( file( $git_dir, 'description' ),
387 "Unnamed repository; edit this file to name it for gitweb.\n" );
389 file( $git_dir, 'hooks', 'applypatch-msg' ),
390 "# add shell script and make executable to enable\n"
392 $class->_add_file( file( $git_dir, 'hooks', 'post-commit' ),
393 "# add shell script and make executable to enable\n" );
395 file( $git_dir, 'hooks', 'post-receive' ),
396 "# add shell script and make executable to enable\n"
398 $class->_add_file( file( $git_dir, 'hooks', 'post-update' ),
399 "# add shell script and make executable to enable\n" );
401 file( $git_dir, 'hooks', 'pre-applypatch' ),
402 "# add shell script and make executable to enable\n"
404 $class->_add_file( file( $git_dir, 'hooks', 'pre-commit' ),
405 "# add shell script and make executable to enable\n" );
406 $class->_add_file( file( $git_dir, 'hooks', 'pre-rebase' ),
407 "# add shell script and make executable to enable\n" );
408 $class->_add_file( file( $git_dir, 'hooks', 'update' ),
409 "# add shell script and make executable to enable\n" );
411 dir( $git_dir, 'info' )->mkpath;
412 $class->_add_file( file( $git_dir, 'info', 'exclude' ),
413 "# *.[oa]\n# *~\n" );
415 return $class->new(%arguments);
419 my ( $self, $directory, $tree ) = @_;
420 $directory ||= $self->directory;
421 $tree ||= $self->master->tree;
422 confess("Missing tree") unless $tree;
423 foreach my $directory_entry ( $tree->directory_entries ) {
424 my $filename = file( $directory, $directory_entry->filename );
425 my $sha1 = $directory_entry->sha1;
426 my $mode = $directory_entry->mode;
427 my $object = $self->get_object($sha1);
428 if ( $object->kind eq 'blob' ) {
429 $self->_add_file( $filename, $object->content );
430 chmod( oct( '0' . $mode ), $filename )
431 || die "Error chmoding $filename to $mode: $!";
432 } elsif ( $object->kind eq 'tree' ) {
433 dir($filename)->mkpath;
434 $self->checkout( $filename, $object );
442 my ( $self, $hostname, $project ) = @_;
443 my $protocol = Git::PurePerl::Protocol->new(
444 hostname => $hostname,
448 my $sha1s = $protocol->connect;
449 my $head = $sha1s->{HEAD};
450 my $data = $protocol->fetch_pack($head);
453 = file( $self->gitdir, 'objects', 'pack', 'pack-' . $head . '.pack' );
454 $self->_add_file( $filename, $data );
457 = Git::PurePerl::Pack::WithoutIndex->new( filename => $filename );
458 $pack->create_index();
460 $self->update_ref( master => $head );
464 my ( $class, $filename, $contents ) = @_;
465 my $fh = $filename->openw || confess "Error opening to $filename: $!";
466 binmode($fh); #important for Win32
467 $fh->print($contents) || confess "Error writing to $filename: $!";
468 $fh->close || confess "Error closing $filename: $!";
477 Git::PurePerl - A Pure Perl interface to Git repositories
481 my $git = Git::PurePerl->new(
482 directory => '/path/to/git/'
484 $git->master->committer;
485 $git->master->comment;
486 $git->get_object($git->master->tree);
490 This module is a Pure Perl interface to Git repositories.
492 It was mostly based on Grit L<http://grit.rubyforge.org/>.
502 =item get_object_packed
504 =item get_object_loose
514 This module is maintained in git at L<http://github.com/bobtfish/git-pureperl/>.
516 Patches are welcome, please come speak to one of the L<Gitalist> team
521 Leon Brocard <acme@astray.com>
527 =item Chris Reinhardt
529 =item Tomas (t0m) Doran
531 =item Dan (broquaint) Brook
535 =item Dagfinn Ilmari MannsE<aring>ker
541 Copyright (C) 2008, Leon Brocard and the above mentioned contributors.
545 This module is free software; you can redistribute it or
546 modify it under the same terms as Perl itself.