#!/usr/bin/env perl =pod =head1 NAME git-feed - generate an Atom feed of git commits in the current repository =head1 SYNOPSIS git-feed [ B<--project_url=...> ] =head1 DESCRIPTION I keep a lot of projects in git, and I like to provide feeds for things I'm working on. This script wraps git and XML::Atom::SimpleFeed to generate a basic Atom feed of commits. It tries to use information already available in the environment, but needs to be given a URL for your project (and probably for the feed itself). It can be configured for a given repository by doing something like the following: git config feed.projecturl https://p1k3.com/userland-book/ git config feed.url https://p1k3.com/userland-book/feed.xml echo 'userland: a book about the command line for human beings' > .git/description I have I whether this sort of thing is considered an acceptable use of git configuration, but it seems to work. ...or you can supply various options on the command line. B<--project_url=...> Set a URL for the project. B<--feed_url=...> Set an explicit URL for the feed itself. B<--title=...> Set an explicit title for the feed. Will otherwise be read from .git/description, if available. B<--entries=>I Explicitly request I entries. Defaults to 10. B<--author=...> Explicitly set an author string. Defaults to git config's current idea of user.name. =head1 EXAMPLES git-feed --project_url=https://p1k3.com/userland-book/ > feed.xml =head1 INSTALLING I'll wrap this in a module and add a build script. For now, install XML::Atom::SimpleFeed first. I would probably do that either with: cpanp -i XML::Atom::SimpleFeed or: apt-get install libxml-atom-simplefeed-perl ...depending on which one gave me less static on a given system. I don't really know what I'm doing in the Perl ecosystem any more, though. Anyway, if you put git-feed in your path, you can probably invoke it like any other git subcommand: git feed > feed.xml =head1 AUTHOR Brennen Bearnes =head1 LICENSE This utility is free software, licensed under the terms of the GPL, v2. See COPYING for a complete copy of the license. =cut # temporary hack # use lib '/home/brennen/code/perl_libs'; use warnings; use strict; use 5.10.0; use XML::Atom::SimpleFeed; use HTML::Entities; use Getopt::Long; use POSIX qw(strftime); # Turns out to be pretty much exactly what I want for system calls: use IPC::System::Simple qw(capturex); # Figure out where top-level .git/ resides. Naturally, "rev-parse" was the # first command that leapt to mind when I wondered if there was a builtin way # to ask that question. I mean, what kind of loser would have had to google # something so _blindingly obvious_? my $repo_base = git('rev-parse', '--show-toplevel'); chomp($repo_base); my $title = 'git log'; $title = slurp("$repo_base/.git/description") if (-f "$repo_base/.git/description"); my $author = git_config('user.name'); chomp($author); my $feed_url = git_config('feed.url'); my $project_url = git_config('feed.projecturl'); my $entries = 10; GetOptions( "author:s" => \$author, "entries:i" => \$entries, "feed_url:s" => \$feed_url, "project_url:s" => \$project_url, "title:s" => \$title, ); # Where does this project live on the web? unless ($project_url) { say "Project URL required - use one of:"; say "\tgit feed --project_url=https://yourlinkhere/"; say "\tgit config feed.projecturl https://yourlinkhere/"; exit(1); } # No feed link set? Make something up. unless ($feed_url) { $feed_url = $project_url . 'feed.xml'; } my $log = capturex('git', 'log', "-$entries", qq{--pretty=format:%H _ %ai _ %s}); my $feed = XML::Atom::SimpleFeed->new( title => $title, link => $project_url, link => { rel => 'self', href => $feed_url, }, author => $author, id => $project_url, generator => 'XML::Atom::SimpleFeed', # This could be better - just uses current time, at the moment: updated => iso_date(time()), ); while ($log =~ m/^([a-z0-9]+) _ (.*) _ (.*)$/gm) { my $hash = $1; my $full_commit = git('show', $hash); my $formatted_commit = '
' . encode_entities($full_commit) . '
'; my $date = $2; my $subj = $3; # Right now, this doesn't do anything clever with links for individual # commits. That's because I'm mostly using it for things that are single # HTML files, so it doesn't seem to matter much. It would probably be smart # to let users specify a different base path for these so they can link # GitHub or what-have-you. $feed->add_entry( title => $subj, link => $project_url, id => $hash, content => $formatted_commit, updated => $date, ); } print $feed->as_string; sub iso_date { my ($time) = @_; return strftime("%Y-%m-%dT%H:%M:%SZ", localtime($time)); } # Get contents of a file as a string, because a File::Slurp dependency seemed # like overkill: sub slurp { my ($file) = @_; my $everything; open my $fh, '<', $file or die "Couldn't open $file: $!\n"; # line separator: local $/ = undef; $everything = <$fh>; close $fh or die "Couldn't close $file: $!"; return $everything; } # Run git with some options, return result: sub git { capturex('git', @_); } # Return a git config value - will be an empty string for unset values: sub git_config { my ($key) = @_; my $value = capturex([0,1], 'git', 'config', '--get', $key); chomp($value); return $value; }