The Future of SVN::Notify

This week, I imported pgTAP into GitHub. It took me a day or so to wrap my brain around how it's all supposed to work, with generous help from Tekkub. But I'm starting to get the hang of it, and I like it. By the end of the day, I had sent push requests to Test::More and Blosxom Plugins. I'm well on my way to being hooked.

One of the things I want, however, is SVN::Notify-type commit emails. I know that there are feeds, but they don't have diffs, and for however much I like using NetNewsWire to feed by political news addiction, it never worked for me for commit activity. And besides, why download the whole damn thing again, diffs and all (assuming that ever happens), for every refresh. Seems like a hell of a lot unnecessary network activity—not to mention actual CPU cycles.

So I would need a decent notification application. I happen to have one. I originally wrote SVN::Notify after I had already written activitymail, which sends noticies for CVS commits. SVN::Notify has changed a lot over the years, and now it's looking a bit daunting to consider porting it to Git.

However, just to start thinking about it, SVN::Notify really does several different things:

  • Fetches relevant information about a Subversion event.
  • Parses that information for a number of different outputs.
  • Writes the event information into one or more outputs (currently plain text or XHTML).
  • Constructs an email message from the outputs
  • Sends the email message via a specified method (sendmail or SMTP).

For the initial implementation of SVN::Notify, this made a lot of sense, because it was doing something fairly simple. It was designed to be extensible by subclassing (successfully done by SVN::Notify::Config and SVN::Notify::Mirror), and, later, by output filters, and that was about it.

But as I think about moving stuff to Git, and consider the weaknesses of extensibility by subclassing (it's just not pretty), I'm naturally rethinking this architecture. I wouldn't want to have to do it all over again should some future SCM system come along in the future. So, following from a private exchange with Martijn Van Beers, I have some preliminary thoughts on how a hypothetical SCM::Notify (VCS::Notify?) module might be constructed:

  • A single interface for fetching SCM activity information. There could be any number of implementations, just as long as they all provided the same interface. There would be a class for fetching information from Subversion, one for Git, one for CVS, etc.
  • A single interface for writing a report for a given transaction. Again, there could be any number of implementations, but all would have the same interface: taking an SCM module and writing output to a file handle.
  • A single interface for doing something with one or more outputs. Again, they can do things as varied as simply writing files to disk, appending to a feed, inserting into a database, or, of course, sending an email.
  • The core module would process command-line arguments to determine what SCM is being used any necessary contextual information and just pass it on to the appropriate classes.

In psedudo-code, what I'm thinking is something like this:

package SCM::Notify;

sub run {
    my $args = shift->getopt;
    my $scm  = SCM::Interface->new(
        scm      => $args->{scm} # e.g., "SVN" or "Git", etc.
        revision => $args->{revision},
        context  => $args->{context} # Might include repository path for SVN.
    );

    my $report = SCM::Report->new(
        method => $opts->{method}, # e.g., SMTP, sendmail, Atom, etc.
        scm    => $scm,
        format => $args->{output}, # text, html, both, etc.
        params => $args->{params}, # to, from, subject, etc.
    );

    $report->send;
}

Then a report class just has to create report in the specified format or formats and do something with them. For example, a Sendmail report would put together a report as a multipart message with each format in a single part, and then deliver it via /sbin/sendmail, something like this:

package SCM::Report::Sendmail;

sub send {
    my $self = shift;
    my $fh = $self->fh;
    for my $format ( $self->formats ) {
        print $fh SCM::Format->new(
            format => $format,
            scm    => $self->scm,
        );
    }

    $self->deliver;
}

So those are my rather preliminary thoughts. I think it'd actually be pretty easy to port the logic of this stuff over from SVN::Notify; what needs some more thought is what the command-line interface might look like and how options are passed to the various classes, since the Sendmail report class will require different parameters than the SMTP report class or the Atom report class. But once that's worked out in a way that can be handled neutrally, we'll have a much more extensible implementation that will be easy to add on to going forward.

Any suggestions for passing different parameters to different classes in a single interface? Everything needs to be able to be handled via command-line options and not be ugly or difficult to use.

So, you wanna work on this? :-)

Backtalk

Justin Mason wrote:

How I would do that:

The "main" scripts, one for each combo of vc system and output format, would create the relevant set of handler objects upfront, without providing them with configuration (yet).

Then, call a ->get_options() method on each one, which would provide their Getopt::Long entries. Then merge them all for one big Getopt::Long call at the main() level. (bear in mind G:L can call back to closures, thereby scoping the data returned nicely!)

Finally call a ->run() method on each handler to actually perform its operations.

does that make sense?

Theory wrote:

Justin,

Yes, it does. And after I shut down and went to bed last night, I realized that this is essentially how SVN::Notify works right now: filters can specify command-line options, and the main class simply instantiates each one and has it process @ARGV. It works pretty well, so it should work well here, too.

Thanks for the feedback.

—Theory

Jon Jensen wrote:

I may be missing something, but it sounds like what you want is already distributed with Git, in contrib/hooks/post-receive-email. It doesn't include diffs by default, and a patch I've submitted has been ignored by the maintainers, but works well for me:

http://www.devcamps.org/cgi-bin/gitweb?p=camps.git;a=blob;f=misc/git-post-receive-email-emaildiff.patch;h=b9c368a7d15c226d9f90a9d58b59949b56bdcd44;hb=HEAD

Github has its own selectable hook for sending commit notifications too.

Theory wrote:

Jon,

SVN::Notify is popular because it sends diffs that look like this. I want that for my Git repositories, too—or for any SCM. GitHub doesn't have anything like this, and I doubt that Git will ever include anything that does quite this much work. It's the perfect opportunity for a third-party module (which was true for Subversion, too, and is why SVN::Notify is probably my most widely-distributed code).

—Theory