diff options
Diffstat (limited to 'lib/PublicInbox/RepoGitTag.pm')
-rw-r--r-- | lib/PublicInbox/RepoGitTag.pm | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/lib/PublicInbox/RepoGitTag.pm b/lib/PublicInbox/RepoGitTag.pm new file mode 100644 index 00000000..2a281ed6 --- /dev/null +++ b/lib/PublicInbox/RepoGitTag.pm @@ -0,0 +1,211 @@ +# Copyright (C) 2016 all contributors <meta@public-inbox.org> +# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> + +# shows the /tag/ endpoint for git repositories +package PublicInbox::RepoGitTag; +use strict; +use warnings; +use base qw(PublicInbox::RepoBase); +use POSIX qw(strftime); +use PublicInbox::Hval qw(utf8_html); +use PublicInbox::Qspawn; + +my %cmd_map = ( # type => action + commit => 'commit', + tag => 'tag', + # tree/blob fall back to 'show' +); + +sub call_git_tag { + my ($self, $req) = @_; + + my $tip = $req->{tip}; + defined $tip or return git_tag_list($self, $req); + sub { + my ($res) = @_; + git_tag_show($self, $req, $tip, $res); + } +} + +sub read_err { + my ($fh, $type, $hex) = @_; + + $fh->write("</pre><hr /><pre><b>error reading $type $hex</b>"); +} + +sub git_show_tag_as_tag { + my ($self, $fh, $req, $h, $cat, $left, $type, $hex) = @_; + my $buf = ''; + my $offset = 0; + while ($$left > 0) { + my $r = read($cat, $buf, $$left, $offset); + unless (defined $r) { + read_err($fh, $type, $hex); + last; + } + $offset += $r; + $$left -= $r; + } + my $head; + ($head, $buf) = split(/\r?\n\r?\n/, $buf, 2); + + my %h = map { split(/[ \t]/, $_, 2) } split(/\r?\n/, $head); + my $tag = utf8_html($h{tag}); + $type = $h{type} || '(unknown)'; + my $obj = $h{object}; + $h = $self->html_start($req, 'tag: ' . $tag); + my $label = "$type $obj"; + my $cmd = $cmd_map{$type} || 'show'; + my $rel = $req->{relcmd}; + my $obj_link = qq(<a\nhref="$rel$cmd/$obj">$label</a>); + $head = $h . "\n\n tag <b>$tag</b>\nobject $obj_link\n"; + if (my $tagger = $h{tagger}) { + $head .= 'tagger ' . join("\t", creator_split($tagger)) . "\n"; + } + $fh->write($head . "\n"); + + # n.b. tag subjects may not have a blank line after them, + # but we bold the first line anyways + my @buf = split(/\r?\n/s, $buf); + if (defined(my $subj = shift @buf)) { + $fh->write('<b>' . utf8_html($subj) . "</b>\n"); + + $fh->write(utf8_html($_) . "\n") foreach @buf; + } +} + +sub git_tag_show { + my ($self, $req, $h, $res) = @_; + my $git = $req->{-repo}->{git}; + my $fh; + + # yes, this could still theoretically show anything, + # but a tag could also point to anything: + $git->cat_file("refs/tags/$h", sub { + my ($cat, $left, $type, $hex) = @_; + $fh = $res->($self->rt(200, 'html')); + $h = PublicInbox::Hval->utf8($h); + my $m = "git_show_${type}_as_tag"; + + # git_show_tag_as_tag, git_show_commit_as_tag, + # git_show_tree_as_tag, git_show_blob_as_tag + if ($self->can($m)) { + $self->$m($fh, $req, $h, $cat, $left, $type, $hex); + } else { + $self->unknown_tag_type($fh, $req, $h, $type, $hex); + } + }); + unless ($fh) { + $fh = $res->($self->rt(404, 'html')); + $fh->write(invalid_tag_start($req, $h)); + } + $fh->write('</pre></body></html>'); + $fh->close; +} + +sub invalid_tag_start { + my ($self, $req, $h) = @_; + my $rel = $req->{relcmd}; + $h = 'missing tag: ' . utf8_html($h); + $self->html_start($req, $h) . "\n\n\t$h\n\n" . + qq(see <a\nhref="${rel}tag">tag list</a> for valid tags.); +} + +sub git_each_tag_sed ($$) { + my ($self, $req) = @_; + my $repo = $req->{-repo}; + my $buf = ''; + my $nr = 0; + $req->{thtml} = $self->html_start($req, "$repo->{repo}: tag list") . + '</pre><table><tr>' . + join('', map { "<th><tt>$_</tt></th>" } qw(tag date subject)). + '</tr>'; + sub { + my $dst = delete $req->{thtml} || ''; + my $end = ''; + my @lines; + if (defined $_[0]) { + @lines = split(/\n/, $buf .= $_[0]); + $buf = pop @lines if @lines; + } else { # for-each-ref EOF + @lines = split(/\n/, $buf); + $buf = undef; + if ($nr == $req->{-tag_count}) { + $end = "<pre>Showing the latest $nr tags</pre>"; + } elsif ($nr == 0) { + $end = '<pre>no tags to show</pre>'; + } + $end = "</table>$end</body></html>"; + } + for (@lines) { + my ($ref, $date, $s) = split(' ', $_, 3); + ++$nr; + $ref =~ s!\Arefs/tags/!!; + $ref = PublicInbox::Hval->utf8($ref); + my $h = $ref->as_html; + $ref = $ref->as_href; + $dst .= qq(<tr><td><tt>) . + qq(<a\nhref="tag/$ref"><b>$h</b></a>) . + qq(</tt></td><td><tt>$date</tt></td><td><tt>) . + utf8_html($s) . '</tt></td></tr>'; + } + $dst .= $end; + } +} + +sub git_tag_list { + my ($self, $req) = @_; + my $git = $req->{-repo}->{git}; + + # TODO: use Xapian so we can more easily handle offsets/limits + # for pagination instead of limiting + my $count = $req->{-tag_count} = 50; + my $cmd = $git->cmd(qw(for-each-ref --sort=-creatordate), + '--format=%(refname) %(creatordate:short) %(subject)', + "--count=$count", 'refs/tags/'); + my $rdr = { 2 => $git->err_begin }; + my $qsp = PublicInbox::Qspawn->new($cmd, undef, $rdr); + my $env = $req->{env}; + $env->{'qspawn.quiet'} = 1; + $qsp->psgi_return($env, undef, sub { # parse output + my ($r) = @_; + if (!defined $r) { + my $errmsg = $git->err; + [ 500, [ 'Content-Type', 'text/html; charset=UTF-8'], + [ $errmsg ] ]; + } else { + $env->{'qspawn.filter'} = git_each_tag_sed($self, $req); + [ 200, [ 'Content-Type', 'text/html; charset=UTF-8' ]]; + } + }); +} + +sub unknown_tag_type { + my ($self, $fh, $req, $h, $type, $hex) = @_; + my $repo = $req->{-repo}; + $h = $h->as_html; + my $rel = $req->{relcmd}; + my $label = "$type $hex"; + my $cmd = $cmd_map{$type} || 'show'; + my $obj_link = qq(<a\nhref="$rel$cmd/$hex">$label</a>\n); + + $fh->write($self->html_start($req, + "$repo->{repo}: ref: $h") . + "\n\n <b>$h</b> (lightweight tag)\nobject $obj_link\n"); +} + +sub creator_split { + my ($tagger) = @_; + $tagger =~ s/\s*(\d+)(?:\s+([\+\-])?([ \d]{1,2})(\d\d))\z// or + return ($tagger, 0); + my ($tz_sign, $tz_H, $tz_M) = ($2, $3, $4); + my $sec = $1; + my $off = $tz_H * 3600 + $tz_M * 60; + $off *= -1 if $tz_sign eq '-'; + my @time = gmtime($sec + $off); + my $time = strftime('%Y-%m-%d %H:%M:%S', @time)." $tz_sign$tz_H$tz_M"; + + (utf8_html($tagger), $time); +} + +1; |