1package SVN::Web::Browse; 2 3use strict; 4use warnings; 5 6use base 'SVN::Web::action'; 7 8use Encode (); 9use SVN::Ra; 10use SVN::Client; 11use SVN::Web::X; 12 13our $VERSION = 0.63; 14 15=head1 NAME 16 17SVN::Web::Browse - SVN::Web action to browse a Subversion repository 18 19=head1 SYNOPSIS 20 21In F<config.yaml> 22 23 actions: 24 ... 25 browse: 26 class: SVN::Web::Browse 27 action_menu: 28 show: 29 - directory 30 link_text: (browse directory) 31 ... 32 33=head1 DESCRIPTION 34 35Returns a file/directory listing for the given repository path. 36 37=head1 OPTIONS 38 39=over 4 40 41=item rev 42 43The repository revision to show. Defaults to the repository's youngest 44revision. 45 46=back 47 48=head1 TEMPLATE VARIABLES 49 50=over 4 51 52=item at_head 53 54A boolean value, indicating whether or not the user is currently 55browsing the HEAD of the repository. 56 57=item context 58 59Always C<directory>. 60 61=item entries 62 63A list of hash refs, one for each file and directory entry in the browsed 64path. The list is ordered with directories first, then files, sorted 65alphabetically. 66 67Each hash ref has the following keys. 68 69=over 8 70 71=item name 72 73The entry's name. 74 75=item path 76 77The entry's full path. 78 79=item rev 80 81The entry's most recent interesting revision. 82 83=item size 84 85The entry's size, in bytes. The empty string C<''> for directories. 86 87=item type 88 89The entry's C<svn:mime-type> property. Not set for directories. 90 91=item author 92 93The userid that committed the most recent interesting revision for this 94entry. 95 96=item date 97 98The date of the entry's most recent interesting revision, formatted 99according to L<SVN::Web/"Time and date formatting">. 100 101=item msg 102 103The log message for the entry's most recent interesting revision. 104 105=back 106 107=item rev 108 109The repository revision that is being browsed. Will be the same as the 110C<rev> parameter given to the action, unless that parameter was not set, 111in which case it will be the repository's youngest revision. 112 113=item youngest_rev 114 115The repository's youngest revision. 116 117=back 118 119=head1 EXCEPTIONS 120 121=over 4 122 123=item (path %1 does not exist in revision %2) 124 125The given path is not present in the repository at the given revision. 126 127=item (path %1 is not a directory in revision %2) 128 129The given path exists in the repository at the given revision, but is 130not a directory. This action is only used to browse directories. 131 132=back 133 134=cut 135 136sub cache_key { 137 my $self = shift; 138 my $path = $self->{path}; 139 140 my ( undef, undef, $act_rev, $at_head ) = $self->get_revs(); 141 142 return "$act_rev:$at_head:$path"; 143} 144 145sub run { 146 my $self = shift; 147 my $uri = $self->{repos}{uri}; 148 $uri .= '/'.$self->rpath if $self->rpath; 149 150 my ( $exp_rev, $yng_rev, $act_rev, $at_head ) = $self->get_revs(); 151 152 my $rev = $act_rev; 153 154 my $node_kind = $self->svn_get_node_kind($uri, $rev, $rev); 155 156 if ( $node_kind == $SVN::Node::none ) { 157 SVN::Web::X->throw( 158 error => '(path %1 does not exist in revision %2)', 159 vars => [ $self->rpath, $rev ] 160 ); 161 } 162 163 if ( $node_kind != $SVN::Node::dir ) { 164 SVN::Web::X->throw( 165 error => '(path %1 is not a directory in revision %2)', 166 vars => [ $self->rpath, $rev ], 167 ); 168 } 169 170 my $dirents = $self->ctx_ls( $uri, $rev, 0 ); 171 172 my $entries = []; 173 my $current_time = time(); 174 175 my $base_path = $self->rpath; 176 while ( my ( $svn_name, $dirent ) = each %{$dirents} ) { 177 my $name = Encode::decode('utf8',$svn_name); 178 my $node_kind = $dirent->kind(); 179 180 my @log_result = $self->recent_interesting_rev( "$base_path/$name", $rev ); 181 182 push @{$entries}, 183 { 184 name => $name, 185 rev => $log_result[1], 186 kind => $node_kind, 187 isdir => ( $node_kind == $SVN::Node::dir ), 188 size => ( $node_kind == $SVN::Node::dir ? '' : $dirent->size() ), 189 author => $dirent->last_author(), 190 has_props => $dirent->has_props(), 191 time => $dirent->time() / 1_000_000, 192 age => $current_time - ( $dirent->time() / 1_000_000 ), 193 msg => Encode::decode('utf8',$log_result[4]), 194 }; 195 } 196 197 # TODO: custom sorting 198 @$entries = 199 sort { ( $b->{isdir} <=> $a->{isdir} ) || ( $a->{name} cmp $b->{name} ) } 200 @$entries; 201 202 my @props = (); 203 foreach my $prop_name (qw(svn:externals)) { 204 my $prop_value = ( $self->ctx_revprop_get( $prop_name, $uri, $rev ) )[0]; 205 if ( defined $prop_value ) { 206 $prop_value =~ s/\s*\n$//ms; 207 push @props, { name => $prop_name, value => $prop_value }; 208 } 209 } 210 211 return { 212 template => 'browse', 213 data => { 214 context => 'directory', 215 entries => $entries, 216 rev => $act_rev, 217 youngest_rev => $yng_rev, 218 at_head => $at_head, 219 props => \@props, 220 } 221 }; 222} 223 2241; 225 226=head1 COPYRIGHT 227 228Copyright 2003-2004 by Chia-liang Kao C<< <clkao@clkao.org> >>. 229 230Copyright 2005-2007 by Nik Clayton C<< <nik@FreeBSD.org> >>. 231 232This program is free software; you can redistribute it and/or modify it 233under the same terms as Perl itself. 234 235See L<http://www.perl.com/perl/misc/Artistic.html> 236 237=cut 238