Skip to content

Commit 1d4bf0b

Browse files
committed
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
2 parents c535d76 + 7d25ef4 commit 1d4bf0b

File tree

3 files changed

+196
-46
lines changed

3 files changed

+196
-46
lines changed

gitweb/gitweb.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ img.logo {
2828
border-width: 0px;
2929
}
3030

31+
img.avatar {
32+
vertical-align: middle;
33+
}
34+
3135
div.page_header {
3236
height: 25px;
3337
padding: 8px;
@@ -132,11 +136,14 @@ div.list_head {
132136
font-style: italic;
133137
}
134138

139+
.author_date, .author {
140+
font-style: italic;
141+
}
142+
135143
div.author_date {
136144
padding: 8px;
137145
border: solid #d9d8d1;
138146
border-width: 0px 0px 1px 0px;
139-
font-style: italic;
140147
}
141148

142149
a.list {

gitweb/gitweb.perl

Lines changed: 186 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,14 @@ BEGIN
195195
'x-zip' => undef, '' => undef,
196196
);
197197

198+
# Pixel sizes for icons and avatars. If the default font sizes or lineheights
199+
# are changed, it may be appropriate to change these values too via
200+
# $GITWEB_CONFIG.
201+
our %avatar_size = (
202+
'default' => 16,
203+
'double' => 32
204+
);
205+
198206
# You define site-wide feature defaults here; override them with
199207
# $GITWEB_CONFIG as necessary.
200208
our %feature = (
@@ -365,6 +373,27 @@ BEGIN
365373
'sub' => \&feature_patches,
366374
'override' => 0,
367375
'default' => [16]},
376+
377+
# Avatar support. When this feature is enabled, views such as
378+
# shortlog or commit will display an avatar associated with
379+
# the email of the committer(s) and/or author(s).
380+
381+
# Currently available providers are gravatar and picon.
382+
# If an unknown provider is specified, the feature is disabled.
383+
384+
# Gravatar depends on Digest::MD5.
385+
# Picon currently relies on the indiana.edu database.
386+
387+
# To enable system wide have in $GITWEB_CONFIG
388+
# $feature{'avatar'}{'default'} = ['<provider>'];
389+
# where <provider> is either gravatar or picon.
390+
# To have project specific config enable override in $GITWEB_CONFIG
391+
# $feature{'avatar'}{'override'} = 1;
392+
# and in project config gitweb.avatar = <provider>;
393+
'avatar' => {
394+
'sub' => \&feature_avatar,
395+
'override' => 0,
396+
'default' => ['']},
368397
);
369398

370399
sub gitweb_get_feature {
@@ -433,6 +462,12 @@ sub feature_patches {
433462
return ($_[0]);
434463
}
435464

465+
sub feature_avatar {
466+
my @val = (git_get_project_config('avatar'));
467+
468+
return @val ? @val : @_;
469+
}
470+
436471
# checking HEAD file with -e is fragile if the repository was
437472
# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
438473
# and then pruned.
@@ -814,6 +849,19 @@ sub evaluate_path_info {
814849
our @snapshot_fmts = gitweb_get_feature('snapshot');
815850
@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
816851

852+
# check that the avatar feature is set to a known provider name,
853+
# and for each provider check if the dependencies are satisfied.
854+
# if the provider name is invalid or the dependencies are not met,
855+
# reset $git_avatar to the empty string.
856+
our ($git_avatar) = gitweb_get_feature('avatar');
857+
if ($git_avatar eq 'gravatar') {
858+
$git_avatar = '' unless (eval { require Digest::MD5; 1; });
859+
} elsif ($git_avatar eq 'picon') {
860+
# no dependencies
861+
} else {
862+
$git_avatar = '';
863+
}
864+
817865
# dispatch
818866
if (!defined $action) {
819867
if (defined $hash) {
@@ -1469,6 +1517,82 @@ sub format_subject_html {
14691517
}
14701518
}
14711519

1520+
# Rather than recomputing the url for an email multiple times, we cache it
1521+
# after the first hit. This gives a visible benefit in views where the avatar
1522+
# for the same email is used repeatedly (e.g. shortlog).
1523+
# The cache is shared by all avatar engines (currently gravatar only), which
1524+
# are free to use it as preferred. Since only one avatar engine is used for any
1525+
# given page, there's no risk for cache conflicts.
1526+
our %avatar_cache = ();
1527+
1528+
# Compute the picon url for a given email, by using the picon search service over at
1529+
# http://www.cs.indiana.edu/picons/search.html
1530+
sub picon_url {
1531+
my $email = lc shift;
1532+
if (!$avatar_cache{$email}) {
1533+
my ($user, $domain) = split('@', $email);
1534+
$avatar_cache{$email} =
1535+
"http://www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" .
1536+
"$domain/$user/" .
1537+
"users+domains+unknown/up/single";
1538+
}
1539+
return $avatar_cache{$email};
1540+
}
1541+
1542+
# Compute the gravatar url for a given email, if it's not in the cache already.
1543+
# Gravatar stores only the part of the URL before the size, since that's the
1544+
# one computationally more expensive. This also allows reuse of the cache for
1545+
# different sizes (for this particular engine).
1546+
sub gravatar_url {
1547+
my $email = lc shift;
1548+
my $size = shift;
1549+
$avatar_cache{$email} ||=
1550+
"http://www.gravatar.com/avatar/" .
1551+
Digest::MD5::md5_hex($email) . "?s=";
1552+
return $avatar_cache{$email} . $size;
1553+
}
1554+
1555+
# Insert an avatar for the given $email at the given $size if the feature
1556+
# is enabled.
1557+
sub git_get_avatar {
1558+
my ($email, %opts) = @_;
1559+
my $pre_white = ($opts{-pad_before} ? "&nbsp;" : "");
1560+
my $post_white = ($opts{-pad_after} ? "&nbsp;" : "");
1561+
$opts{-size} ||= 'default';
1562+
my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
1563+
my $url = "";
1564+
if ($git_avatar eq 'gravatar') {
1565+
$url = gravatar_url($email, $size);
1566+
} elsif ($git_avatar eq 'picon') {
1567+
$url = picon_url($email);
1568+
}
1569+
# Other providers can be added by extending the if chain, defining $url
1570+
# as needed. If no variant puts something in $url, we assume avatars
1571+
# are completely disabled/unavailable.
1572+
if ($url) {
1573+
return $pre_white .
1574+
"<img width=\"$size\" " .
1575+
"class=\"avatar\" " .
1576+
"src=\"$url\" " .
1577+
"alt=\"\" " .
1578+
"/>" . $post_white;
1579+
} else {
1580+
return "";
1581+
}
1582+
}
1583+
1584+
# format the author name of the given commit with the given tag
1585+
# the author name is chopped and escaped according to the other
1586+
# optional parameters (see chop_str).
1587+
sub format_author_html {
1588+
my $tag = shift;
1589+
my $co = shift;
1590+
my $author = chop_and_escape_str($co->{'author_name'}, @_);
1591+
return "<$tag class=\"author\">" .
1592+
git_get_avatar($co->{'author_email'}, -pad_after => 1) .
1593+
$author . "</$tag>";
1594+
}
1595+
14721596
# format git diff header line, i.e. "diff --(git|combined|cc) ..."
14731597
sub format_git_diff_header_line {
14741598
my $line = shift;
@@ -2399,8 +2523,14 @@ sub parse_tag {
23992523
$tag{'name'} = $1;
24002524
} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
24012525
$tag{'author'} = $1;
2402-
$tag{'epoch'} = $2;
2403-
$tag{'tz'} = $3;
2526+
$tag{'author_epoch'} = $2;
2527+
$tag{'author_tz'} = $3;
2528+
if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
2529+
$tag{'author_name'} = $1;
2530+
$tag{'author_email'} = $2;
2531+
} else {
2532+
$tag{'author_name'} = $tag{'author'};
2533+
}
24042534
} elsif ($line =~ m/--BEGIN/) {
24052535
push @comment, $line;
24062536
last;
@@ -3214,21 +3344,54 @@ sub git_print_header_div {
32143344
"\n</div>\n";
32153345
}
32163346

3347+
sub print_local_time {
3348+
my %date = @_;
3349+
if ($date{'hour_local'} < 6) {
3350+
printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
3351+
$date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
3352+
} else {
3353+
printf(" (%02d:%02d %s)",
3354+
$date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
3355+
}
3356+
}
3357+
3358+
# Outputs the author name and date in long form
32173359
sub git_print_authorship {
32183360
my $co = shift;
3361+
my %opts = @_;
3362+
my $tag = $opts{-tag} || 'div';
32193363

32203364
my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
3221-
print "<div class=\"author_date\">" .
3365+
print "<$tag class=\"author_date\">" .
32223366
esc_html($co->{'author_name'}) .
32233367
" [$ad{'rfc2822'}";
3224-
if ($ad{'hour_local'} < 6) {
3225-
printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
3226-
$ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
3227-
} else {
3228-
printf(" (%02d:%02d %s)",
3229-
$ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
3368+
print_local_time(%ad) if ($opts{-localtime});
3369+
print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
3370+
. "</$tag>\n";
3371+
}
3372+
3373+
# Outputs table rows containing the full author or committer information,
3374+
# in the format expected for 'commit' view (& similia).
3375+
# Parameters are a commit hash reference, followed by the list of people
3376+
# to output information for. If the list is empty it defalts to both
3377+
# author and committer.
3378+
sub git_print_authorship_rows {
3379+
my $co = shift;
3380+
# too bad we can't use @people = @_ || ('author', 'committer')
3381+
my @people = @_;
3382+
@people = ('author', 'committer') unless @people;
3383+
foreach my $who (@people) {
3384+
my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
3385+
print "<tr><td>$who</td><td>" . esc_html($co->{$who}) . "</td>" .
3386+
"<td rowspan=\"2\">" .
3387+
git_get_avatar($co->{"${who}_email"}, -size => 'double') .
3388+
"</td></tr>\n" .
3389+
"<tr>" .
3390+
"<td></td><td> $wd{'rfc2822'}";
3391+
print_local_time(%wd);
3392+
print "</td>" .
3393+
"</tr>\n";
32303394
}
3231-
print "]</div>\n";
32323395
}
32333396

32343397
sub git_print_page_path {
@@ -4142,11 +4305,9 @@ sub git_shortlog_body {
41424305
print "<tr class=\"light\">\n";
41434306
}
41444307
$alternate ^= 1;
4145-
my $author = chop_and_escape_str($co{'author_name'}, 10);
41464308
# git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
41474309
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
4148-
"<td><i>" . $author . "</i></td>\n" .
4149-
"<td>";
4310+
format_author_html('td', \%co, 10) . "<td>";
41504311
print format_subject_html($co{'title'}, $co{'title_short'},
41514312
href(action=>"commit", hash=>$commit), $ref);
41524313
print "</td>\n" .
@@ -4193,11 +4354,9 @@ sub git_history_body {
41934354
print "<tr class=\"light\">\n";
41944355
}
41954356
$alternate ^= 1;
4196-
# shortlog uses chop_str($co{'author_name'}, 10)
4197-
my $author = chop_and_escape_str($co{'author_name'}, 15, 3);
41984357
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
4199-
"<td><i>" . $author . "</i></td>\n" .
4200-
"<td>";
4358+
# shortlog: format_author_html('td', \%co, 10)
4359+
format_author_html('td', \%co, 15, 3) . "<td>";
42014360
# originally git_history used chop_str($co{'title'}, 50)
42024361
print format_subject_html($co{'title'}, $co{'title_short'},
42034362
href(action=>"commit", hash=>$commit), $ref);
@@ -4350,9 +4509,8 @@ sub git_search_grep_body {
43504509
print "<tr class=\"light\">\n";
43514510
}
43524511
$alternate ^= 1;
4353-
my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
43544512
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
4355-
"<td><i>" . $author . "</i></td>\n" .
4513+
format_author_html('td', \%co, 15, 5) .
43564514
"<td>" .
43574515
$cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
43584516
-class => "list subject"},
@@ -4586,11 +4744,7 @@ sub git_tag {
45864744
$tag{'type'}) . "</td>\n" .
45874745
"</tr>\n";
45884746
if (defined($tag{'author'})) {
4589-
my %ad = parse_date($tag{'epoch'}, $tag{'tz'});
4590-
print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
4591-
print "<tr><td></td><td>" . $ad{'rfc2822'} .
4592-
sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) .
4593-
"</td></tr>\n";
4747+
git_print_authorship_rows(\%tag, 'author');
45944748
}
45954749
print "</table>\n\n" .
45964750
"</div>\n";
@@ -5094,9 +5248,9 @@ sub git_log {
50945248
" | " .
50955249
$cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
50965250
"<br/>\n" .
5097-
"</div>\n" .
5098-
"<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" .
50995251
"</div>\n";
5252+
git_print_authorship(\%co, -tag => 'span');
5253+
print "<br/>\n</div>\n";
51005254

51015255
print "<div class=\"log_body\">\n";
51025256
git_print_log($co{'comment'}, -final_empty_line=> 1);
@@ -5115,8 +5269,6 @@ sub git_commit {
51155269
$hash ||= $hash_base || "HEAD";
51165270
my %co = parse_commit($hash)
51175271
or die_error(404, "Unknown commit object");
5118-
my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
5119-
my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
51205272

51215273
my $parent = $co{'parent'};
51225274
my $parents = $co{'parents'}; # listref
@@ -5183,22 +5335,7 @@ sub git_commit {
51835335
}
51845336
print "<div class=\"title_text\">\n" .
51855337
"<table class=\"object_header\">\n";
5186-
print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n".
5187-
"<tr>" .
5188-
"<td></td><td> $ad{'rfc2822'}";
5189-
if ($ad{'hour_local'} < 6) {
5190-
printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
5191-
$ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
5192-
} else {
5193-
printf(" (%02d:%02d %s)",
5194-
$ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
5195-
}
5196-
print "</td>" .
5197-
"</tr>\n";
5198-
print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n";
5199-
print "<tr><td></td><td> $cd{'rfc2822'}" .
5200-
sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) .
5201-
"</td></tr>\n";
5338+
git_print_authorship_rows(\%co);
52025339
print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
52035340
print "<tr>" .
52045341
"<td>tree</td>" .
@@ -5579,7 +5716,11 @@ sub git_commitdiff {
55795716
git_header_html(undef, $expires);
55805717
git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
55815718
git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
5582-
git_print_authorship(\%co);
5719+
print "<div class=\"title_text\">\n" .
5720+
"<table class=\"object_header\">\n";
5721+
git_print_authorship_rows(\%co);
5722+
print "</table>".
5723+
"</div>\n";
55835724
print "<div class=\"page_body\">\n";
55845725
if (@{$co{'comment'}} > 1) {
55855726
print "<div class=\"log\">\n";

t/t9500-gitweb-standalone-no-errors.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ cat >>gitweb_config.perl <<EOF
660660
661661
\$feature{'blame'}{'override'} = 1;
662662
\$feature{'snapshot'}{'override'} = 1;
663+
\$feature{'avatar'}{'override'} = 1;
663664
EOF
664665

665666
test_expect_success \
@@ -671,6 +672,7 @@ test_expect_success \
671672
'config override: tree view, features disabled in repo config' \
672673
'git config gitweb.blame no &&
673674
git config gitweb.snapshot none &&
675+
git config gitweb.avatar gravatar &&
674676
gitweb_run "p=.git;a=tree"'
675677
test_debug 'cat gitweb.log'
676678

0 commit comments

Comments
 (0)