# Copyright (C) all contributors # License: AGPL-3.0+ # # git log => Atom feed (cgit-compatible: $REPO/atom/[PATH]?h=$tip package PublicInbox::RepoAtom; use v5.12; use parent qw(PublicInbox::GzipFilter); use POSIX qw(strftime); use URI::Escape qw(uri_escape); use Scalar::Util (); use PublicInbox::Hval qw(ascii_html); # git for-each-ref and log use different format fields :< my $ATOM_FMT = '--pretty=tformat:'.join('%n', map { "%$_" } qw(H ct an ae at s b)).'%x00'; my $EACH_REF_FMT = '--format='.join(';', map { "\$r{'$_'}=%($_)" } qw( objectname refname:short creator contents:subject contents:body *subject *body)).'%00'; sub atom_ok { # parse_hdr for qspawn my ($r, $bref, $ctx) = @_; return [ 404, [], [ "Not Found\n"] ] if $r == 0; bless $ctx, __PACKAGE__; my $h = [ 'Content-Type' => 'application/atom+xml; charset=UTF-8' ]; $ctx->{gz} = $ctx->can('gz_or_noop')->($h, $ctx->{env}); my $title = ascii_html(delete $ctx->{-feed_title}); my $desc = ascii_html($ctx->{git}->description); my $url = ascii_html($ctx->{git}->base_url($ctx->{env})); $ctx->{-base_url} = $url; $ctx->zmore(< $title$desc EOM [ 200, $h, $ctx ]; # [2] is qspawn.filter } # called by GzipFilter->close sub zflush { $_[0]->SUPER::zflush('') } # called by GzipFilter->write or GetlineBody->getline sub translate { my $self = shift; my $rec = $_[0] // return $self->zflush; # getline my @out; my $lbuf = delete($self->{lbuf}) // shift; $lbuf .= shift while @_; my $is_tag = $self->{-is_tag}; my ($H, $ct, $an, $ae, $at, $s, $bdy); while ($lbuf =~ s/\A([^\0]+)\0\n//s) { utf8::decode($bdy = $1); if ($is_tag) { my %r; eval "$bdy"; for (qw(contents:subject contents:body)) { $r{$_} =~ /\S/ or delete($r{$_}) } $H = $r{objectname}; $s = $r{'contents:subject'} // $r{'*subject'}; $bdy = $r{'contents:body'} // $r{'*body'}; $s .= " ($r{'refname:short'})"; $_ = ascii_html($_) for ($s, $bdy, $r{creator}); ($an, $ae, $at) = split(/\s*&[gl]t;\s*/, $r{creator}); $at =~ s/ .*\z//; # no TZ $ct = $at = strftime('%Y-%m-%dT%H:%M:%SZ', gmtime($at)); } else { $bdy = ascii_html($bdy); ($H, $ct, $an, $ae, $at, $s, $bdy) = split(/\n/, $bdy, 7); $at = strftime('%Y-%m-%dT%H:%M:%SZ', gmtime($at)); $ct = strftime('%Y-%m-%dT%H:%M:%SZ', gmtime($ct)); } $bdy //= ''; push @out, <<""; $s$ct$an $ae$at$H push @out, <<'', $bdy, '' if $bdy ne '';

		push @out, '';
	}
	$self->{lbuf} = $lbuf;
	chomp @out;
	$self->SUPER::translate(@out);
}

# $REPO/tags.atom endpoint
sub srv_tags_atom {
	my ($ctx) = @_;
	my $max = 50; # TODO configurable
	my @cmd = ('git', "--git-dir=$ctx->{git}->{git_dir}",
			qw(for-each-ref --sort=-creatordate), "--count=$max",
			'--perl', $EACH_REF_FMT, 'refs/tags');
	$ctx->{-feed_title} = "$ctx->{git}->{nick} tags";
	my $qsp = PublicInbox::Qspawn->new(\@cmd);
	$ctx->{-is_tag} = 1;
	$qsp->psgi_return($ctx->{env}, undef, \&atom_ok, $ctx);
}

sub srv_atom {
	my ($ctx, $path) = @_;
	return if index($path, '//') >= 0 || index($path, '/') == 0;
	my $max = 50; # TODO configurable
	my @cmd = ('git', "--git-dir=$ctx->{git}->{git_dir}",
			qw(log --no-notes --no-color --no-abbrev),
			$ATOM_FMT, "-$max");
	my $tip = $ctx->{qp}->{h}; # same as cgit
	$ctx->{-feed_title} = $ctx->{git}->{nick};
	$ctx->{-feed_title} .= " $path" if $path ne '';
	if (defined($tip)) {
		push @cmd, $tip;
		$ctx->{-feed_title} .= ", $tip";
	}
	# else: let git decide based on HEAD if $tip isn't defined
	push @cmd, '--';
	push @cmd, $path if $path ne '';
	my $qsp = PublicInbox::Qspawn->new(\@cmd, undef,
					{ quiet => 1, 2 => $ctx->{lh} });
	$qsp->psgi_return($ctx->{env}, undef, \&atom_ok, $ctx);
}

1;