The really interesting thing about Git::Wrapper

Since I maintain the Git::Wrapper module, I was super happy to see the recent ETOOBUSY post about it — but I thought that the post, which starts with the TL;DR of “Git::Wrapper is an interesting Perl module.” didn’t mention the thing I think is the most interesting feature of Git::Wrapper.

In my opinion, the most interesting part of the module is the core of the implementation (which was originally written by HDP). Because of the way Git::Wrapper is written, it will continue to support any new sub-commands or sub-command options as they’re added to the Git binary, without requiring a single update to the Git::Wrapper code itself!

This is accomplished by the use of Perl’s AUTOLOAD subroutine. The way AUTOLOAD works is, if a non-existent subroutine is called when an AUTOLOAD subroutine exists, the AUTOLOAD sub is called instead of Perl throwing an error about a missing subroutine — and it is given the name of the method that was called. (Folks may be familiar with similar features in other languages, like Ruby’s method_missing.)

Using AUTOLOAD means the core of the module is as simple as this:

sub AUTOLOAD {
my $self = shift;

(my $meth = our $AUTOLOAD) =~ s/.+:://;
return if $meth eq 'DESTROY';

$meth =~ tr/_/-/;

return $self->RUN($meth, @_);
}

Git::Wrapper is an object-based module, so the first thing we do is get the object our missing method was called on. In Perl, you do this by extracting the first element from the special @_ variable. (@_ contains all the arguments that were provided when the method was called. I’m pointing this out now, because it will be relevant here in a paragraph or three.)

  my $self = shift;

Inside the AUTOLOAD method, the name of the method that was called is available in the special $AUTOLOAD variable. Since this code is in a module, the full name of the method that gets called is going to be prefixed with the module name, Git::Wrapper::. The regex in the first line strips that off, so we get just the bare name of the method that was called.

To give a concrete example, if we had a Git::Wrapper object called $git, and we had a $git->branch() method call, after these lines execute, $meth would be set to branch.

  (my $meth = our $AUTOLOAD) =~ s/.+:://;
return if $meth eq 'DESTROY';

(DESTROY is the name of a method that Perl calls on objects when they go out of scope, to allow them to run clean-up code. We don’t have any clean-up code to run, so if this AUTOLOAD invocation is because the object is being destroyed, we just bail out.)

Since Git sub-commands are written using kebab-case and Perl method names have to be snake_case, we convert from snake_case to kebab-case in this line:

  $meth =~ tr/_/-/;

And now, the big finale: we call the RUN method with the transformed method name, and pass in whatever other arguments were provided in the original method call that resulted in AUTOLOAD being invoked. RUN calls the git binary, with the transformed method name as the sub-command — so $git->branch() results in a shell execution of git branch! RUN also handles parsing and organizing the sub-command options, collects the output from the command execution, deals with any errors, and basically does all the actual work.

  return $self->RUN($meth, @_);

The RUN method is factored out into a distinct method instead of being inline in AUTOLOAD because there are some use cases for the module where somebody might want to call the RUN method directly, to take advantage of the error and output parsing in there.

So, that is what I think is the most interesting part of Git::Wrapper: it contains what I’m pretty sure is the only justfiable use of AUTOLOAD that I’ve ever personally worked on!