package SVN::Web::Log; use strict; use warnings; use SVN::Core; use SVN::Ra; use base 'SVN::Web::action'; our $VERSION = 0.53; =head1 NAME SVN::Web::Log - SVN::Web action to show log messages for a repository path =head1 SYNOPSIS In F actions: ... log: class: SVN::Web::Log action_menu: show: - file - directory link_text: (view revision log) ... =head1 DESCRIPTION Shows log messages (in reverse order) for interesting revisions of a given file or directory in the repository. =head1 OPTIONS =over 8 =item limit The number of log entries to retrieve. The default is 20. =item rev The repository revision to start with. The default is the repository's youngest revision. =back =head1 TEMPLATE VARIABLES =over 8 =item context Either C or C. =item at_head A boolean value, true if the log starts with the most recent revision. =item at_oldest A boolean value, true if the list of revisions (C) includes the oldest revision for this path. =item isdir A boolean value, true if the given path is a directory. =item rev The repository revision that the log starts with. =item revs A list of hashes. Each entry corresponds to a particular repository revision, and has the following keys. =over 8 =item rev The repository revision this entry is for. =item youngest_rev The repository's youngest revision. =item author The author of this change. =item date The date of this change, formatted according to L. =item msg The log message for this change. =item paths A list of hashes containing information about the paths that were changed with this commit. Each hash key is the path name that was modified with this commit. Each key is a hash ref of extra information about the change to this path. These hash refs have the following keys. =over 8 =item action A single letter indicating the action that was carried out on the path. A file was either added C, modified C, replaced C, or deleted C. =item copyfrom If the file was copied from another file then this is the path of the source of the copy. =item copyfromrev If the file was copied from another file then this is the revision of the file that it was copied from. =back =back =item limit The value of the C parameter. =back =head1 EXCEPTIONS None. =cut sub _log { my($self, $paths, $rev, $author, $date, $msg, $pool) = @_; return unless $rev > 0; my $data = { rev => $rev, author => $author, date => $self->format_svn_timestamp($date), msg => $msg, }; $data->{paths} = { map { $_ => { action => $paths->{$_}->action(), copyfrom => $paths->{$_}->copyfrom_path(), copyfromrev => $paths->{$_}->copyfrom_rev(), } } keys %$paths }; push @{ $self->{REVS} }, $data; } sub cache_key { my $self = shift; my $path = $self->{path}; my(undef, undef, $act_rev, $head) = $self->get_revs(); my $limit = $self->_get_limit(); return "$act_rev:$limit:$head:$path"; } # Obtain the correct 'limit' value. Use the CGI parameter if it's defined, # supporting the special value 'all' to mean all revisions. Default to 20 # if it's not defined. sub _get_limit { my $self = shift; my $limit = $self->{cgi}->param('limit'); if(defined $limit) { return $limit eq '(all)' ? 0 : $limit; } return 20; } sub run { my $self = shift; my $ctx = $self->{repos}{client}; my $ra = $self->{repos}{ra}; my $uri = $self->{repos}{uri}; my $limit = $self->_get_limit(); my $rev = $self->{cgi}->param('rev') || $ra->get_latest_revnum(); my $path = $self->{path}; $path =~ s{/+$}{}; my(undef, $yng_rev, undef, $head) = $self->get_revs(); # Handle log paging my $at_oldest; if($limit) { # $limit not 'all' # Get one more log entry than asked for. If we get back this # many log entries then we know there's at least one more page # of results to show. If we get back $limit or less log # entries then we're on the last page. # # If we're not on the last page then pop off the extra log entry $ra->get_log([$path], $rev, 1, $limit + 1, 1, 1, sub { $self->_log(@_) }); $at_oldest = @{ $self->{REVS} } <= $limit; pop @{ $self->{REVS} } unless $at_oldest; } else { # We must be displaying to the oldest rev, so no paging required $ra->get_log([$path], $rev, 1, $limit, 1, 1, sub { $self->_log(@_) }); $at_oldest = 1; } # $self->_resolve_changed_paths(); my $is_dir; $ctx->info("$uri$path", $rev, $rev, sub { my($path, $info, $pool) = @_; $is_dir = $info->kind() == $SVN::Node::dir; }, 0); return { template => 'log', data => { context => $is_dir ? 'directory' : 'file', isdir => $is_dir, revs => $self->{REVS}, limit => $limit, rev => $rev, youngest_rev => $yng_rev, at_oldest => $at_oldest, at_head => $head, } }; } # Add 'isdir' keys to the paths if appropriate. Also, add trailing slashes # if necessary. # # This code used to be in get_log() when it used the repos layer. When # the code was changed to use the ra layer it had to be moved out, as you # can't call ra functions from a get_log() callback. # # XXX Very similar code in Revision.pm, needs refactoring sub _resolve_changed_paths { my $self = shift; my $ctx = $self->{repos}{client}; my $ra = $self->{repos}{ra}; my $uri = $self->{repos}{uri}; my $subpool = SVN::Pool->new(); my $node_kind; foreach my $data (@{ $self->{REVS} }) { foreach my $path (keys %{ $data->{paths} }) { $subpool->clear(); $ctx->info("$uri$path", $data->{rev}, $data->{rev}, sub { $node_kind = $_[1]->kind() }, 0, $subpool); $data->{paths}{$path}{isdir} = $node_kind == $SVN::Node::dir; } } } 1; =head1 COPYRIGHT Copyright 2003-2004 by Chia-liang Kao C<< >>. Copyright 2005-2007 by Nik Clayton C<< >>. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L =cut