# URI and label (title) of GIT logo link
#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
#our $logo_label = "git documentation";
-our $logo_url = "http://git.or.cz/";
+our $logo_url = "http://git-scm.com/";
our $logo_label = "git homepage";
# source of projects list
'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
# $GITWEB_CONFIG as necessary.
our %feature = (
'sub' => \&feature_patches,
'override' => 0,
'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 {
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
# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
# and then pruned.
our @snapshot_fmts = gitweb_get_feature('snapshot');
@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
if (!defined $action) {
if (defined $hash) {
if (defined $params{'hash_parent_base'}) {
$href .= esc_url($params{'hash_parent_base'});
# skip the file_parent if it's the same as the file_name
- delete $params{'file_parent'} if $params{'file_parent'} eq $params{'file_name'};
- if (defined $params{'file_parent'} && $params{'file_parent'} !~ /\.\./) {
- $href .= ":/".esc_url($params{'file_parent'});
- delete $params{'file_parent'};
+ if (defined $params{'file_parent'}) {
+ if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
+ delete $params{'file_parent'};
+ } elsif ($params{'file_parent'} !~ /\.\./) {
+ $href .= ":/".esc_url($params{'file_parent'});
+ delete $params{'file_parent'};
+ }
}
$href .= "..";
delete $params{'hash_parent'};
}
}
+# 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} ? " " : "");
+ my $post_white = ($opts{-pad_after} ? " " : "");
+ $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).
my $tag = shift;
my $co = shift;
my $author = chop_and_escape_str($co->{'author_name'}, @_);
- return "<$tag class=\"author\">" . $author . "</$tag>";
+ 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) ..."
$tag{'name'} = $1;
} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
$tag{'author'} = $1;
- $tag{'epoch'} = $2;
- $tag{'tz'} = $3;
+ $tag{'author_epoch'} = $2;
+ $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/) {
push @comment, $line;
last;
esc_html($co->{'author_name'}) .
" [$ad{'rfc2822'}";
print_local_time(%ad) if ($opts{-localtime});
- print "]</$tag>\n";
+ print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
+ . "</$tag>\n";
}
# Outputs table rows containing the full author or committer information,
@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></tr>\n".
+ 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);
$tag{'type'}) . "</td>\n" .
"</tr>\n";
if (defined($tag{'author'})) {
- my %ad = parse_date($tag{'epoch'}, $tag{'tz'});
- 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";
+ git_print_authorship_rows(\%tag, 'author');
}
print "</table>\n\n" .
"</div>\n";
git_header_html(undef, $expires);
git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
- git_print_authorship(\%co, -localtime => 1);
+ 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";
if (@{$co{'comment'}} > 1) {
print "<div class=\"log\">\n";