diff options
-rw-r--r-- | lib/PublicInbox/Repobrowse.pm | 2 | ||||
-rw-r--r-- | lib/PublicInbox/RepobrowseGitAtom.pm | 117 | ||||
-rw-r--r-- | t/repobrowse_git_atom.t | 39 |
3 files changed, 157 insertions, 1 deletions
diff --git a/lib/PublicInbox/Repobrowse.pm b/lib/PublicInbox/Repobrowse.pm index 91d0ab20..82d38f90 100644 --- a/lib/PublicInbox/Repobrowse.pm +++ b/lib/PublicInbox/Repobrowse.pm @@ -23,7 +23,7 @@ use warnings; use URI::Escape qw(uri_escape_utf8 uri_unescape); use PublicInbox::RepobrowseConfig; -my %CMD = map { lc($_) => $_ } qw(Log Commit Tree Patch Blob Plain Tag); +my %CMD = map { lc($_) => $_ } qw(Log Commit Tree Patch Blob Plain Tag Atom); my %VCS = (git => 'Git'); my %LOADED; diff --git a/lib/PublicInbox/RepobrowseGitAtom.pm b/lib/PublicInbox/RepobrowseGitAtom.pm new file mode 100644 index 00000000..9326841d --- /dev/null +++ b/lib/PublicInbox/RepobrowseGitAtom.pm @@ -0,0 +1,117 @@ +# Copyright (C) 2016 all contributors <meta@public-inbox.org> +# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> + +# show log as an Atom feed +package PublicInbox::RepobrowseGitAtom; +use strict; +use warnings; +use PublicInbox::Hval qw(utf8_html); +use base qw(PublicInbox::RepobrowseBase); +my $ATOM_FMT = '--pretty=tformat:'. + join('%x00', qw(%s %ct %an %ae %at %h %H %b), '', ''); + +use constant DATEFMT => '%Y-%m-%dT%H:%M:%SZ'; +use POSIX qw(strftime); + +sub call_git_atom { + my ($self, $req) = @_; + my $repo_info = $req->{repo_info}; + my $max = $repo_info->{max_commit_count} || 10; + $max = int($max); + $max = 50 if $max == 0; + + my $git = $repo_info->{git}; + my $q = PublicInbox::RepobrowseGitQuery->new($req->{cgi}); + my $h = $q->{h}; + $h eq '' and chomp($h = $git->qx(qw(symbolic-ref --short HEAD))); + + my @cmd = (qw(log --no-notes --no-color --abbrev-commit), + $git->abbrev, $ATOM_FMT, "-$max", $h, '--'); + push @cmd, $req->{expath} if length($req->{expath}); + my $log = $git->popen(@cmd); + + sub { + my ($res) = @_; # Plack callback + my @h = ( 'Content-Type' => 'application/atom+xml' ); + my $fh = $res->([200, \@h]); + $self->git_atom_stream($req, $q, $log, $fh, $h); + $fh->close; + } +} + +sub repo_root_url { + my ($self, $req) = @_; + my $cgi = $req->{cgi}; + my $uri = $cgi->request_uri; + $uri =~ s/\?.+\z//; # no query string + my @uri = split(m!/+!, $uri); + shift @uri; # leading slash + my @extra = @{$req->{extra}}; + while (@uri && @extra && $uri[-1] eq $extra[-1]) { + pop @uri; + pop @extra; + } + pop @uri if $uri[-1] eq 'atom'; # warn if not equal? + $cgi->base . join('/', @uri); +} + +sub git_atom_stream { + my ($self, $req, $q, $log, $fh, $h) = @_; + my $repo_info = $req->{repo_info}; + my $title = join('/', $repo_info->{repo}, @{$req->{extra}}); + $title = utf8_html("$title, branch $h"); + + my $url = $self->repo_root_url($req); + $fh->write(qq(<?xml version="1.0"?>\n) . + qq(<feed\nxmlns="http://www.w3.org/2005/Atom">) . + qq(<title>$title</title>) . + qq(<subtitle>$repo_info->{desc_html}</subtitle>) . + qq(<link\nrel="alternate"\ntype="text/html"\nhref="$url"\n/>)); + my $rel = $req->{relcmd}; + my %acache; + local $/ = "\0"; + while (defined(my $s = <$log>)) { + chomp $s; + my $entry = '<entry><title>'; + $entry .= utf8_html($s); # commit subject + $entry .= '</title><updated>'; + + chomp($s = <$log>); # commit time + $entry .= strftime(DATEFMT, gmtime($s)); + $entry .= '</updated><author><name>'; + + chomp($s = <$log>); # author name + $entry .= $acache{$s} ||= utf8_html($s); + $entry .= '</name><email>'; + + chomp($s = <$log>); # author email + $entry .= $acache{$s} ||= utf8_html($s); + $entry .= '</email></author><published>'; + + chomp($s = <$log>); # author time + $entry .= strftime(DATEFMT, gmtime($s)); + $entry .= '</published>'; + + $entry .= qq(<link\nrel="alternate"\ntype="text/html"\nhref="); + $entry .= $url; + chomp($s = <$log>); # abbreviated commit hash for URL + $entry .= qq(/commit?id=$s"\n/><id>); + chomp($s = <$log>); # unabbreviated commit hash + $entry .= $s; + $entry .= qq(</id>); + + $entry .= qq(<content\ntype="xhtml"><div\nxmlns="); + $entry .= qq(http://www.w3.org/1999/xhtml">); + $entry .= qq(<pre\nstyle="white-space:pre-wrap">\n); + chomp($s = <$log>); + $entry .= utf8_html($s); # body + $fh->write($entry .= qq(</pre></div></content></entry>)); + eval { + local $/ = "\0\n"; + $s = <$log>; + }; + } + $fh->write('</feed>'); +} + +1; diff --git a/t/repobrowse_git_atom.t b/t/repobrowse_git_atom.t new file mode 100644 index 00000000..2525effd --- /dev/null +++ b/t/repobrowse_git_atom.t @@ -0,0 +1,39 @@ +# Copyright (C) 2016 all contributors <meta@public-inbox.org> +# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> +use strict; +use warnings; +my $have_xml_feed = eval { require XML::Feed; 1 }; +my $test = require './t/repobrowse_common_git.perl'; +use Test::More; + +test_psgi($test->{app}, sub { + my ($cb) = @_; + my $req = 'http://example.com/test.git/atom'; + my $res = $cb->(GET($req)); + is($res->code, 200, 'got 200'); + is($res->header('Content-Type'), 'application/atom+xml', + 'got correct Content-Type'); + my $body = dechunk($res); + SKIP: { + skip 'XML::Feed missing', 2 unless $have_xml_feed; + my $p = XML::Feed->parse(\$body); + is($p->format, "Atom", "parsed atom feed"); + is(scalar $p->entries, 6, "parsed six entries"); + } + + $res = $cb->(GET($req . '/')); + my $sl = dechunk($res); + is($body, $sl, 'slash returned identical to non-trailing slash'); + + $res = $cb->(GET($req . '/foo.txt')); + is($res->code, 200, 'got 200'); + $body = dechunk($res); + SKIP: { + skip 'XML::Feed missing', 2 unless $have_xml_feed; + my $p = XML::Feed->parse(\$body); + is($p->format, "Atom", "parsed atom feed"); + is(scalar $p->entries, 4, "parsed 4 entries"); + } +}); + +done_testing(); |