From d9563ea5516e8e786debf223e10ec11695aee9d7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 9 Feb 2017 01:37:03 +0000 Subject: repobrowse: shorten internal names We'll still be keeping "repobrowse" for the public API for use with .psgi files, but shortening the name means less typing and we may have command-line tools, too. --- lib/PublicInbox/RepoGitTag.pm | 213 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 lib/PublicInbox/RepoGitTag.pm (limited to 'lib/PublicInbox/RepoGitTag.pm') diff --git a/lib/PublicInbox/RepoGitTag.pm b/lib/PublicInbox/RepoGitTag.pm new file mode 100644 index 00000000..96835b2c --- /dev/null +++ b/lib/PublicInbox/RepoGitTag.pm @@ -0,0 +1,213 @@ +# Copyright (C) 2016 all contributors +# License: AGPL-3.0+ + +# 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 $q = PublicInbox::RepoGitQuery->new($req->{env}); + my $h = $q->{h}; + $h eq '' and return git_tag_list($self, $req); + sub { + my ($res) = @_; + git_tag_show($self, $req, $h, $res); + } +} + +sub read_err { + my ($fh, $type, $hex) = @_; + + $fh->write("
error reading $type $hex");
+}
+
+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($label);
+	$head = $h . "\n\n   tag $tag\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('' . utf8_html($subj) . "\n");
+
+		$fh->write(utf8_html($_) . "\n") foreach @buf;
+	}
+}
+
+sub git_tag_show {
+	my ($self, $req, $h, $res) = @_;
+	my $git = $req->{repo_info}->{git};
+	my $fh;
+	my $hdr = ['Content-Type', 'text/html; charset=UTF-8'];
+
+	# 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->([200, $hdr]);
+		$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->([404, $hdr]);
+		$fh->write(invalid_tag_start($req, $h));
+	}
+	$fh->write('
'); + $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 tag list for valid tags.); +} + +sub git_each_tag_sed ($$) { + my ($self, $req) = @_; + my $repo_info = $req->{repo_info}; + my $buf = ''; + my $nr = 0; + $req->{thtml} = $self->html_start($req, "$repo_info->{repo}: tag list") . + '' . + join('', map { "" } qw(tag date subject)). + ''; + 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 = "
Showing the latest $nr tags
"; + } elsif ($nr == 0) { + $end = '
no tags to show
'; + } + $end = "
$_
$end"; + } + 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() . + qq($h) . + qq($date) . + utf8_html($s) . ''; + } + $dst .= $end; + } +} + +sub git_tag_list { + my ($self, $req) = @_; + my $git = $req->{repo_info}->{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_info = $req->{repo_info}; + $h = $h->as_html; + my $rel = $req->{relcmd}; + my $label = "$type $hex"; + my $cmd = $cmd_map{$type} || 'show'; + my $obj_link = qq($label\n); + + $fh->write($self->html_start($req, + "$repo_info->{repo}: ref: $h") . + "\n\n $h (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; -- cgit v1.2.3-24-ge0c7