From: Junio C Hamano Date: Wed, 8 Dec 2010 19:24:13 +0000 (-0800) Subject: Merge branch 'gb/gitweb-remote-heads' X-Git-Tag: v1.7.4-rc0~80 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/c3f7d51bdafe6f87f38aaa355b2153bb78afc821?hp=-c Merge branch 'gb/gitweb-remote-heads' * gb/gitweb-remote-heads: git instaweb: enable remote_heads gitweb: group remote heads by remote gitweb: provide a routine to display (sub)sections gitweb: refactor repository URL printing gitweb: remotes view for a single remote gitweb: allow action specialization in page header gitweb: nagivation menu for tags, heads and remotes gitweb: separate heads and remotes lists gitweb: git_get_heads_list accepts an optional list of refs gitweb: introduce remote_heads feature gitweb: use fullname as hash_base in heads link --- c3f7d51bdafe6f87f38aaa355b2153bb78afc821 diff --combined gitweb/gitweb.perl index 679f2da3ee,f4715531f7..a1a3797b6d --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@@ -493,6 -493,18 +493,18 @@@ our %feature = 'sub' => sub { feature_bool('highlight', @_) }, 'override' => 0, 'default' => [0]}, + + # Enable displaying of remote heads in the heads list + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'remote_heads'}{'default'} = [1]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'remote_heads'}{'override'} = 1; + # and in project config gitweb.remote_heads = 0|1; + 'remote_heads' => { + 'sub' => sub { feature_bool('remote_heads', @_) }, + 'override' => 0, + 'default' => [0]}, ); sub gitweb_get_feature { @@@ -707,6 -719,7 +719,7 @@@ our %actions = "log" => \&git_log, "patch" => \&git_patch, "patches" => \&git_patches, + "remotes" => \&git_remotes, "rss" => \&git_rss, "atom" => \&git_atom, "search" => \&git_search, @@@ -1075,7 -1088,6 +1088,7 @@@ sub run_request evaluate_uri(); evaluate_gitweb_config(); + evaluate_git_version(); check_loadavg(); # $projectroot and $projects_list might be set in gitweb config file @@@ -1128,6 -1140,7 +1141,6 @@@ sub evaluate_argv sub run { evaluate_argv(); - evaluate_git_version(); $pre_listen_hook->() if $pre_listen_hook; @@@ -2759,6 -2772,44 +2772,44 @@@ sub git_get_last_activity return (undef, undef); } + # Implementation note: when a single remote is wanted, we cannot use 'git + # remote show -n' because that command always work (assuming it's a remote URL + # if it's not defined), and we cannot use 'git remote show' because that would + # try to make a network roundtrip. So the only way to find if that particular + # remote is defined is to walk the list provided by 'git remote -v' and stop if + # and when we find what we want. + sub git_get_remotes_list { + my $wanted = shift; + my %remotes = (); + + open my $fd, '-|' , git_cmd(), 'remote', '-v'; + return unless $fd; + while (my $remote = <$fd>) { + chomp $remote; + $remote =~ s!\t(.*?)\s+\((\w+)\)$!!; + next if $wanted and not $remote eq $wanted; + my ($url, $key) = ($1, $2); + + $remotes{$remote} ||= { 'heads' => () }; + $remotes{$remote}{$key} = $url; + } + close $fd or return; + return wantarray ? %remotes : \%remotes; + } + + # Takes a hash of remotes as first parameter and fills it by adding the + # available remote heads for each of the indicated remotes. + sub fill_remote_heads { + my $remotes = shift; + my @heads = map { "remotes/$_" } keys %$remotes; + my @remoteheads = git_get_heads_list(undef, @heads); + foreach my $remote (keys %$remotes) { + $remotes->{$remote}{'heads'} = [ grep { + $_->{'name'} =~ s!^$remote/!! + } @remoteheads ]; + } + } + sub git_get_references { my $type = shift || ""; my %refs; @@@ -3157,13 -3208,15 +3208,15 @@@ sub parse_from_to_diffinfo ## parse to array of hashes functions sub git_get_heads_list { - my $limit = shift; + my ($limit, @classes) = @_; + @classes = ('heads') unless @classes; + my @patterns = map { "refs/$_" } @classes; my @headslist; open my $fd, '-|', git_cmd(), 'for-each-ref', ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate', '--format=%(objectname) %(refname) %(subject)%00%(committer)', - 'refs/heads' + @patterns or return; while (my $line = <$fd>) { my %ref_item; @@@ -3174,7 -3227,7 +3227,7 @@@ my ($committer, $epoch, $tz) = ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/); $ref_item{'fullname'} = $name; - $name =~ s!^refs/heads/!!; + $name =~ s!^refs/(?:head|remote)s/!!; $ref_item{'name'} = $name; $ref_item{'id'} = $hash; @@@ -3511,7 -3564,15 +3564,15 @@@ EO if (defined $project) { print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); if (defined $action) { - print " / $action"; + my $action_print = $action ; + if (defined $opts{-action_extra}) { + $action_print = $cgi->a({-href => href(action=>$action)}, + $action); + } + print " / $action_print"; + } + if (defined $opts{-action_extra}) { + print " / $opts{-action_extra}"; } print "\n"; } @@@ -3718,6 -3779,19 +3779,19 @@@ sub git_print_page_nav "\n"; } + # returns a submenu for the nagivation of the refs views (tags, heads, + # remotes) with the current view disabled and the remotes view only + # available if the feature is enabled + sub format_ref_views { + my ($current) = @_; + my @ref_views = qw{tags heads}; + push @ref_views, 'remotes' if gitweb_check_feature('remote_heads'); + return join " | ", map { + $_ eq $current ? $_ : + $cgi->a({-href => href(action=>$_)}, $_) + } @ref_views + } + sub format_paging_nav { my ($action, $page, $has_next_link) = @_; my $paging_nav; @@@ -3761,6 -3835,49 +3835,49 @@@ sub git_print_header_div "\n\n"; } + sub format_repo_url { + my ($name, $url) = @_; + return "$name$url\n"; + } + + # Group output by placing it in a DIV element and adding a header. + # Options for start_div() can be provided by passing a hash reference as the + # first parameter to the function. + # Options to git_print_header_div() can be provided by passing an array + # reference. This must follow the options to start_div if they are present. + # The content can be a scalar, which is output as-is, a scalar reference, which + # is output after html escaping, an IO handle passed either as *handle or + # *handle{IO}, or a function reference. In the latter case all following + # parameters will be taken as argument to the content function call. + sub git_print_section { + my ($div_args, $header_args, $content); + my $arg = shift; + if (ref($arg) eq 'HASH') { + $div_args = $arg; + $arg = shift; + } + if (ref($arg) eq 'ARRAY') { + $header_args = $arg; + $arg = shift; + } + $content = $arg; + + print $cgi->start_div($div_args); + git_print_header_div(@$header_args); + + if (ref($content) eq 'CODE') { + $content->(@_); + } elsif (ref($content) eq 'SCALAR') { + print esc_html($$content); + } elsif (ref($content) eq 'GLOB' or ref($content) eq 'IO::Handle') { + print <$content>; + } elsif (!ref($content) && defined($content)) { + print $content; + } + + print $cgi->end_div; + } + sub print_local_time { print format_local_time(@_); } @@@ -4960,7 -5077,7 +5077,7 @@@ sub git_heads_body "" . $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " . $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") . + $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'fullname'})}, "tree") . "\n" . ""; } @@@ -4972,6 -5089,101 +5089,101 @@@ print "\n"; } + # Display a single remote block + sub git_remote_block { + my ($remote, $rdata, $limit, $head) = @_; + + my $heads = $rdata->{'heads'}; + my $fetch = $rdata->{'fetch'}; + my $push = $rdata->{'push'}; + + my $urls_table = "\n" ; + + if (defined $fetch) { + if ($fetch eq $push) { + $urls_table .= format_repo_url("URL", $fetch); + } else { + $urls_table .= format_repo_url("Fetch URL", $fetch); + $urls_table .= format_repo_url("Push URL", $push) if defined $push; + } + } elsif (defined $push) { + $urls_table .= format_repo_url("Push URL", $push); + } else { + $urls_table .= format_repo_url("", "No remote URL"); + } + + $urls_table .= "
\n"; + + my $dots; + if (defined $limit && $limit < @$heads) { + $dots = $cgi->a({-href => href(action=>"remotes", hash=>$remote)}, "..."); + } + + print $urls_table; + git_heads_body($heads, $head, 0, $limit, $dots); + } + + # Display a list of remote names with the respective fetch and push URLs + sub git_remotes_list { + my ($remotedata, $limit) = @_; + print "\n"; + my $alternate = 1; + my @remotes = sort keys %$remotedata; + + my $limited = $limit && $limit < @remotes; + + $#remotes = $limit - 1 if $limited; + + while (my $remote = shift @remotes) { + my $rdata = $remotedata->{$remote}; + my $fetch = $rdata->{'fetch'}; + my $push = $rdata->{'push'}; + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print ""; + print ""; + + print "\n"; + } + + if ($limited) { + print "\n" . + "\n" . "\n"; + } + + print "
" . + $cgi->a({-href=> href(action=>'remotes', hash=>$remote), + -class=> "list name"},esc_html($remote)) . + "" . + (defined $fetch ? $cgi->a({-href=> $fetch}, "fetch") : "fetch") . + " | " . + (defined $push ? $cgi->a({-href=> $push}, "push") : "push") . + "
" . + $cgi->a({-href => href(action=>"remotes")}, "...") . + "
"; + } + + # Display remote heads grouped by remote, unless there are too many + # remotes, in which case we only display the remote names + sub git_remotes_body { + my ($remotedata, $limit, $head) = @_; + if ($limit and $limit < keys %$remotedata) { + git_remotes_list($remotedata, $limit); + } else { + fill_remote_heads($remotedata); + while (my ($remote, $rdata) = each %$remotedata) { + git_print_section({-class=>"remote", -id=>$remote}, + ["remotes", $remote, $remote], sub { + git_remote_block($remote, $rdata, $limit, $head); + }); + } + } + } + sub git_search_grep_body { my ($commitlist, $from, $to, $extra) = @_; $from = 0 unless defined $from; @@@ -5109,6 -5321,7 +5321,7 @@@ sub git_summary my %co = parse_commit("HEAD"); my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : (); my $head = $co{'id'}; + my $remote_heads = gitweb_check_feature('remote_heads'); my $owner = git_get_project_owner($project); @@@ -5117,6 -5330,7 +5330,7 @@@ # there are more ... my @taglist = git_get_tags_list(16); my @headlist = git_get_heads_list(16); + my %remotedata = $remote_heads ? git_get_remotes_list() : (); my @forklist; my $check_forks = gitweb_check_feature('forks'); @@@ -5142,7 -5356,7 +5356,7 @@@ @url_list = map { "$_/$project" } @git_base_url_list unless @url_list; foreach my $git_url (@url_list) { next unless $git_url; - print "$url_tag$git_url\n"; + print format_repo_url($url_tag, $git_url); $url_tag = ""; } @@@ -5194,6 -5408,11 +5408,11 @@@ $cgi->a({-href => href(action=>"heads")}, "...")); } + if (%remotedata) { + git_print_header_div('remotes'); + git_remotes_body(\%remotedata, 15, $head); + } + if (@forklist) { git_print_header_div('forks'); git_project_list_body(\@forklist, 'age', 0, 15, @@@ -5485,7 -5704,7 +5704,7 @@@ sub git_blame_data sub git_tags { my $head = git_get_head_hash($project); git_header_html(); - git_print_page_nav('','', $head,undef,$head); + git_print_page_nav('','', $head,undef,$head,format_ref_views('tags')); git_print_header_div('summary', $project); my @tagslist = git_get_tags_list(); @@@ -5498,7 -5717,7 +5717,7 @@@ sub git_heads { my $head = git_get_head_hash($project); git_header_html(); - git_print_page_nav('','', $head,undef,$head); + git_print_page_nav('','', $head,undef,$head,format_ref_views('heads')); git_print_header_div('summary', $project); my @headslist = git_get_heads_list(); @@@ -5508,6 -5727,39 +5727,39 @@@ git_footer_html(); } + # used both for single remote view and for list of all the remotes + sub git_remotes { + gitweb_check_feature('remote_heads') + or die_error(403, "Remote heads view is disabled"); + + my $head = git_get_head_hash($project); + my $remote = $input_params{'hash'}; + + my $remotedata = git_get_remotes_list($remote); + die_error(500, "Unable to get remote information") unless defined $remotedata; + + unless (%$remotedata) { + die_error(404, defined $remote ? + "Remote $remote not found" : + "No remotes found"); + } + + git_header_html(undef, undef, -action_extra => $remote); + git_print_page_nav('', '', $head, undef, $head, + format_ref_views($remote ? '' : 'remotes')); + + fill_remote_heads($remotedata); + if (defined $remote) { + git_print_header_div('remotes', "$remote remote for $project"); + git_remote_block($remote, $remotedata->{$remote}, undef, $head); + } else { + git_print_header_div('summary', "$project remotes"); + git_remotes_body($remotedata, undef, $head); + } + + git_footer_html(); + } + sub git_blob_plain { my $type = shift; my $expires;