Merge branch 'jc/web'
authorJunio C Hamano <junkio@cox.net>
Wed, 25 Oct 2006 20:30:18 +0000 (13:30 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 25 Oct 2006 20:30:18 +0000 (13:30 -0700)
* jc/web:
gitweb: Print commit message without title in commitdiff only if there is any
gitweb: Filter out commit ID from @difftree in git_commit and git_commitdiff
gitweb: Get rid of git_print_simplified_log
gitweb: Show project's README.html if available
gitweb: Do not automatically append " git" to custom site name
gitweb: Make search type a popup menu
gitweb: Restore object-named links in item lists
gitweb: use for-each-ref to show the latest activity across branches

1  2 
gitweb/gitweb.perl
diff --combined gitweb/gitweb.perl
index 9eb19bb82ed843c6de6040c28cbbbff8e1ea9595,c82fc6268b9acf925b6faa37fe21b3bf1fd44afa..ba7a42a98353df87b566d26d1ea3dd76fcbd1e6b
@@@ -39,7 -39,8 +39,8 @@@ our $home_link_str = "++GITWEB_HOME_LIN
  
  # name of your site or organization to appear in page titles
  # replace this with something more descriptive for clearer bookmarks
- our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";
+ our $site_name = "++GITWEB_SITENAME++"
+                  || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
  
  # filename of html text to include at top of each page
  our $site_header = "++GITWEB_SITE_HEADER++";
@@@ -342,6 -343,13 +343,13 @@@ if (defined $searchtext) 
        $searchtext = quotemeta $searchtext;
  }
  
+ our $searchtype = $cgi->param('st');
+ if (defined $searchtype) {
+       if ($searchtype =~ m/[^a-z]/) {
+               die_error(undef, "Invalid searchtype parameter");
+       }
+ }
  # now read PATH_INFO and use it as alternative to parameters
  sub evaluate_path_info {
        return if defined $project;
@@@ -406,6 -414,7 +414,7 @@@ my %actions = 
        "log" => \&git_log,
        "rss" => \&git_rss,
        "search" => \&git_search,
+       "search_help" => \&git_search_help,
        "shortlog" => \&git_shortlog,
        "summary" => \&git_summary,
        "tag" => \&git_tag,
@@@ -455,6 -464,7 +464,7 @@@ sub href(%) 
                page => "pg",
                order => "o",
                searchtext => "s",
+               searchtype => "st",
        );
        my %mapping = @mapping;
  
@@@ -1035,9 -1045,6 +1045,9 @@@ sub parse_date 
        $date{'hour_local'} = $hour;
        $date{'minute_local'} = $min;
        $date{'tz_local'} = $tz;
 +      $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
 +                                 1900+$year, $mon+1, $mday,
 +                                 $hour, $min, $sec, $tz);
        return %date;
  }
  
@@@ -1076,6 -1083,24 +1086,24 @@@ sub parse_tag 
        return %tag
  }
  
+ sub git_get_last_activity {
+       my ($path) = @_;
+       my $fd;
+       $git_dir = "$projectroot/$path";
+       open($fd, "-|", git_cmd(), 'for-each-ref',
+            '--format=%(refname) %(committer)',
+            '--sort=-committerdate',
+            'refs/heads') or return;
+       my $most_recent = <$fd>;
+       close $fd or return;
+       if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
+               my $timestamp = $1;
+               my $age = time - $timestamp;
+               return ($age, age_string($age));
+       }
+ }
  sub parse_commit {
        my $commit_id = shift;
        my $commit_text = shift;
@@@ -1405,7 -1430,7 +1433,7 @@@ sub git_header_html 
        my $status = shift || "200 OK";
        my $expires = shift;
  
-       my $title = "$site_name git";
+       my $title = "$site_name";
        if (defined $project) {
                $title .= " - $project";
                if (defined $action) {
                      $cgi->hidden(-name => "p") . "\n" .
                      $cgi->hidden(-name => "a") . "\n" .
                      $cgi->hidden(-name => "h") . "\n" .
+                     $cgi->popup_menu(-name => 'st', -default => 'commit',
+                                      -values => ['commit', 'author', 'committer', 'pickaxe']) .
+                     $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+                     " search:\n",
                      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
                      "</div>" .
                      $cgi->end_form() . "\n";
@@@ -1747,15 -1776,6 +1779,6 @@@ sub git_print_log ($;%) 
        }
  }
  
- sub git_print_simplified_log {
-       my $log = shift;
-       my $remove_title = shift;
-       git_print_log($log,
-               -final_empty_line=> 1,
-               -remove_title => $remove_title);
- }
  # print tree entry (row of git_tree), but without encompassing <tr> element
  sub git_print_tree_entry {
        my ($t, $basedir, $hash_base, $have_blame) = @_;
                                               file_name=>"$basedir$t->{'name'}", %base_key),
                                -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
                print "<td class=\"link\">";
+               print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "blob");
                if ($have_blame) {
-                       print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
+                       print " | " .
+                             $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
                                                           file_name=>"$basedir$t->{'name'}", %base_key)},
                                            "blame");
                }
                if (defined $hash_base) {
-                       if ($have_blame) {
-                               print " | ";
-                       }
-                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+                       print " | " .
+                             $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
                                                     hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
                                      "history");
                }
                              esc_html($t->{'name'}));
                print "</td>\n";
                print "<td class=\"link\">";
+               print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "tree");
                if (defined $hash_base) {
-                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+                       print " | " .
+                             $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
                                                     file_name=>"$basedir$t->{'name'}")},
                                      "history");
                }
@@@ -1884,6 -1910,9 +1913,9 @@@ sub git_difftree_body 
                                print $cgi->a({-href => "#patch$patchno"}, "patch");
                                print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+                                                    hash_base=>$parent, file_name=>$diff{'file'})},
+                                     "blob") . " | ";
                        print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
                                                     file_name=>$diff{'file'})},
                                      "blame") . " | ";
                                }
                                print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff{'file'})},
+                                     "blob") . " | ";
                        print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
                                                     file_name=>$diff{'file'})},
                                      "blame") . " | ";
                                }
                                print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+                                                    hash_base=>$parent, file_name=>$diff{'from_file'})},
+                                     "blob") . " | ";
                        print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
                                                     file_name=>$diff{'from_file'})},
                                      "blame") . " | ";
@@@ -2136,6 -2171,7 +2174,7 @@@ sub git_shortlog_body 
                                          href(action=>"commit", hash=>$commit), $ref);
                print "</td>\n" .
                      "<td class=\"link\">" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
                if (gitweb_have_snapshot()) {
@@@ -2330,16 -2366,11 +2369,11 @@@ sub git_project_list 
                die_error(undef, "No projects found");
        }
        foreach my $pr (@list) {
-               my $head = git_get_head_hash($pr->{'path'});
-               if (!defined $head) {
+               my (@aa) = git_get_last_activity($pr->{'path'});
+               unless (@aa) {
                        next;
                }
-               $git_dir = "$projectroot/$pr->{'path'}";
-               my %co = parse_commit($head);
-               if (!%co) {
-                       next;
-               }
-               $pr->{'commit'} = \%co;
+               ($pr->{'age'}, $pr->{'age_string'}) = @aa;
                if (!defined $pr->{'descr'}) {
                        my $descr = git_get_project_description($pr->{'path'}) || "";
                        $pr->{'descr'} = chop_str($descr, 25, 5);
                      "</th>\n";
        }
        if ($order eq "age") {
-               @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
+               @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
                print "<th>Last Change</th>\n";
        } else {
                print "<th>" .
                                        -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
                      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
                      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
-               print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" .
-                     $pr->{'commit'}{'age_string'} . "</td>\n" .
+               print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+                     $pr->{'age_string'} . "</td>\n" .
                      "<td class=\"link\">" .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
@@@ -2490,6 -2521,14 +2524,14 @@@ sub git_summary 
        }
        print "</table>\n";
  
+       if (-s "$projectroot/$project/README.html") {
+               if (open my $fd, "$projectroot/$project/README.html") {
+                       print "<div class=\"title\">readme</div>\n";
+                       print $_ while (<$fd>);
+                       close $fd;
+               }
+       }
        open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
                git_get_head_hash($project)
                or die_error(undef, "Open git-rev-list failed");
@@@ -2568,8 -2607,7 +2610,8 @@@ sub git_blame2 
        if ($ftype !~ "blob") {
                die_error("400 Bad Request", "Object is not a blob");
        }
 -      open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base)
 +      open ($fd, "-|", git_cmd(), "blame", '-p', '--',
 +            $file_name, $hash_base)
                or die_error(undef, "Open git-blame failed");
        git_header_html();
        my $formats_nav =
  <table class="blame">
  <tr><th>Commit</th><th>Line</th><th>Data</th></tr>
  HTML
 -      while (<$fd>) {
 -              my ($full_rev, $author, $date, $lineno, $data) =
 -                      /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/;
 +      my %metainfo = ();
 +      while (1) {
 +              $_ = <$fd>;
 +              last unless defined $_;
 +              my ($full_rev, $orig_lineno, $lineno, $group_size) =
 +                  /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
 +              if (!exists $metainfo{$full_rev}) {
 +                      $metainfo{$full_rev} = {};
 +              }
 +              my $meta = $metainfo{$full_rev};
 +              while (<$fd>) {
 +                      last if (s/^\t//);
 +                      if (/^(\S+) (.*)$/) {
 +                              $meta->{$1} = $2;
 +                      }
 +              }
 +              my $data = $_;
                my $rev = substr($full_rev, 0, 8);
 -              my $print_c8 = 0;
 -
 -              if (!defined $last_rev) {
 -                      $last_rev = $full_rev;
 -                      $print_c8 = 1;
 -              } elsif ($last_rev ne $full_rev) {
 -                      $last_rev = $full_rev;
 +              my $author = $meta->{'author'};
 +              my %date = parse_date($meta->{'author-time'},
 +                                    $meta->{'author-tz'});
 +              my $date = $date{'iso-tz'};
 +              if ($group_size) {
                        $current_color = ++$current_color % $num_colors;
 -                      $print_c8 = 1;
                }
                print "<tr class=\"$rev_color[$current_color]\">\n";
 -              print "<td class=\"sha1\"";
 -              if ($print_c8 == 1) {
 +              if ($group_size) {
 +                      print "<td class=\"sha1\"";
                        print " title=\"$author, $date\"";
 -              }
 -              print ">";
 -              if ($print_c8 == 1) {
 -                      print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)},
 +                      print " rowspan=\"$group_size\"" if ($group_size > 1);
 +                      print ">";
 +                      print $cgi->a({-href => href(action=>"commit",
 +                                                   hash=>$full_rev,
 +                                                   file_name=>$file_name)},
                                      esc_html($rev));
 +                      print "</td>\n";
                }
 -              print "</td>\n";
 -              print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" .
 -                    esc_html($lineno) . "</a></td>\n";
 +              my $blamed = href(action => 'blame',
 +                                file_name => $meta->{'filename'},
 +                                hash_base => $full_rev);
 +              print "<td class=\"linenr\">";
 +              print $cgi->a({ -href => "$blamed#l$orig_lineno",
 +                              -id => "l$lineno",
 +                              -class => "linenr" },
 +                            esc_html($lineno));
 +              print "</td>";
                print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
                print "</tr>\n";
        }
@@@ -3073,7 -3092,7 +3115,7 @@@ sub git_log 
                      "</div>\n";
  
                print "<div class=\"log_body\">\n";
-               git_print_simplified_log($co{'comment'});
+               git_print_log($co{'comment'}, -final_empty_line=> 1);
                print "</div>\n";
        }
        git_footer_html();
@@@ -3096,6 -3115,9 +3138,9 @@@ sub git_commit 
        my @difftree = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading git-diff-tree failed");
  
+       # filter out commit ID output
+       @difftree = grep(!/^[0-9a-fA-F]{40}$/, @difftree);
        # non-textual hash id's can be cached
        my $expires;
        if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
@@@ -3372,7 -3394,9 +3417,9 @@@ sub git_commitdiff 
                while (chomp(my $line = <$fd>)) {
                        # empty line ends raw part of diff-tree output
                        last unless $line;
-                       push @difftree, $line;
+                       # filter out commit ID output
+                       push @difftree, $line
+                               unless $line =~ m/^[0-9a-fA-F]{40}$/;
                }
  
        } elsif ($format eq 'plain') {
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
                git_print_authorship(\%co);
                print "<div class=\"page_body\">\n";
-               print "<div class=\"log\">\n";
-               git_print_simplified_log($co{'comment'}, 1); # skip title
-               print "</div>\n"; # class="log"
+               if (@{$co{'comment'}} > 1) {
+                       print "<div class=\"log\">\n";
+                       git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
+                       print "</div>\n"; # class="log"
+               }
  
        } elsif ($format eq 'plain') {
                my $refs = git_get_references("tags");
@@@ -3538,18 -3564,8 +3587,8 @@@ sub git_search 
                die_error(undef, "Unknown commit object");
        }
  
-       my $commit_search = 1;
-       my $author_search = 0;
-       my $committer_search = 0;
-       my $pickaxe_search = 0;
-       if ($searchtext =~ s/^author\\://i) {
-               $author_search = 1;
-       } elsif ($searchtext =~ s/^committer\\://i) {
-               $committer_search = 1;
-       } elsif ($searchtext =~ s/^pickaxe\\://i) {
-               $commit_search = 0;
-               $pickaxe_search = 1;
+       $searchtype ||= 'commit';
+       if ($searchtype eq 'pickaxe') {
                # pickaxe may take all resources of your box and run for several minutes
                # with every query - so decide by yourself how public you make this feature
                my ($have_pickaxe) = gitweb_check_feature('pickaxe');
                        die_error('403 Permission denied', "Permission denied");
                }
        }
        git_header_html();
        git_print_page_nav('','', $hash,$co{'tree'},$hash);
        git_print_header_div('commit', esc_html($co{'title'}), $hash);
  
        print "<table cellspacing=\"0\">\n";
        my $alternate = 1;
-       if ($commit_search) {
+       if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
                $/ = "\0";
                open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next;
                while (my $commit_text = <$fd>) {
                        if (!grep m/$searchtext/i, $commit_text) {
                                next;
                        }
-                       if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
+                       if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) {
                                next;
                        }
-                       if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
+                       if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
                                next;
                        }
                        my @commit_lines = split "\n", $commit_text;
                close $fd;
        }
  
-       if ($pickaxe_search) {
+       if ($searchtype eq 'pickaxe') {
                $/ = "\n";
                my $git_command = git_cmd_str();
                open my $fd, "-|", "$git_command rev-list $hash | " .
        git_footer_html();
  }
  
+ sub git_search_help {
+       git_header_html();
+       git_print_page_nav('','', $hash,$hash,$hash);
+       print <<EOT;
+ <dl>
+ <dt><b>commit</b></dt>
+ <dd>The commit messages and authorship information will be scanned for the given string.</dd>
+ <dt><b>author</b></dt>
+ <dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.</dd>
+ <dt><b>committer</b></dt>
+ <dd>Name and e-mail of the committer and date of commit will be scanned for the given string.</dd>
+ EOT
+       my ($have_pickaxe) = gitweb_check_feature('pickaxe');
+       if ($have_pickaxe) {
+               print <<EOT;
+ <dt><b>pickaxe</b></dt>
+ <dd>All commits that caused the string to appear or disappear from any file (changes that
+ added, removed or "modified" the string) will be listed. This search can take a while and
+ takes a lot of strain on the server, so please use it wisely.</dd>
+ EOT
+       }
+       print "</dl>\n";
+       git_footer_html();
+ }
  sub git_shortlog {
        my $head = git_get_head_hash($project);
        if (!defined $hash) {
@@@ -3783,7 -3825,7 +3848,7 @@@ sub git_opml 
  <?xml version="1.0" encoding="utf-8"?>
  <opml version="1.0">
  <head>
-   <title>$site_name Git OPML Export</title>
+   <title>$site_name OPML Export</title>
  </head>
  <body>
  <outline text="git RSS feeds">