@@ -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.
200208our %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
370399sub 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 {
814849our @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
818866if (!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} ? " " : " " );
1560+ my $post_white = ($opts {-pad_after} ? " " : " " );
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) ..."
14731597sub 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
32173359sub 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
32343397sub 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 " ;
0 commit comments