Merge branch 'gb/gitweb-avatar'

* gb/gitweb-avatar:
  gitweb: add empty alt text to avatar img
  gitweb: picon avatar provider
  gitweb: gravatar url cache
  gitweb: (gr)avatar support
  gitweb: use git_print_authorship_rows in 'tag' view too
  gitweb: uniform author info for commit and commitdiff
  gitweb: refactor author name insertion
This commit is contained in:
Junio C Hamano 2009-07-09 01:00:59 -07:00
Родитель c535d767f7 7d25ef41c8
Коммит 1d4bf0b362
3 изменённых файлов: 199 добавлений и 49 удалений

Просмотреть файл

@ -28,6 +28,10 @@ img.logo {
border-width: 0px; border-width: 0px;
} }
img.avatar {
vertical-align: middle;
}
div.page_header { div.page_header {
height: 25px; height: 25px;
padding: 8px; padding: 8px;
@ -132,11 +136,14 @@ div.list_head {
font-style: italic; font-style: italic;
} }
.author_date, .author {
font-style: italic;
}
div.author_date { div.author_date {
padding: 8px; padding: 8px;
border: solid #d9d8d1; border: solid #d9d8d1;
border-width: 0px 0px 1px 0px; border-width: 0px 0px 1px 0px;
font-style: italic;
} }
a.list { a.list {

Просмотреть файл

@ -195,6 +195,14 @@ our %known_snapshot_format_aliases = (
'x-zip' => undef, '' => undef, 'x-zip' => undef, '' => undef,
); );
# Pixel sizes for icons and avatars. If the default font sizes or lineheights
# are changed, it may be appropriate to change these values too via
# $GITWEB_CONFIG.
our %avatar_size = (
'default' => 16,
'double' => 32
);
# You define site-wide feature defaults here; override them with # You define site-wide feature defaults here; override them with
# $GITWEB_CONFIG as necessary. # $GITWEB_CONFIG as necessary.
our %feature = ( our %feature = (
@ -365,6 +373,27 @@ our %feature = (
'sub' => \&feature_patches, 'sub' => \&feature_patches,
'override' => 0, 'override' => 0,
'default' => [16]}, 'default' => [16]},
# Avatar support. When this feature is enabled, views such as
# shortlog or commit will display an avatar associated with
# the email of the committer(s) and/or author(s).
# Currently available providers are gravatar and picon.
# If an unknown provider is specified, the feature is disabled.
# Gravatar depends on Digest::MD5.
# Picon currently relies on the indiana.edu database.
# To enable system wide have in $GITWEB_CONFIG
# $feature{'avatar'}{'default'} = ['<provider>'];
# where <provider> is either gravatar or picon.
# To have project specific config enable override in $GITWEB_CONFIG
# $feature{'avatar'}{'override'} = 1;
# and in project config gitweb.avatar = <provider>;
'avatar' => {
'sub' => \&feature_avatar,
'override' => 0,
'default' => ['']},
); );
sub gitweb_get_feature { sub gitweb_get_feature {
@ -433,6 +462,12 @@ sub feature_patches {
return ($_[0]); return ($_[0]);
} }
sub feature_avatar {
my @val = (git_get_project_config('avatar'));
return @val ? @val : @_;
}
# checking HEAD file with -e is fragile if the repository was # checking HEAD file with -e is fragile if the repository was
# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed # initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
# and then pruned. # and then pruned.
@ -814,6 +849,19 @@ $git_dir = "$projectroot/$project" if $project;
our @snapshot_fmts = gitweb_get_feature('snapshot'); our @snapshot_fmts = gitweb_get_feature('snapshot');
@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts); @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
# check that the avatar feature is set to a known provider name,
# and for each provider check if the dependencies are satisfied.
# if the provider name is invalid or the dependencies are not met,
# reset $git_avatar to the empty string.
our ($git_avatar) = gitweb_get_feature('avatar');
if ($git_avatar eq 'gravatar') {
$git_avatar = '' unless (eval { require Digest::MD5; 1; });
} elsif ($git_avatar eq 'picon') {
# no dependencies
} else {
$git_avatar = '';
}
# dispatch # dispatch
if (!defined $action) { if (!defined $action) {
if (defined $hash) { if (defined $hash) {
@ -1469,6 +1517,82 @@ sub format_subject_html {
} }
} }
# Rather than recomputing the url for an email multiple times, we cache it
# after the first hit. This gives a visible benefit in views where the avatar
# for the same email is used repeatedly (e.g. shortlog).
# The cache is shared by all avatar engines (currently gravatar only), which
# are free to use it as preferred. Since only one avatar engine is used for any
# given page, there's no risk for cache conflicts.
our %avatar_cache = ();
# Compute the picon url for a given email, by using the picon search service over at
# http://www.cs.indiana.edu/picons/search.html
sub picon_url {
my $email = lc shift;
if (!$avatar_cache{$email}) {
my ($user, $domain) = split('@', $email);
$avatar_cache{$email} =
"http://www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" .
"$domain/$user/" .
"users+domains+unknown/up/single";
}
return $avatar_cache{$email};
}
# Compute the gravatar url for a given email, if it's not in the cache already.
# Gravatar stores only the part of the URL before the size, since that's the
# one computationally more expensive. This also allows reuse of the cache for
# different sizes (for this particular engine).
sub gravatar_url {
my $email = lc shift;
my $size = shift;
$avatar_cache{$email} ||=
"http://www.gravatar.com/avatar/" .
Digest::MD5::md5_hex($email) . "?s=";
return $avatar_cache{$email} . $size;
}
# Insert an avatar for the given $email at the given $size if the feature
# is enabled.
sub git_get_avatar {
my ($email, %opts) = @_;
my $pre_white = ($opts{-pad_before} ? "&nbsp;" : "");
my $post_white = ($opts{-pad_after} ? "&nbsp;" : "");
$opts{-size} ||= 'default';
my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
my $url = "";
if ($git_avatar eq 'gravatar') {
$url = gravatar_url($email, $size);
} elsif ($git_avatar eq 'picon') {
$url = picon_url($email);
}
# Other providers can be added by extending the if chain, defining $url
# as needed. If no variant puts something in $url, we assume avatars
# are completely disabled/unavailable.
if ($url) {
return $pre_white .
"<img width=\"$size\" " .
"class=\"avatar\" " .
"src=\"$url\" " .
"alt=\"\" " .
"/>" . $post_white;
} else {
return "";
}
}
# format the author name of the given commit with the given tag
# the author name is chopped and escaped according to the other
# optional parameters (see chop_str).
sub format_author_html {
my $tag = shift;
my $co = shift;
my $author = chop_and_escape_str($co->{'author_name'}, @_);
return "<$tag class=\"author\">" .
git_get_avatar($co->{'author_email'}, -pad_after => 1) .
$author . "</$tag>";
}
# format git diff header line, i.e. "diff --(git|combined|cc) ..." # format git diff header line, i.e. "diff --(git|combined|cc) ..."
sub format_git_diff_header_line { sub format_git_diff_header_line {
my $line = shift; my $line = shift;
@ -2399,8 +2523,14 @@ sub parse_tag {
$tag{'name'} = $1; $tag{'name'} = $1;
} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) { } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
$tag{'author'} = $1; $tag{'author'} = $1;
$tag{'epoch'} = $2; $tag{'author_epoch'} = $2;
$tag{'tz'} = $3; $tag{'author_tz'} = $3;
if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
$tag{'author_name'} = $1;
$tag{'author_email'} = $2;
} else {
$tag{'author_name'} = $tag{'author'};
}
} elsif ($line =~ m/--BEGIN/) { } elsif ($line =~ m/--BEGIN/) {
push @comment, $line; push @comment, $line;
last; last;
@ -3214,21 +3344,54 @@ sub git_print_header_div {
"\n</div>\n"; "\n</div>\n";
} }
sub git_print_authorship { sub print_local_time {
my $co = shift; my %date = @_;
if ($date{'hour_local'} < 6) {
my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
print "<div class=\"author_date\">" .
esc_html($co->{'author_name'}) .
" [$ad{'rfc2822'}";
if ($ad{'hour_local'} < 6) {
printf(" (<span class=\"atnight\">%02d:%02d</span> %s)", printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
$ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
} else { } else {
printf(" (%02d:%02d %s)", printf(" (%02d:%02d %s)",
$ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
}
}
# Outputs the author name and date in long form
sub git_print_authorship {
my $co = shift;
my %opts = @_;
my $tag = $opts{-tag} || 'div';
my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
print "<$tag class=\"author_date\">" .
esc_html($co->{'author_name'}) .
" [$ad{'rfc2822'}";
print_local_time(%ad) if ($opts{-localtime});
print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
. "</$tag>\n";
}
# Outputs table rows containing the full author or committer information,
# in the format expected for 'commit' view (& similia).
# Parameters are a commit hash reference, followed by the list of people
# to output information for. If the list is empty it defalts to both
# author and committer.
sub git_print_authorship_rows {
my $co = shift;
# too bad we can't use @people = @_ || ('author', 'committer')
my @people = @_;
@people = ('author', 'committer') unless @people;
foreach my $who (@people) {
my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
print "<tr><td>$who</td><td>" . esc_html($co->{$who}) . "</td>" .
"<td rowspan=\"2\">" .
git_get_avatar($co->{"${who}_email"}, -size => 'double') .
"</td></tr>\n" .
"<tr>" .
"<td></td><td> $wd{'rfc2822'}";
print_local_time(%wd);
print "</td>" .
"</tr>\n";
} }
print "]</div>\n";
} }
sub git_print_page_path { sub git_print_page_path {
@ -4142,11 +4305,9 @@ sub git_shortlog_body {
print "<tr class=\"light\">\n"; print "<tr class=\"light\">\n";
} }
$alternate ^= 1; $alternate ^= 1;
my $author = chop_and_escape_str($co{'author_name'}, 10);
# git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" . # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
"<td><i>" . $author . "</i></td>\n" . format_author_html('td', \%co, 10) . "<td>";
"<td>";
print format_subject_html($co{'title'}, $co{'title_short'}, print format_subject_html($co{'title'}, $co{'title_short'},
href(action=>"commit", hash=>$commit), $ref); href(action=>"commit", hash=>$commit), $ref);
print "</td>\n" . print "</td>\n" .
@ -4193,11 +4354,9 @@ sub git_history_body {
print "<tr class=\"light\">\n"; print "<tr class=\"light\">\n";
} }
$alternate ^= 1; $alternate ^= 1;
# shortlog uses chop_str($co{'author_name'}, 10)
my $author = chop_and_escape_str($co{'author_name'}, 15, 3);
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
"<td><i>" . $author . "</i></td>\n" . # shortlog: format_author_html('td', \%co, 10)
"<td>"; format_author_html('td', \%co, 15, 3) . "<td>";
# originally git_history used chop_str($co{'title'}, 50) # originally git_history used chop_str($co{'title'}, 50)
print format_subject_html($co{'title'}, $co{'title_short'}, print format_subject_html($co{'title'}, $co{'title_short'},
href(action=>"commit", hash=>$commit), $ref); href(action=>"commit", hash=>$commit), $ref);
@ -4350,9 +4509,8 @@ sub git_search_grep_body {
print "<tr class=\"light\">\n"; print "<tr class=\"light\">\n";
} }
$alternate ^= 1; $alternate ^= 1;
my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
"<td><i>" . $author . "</i></td>\n" . format_author_html('td', \%co, 15, 5) .
"<td>" . "<td>" .
$cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
-class => "list subject"}, -class => "list subject"},
@ -4586,11 +4744,7 @@ sub git_tag {
$tag{'type'}) . "</td>\n" . $tag{'type'}) . "</td>\n" .
"</tr>\n"; "</tr>\n";
if (defined($tag{'author'})) { if (defined($tag{'author'})) {
my %ad = parse_date($tag{'epoch'}, $tag{'tz'}); git_print_authorship_rows(\%tag, 'author');
print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
print "<tr><td></td><td>" . $ad{'rfc2822'} .
sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) .
"</td></tr>\n";
} }
print "</table>\n\n" . print "</table>\n\n" .
"</div>\n"; "</div>\n";
@ -5094,9 +5248,9 @@ sub git_log {
" | " . " | " .
$cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
"<br/>\n" . "<br/>\n" .
"</div>\n" .
"<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" .
"</div>\n"; "</div>\n";
git_print_authorship(\%co, -tag => 'span');
print "<br/>\n</div>\n";
print "<div class=\"log_body\">\n"; print "<div class=\"log_body\">\n";
git_print_log($co{'comment'}, -final_empty_line=> 1); git_print_log($co{'comment'}, -final_empty_line=> 1);
@ -5115,8 +5269,6 @@ sub git_commit {
$hash ||= $hash_base || "HEAD"; $hash ||= $hash_base || "HEAD";
my %co = parse_commit($hash) my %co = parse_commit($hash)
or die_error(404, "Unknown commit object"); or die_error(404, "Unknown commit object");
my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
my $parent = $co{'parent'}; my $parent = $co{'parent'};
my $parents = $co{'parents'}; # listref my $parents = $co{'parents'}; # listref
@ -5183,22 +5335,7 @@ sub git_commit {
} }
print "<div class=\"title_text\">\n" . print "<div class=\"title_text\">\n" .
"<table class=\"object_header\">\n"; "<table class=\"object_header\">\n";
print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n". git_print_authorship_rows(\%co);
"<tr>" .
"<td></td><td> $ad{'rfc2822'}";
if ($ad{'hour_local'} < 6) {
printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
$ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
} else {
printf(" (%02d:%02d %s)",
$ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
}
print "</td>" .
"</tr>\n";
print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n";
print "<tr><td></td><td> $cd{'rfc2822'}" .
sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) .
"</td></tr>\n";
print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n"; print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
print "<tr>" . print "<tr>" .
"<td>tree</td>" . "<td>tree</td>" .
@ -5579,7 +5716,11 @@ sub git_commitdiff {
git_header_html(undef, $expires); git_header_html(undef, $expires);
git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
git_print_authorship(\%co); print "<div class=\"title_text\">\n" .
"<table class=\"object_header\">\n";
git_print_authorship_rows(\%co);
print "</table>".
"</div>\n";
print "<div class=\"page_body\">\n"; print "<div class=\"page_body\">\n";
if (@{$co{'comment'}} > 1) { if (@{$co{'comment'}} > 1) {
print "<div class=\"log\">\n"; print "<div class=\"log\">\n";

Просмотреть файл

@ -660,6 +660,7 @@ cat >>gitweb_config.perl <<EOF
\$feature{'blame'}{'override'} = 1; \$feature{'blame'}{'override'} = 1;
\$feature{'snapshot'}{'override'} = 1; \$feature{'snapshot'}{'override'} = 1;
\$feature{'avatar'}{'override'} = 1;
EOF EOF
test_expect_success \ test_expect_success \
@ -671,6 +672,7 @@ test_expect_success \
'config override: tree view, features disabled in repo config' \ 'config override: tree view, features disabled in repo config' \
'git config gitweb.blame no && 'git config gitweb.blame no &&
git config gitweb.snapshot none && git config gitweb.snapshot none &&
git config gitweb.avatar gravatar &&
gitweb_run "p=.git;a=tree"' gitweb_run "p=.git;a=tree"'
test_debug 'cat gitweb.log' test_debug 'cat gitweb.log'