From: Shawn O. Pearce Date: Fri, 3 Oct 2008 14:41:25 +0000 (-0700) Subject: Merge branch 'pb/gitweb-tagcloud' into pb/gitweb X-Git-Tag: v1.6.1-rc1~158^2 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/3e3d4ee7cb7726dcf799f95d27bce384c32b2c8a?ds=inline;hp=-c Merge branch 'pb/gitweb-tagcloud' into pb/gitweb * pb/gitweb-tagcloud: gitweb: Support for simple project search form gitweb: Make the by_tag filter delve in forks as well gitweb: Support for tag clouds ... (+ many updates from master) ... Conflicts: gitweb/gitweb.perl --- 3e3d4ee7cb7726dcf799f95d27bce384c32b2c8a diff --combined gitweb/gitweb.perl index 453cbac7d8,b46af77da0..11168006cf --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@@ -27,6 -27,13 +27,13 @@@ our $version = "++GIT_VERSION++" our $my_url = $cgi->url(); our $my_uri = $cgi->url(-absolute => 1); + # if we're called with PATH_INFO, we have to strip that + # from the URL to find our real URL + if (my $path_info = $ENV{"PATH_INFO"}) { + $my_url =~ s,\Q$path_info\E$,,; + $my_uri =~ s,\Q$path_info\E$,,; + } + # core git executable to use # this can just be "git" if your webserver has a sensible PATH our $GIT = "++GIT_BINDIR++/git"; @@@ -276,25 -283,23 +283,43 @@@ our %feature = 'override' => 0, 'default' => [0]}, + # Insert custom links to the action bar of all project pages. + # This enables you mainly to link to third-party scripts integrating + # into gitweb; e.g. git-browser for graphical history representation + # or custom web-based repository administration interface. + + # The 'default' value consists of a list of triplets in the form + # (label, link, position) where position is the label after which + # to inster the link and link is a format string where %n expands + # to the project name, %f to the project path within the filesystem, + # %h to the current hash (h gitweb parameter) and %b to the current + # hash base (hb gitweb parameter). + + # To enable system wide have in $GITWEB_CONFIG e.g. + # $feature{'actions'}{'default'} = [('graphiclog', + # '/git-browser/by-commit.html?r=%n', 'summary')]; + # Project specific override is not supported. + 'actions' => { + 'override' => 0, + 'default' => []}, ++ + # Allow gitweb scan project content tags described in ctags/ + # of project repository, and display the popular Web 2.0-ish + # "tag cloud" near the project list. Note that this is something + # COMPLETELY different from the normal Git tags. + + # gitweb by itself can show existing tags, but it does not handle + # tagging itself; you need an external application for that. + # For an example script, check Girocco's cgi/tagproj.cgi. + # You may want to install the HTML::TagCloud Perl module to get + # a pretty tag cloud instead of just a list of tags. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'ctags'}{'default'} = ['path_to_tag_script']; + # Project specific override is not supported. + 'ctags' => { + 'override' => 0, + 'default' => [0]}, ); sub gitweb_check_feature { @@@ -795,7 -800,7 +820,7 @@@ sub quot_cec ); my $chr = ( (exists $es{$cntrl}) ? $es{$cntrl} - : sprintf('\%03o', ord($cntrl)) ); + : sprintf('\%2x', ord($cntrl)) ); if ($opts{-nohtml}) { return $chr; } else { @@@ -1775,6 -1780,67 +1800,67 @@@ sub git_get_project_description return $descr; } + sub git_get_project_ctags { + my $path = shift; + my $ctags = {}; + + $git_dir = "$projectroot/$path"; + foreach (<$git_dir/ctags/*>) { + open CT, $_ or next; + my $val = ; + chomp $val; + close CT; + my $ctag = $_; $ctag =~ s#.*/##; + $ctags->{$ctag} = $val; + } + $ctags; + } + + sub git_populate_project_tagcloud { + my $ctags = shift; + + # First, merge different-cased tags; tags vote on casing + my %ctags_lc; + foreach (keys %$ctags) { + $ctags_lc{lc $_}->{count} += $ctags->{$_}; + if (not $ctags_lc{lc $_}->{topcount} + or $ctags_lc{lc $_}->{topcount} < $ctags->{$_}) { + $ctags_lc{lc $_}->{topcount} = $ctags->{$_}; + $ctags_lc{lc $_}->{topname} = $_; + } + } + + my $cloud; + if (eval { require HTML::TagCloud; 1; }) { + $cloud = HTML::TagCloud->new; + foreach (sort keys %ctags_lc) { + # Pad the title with spaces so that the cloud looks + # less crammed. + my $title = $ctags_lc{$_}->{topname}; + $title =~ s/ / /g; + $title =~ s/^/ /g; + $title =~ s/$/ /g; + $cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count}); + } + } else { + $cloud = \%ctags_lc; + } + $cloud; + } + + sub git_show_project_tagcloud { + my ($cloud, $count) = @_; + print STDERR ref($cloud)."..\n"; + if (ref $cloud eq 'HTML::TagCloud') { + return $cloud->html_and_css($count); + } else { + my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud; + return '

' . join (', ', map { + "$cloud->{$_}->{topname}" + } splice(@tags, 0, $count)) . '

'; + } + } + sub git_get_project_url_list { my $path = shift; @@@ -1823,9 -1889,7 +1909,7 @@@ sub git_get_projects_list my $subdir = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot - if ($check_forks and $subdir =~ m#/.#) { - $File::Find::prune = 1; - } elsif (check_export_ok("$projectroot/$filter/$subdir")) { + if (check_export_ok("$projectroot/$filter/$subdir")) { push @list, { path => ($filter ? "$filter/" : '') . $subdir }; $File::Find::prune = 1; } @@@ -2777,26 -2841,13 +2861,26 @@@ sub git_print_page_nav } } } + $arg{'tree'}{'hash'} = $treehead if defined $treehead; $arg{'tree'}{'hash_base'} = $treebase if defined $treebase; + my @actions = gitweb_check_feature('actions'); + while (@actions) { + my ($label, $link, $pos) = (shift(@actions), shift(@actions), shift(@actions)); + @navs = map { $_ eq $pos ? ($_, $label) : $_ } @navs; + # munch munch + $link =~ s#%n#$project#g; + $link =~ s#%f#$git_dir#g; + $treehead ? $link =~ s#%h#$treehead#g : $link =~ s#%h##g; + $treebase ? $link =~ s#%b#$treebase#g : $link =~ s#%b##g; + $arg{$label}{'_href'} = $link; + } + print "
\n" . (join " | ", map { $_ eq $current ? - $_ : $cgi->a({-href => href(%{$arg{$_}})}, "$_") + $_ : $cgi->a({-href => ($arg{$_}{_href} ? $arg{$_}{_href} : href(%{$arg{$_}}))}, "$_") } @navs); print "
\n$extra
\n" . "
\n"; @@@ -3606,6 -3657,7 +3690,7 @@@ sub fill_project_list_info my ($projlist, $check_forks) = @_; my @projects; + my $show_ctags = gitweb_check_feature('ctags'); PROJECT: foreach my $pr (@$projlist) { my (@activity) = git_get_last_activity($pr->{'path'}); @@@ -3632,19 -3684,26 +3717,20 @@@ $pr->{'forks'} = 0; } } + $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'}); push @projects, $pr; } return @projects; } -# print 'sort by' element, either sorting by $key if $name eq $order -# (changing $list), or generating 'sort by $name' replay link otherwise +# print 'sort by' element, generating 'sort by $name' replay link +# if that order is not selected sub print_sort_th { - my ($str_sort, $name, $order, $key, $header, $list) = @_; - $key ||= $name; + my ($name, $order, $header) = @_; $header ||= ucfirst($name); if ($order eq $name) { - if ($str_sort) { - @$list = sort {$a->{$key} cmp $b->{$key}} @$list; - } else { - @$list = sort {$a->{$key} <=> $b->{$key}} @$list; - } print "$header\n"; } else { print "" . @@@ -3654,7 -3713,16 +3740,8 @@@ } } -sub print_sort_th_str { - print_sort_th(1, @_); -} - -sub print_sort_th_num { - print_sort_th(0, @_); -} - sub git_project_list_body { + # actually uses global variable $project my ($projlist, $order, $from, $to, $extra, $no_header) = @_; my ($check_forks) = gitweb_check_feature('forks'); @@@ -3664,35 -3732,51 +3751,60 @@@ $from = 0 unless defined $from; $to = $#projects if (!defined $to || $#projects < $to); + my %order_info = ( + project => { key => 'path', type => 'str' }, + descr => { key => 'descr_long', type => 'str' }, + owner => { key => 'owner', type => 'str' }, + age => { key => 'age', type => 'num' } + ); + my $oi = $order_info{$order}; + if ($oi->{'type'} eq 'str') { + @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects; + } else { + @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects; + } + + my $show_ctags = gitweb_check_feature('ctags'); + if ($show_ctags) { + my %ctags; + foreach my $p (@projects) { + foreach my $ct (keys %{$p->{'ctags'}}) { + $ctags{$ct} += $p->{'ctags'}->{$ct}; + } + } + my $cloud = git_populate_project_tagcloud(\%ctags); + print git_show_project_tagcloud($cloud, 64); + } + print "\n"; unless ($no_header) { print "\n"; if ($check_forks) { print "\n"; } - print_sort_th_str('project', $order, 'path', - 'Project', \@projects); - print_sort_th_str('descr', $order, 'descr_long', - 'Description', \@projects); - print_sort_th_str('owner', $order, 'owner', - 'Owner', \@projects); - print_sort_th_num('age', $order, 'age', - 'Last Change', \@projects); + print_sort_th('project', $order, 'Project'); + print_sort_th('descr', $order, 'Description'); + print_sort_th('owner', $order, 'Owner'); + print_sort_th('age', $order, 'Last Change'); print "\n" . # for links "\n"; } my $alternate = 1; + my $tagfilter = $cgi->param('by_tag'); for (my $i = $from; $i <= $to; $i++) { my $pr = $projects[$i]; + + next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}}; + next if $searchtext and not $pr->{'path'} =~ /$searchtext/ + and not $pr->{'descr_long'} =~ /$searchtext/; + # Weed out forks or non-matching entries of search + if ($check_forks) { + my $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\.git$#/#; + $forkbase="^$forkbase" if $forkbase; + next if not $searchtext and not $tagfilter and $show_ctags + and $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe + } + if ($alternate) { print "\n"; } else { @@@ -4027,6 -4111,11 +4139,11 @@@ sub git_project_list close $fd; print "\n"; } + print $cgi->startform(-method => "get") . + "

Search:\n" . + $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . + "

" . + $cgi->end_form() . "\n"; git_project_list_body(\@list, $order); git_footer_html(); } @@@ -4098,10 -4187,10 +4215,10 @@@ sub git_summary print "
 
\n"; print "
\n" . - "\n" . - "\n"; + "\n" . + "\n"; if (defined $cd{'rfc2822'}) { - print "\n"; + print "\n"; } # use per project git URL list in $projectroot/$project/cloneurl @@@ -4111,9 -4200,23 +4228,23 @@@ @url_list = map { "$_/$project" } @git_base_url_list unless @url_list; foreach my $git_url (@url_list) { next unless $git_url; - print "\n"; + print "\n"; $url_tag = ""; } + + # Tag cloud + my $show_ctags = (gitweb_check_feature('ctags'))[0]; + if ($show_ctags) { + my $ctags = git_get_project_ctags($project); + my $cloud = git_populate_project_tagcloud($ctags); + print "\n\n"; + } + print "
description" . esc_html($descr) . "
owner" . esc_html($owner) . "
description" . esc_html($descr) . "
owner" . esc_html($owner) . "
last change$cd{'rfc2822'}
last change$cd{'rfc2822'}
$url_tag$git_url
Content tags:
"; + print "
" unless %$ctags; + print "
Add:
"; + print "
" if %$ctags; + print git_show_project_tagcloud($cloud, 48); + print "
\n"; if (-s "$projectroot/$project/README.html") { @@@ -4152,10 -4255,10 +4283,10 @@@ if (@forklist) { git_print_header_div('forks'); - git_project_list_body(\@forklist, undef, 0, 15, + git_project_list_body(\@forklist, 'age', 0, 15, $#forklist <= 15 ? undef : $cgi->a({-href => href(action=>"forks")}, "..."), - 'noheader'); + 'no_header'); } git_footer_html(); @@@ -4473,6 -4576,7 +4604,7 @@@ sub git_tree $hash = $hash_base; } } + die_error(404, "No such tree") unless defined($hash); $/ = "\0"; open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash or die_error(500, "Open git-ls-tree failed"); @@@ -4513,8 -4617,8 +4645,8 @@@ if ($basedir ne '' && substr($basedir, -1) ne '/') { $basedir .= '/'; } + git_print_page_path($file_name, 'tree', $hash_base); } - git_print_page_path($file_name, 'tree', $hash_base); print "
\n"; print "\n"; my $alternate = 1;