# Arch Perl library, Copyright (C) 2004 Mikhael Goikhman # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA use 5.005; use strict; package Arch::Session; use base 'Arch::Storage'; use Arch::Util qw(run_tla _parse_revision_descs load_file save_file); use Arch::Backend qw(get_cache_config); use Arch::TempFiles qw(temp_dir_name temp_dir); use Arch::Changeset; use Arch::Library; use Arch::Log; use Arch::Tree; use Arch::Tarball; sub _default_fields ($) { my $this = shift; return ( $this->SUPER::_default_fields, use_library => 1, ); } sub new ($%) { my $class = shift; my %init = @_; my $self = $class->SUPER::new(%init); $self->clear_cache; return $self; } sub archives ($) { my $self = shift; $self->{archives} ||= [ run_tla("archives -n") ]; return $self->{archives}; } *is_archive_registered = *Arch::Storage::is_archive_managed; *is_archive_registered = *is_archive_registered; sub categories ($;$) { my $self = shift; my $archive = $self->_name_operand(shift, 'archive'); unless ($self->{categories}->{$archive}) { $self->{categories}->{$archive} = [ run_tla("categories", $archive) ]; } return $self->{categories}->{$archive}; } sub branches ($;$) { my $self = shift; my $category = $self->_name_operand(shift, 'category'); unless ($self->{branches}->{$category}) { $self->{branches}->{$category} = [ run_tla("branches", $category) ]; } return $self->{branches}->{$category}; } sub versions ($;$) { my $self = shift; my $branch = $self->_name_operand(shift, 'branch'); unless ($self->{versions}->{$branch}) { $self->{versions}->{$branch} = [ run_tla("versions", $branch) ]; # temporarily do this for backward compatibility $self->{versions}->{$branch} = [ map { s/--/----/; $_ } grep !/--.*--/, @{$self->{versions}->{$branch}} ] if $branch->branch eq ''; } return $self->{versions}->{$branch}; } sub revisions ($;$) { my $self = shift; my $version = $self->_name_operand(shift, 'version'); unless ($self->{revisions}->{$version}) { $self->{revisions}->{$version} = [ run_tla("revisions", $version) ]; } return $self->{revisions}->{$version}; } sub get_revision_descs ($;$$) { my $self = shift; my $version = $self->_name_operand(shift, 'version'); my $extra_args = shift || []; die "get_revision_descs: no a|c|b|v ($version)\n" unless $version->is_valid('archive+'); unless ($self->{revision_descs}->{$version}) { my $nonarch_version = $version->nan; # $ok is used to work around the tla bug with branchless version # $prev_line is used to track revisions with no (empty) summary my $ok = 0; my $prev_line = ""; my @revision_lines = map { s/^ //? $_: undef } grep { $ok = /^ \Q$nonarch_version\E$/ if /^ [^ ]/; my $end = ($prev_line =~ /^ /) && ($_ eq ""); $prev_line = $_; ($end || /^ /) && $ok } run_tla("abrowse --desc", @$extra_args, $version); my $revision_descs = _parse_revision_descs(2, \@revision_lines); $self->{revision_descs}->{$version} = $revision_descs; $self->{revisions}->{$version} = [ map { $_->{name} } @$revision_descs ]; } return $self->{revision_descs}->{$version}; } *revision_details = *get_revision_descs; *revision_details = *revision_details; sub clear_cache ($;@) { my $self = shift; my @keys = @_; @keys = qw(archives categories branches versions revisions revision_descs); foreach (@keys) { if (@_ && !exist $self->{$_}) { warn __PACKAGE__ . "::clear_cache: unknown key ($_), ignoring\n"; next; } $self->{$_} = $_ eq 'archives'? undef: {}; } return $self; } sub expanded_versions ($;$$) { my $self = shift; my $archive = $self->_name_operand(shift); my $extra_args = shift || []; die "get_all_versions: no archive+ ($archive)\n" unless $archive->is_valid('archive+'); my $archive0 = $archive->cast('archive'); unless ($self->{all_versions}->{$archive}) { my @versions = map { s/^ //; "$archive0/$_" } grep { /^ [^ ]/ } run_tla("abrowse --desc", @$extra_args, $archive); $self->{all_versions}->{$archive} = \@versions; } return $self->{all_versions}->{$archive}; } # [ # [ category1, [ # [ branch1, [ # [ version1, start_revision1, end_revision1 ], # [ version2, start_revision2, end_revision2 ], # ] ], # [ branch2, [ # [ version3, start_revision3, end_revision3 ], # [ version4, start_revision4, end_revision4 ], # ] ], # ..., # ] ], # ] sub expanded_archive_info ($;$$) { my $self = shift; my $archive_plus = $self->_name_operand(shift); my $full_listing = shift || 0; # currently ignored my $infos = []; my @category_infos = split(/^\b/m, join('', map { s/^ //; "$_\n" } grep { /^ / } run_tla("abrowse $archive_plus") )); my $error = 0; CATEGORY_ITEM: foreach (@category_infos) { my ($category, $branch_infos) = /^([^\s]+)\n( .*)$/s; push @$infos, [ $category, [] ]; unless (defined $category) { $error = 1; next CATEGORY_ITEM; } my @branch_infos = split(/^\b/m, join('', map { s/^ // or $error = 1; "$_\n" } split("\n", $branch_infos) )); $error = 1 unless @branch_infos; foreach (@branch_infos) { my ($branch, $version_infos) = /^\Q$category\E(?:--([^\s]+))?\n( .*)$/s; $branch = "" if defined $version_infos && !defined $branch; unless (defined $branch) { $error = 1; next CATEGORY_ITEM; } push @{$infos->[-1]->[1]}, [ $branch, [] ]; my @version_infos = split(/^\b/m, join('', map { s/^ // or $error = 1; "$_\n" } split("\n", $version_infos) )); $error = 1 unless @version_infos; foreach (@version_infos) { my ($version, $revision0, $revisionl) = /^\Q$category\E(?:--)?\Q$branch\E--([^\s]+)(?:\n ([^\s]+)(?: \.\. ([^\s]+))?\n)?$/s; unless (defined $version) { $error = 1; next CATEGORY_ITEM; } my $revisions2 = []; if ($full_listing) { push @$revisions2, $revision0 if defined $revision0; push @$revisions2, $revisionl if defined $revisionl; } else { $revision0 = '' unless defined $revision0; $revisionl = '' unless defined $revisionl; push @$revisions2, $revision0, $revisionl; } push @{$infos->[-1]->[1]->[-1]->[1]}, [ $version, @$revisions2 ]; } } } continue { if ($error) { warn "Unexpected abrowse output, skipping:\n$_\n"; pop @$infos; $error = 0; } } return $infos; } sub get_revision_changeset ($$;$) { my $self = shift; my $revision = shift; my $dir = shift; # use revlib unless specific result dir requested (and unless disabled) if (!$dir && $self->{use_library}) { $dir = Arch::Library->instance->find_revision_tree($revision); if ($dir) { $dir .= "/,,patch-set"; goto RETURN_CHANGESET; } } # use arch cache if available my $cache_dir = get_cache_config()->{dir}; if (!$dir && $cache_dir) { my $delta_file = "$cache_dir/archives/$revision/delta.tar.gz"; if (-r $delta_file) { my $tarball = Arch::Tarball->new(file => $delta_file); my $subdir = $revision; $subdir =~ s!.*/!!; $dir = $tarball->extract . "/$subdir.patches"; $dir = "" unless -d $dir; goto RETURN_CHANGESET if $dir; } } $dir ||= temp_dir_name("arch-changeset"); die "get_changeset: incorrect dir ($dir)\n" unless $dir && !-d $dir; run_tla("get-changeset", $revision, $dir); RETURN_CHANGESET: return Arch::Changeset->new($revision, $dir); } sub get_changeset ($;$) { my $self = shift; my $dir = shift; my $revision = $self->working_name; die "get_changeset: no working revision\n" unless $revision->is_valid('revision'); return $self->get_revision_changeset($revision, $dir); } sub get_specified_changeset ($$) { my $self = shift; my $arg = shift; die "No changeset specifier (revision name or directory)\n" unless $arg; my $downloaded_file = undef; my $temp_dir = undef; if ($arg =~ m!^http://!) { die "Invalid http:// tarball url ($arg)\n" unless $arg =~ m!/([^/]+\.tar\.gz)$!; my $filename = $1; require Arch::LiteWeb; my $web = Arch::LiteWeb->new; my $content = $web->get($arg); die $web->error_with_url unless defined $content; die "Zero content in $arg\n" unless $content; $temp_dir = temp_dir("arch-download"); $arg = "$temp_dir/$filename"; save_file($arg, \$content); $downloaded_file = $arg; } if ($arg =~ m!([^/]+)\.tar\.gz$!) { die "No tarball file $arg found\n" unless -f $arg; my $basename = $1; require Arch::Tarball; my $tarball = Arch::Tarball->new(file => $arg); my $final_dir = $tarball->extract(dir => $temp_dir) . "/$basename"; # base-0.src.tar.gz tarball extracts to dir without .src part, # but this tree has no tree-version set anyway (and zero changes) die "No way to get tree changes from what seems to be an arch import tarball\n File: $arg\n" if $final_dir =~ /.*--.*--.*\d+\.src$/ && !-d $final_dir; die "No expected $final_dir after extracting $arg\n" unless -d $final_dir; $arg = $final_dir; unlink $downloaded_file if $downloaded_file; } if (-d "$arg/{arch}") { my $tree = Arch::Tree->new($arg); my $cset = $tree->get_changeset(temp_dir_name("arch-changeset")); die qq(Could not get local tree changes\n) . qq( You may be using "untagged-source unrecognized" and have untagged source\n) . qq( files in your tree. Please add file ids or remove the offending files.\n) unless defined $cset; return $cset; } elsif (-f "$arg/mod-dirs-index") { return Arch::Changeset->new('none', $arg); } elsif (-d $arg) { die qq(Invalid directory\n) . qq( "$arg" is neither a changeset directory nor a project tree.\n); } else { # die "No fully qualified revision name ($arg)\n" # unless Arch::Name->is_valid($arg, "revision"); my $cset = eval { $self->get_revision_changeset( $arg, temp_dir_name("arch-changeset") ); }; die qq(get-changeset failed\n) . qq( Could not fetch changeset for revision "$arg".\n) if $@; return $cset; } } sub get_revision_log ($$) { my $self = shift; my $revision = shift || die "get_revision_log: No revision given\n"; my $message; # use arch cache if available my $cache_dir = get_cache_config()->{dir}; if ($cache_dir) { my $log_file = "$cache_dir/archives/$revision/log"; if (-r $log_file) { load_file($log_file, \$message); goto RETURN_LOG; } } $message = run_tla("cat-archive-log", $revision); die "Can't get log of $revision from archive.\n" . "Unexisting revision or system problems.\n" unless $message; RETURN_LOG: return Arch::Log->new($message); } sub get_log ($) { my $self = shift; my $revision = $self->working_name; die "get_log: no working revision\n" unless $revision->is_valid('revision'); return $self->get_revision_log($revision); } sub get_tree ($;$$%) { my $self = shift; my $opts = shift if ref($_[0]) eq 'HASH'; my $revision = $self->_name_operand(shift); die "get_tree: no r|v|b ($revision)\n" unless $revision->is_valid('branch+'); my $dir = shift || temp_dir_name("arch-tree"); die "get_tree: no directory name (internal error?)\n" unless $dir; die "get_tree: directory already exists ($dir)\n" if -d $dir; my @args = (); push @args, "--no-pristine" unless $opts->{pristine}; push @args, "--link" if $opts->{link}; push @args, "--library" if $opts->{library}; push @args, "--sparse" if $opts->{sparse}; push @args, "--non-sparse" if $opts->{non_sparse}; push @args, "--no-greedy-add" if $opts->{no_greedy_add}; run_tla("get --silent", @args, $revision, $dir); die "Can't get revision $revision from archive.\n" . "Unexisting revision or system problems.\n" unless -d $dir; return Arch::Tree->new($dir); } sub init_tree ($$;$) { my $self = shift; my $version = $self->_name_operand(shift, "version"); my $dir = shift || "."; run_tla("init-tree", "-d", $dir, $version); return undef unless $? == 0; return Arch::Tree->new($dir); } sub my_id ($;$) { my $self = shift; my $userid = shift; if (defined $userid) { return 0 unless $userid =~ /<.+\@.*>/; run_tla("my-id", $userid); return !$?; } else { ($userid) = run_tla("my-id"); return $userid; } } 1; __END__ =head1 NAME Arch::Session - access arch archives =head1 SYNOPSIS use Arch::Session; my $session = Arch::Session->new; my $rev = 'migo@homemail.com--Perl-GPL/arch-perl--devel--0--patch-1'; my $log = $session->get_revision_log($rev); my $cset = $session->get_revision_changeset($rev); my $tree = $session->get_tree($rev); =head1 DESCRIPTION Arch::Session provides an interface to access changesets and logs stored in arch archives. =head1 METHODS The following common methods (inherited and pure virtual that this class implements) are documented in L: B, B, B, B, B, B, B. B, B, B, B, B, B, B, B, B, B, B. Additionally, the following methods are available: B, B, B, B, B. =over 4 =item B I Get changeset object (Arch::Changeset) by a user specified input. I may be revision name, or changeset directory, or tree directory (then changeset for tree changes is constructed), and in the future local tarball filepath or remote tarball url. =item B [I ..] For performance reasons, most method results are cached (memoized in fact). Use this method to explicitly request this cache to be cleared. By default all cached keys are cleared; I may be one of the strings 'archives', 'categories', 'branches', 'versions', 'revisions' or 'revision_descs'. =item B [{ I }] [I [I]] Construct a working tree for I or B in I. If I is not specified, a new temporary directory is automatically created. Keys of I may be I, I, I, I, I, I; all are false by default. See C. =item B I Run C in I. =item B [I] Get or set C. =back =head1 BUGS No known bugs. =head1 AUTHORS Mikhael Goikhman (migo@homemail.com--Perl-GPL/arch-perl--devel). Enno Cramer (uebergeek@web.de--2003/arch-perl--devel). =head1 SEE ALSO For more information, see L, L, L, L, L, L. =cut