aboutsummaryrefslogtreecommitdiffstats
path: root/lib/LXRng/Repo
diff options
context:
space:
mode:
Diffstat (limited to 'lib/LXRng/Repo')
-rw-r--r--lib/LXRng/Repo/Directory.pm17
-rw-r--r--lib/LXRng/Repo/File.pm17
-rw-r--r--lib/LXRng/Repo/Git.pm111
-rw-r--r--lib/LXRng/Repo/Git/Directory.pm56
-rw-r--r--lib/LXRng/Repo/Git/File.pm80
-rw-r--r--lib/LXRng/Repo/Git/Iterator.pm33
-rw-r--r--lib/LXRng/Repo/Git/TarFile.pm98
-rw-r--r--lib/LXRng/Repo/Plain.pm38
-rw-r--r--lib/LXRng/Repo/Plain/Directory.pm45
-rw-r--r--lib/LXRng/Repo/Plain/File.pm51
-rw-r--r--lib/LXRng/Repo/Plain/Iterator.pm29
-rw-r--r--lib/LXRng/Repo/TmpFile.pm30
12 files changed, 605 insertions, 0 deletions
diff --git a/lib/LXRng/Repo/Directory.pm b/lib/LXRng/Repo/Directory.pm
new file mode 100644
index 0000000..ad892b2
--- /dev/null
+++ b/lib/LXRng/Repo/Directory.pm
@@ -0,0 +1,17 @@
+package LXRng::Repo::Directory;
+
+use strict;
+
+sub name {
+ my ($self) = @_;
+
+ return $$self{'name'};
+}
+
+sub node {
+ my ($self) = @_;
+
+ $self->name =~ m,([^/]+)/?$, and return $1;
+}
+
+1;
diff --git a/lib/LXRng/Repo/File.pm b/lib/LXRng/Repo/File.pm
new file mode 100644
index 0000000..7df5a04
--- /dev/null
+++ b/lib/LXRng/Repo/File.pm
@@ -0,0 +1,17 @@
+package LXRng::Repo::File;
+
+use strict;
+
+sub name {
+ my ($self) = @_;
+
+ return $$self{'name'};
+}
+
+sub node {
+ my ($self) = @_;
+
+ $self->name =~ m,([^/]+)$, and return $1;
+}
+
+1;
diff --git a/lib/LXRng/Repo/Git.pm b/lib/LXRng/Repo/Git.pm
new file mode 100644
index 0000000..2d6ea33
--- /dev/null
+++ b/lib/LXRng/Repo/Git.pm
@@ -0,0 +1,111 @@
+package LXRng::Repo::Git;
+
+use strict;
+use Memoize;
+use LXRng::Cached;
+use LXRng::Repo::Git::Iterator;
+use LXRng::Repo::Git::File;
+use LXRng::Repo::Git::Directory;
+
+sub _git_cmd {
+ my ($self, $cmd, @args) = @_;
+
+ my $git;
+ my $pid = open($git, "-|");
+ die $! unless defined $pid;
+ if ($pid == 0) {
+ $ENV{'GIT_DIR'} = $$self{'root'};
+ exec('git', $cmd, @args);
+ warn $!;
+ kill(9, $$);
+ }
+ return $git;
+}
+
+sub new {
+ my ($class, $root, %args) = @_;
+
+ memoize('_release_timestamp');
+
+ return bless({root => $root, %args}, $class);
+}
+
+sub _release_timestamp {
+ my ($self, $release) = @_;
+
+ my $cinfo = $self->_git_cmd('cat-file', 'commit', $release);
+
+ my $time;
+ while (<$cinfo>) {
+ $time = $1 if /^author .*? (\d+(?: [-+]\d+|))$/ ;
+ $time ||= $1 if /^committer .*? (\d+(?: [-+]\d+|))$/ ;
+ }
+
+ return $time;
+}
+
+sub _use_author_timestamp {
+ my ($self) = @_;
+
+ return $$self{'author_timestamp'};
+}
+
+sub _sort_key {
+ my ($v) = @_;
+
+ $v =~ s/(\d+)/sprintf("%05d", $1)/ge;
+ return $v;
+}
+
+sub allversions {
+ my ($self) = @_;
+
+ cached {
+ my @tags;
+ my $tags = $self->_git_cmd('tag', '-l');
+ while (<$tags>) {
+ chomp;
+ next if $$self{'release_re'} and $_ !~ $$self{'release_re'};
+ push(@tags, $_);
+ }
+
+ return (sort {_sort_key($b) cmp _sort_key($a) } @tags);
+ };
+}
+
+sub node {
+ my ($self, $path, $release) = @_;
+
+ $path =~ s,^/+,,;
+ $path =~ s,/+$,,;
+
+ if ($path eq '') {
+ open(my $tag, '<', $$self{'root'}.'/refs/tags/'.$release)
+ or return undef;
+ my $ref = <$tag>;
+ close($tag);
+ chomp($ref);
+ return LXRng::Repo::Git::Directory->new($self, '', $ref);
+ }
+
+ my $git = $self->_git_cmd('ls-tree', $release, $path);
+ my ($mode, $type, $ref, $gitpath) = split(" ", <$git>);
+
+ if ($type eq 'tree') {
+ return LXRng::Repo::Git::Directory->new($self, $path, $ref, $release);
+ }
+ elsif ($type eq 'blob') {
+ return LXRng::Repo::Git::File->new($self, $path, $ref, $release);
+ }
+ else {
+ return undef;
+ }
+}
+
+sub iterator {
+ my ($self, $release) = @_;
+
+ return LXRng::Repo::Git::Iterator->new($self, $release);
+}
+
+1;
diff --git a/lib/LXRng/Repo/Git/Directory.pm b/lib/LXRng/Repo/Git/Directory.pm
new file mode 100644
index 0000000..592e608
--- /dev/null
+++ b/lib/LXRng/Repo/Git/Directory.pm
@@ -0,0 +1,56 @@
+package LXRng::Repo::Git::Directory;
+
+use strict;
+
+use base qw(LXRng::Repo::Directory);
+
+sub new {
+ my ($class, $repo, $name, $ref, $rel) = @_;
+
+ $name =~ s,/*$,/,;
+ return bless({repo => $repo, name => $name, ref => $ref, rel => $rel},
+ $class);
+}
+
+sub time {
+ my ($self) = @_;
+
+ return 0;
+# return $$self{'stat'}[9];
+}
+
+sub size {
+ my ($self) = @_;
+
+ return '';
+}
+
+sub contents {
+ my ($self) = @_;
+
+ my $git = $$self{'repo'}->_git_cmd('ls-tree', $$self{'ref'});
+
+ my $prefix = $$self{'name'};
+ $prefix =~ s,^/+,,;
+ my (@dirs, @files);
+ while (<$git>) {
+ chomp;
+ my ($mode, $type, $ref, $node) = split(" ", $_);
+ if ($type eq 'tree') {
+ push(@dirs, LXRng::Repo::Git::Directory->new($$self{'repo'},
+ $prefix.$node,
+ $ref,
+ $$self{'rel'}));
+ }
+ elsif ($type eq 'blob') {
+ push(@files, LXRng::Repo::Git::File->new($$self{'repo'},
+ $prefix.$node,
+ $ref,
+ $$self{'rel'}));
+ }
+ }
+
+ return (@dirs, @files);
+}
+
+1;
diff --git a/lib/LXRng/Repo/Git/File.pm b/lib/LXRng/Repo/Git/File.pm
new file mode 100644
index 0000000..b0bb9a3
--- /dev/null
+++ b/lib/LXRng/Repo/Git/File.pm
@@ -0,0 +1,80 @@
+package LXRng::Repo::Git::File;
+
+use strict;
+
+use base qw(LXRng::Repo::File);
+use LXRng::Repo::TmpFile;
+use File::Temp qw(tempdir);
+
+sub new {
+ my ($class, $repo, $name, $ref, $rel) = @_;
+
+ return bless({repo => $repo, name => $name, ref => $ref, rel => $rel},
+ $class);
+}
+
+sub time {
+ my ($self) = @_;
+
+ if ($$self{'repo'}->_use_author_timestamp) {
+ # This is painfully slow. It is only performed index-time,
+ # but that might stil be bad enough that you would want to
+ # just use the release-timestamp insted.
+ my $cinfo = $$self{'repo'}->_git_cmd('log', '--pretty=raw',
+ '--max-count=1', '--all',
+ '..'.$$self{'rel'},
+ '--', $self->name);
+
+ my $time;
+ while (<$cinfo>) {
+ $time = $1 if /^author .*? (\d+(?: [-+]\d+|))$/ ;
+ $time ||= $1 if /^committer .*? (\d+(?: [-+]\d+|))$/ ;
+ }
+
+ return $time if $time;
+ }
+
+ return $$self{'repo'}->_release_timestamp($$self{'rel'});
+}
+
+sub size {
+ my ($self) = @_;
+
+ my $git = $$self{'repo'}->_git_cmd('cat-file', '-s', $$self{'ref'});
+ my $size = <$git>;
+ close($git);
+ chomp($size);
+ return $size;
+}
+
+sub handle {
+ my ($self) = @_;
+
+ return $$self{'repo'}->_git_cmd('cat-file', 'blob', $$self{'ref'});
+}
+
+sub revision {
+ my ($self) = @_;
+
+ return $$self{'ref'};
+}
+
+sub phys_path {
+ my ($self) = @_;
+
+ my $tmpdir = tempdir() or die($!);
+ open(my $phys, ">", $tmpdir.'/'.$self->node) or die($!);
+
+ my $handle = $self->handle();
+ my $buf = '';
+ while (sysread($handle, $buf, 64*1024) > 0) {
+ print($phys $buf) or die($!);
+ }
+ close($handle);
+ close($phys) or die($!);
+
+ return LXRng::Repo::TmpFile->new(dir => $tmpdir,
+ node => $self->node);
+}
+
+1;
diff --git a/lib/LXRng/Repo/Git/Iterator.pm b/lib/LXRng/Repo/Git/Iterator.pm
new file mode 100644
index 0000000..978e584
--- /dev/null
+++ b/lib/LXRng/Repo/Git/Iterator.pm
@@ -0,0 +1,33 @@
+package LXRng::Repo::Git::Iterator;
+
+use strict;
+use LXRng::Repo::Git::File;
+
+sub new {
+ my ($class, $repo, $release) = @_;
+
+ my @refs;
+ my $git = $repo->_git_cmd('ls-tree', '-r', $release);
+ while (<$git>) {
+ if (/\S+\s+blob\s+(\S+)\s+(\S+)/) {
+ push(@refs, [$2, $1]);
+ }
+ }
+ close($git);
+
+ return bless({refs => \@refs, repo => $repo, rel => $release}, $class);
+}
+
+sub next {
+ my ($self) = @_;
+
+ return undef unless @{$$self{'refs'}} > 0;
+ my $file = shift(@{$$self{'refs'}});
+
+ return LXRng::Repo::Git::File->new($$self{'repo'},
+ $$file[0],
+ $$file[1],
+ $$self{'rel'});
+}
+
+1;
diff --git a/lib/LXRng/Repo/Git/TarFile.pm b/lib/LXRng/Repo/Git/TarFile.pm
new file mode 100644
index 0000000..33af87d
--- /dev/null
+++ b/lib/LXRng/Repo/Git/TarFile.pm
@@ -0,0 +1,98 @@
+package LXRng::Repo::Git::TarFile;
+
+use strict;
+use File::Temp qw(tempdir);
+use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);
+
+use base qw(LXRng::Repo::File);
+
+sub new {
+ my ($class, $tar, $ref) = @_;
+
+ return bless({tar => $tar, ref => $ref}, $class);
+}
+
+sub name {
+ my ($self) = @_;
+
+ return $$self{'tar'}->name();
+}
+
+sub node {
+ my ($self) = @_;
+
+ $self->name =~ m,.*/([^/]+), and return $1;
+}
+
+sub time {
+ my ($self) = @_;
+
+ return $$self{'tar'}->mtime();
+}
+
+sub size {
+ my ($self) = @_;
+
+ return $$self{'tar'}->size;
+}
+
+sub phys_path {
+ my ($self) = @_;
+
+ my $tmpdir = tempdir() or die($!);
+ open(my $phys, ">", $tmpdir.'/'.$self->node);
+
+ my $data = $$self{'tar'}->get_content_by_ref();
+ my $len = $$self{'tar'}->size();
+ my $pos = 0;
+ while ($pos < $len) {
+ print($phys substr($$data, $pos, 64*1024));
+ $pos += 64*1024;
+ }
+ close($phys);
+
+ return LXRng::Repo::Git::TarFile::Virtual->new(dir => $tmpdir,
+ node => $self->node);
+}
+
+sub handle {
+ my ($self) = @_;
+
+ my $data = $$self{'tar'}->get_content_by_ref();
+ open(my $fh, "<", $data);
+
+ return $fh;
+}
+
+sub revision {
+ my ($self) = @_;
+
+ $$self{'ref'} ||= $self->time.'.'.$self->size;
+ return $$self{'ref'};
+}
+
+package LXRng::Repo::Git::TarFile::Virtual;
+
+use strict;
+use overload '""' => \&filename;
+
+sub new {
+ my ($class, %args) = @_;
+
+ return bless(\%args, $class);
+}
+
+sub filename {
+ my ($self) = @_;
+
+ return $$self{'dir'}.'/'.$$self{'node'};
+}
+
+sub DESTROY {
+ my ($self) = @_;
+ unlink($$self{'dir'}.'/'.$$self{'node'});
+ rmdir($$self{'dir'});
+# kill(9, $$self{'pid'});
+}
+
+1;
diff --git a/lib/LXRng/Repo/Plain.pm b/lib/LXRng/Repo/Plain.pm
new file mode 100644
index 0000000..c30835e
--- /dev/null
+++ b/lib/LXRng/Repo/Plain.pm
@@ -0,0 +1,38 @@
+package LXRng::Repo::Plain;
+
+use strict;
+use LXRng::Repo::Plain::Iterator;
+use LXRng::Repo::Plain::File;
+use LXRng::Repo::Plain::Directory;
+
+sub new {
+ my ($class, $root) = @_;
+
+ return bless({root => $root}, $class);
+}
+
+sub allversions {
+ my ($self) = @_;
+
+ my @ver = (sort
+ grep { $_ ne "." and $_ ne ".." }
+ map { substr($_, length($$self{'root'})) =~ /([^\/]*)/; $1 }
+ glob($$self{'root'}."*/"));
+
+ return @ver;
+}
+
+sub node {
+ my ($self, $path, $release) = @_;
+
+ my $realpath = join('/', $$self{'root'}, $release, $path);
+ return LXRng::Repo::Plain::File->new($path, $realpath);
+}
+
+sub iterator {
+ my ($self, $release) = @_;
+
+ return LXRng::Repo::Plain::Iterator->new($self->node('', $release));
+}
+
+1;
diff --git a/lib/LXRng/Repo/Plain/Directory.pm b/lib/LXRng/Repo/Plain/Directory.pm
new file mode 100644
index 0000000..8d7e701
--- /dev/null
+++ b/lib/LXRng/Repo/Plain/Directory.pm
@@ -0,0 +1,45 @@
+package LXRng::Repo::Plain::Directory;
+
+use strict;
+
+use base qw(LXRng::Repo::Directory);
+
+sub new {
+ my ($class, $name, $path, $stat) = @_;
+
+ $name =~ s,(.)/*$,$1/,;
+ $path =~ s,/*$,/,;
+ return bless({name => $name, path => $path, stat => $stat}, $class);
+}
+
+sub time {
+ my ($self) = @_;
+
+ return $$self{'stat'}[9];
+}
+
+sub size {
+ my ($self) = @_;
+
+ return '';
+}
+
+sub contents {
+ my ($self) = @_;
+
+ my (@dirs, @files);
+ my ($dir, $node);
+ opendir($dir, $$self{'path'}) or die("Can't open ".$$self{'path'}.": $!");
+ while (defined($node = readdir($dir))) {
+ next if $node =~ /^\.|~$|\.orig$/;
+ next if $node eq 'CVS';
+
+ push(@files, LXRng::Repo::Plain::File->new($$self{'name'}.$node,
+ $$self{'path'}.$node));
+ }
+ closedir($dir);
+
+ return sort { ref($a) cmp ref($b) || $$a{'name'} cmp $$b{'name'} } @files;
+}
+
+1;
diff --git a/lib/LXRng/Repo/Plain/File.pm b/lib/LXRng/Repo/Plain/File.pm
new file mode 100644
index 0000000..cf2d6d5
--- /dev/null
+++ b/lib/LXRng/Repo/Plain/File.pm
@@ -0,0 +1,51 @@
+package LXRng::Repo::Plain::File;
+
+use strict;
+
+use base qw(LXRng::Repo::File);
+use Fcntl;
+
+sub new {
+ my ($class, $name, $path) = @_;
+
+ my @stat = stat($path);
+
+ return undef unless @stat;
+
+ return LXRng::Repo::Plain::Directory->new($name, $path, \@stat) if -d _;
+
+ return bless({name => $name, path => $path, stat => \@stat}, $class);
+}
+
+sub time {
+ my ($self) = @_;
+
+ return $$self{'stat'}[9];
+}
+
+sub size {
+ my ($self) = @_;
+
+ return $$self{'stat'}[7];
+}
+
+sub phys_path {
+ my ($self) = @_;
+
+ return $$self{'path'};
+}
+
+sub revision {
+ my ($self) = @_;
+
+ return $self->time.'.'.$self->size;
+}
+
+sub handle {
+ my ($self) = @_;
+
+ sysopen(my $handle, $self->phys_path, O_RDONLY) or die($!);
+ return $handle;
+}
+
+1;
diff --git a/lib/LXRng/Repo/Plain/Iterator.pm b/lib/LXRng/Repo/Plain/Iterator.pm
new file mode 100644
index 0000000..b086860
--- /dev/null
+++ b/lib/LXRng/Repo/Plain/Iterator.pm
@@ -0,0 +1,29 @@
+package LXRng::Repo::Plain::Iterator;
+
+use strict;
+use LXRng::Repo::Plain;
+
+sub new {
+ my ($class, $dir) = @_;
+
+ return bless({dir => $dir, stack => [], nodes => [$dir->contents]}, $class);
+}
+
+sub next {
+ my ($self) = @_;
+
+ while (@{$$self{'nodes'}} == 0) {
+ return undef unless @{$$self{'stack'}};
+ $$self{'nodes'} = pop(@{$$self{'stack'}});
+ }
+
+ my $node = shift(@{$$self{'nodes'}});
+ if ($node->isa('LXRng::Repo::Directory')) {
+ push(@{$$self{'stack'}}, $$self{'nodes'});
+ $$self{'nodes'} = [$node->contents];
+ return $self->next;
+ }
+ return $node;
+}
+
+1;
diff --git a/lib/LXRng/Repo/TmpFile.pm b/lib/LXRng/Repo/TmpFile.pm
new file mode 100644
index 0000000..bc9024a
--- /dev/null
+++ b/lib/LXRng/Repo/TmpFile.pm
@@ -0,0 +1,30 @@
+package LXRng::Repo::TmpFile;
+
+# This package is used to hold on to a reference to a physical copy of
+# a file normally only present inside a repo of some sort. When it
+# leaves scopy, the destructor will remove it. (The object acts as
+# string containing the path of the physical manifestation of the
+# file.)
+
+use strict;
+use overload '""' => \&filename;
+
+sub new {
+ my ($class, %args) = @_;
+
+ return bless(\%args, $class);
+}
+
+sub filename {
+ my ($self) = @_;
+
+ return $$self{'dir'}.'/'.$$self{'node'};
+}
+
+sub DESTROY {
+ my ($self) = @_;
+ unlink($$self{'dir'}.'/'.$$self{'node'});
+ rmdir($$self{'dir'});
+}
+
+1;