@@ -195,6 +195,14 @@ BEGIN
195
195
' x-zip' => undef , ' ' => undef ,
196
196
);
197
197
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
+
198
206
# You define site-wide feature defaults here; override them with
199
207
# $GITWEB_CONFIG as necessary.
200
208
our %feature = (
@@ -365,6 +373,27 @@ BEGIN
365
373
' sub' => \&feature_patches,
366
374
' override' => 0,
367
375
' 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' => [' ' ]},
368
397
);
369
398
370
399
sub gitweb_get_feature {
@@ -433,6 +462,12 @@ sub feature_patches {
433
462
return ($_ [0]);
434
463
}
435
464
465
+ sub feature_avatar {
466
+ my @val = (git_get_project_config(' avatar' ));
467
+
468
+ return @val ? @val : @_ ;
469
+ }
470
+
436
471
# checking HEAD file with -e is fragile if the repository was
437
472
# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
438
473
# and then pruned.
@@ -814,6 +849,19 @@ sub evaluate_path_info {
814
849
our @snapshot_fmts = gitweb_get_feature(' snapshot' );
815
850
@snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts );
816
851
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
+
817
865
# dispatch
818
866
if (!defined $action ) {
819
867
if (defined $hash ) {
@@ -1469,6 +1517,82 @@ sub format_subject_html {
1469
1517
}
1470
1518
}
1471
1519
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
+
1472
1596
# format git diff header line, i.e. "diff --(git|combined|cc) ..."
1473
1597
sub format_git_diff_header_line {
1474
1598
my $line = shift ;
@@ -2399,8 +2523,14 @@ sub parse_tag {
2399
2523
$tag {' name' } = $1 ;
2400
2524
} elsif ($line =~ m / ^tagger (.*) ([0-9]+) (.*)$ / ) {
2401
2525
$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
+ }
2404
2534
} elsif ($line =~ m / --BEGIN/ ) {
2405
2535
push @comment , $line ;
2406
2536
last ;
@@ -3214,21 +3344,54 @@ sub git_print_header_div {
3214
3344
" \n </div>\n " ;
3215
3345
}
3216
3346
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
3217
3359
sub git_print_authorship {
3218
3360
my $co = shift ;
3361
+ my %opts = @_ ;
3362
+ my $tag = $opts {-tag} || ' div' ;
3219
3363
3220
3364
my %ad = parse_date($co -> {' author_epoch' }, $co -> {' author_tz' });
3221
- print " <div class=\" author_date\" >" .
3365
+ print " <$tag class=\" author_date\" >" .
3222
3366
esc_html($co -> {' author_name' }) .
3223
3367
" [$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 " ;
3230
3394
}
3231
- print " ]</div>\n " ;
3232
3395
}
3233
3396
3234
3397
sub git_print_page_path {
@@ -4142,11 +4305,9 @@ sub git_shortlog_body {
4142
4305
print " <tr class=\" light\" >\n " ;
4143
4306
}
4144
4307
$alternate ^= 1;
4145
- my $author = chop_and_escape_str($co {' author_name' }, 10);
4146
4308
# git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
4147
4309
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>" ;
4150
4311
print format_subject_html($co {' title' }, $co {' title_short' },
4151
4312
href(action => " commit" , hash => $commit ), $ref );
4152
4313
print " </td>\n " .
@@ -4193,11 +4354,9 @@ sub git_history_body {
4193
4354
print " <tr class=\" light\" >\n " ;
4194
4355
}
4195
4356
$alternate ^= 1;
4196
- # shortlog uses chop_str($co{'author_name'}, 10)
4197
- my $author = chop_and_escape_str($co {' author_name' }, 15, 3);
4198
4357
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>" ;
4201
4360
# originally git_history used chop_str($co{'title'}, 50)
4202
4361
print format_subject_html($co {' title' }, $co {' title_short' },
4203
4362
href(action => " commit" , hash => $commit ), $ref );
@@ -4350,9 +4509,8 @@ sub git_search_grep_body {
4350
4509
print " <tr class=\" light\" >\n " ;
4351
4510
}
4352
4511
$alternate ^= 1;
4353
- my $author = chop_and_escape_str($co {' author_name' }, 15, 5);
4354
4512
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) .
4356
4514
" <td>" .
4357
4515
$cgi -> a({-href => href(action => " commit" , hash => $co {' id' }),
4358
4516
-class => " list subject" },
@@ -4586,11 +4744,7 @@ sub git_tag {
4586
4744
$tag {' type' }) . " </td>\n " .
4587
4745
" </tr>\n " ;
4588
4746
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' );
4594
4748
}
4595
4749
print " </table>\n\n " .
4596
4750
" </div>\n " ;
@@ -5094,9 +5248,9 @@ sub git_log {
5094
5248
" | " .
5095
5249
$cgi -> a({-href => href(action => " tree" , hash => $commit , hash_base => $commit )}, " tree" ) .
5096
5250
" <br/>\n " .
5097
- " </div>\n " .
5098
- " <i>" . esc_html($co {' author_name' }) . " [$ad {'rfc2822'}]</i><br/>\n " .
5099
5251
" </div>\n " ;
5252
+ git_print_authorship(\%co , -tag => ' span' );
5253
+ print " <br/>\n </div>\n " ;
5100
5254
5101
5255
print " <div class=\" log_body\" >\n " ;
5102
5256
git_print_log($co {' comment' }, -final_empty_line => 1);
@@ -5115,8 +5269,6 @@ sub git_commit {
5115
5269
$hash ||= $hash_base || " HEAD" ;
5116
5270
my %co = parse_commit($hash )
5117
5271
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' });
5120
5272
5121
5273
my $parent = $co {' parent' };
5122
5274
my $parents = $co {' parents' }; # listref
@@ -5183,22 +5335,7 @@ sub git_commit {
5183
5335
}
5184
5336
print " <div class=\" title_text\" >\n " .
5185
5337
" <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 );
5202
5339
print " <tr><td>commit</td><td class=\" sha1\" >$co {'id'}</td></tr>\n " ;
5203
5340
print " <tr>" .
5204
5341
" <td>tree</td>" .
@@ -5579,7 +5716,11 @@ sub git_commitdiff {
5579
5716
git_header_html(undef , $expires );
5580
5717
git_print_page_nav(' commitdiff' ,' ' , $hash ,$co {' tree' },$hash , $formats_nav );
5581
5718
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 " ;
5583
5724
print " <div class=\" page_body\" >\n " ;
5584
5725
if (@{$co {' comment' }} > 1) {
5585
5726
print " <div class=\" log\" >\n " ;
0 commit comments