Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Tue, 4 Jan 2011 19:23:45 +0000 (11:23 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 4 Jan 2011 19:23:45 +0000 (11:23 -0800)
* maint:
gitweb: skip logo in atom feed when there is none
t9001: Fix test prerequisites

1  2 
gitweb/gitweb.perl
t/t9001-send-email.sh
diff --combined gitweb/gitweb.perl
index 47796180d2340ead3795ee7004ddae57d5bc1516,59b2e08b980b968e3ba5e282fcb237b1af5bb579..c65af1a00a42dccda70ddbc0f3231ae1b7a53644
@@@ -17,10 -17,12 +17,10 @@@ use Encode
  use Fcntl ':mode';
  use File::Find qw();
  use File::Basename qw(basename);
 +use Time::HiRes qw(gettimeofday tv_interval);
  binmode STDOUT, ':utf8';
  
 -our $t0;
 -if (eval { require Time::HiRes; 1; }) {
 -      $t0 = [Time::HiRes::gettimeofday()];
 -}
 +our $t0 = [ gettimeofday() ];
  our $number_of_git_cmds = 0;
  
  BEGIN {
@@@ -164,12 -166,6 +164,12 @@@ our @diff_opts = ('-M'); # taken from g
  # the gitweb domain.
  our $prevent_xss = 0;
  
 +# Path to the highlight executable to use (must be the one from
 +# http://www.andre-simon.de due to assumptions about parameters and output).
 +# Useful if highlight is not installed on your webserver's PATH.
 +# [Default: highlight]
 +our $highlight_bin = "++HIGHLIGHT_BIN++";
 +
  # information about snapshot formats that gitweb is capable of serving
  our %known_snapshot_formats = (
        # name => {
@@@ -491,18 -487,6 +491,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 {
@@@ -611,14 -595,6 +611,14 @@@ sub filter_snapshot_fmts 
                !$known_snapshot_formats{$_}{'disabled'}} @fmts;
  }
  
 +# If it is set to code reference, it is code that it is to be run once per
 +# request, allowing updating configurations that change with each request,
 +# while running other code in config file only once.
 +#
 +# Otherwise, if it is false then gitweb would process config file only once;
 +# if it is true then gitweb config would be run for each request.
 +our $per_request_config = 1;
 +
  our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
  sub evaluate_gitweb_config {
        our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
@@@ -725,7 -701,6 +725,7 @@@ our %actions = 
        "log" => \&git_log,
        "patch" => \&git_patch,
        "patches" => \&git_patches,
 +      "remotes" => \&git_remotes,
        "rss" => \&git_rss,
        "atom" => \&git_atom,
        "search" => \&git_search,
@@@ -800,10 -775,10 +800,10 @@@ sub evaluate_path_info 
                'history',
        );
  
 -      # we want to catch
 +      # we want to catch, among others
        # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
        my ($parentrefname, $parentpathname, $refname, $pathname) =
 -              ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?(.+?)(?::(.+))?$/);
 +              ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?([^:]+?)?(?::(.+))?$/);
  
        # first, analyze the 'current' part
        if (defined $pathname) {
                # hash_base instead. It should also be noted that hand-crafted
                # links having 'history' as an action and no pathname or hash
                # set will fail, but that happens regardless of PATH_INFO.
 -              $input_params{'action'} ||= "shortlog";
 -              if (grep { $_ eq $input_params{'action'} } @wants_base) {
 +              if (defined $parentrefname) {
 +                      # if there is parent let the default be 'shortlog' action
 +                      # (for http://git.example.com/repo.git/A..B links); if there
 +                      # is no parent, dispatch will detect type of object and set
 +                      # action appropriately if required (if action is not set)
 +                      $input_params{'action'} ||= "shortlog";
 +              }
 +              if ($input_params{'action'} &&
 +                  grep { $_ eq $input_params{'action'} } @wants_base) {
                        $input_params{'hash_base'} ||= $refname;
                } else {
                        $input_params{'hash'} ||= $refname;
@@@ -1084,27 -1052,16 +1084,27 @@@ sub dispatch 
  }
  
  sub reset_timer {
 -      our $t0 = [Time::HiRes::gettimeofday()]
 +      our $t0 = [ gettimeofday() ]
                if defined $t0;
        our $number_of_git_cmds = 0;
  }
  
 +our $first_request = 1;
  sub run_request {
        reset_timer();
  
        evaluate_uri();
 -      evaluate_gitweb_config();
 +      if ($first_request) {
 +              evaluate_gitweb_config();
 +              evaluate_git_version();
 +      }
 +      if ($per_request_config) {
 +              if (ref($per_request_config) eq 'CODE') {
 +                      $per_request_config->();
 +              } elsif (!$first_request) {
 +                      evaluate_gitweb_config();
 +              }
 +      }
        check_loadavg();
  
        # $projectroot and $projects_list might be set in gitweb config file
@@@ -1157,8 -1114,8 +1157,8 @@@ sub evaluate_argv 
  
  sub run {
        evaluate_argv();
 -      evaluate_git_version();
  
 +      $first_request = 1;
        $pre_listen_hook->()
                if $pre_listen_hook;
  
  
                $post_dispatch_hook->()
                        if $post_dispatch_hook;
 +              $first_request = 0;
  
                last REQUEST if ($is_last_request->());
        }
@@@ -1230,7 -1186,7 +1230,7 @@@ sub href 
                $href =~ s,/$,,;
  
                # Then add the project name, if present
 -              $href .= "/".esc_url($params{'project'});
 +              $href .= "/".esc_path_info($params{'project'});
                delete $params{'project'};
  
                # since we destructively absorb parameters, we keep this
                # Summary just uses the project path URL, any other action is
                # added to the URL
                if (defined $params{'action'}) {
 -                      $href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary';
 +                      $href .= "/".esc_path_info($params{'action'})
 +                              unless $params{'action'} eq 'summary';
                        delete $params{'action'};
                }
  
                        || $params{'hash_parent'} || $params{'hash'});
                if (defined $params{'hash_base'}) {
                        if (defined $params{'hash_parent_base'}) {
 -                              $href .= esc_url($params{'hash_parent_base'});
 +                              $href .= esc_path_info($params{'hash_parent_base'});
                                # skip the file_parent if it's the same as the file_name
                                if (defined $params{'file_parent'}) {
                                        if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
                                                delete $params{'file_parent'};
                                        } elsif ($params{'file_parent'} !~ /\.\./) {
 -                                              $href .= ":/".esc_url($params{'file_parent'});
 +                                              $href .= ":/".esc_path_info($params{'file_parent'});
                                                delete $params{'file_parent'};
                                        }
                                }
                                delete $params{'hash_parent'};
                                delete $params{'hash_parent_base'};
                        } elsif (defined $params{'hash_parent'}) {
 -                              $href .= esc_url($params{'hash_parent'}). "..";
 +                              $href .= esc_path_info($params{'hash_parent'}). "..";
                                delete $params{'hash_parent'};
                        }
  
 -                      $href .= esc_url($params{'hash_base'});
 +                      $href .= esc_path_info($params{'hash_base'});
                        if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
 -                              $href .= ":/".esc_url($params{'file_name'});
 +                              $href .= ":/".esc_path_info($params{'file_name'});
                                delete $params{'file_name'};
                        }
                        delete $params{'hash'};
                        delete $params{'hash_base'};
                } elsif (defined $params{'hash'}) {
 -                      $href .= esc_url($params{'hash'});
 +                      $href .= esc_path_info($params{'hash'});
                        delete $params{'hash'};
                }
  
        }
        $href .= "?" . join(';', @result) if scalar @result;
  
 +      # final transformation: trailing spaces must be escaped (URI-encoded)
 +      $href =~ s/(\s+)$/CGI::escape($1)/e;
 +
        return $href;
  }
  
@@@ -1395,17 -1347,6 +1395,17 @@@ sub esc_param 
        return $str;
  }
  
 +# the quoting rules for path_info fragment are slightly different
 +sub esc_path_info {
 +      my $str = shift;
 +      return undef unless defined $str;
 +
 +      # path_info doesn't treat '+' as space (specially), but '?' must be escaped
 +      $str =~ s/([^A-Za-z0-9\-_.~();\/;:@&= +]+)/CGI::escape($1)/eg;
 +
 +      return $str;
 +}
 +
  # quote unsafe chars in whole URL, so some characters cannot be quoted
  sub esc_url {
        my $str = shift;
@@@ -2812,44 -2753,6 +2812,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;
@@@ -3248,15 -3151,13 +3248,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;
                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;
@@@ -3467,8 -3368,7 +3467,8 @@@ sub run_highlighter 
        close $fd
                or die_error(404, "Reading blob failed");
        open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
 -                "highlight --xhtml --fragment --syntax $syntax |"
 +                quote_command($highlight_bin).
 +                " --xhtml --fragment --syntax $syntax |"
                or die_error(500, "Couldn't open file or run syntax highlighter");
        return $fd;
  }
        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";
        }
@@@ -3696,7 -3588,7 +3696,7 @@@ sub git_footer_html 
                print "<div id=\"generating_info\">\n";
                print 'This page took '.
                      '<span id="generating_time" class="time_span">'.
 -                    Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
 +                    tv_interval($t0, [ gettimeofday() ]).
                      ' seconds </span>'.
                      ' and '.
                      '<span id="generating_cmd">'.
@@@ -3824,19 -3716,6 +3824,19 @@@ sub git_print_page_nav 
              "</div>\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;
@@@ -3880,49 -3759,6 +3880,49 @@@ sub git_print_header_div 
              "\n</div>\n";
  }
  
 +sub format_repo_url {
 +      my ($name, $url) = @_;
 +      return "<tr class=\"metadata_url\"><td>$name</td><td>$url</td></tr>\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(@_);
  }
@@@ -5122,7 -4958,7 +5122,7 @@@ sub git_heads_body 
                      "<td class=\"link\">" .
                      $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") .
                      "</td>\n" .
                      "</tr>";
        }
        print "</table>\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 = "<table class=\"projects_list\">\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 .= "</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 "<table class=\"heads\">\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 "<tr class=\"dark\">\n";
 +              } else {
 +                      print "<tr class=\"light\">\n";
 +              }
 +              $alternate ^= 1;
 +              print "<td>" .
 +                    $cgi->a({-href=> href(action=>'remotes', hash=>$remote),
 +                             -class=> "list name"},esc_html($remote)) .
 +                    "</td>";
 +              print "<td class=\"link\">" .
 +                    (defined $fetch ? $cgi->a({-href=> $fetch}, "fetch") : "fetch") .
 +                    " | " .
 +                    (defined $push ? $cgi->a({-href=> $push}, "push") : "push") .
 +                    "</td>";
 +
 +              print "</tr>\n";
 +      }
 +
 +      if ($limited) {
 +              print "<tr>\n" .
 +                    "<td colspan=\"3\">" .
 +                    $cgi->a({-href => href(action=>"remotes")}, "...") .
 +                    "</td>\n" . "</tr>\n";
 +      }
 +
 +      print "</table>";
 +}
 +
 +# 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;
@@@ -5366,7 -5107,6 +5366,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);
  
        # 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');
  
        @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
        foreach my $git_url (@url_list) {
                next unless $git_url;
 -              print "<tr class=\"metadata_url\"><td>$url_tag</td><td>$git_url</td></tr>\n";
 +              print format_repo_url($url_tag, $git_url);
                $url_tag = "";
        }
  
                               $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,
@@@ -5562,7 -5296,7 +5562,7 @@@ sub git_blame_common 
                print 'END';
                if (defined $t0 && gitweb_check_feature('timed')) {
                        print ' '.
 -                            Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
 +                            tv_interval($t0, [ gettimeofday() ]).
                              ' '.$number_of_git_cmds;
                }
                print "\n";
@@@ -5749,7 -5483,7 +5749,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();
  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();
        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;
@@@ -7169,7 -6870,7 +7169,7 @@@ XM
                if (defined $favicon) {
                        print "<icon>" . esc_url($favicon) . "</icon>\n";
                }
-               if (defined $logo_url) {
+               if (defined $logo) {
                        # not twice as wide as tall: 72 x 27 pixels
                        print "<logo>" . esc_url($logo) . "</logo>\n";
                }
diff --combined t/t9001-send-email.sh
index 1dc4a9228ed045871fa1eda7ed66ac6737facd36,f1047d2ef3ee473a924f5819441dd1a7b1829768..3271426f795e7c3e1e424f4f17d96c2f27daa868
@@@ -201,28 -201,10 +201,28 @@@ test_expect_success $PREREQ 'Prompting 
                grep "^To: to@example.com\$" msgtxt1
  '
  
 +test_expect_success $PREREQ 'tocmd works' '
 +      clean_fake_sendmail &&
 +      cp $patches tocmd.patch &&
 +      echo tocmd--tocmd@example.com >>tocmd.patch &&
 +      {
 +        echo "#!$SHELL_PATH"
 +        echo sed -n -e s/^tocmd--//p \"\$1\"
 +      } > tocmd-sed &&
 +      chmod +x tocmd-sed &&
 +      git send-email \
 +              --from="Example <nobody@example.com>" \
 +              --to-cmd=./tocmd-sed \
 +              --smtp-server="$(pwd)/fake.sendmail" \
 +              tocmd.patch \
 +              &&
 +      grep "^To: tocmd@example.com" msgtxt1
 +'
 +
  test_expect_success $PREREQ 'cccmd works' '
        clean_fake_sendmail &&
        cp $patches cccmd.patch &&
 -      echo cccmd--cccmd@example.com >>cccmd.patch &&
 +      echo "cccmd--  cccmd@example.com" >>cccmd.patch &&
        {
          echo "#!$SHELL_PATH"
          echo sed -n -e s/^cccmd--//p \"\$1\"
@@@ -313,49 -295,6 +313,49 @@@ test_expect_success $PREREQ 'Valid In-R
        ! grep "^In-Reply-To: < *>" msgtxt1
  '
  
 +test_expect_success $PREREQ 'In-Reply-To without --chain-reply-to' '
 +      clean_fake_sendmail &&
 +      echo "<unique-message-id@example.com>" >expect &&
 +      git send-email \
 +              --from="Example <nobody@example.com>" \
 +              --to=nobody@example.com \
 +              --nochain-reply-to \
 +              --in-reply-to="$(cat expect)" \
 +              --smtp-server="$(pwd)/fake.sendmail" \
 +              $patches $patches $patches \
 +              2>errors &&
 +      # The first message is a reply to --in-reply-to
 +      sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt1 >actual &&
 +      test_cmp expect actual &&
 +      # Second and subsequent messages are replies to the first one
 +      sed -n -e "s/^Message-Id: *\(.*\)/\1/p" msgtxt1 >expect &&
 +      sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt2 >actual &&
 +      test_cmp expect actual &&
 +      sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt3 >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success $PREREQ 'In-Reply-To with --chain-reply-to' '
 +      clean_fake_sendmail &&
 +      echo "<unique-message-id@example.com>" >expect &&
 +      git send-email \
 +              --from="Example <nobody@example.com>" \
 +              --to=nobody@example.com \
 +              --chain-reply-to \
 +              --in-reply-to="$(cat expect)" \
 +              --smtp-server="$(pwd)/fake.sendmail" \
 +              $patches $patches $patches \
 +              2>errors &&
 +      sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt1 >actual &&
 +      test_cmp expect actual &&
 +      sed -n -e "s/^Message-Id: *\(.*\)/\1/p" msgtxt1 >expect &&
 +      sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt2 >actual &&
 +      test_cmp expect actual &&
 +      sed -n -e "s/^Message-Id: *\(.*\)/\1/p" msgtxt2 >expect &&
 +      sed -n -e "s/^In-Reply-To: *\(.*\)/\1/p" msgtxt3 >actual &&
 +      test_cmp expect actual
 +'
 +
  test_expect_success $PREREQ 'setup fake editor' '
        (echo "#!$SHELL_PATH" &&
         echo "echo fake edit >>\"\$1\""
@@@ -1008,45 -947,6 +1008,45 @@@ test_expect_success $PREREQ '--no-bcc o
        ! grep "RCPT TO:<other@ex.com>" stdout
  '
  
 +test_expect_success $PREREQ 'patches To headers are used by default' '
 +      patch=`git format-patch -1 --to="bodies@example.com"` &&
 +      test_when_finished "rm $patch" &&
 +      git send-email \
 +              --dry-run \
 +              --from="Example <nobody@example.com>" \
 +              --smtp-server relay.example.com \
 +              $patch >stdout &&
 +      grep "RCPT TO:<bodies@example.com>" stdout
 +'
 +
 +test_expect_success $PREREQ 'patches To headers are appended to' '
 +      patch=`git format-patch -1 --to="bodies@example.com"` &&
 +      test_when_finished "rm $patch" &&
 +      git send-email \
 +              --dry-run \
 +              --from="Example <nobody@example.com>" \
 +              --to=nobody@example.com \
 +              --smtp-server relay.example.com \
 +              $patch >stdout &&
 +      grep "RCPT TO:<bodies@example.com>" stdout &&
 +      grep "RCPT TO:<nobody@example.com>" stdout
 +'
 +
 +test_expect_success $PREREQ 'To headers from files reset each patch' '
 +      patch1=`git format-patch -1 --to="bodies@example.com"` &&
 +      patch2=`git format-patch -1 --to="other@example.com" HEAD~` &&
 +      test_when_finished "rm $patch1 && rm $patch2" &&
 +      git send-email \
 +              --dry-run \
 +              --from="Example <nobody@example.com>" \
 +              --to="nobody@example.com" \
 +              --smtp-server relay.example.com \
 +              $patch1 $patch2 >stdout &&
 +      test $(grep -c "RCPT TO:<bodies@example.com>" stdout) = 1 &&
 +      test $(grep -c "RCPT TO:<nobody@example.com>" stdout) = 2 &&
 +      test $(grep -c "RCPT TO:<other@example.com>" stdout) = 1
 +'
 +
  test_expect_success $PREREQ 'setup expect' '
  cat >email-using-8bit <<EOF
  From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001
@@@ -1135,7 -1035,7 +1135,7 @@@ test_expect_success $PREREQ '--8bit-enc
  # Note that the patches in this test are deliberately out of order; we
  # want to make sure it works even if the cover-letter is not in the
  # first mail.
- test_expect_success 'refusing to send cover letter template' '
+ test_expect_success $PREREQ 'refusing to send cover letter template' '
        clean_fake_sendmail &&
        rm -fr outdir &&
        git format-patch --cover-letter -2 -o outdir &&
        test -z "$(ls msgtxt*)"
  '
  
- test_expect_success '--force sends cover letter template anyway' '
+ test_expect_success $PREREQ '--force sends cover letter template anyway' '
        clean_fake_sendmail &&
        rm -fr outdir &&
        git format-patch --cover-letter -2 -o outdir &&