DEV Community

Cover image for Writing git extensions in Perl
Juan Julián Merelo Guervós
Juan Julián Merelo Guervós

Posted on

Writing git extensions in Perl

Introduction

Most people will tell you git is a source control tool; some people will tell you that git is a content-addressable filesystem. It's all that, but the interesting thing is that it's a single-tool interface to frameworks that allow you to create products as a team.
Enter the absolutely simple extension mechanism that git has: write a n executable called git-xxx and git will dutifully call it when you make git xxx. Which is why, to make an easier onramp for students in my 7th-semester class in Computer Science, I created an extension called git iv (IV is the acronym for the class). The extension allows them to create branches with specific names, as well as upload those branches, without needing to remember specific git commands.

You might argue that remembering git commands is what students should do, but in fact they don't, and since this is not part of the core of the class, I prefer to eliminate sources of trouble for them (which eventually become sources of trouble for me) using this.

Writing the extension in Perl

There are many good things that can be said about Perl, for this or for anything else. But in this case there's a thing that makes it ideal for writing extensions: git includes a Perl module called Git, which is a Perl interface to all the Git commands. This is distributed with git, so if you've got git, you've got this library.

The whole extension is not hosted in this GitHub repo; this will contain the most up-to-date version as well as documentation and other stuff.

So here's the preamble to the extension:

use strict;
use warnings;
use lib qw( /Library/Developer/CommandLineTools/usr/share/git-core/perl
            /usr/share/perl5 );
use Git;

use v5.14;

my $HELP_FLAG = "-h";
my $USAGE_STRING = <<EOC;
Uso:
    git iv objetivo <número> -- crea una rama para ese objetivo
    git iv sube-objetivo     -- sube al repo remoto la rama

    git iv $HELP_FLAG -- imprime este mensaje
EOC
Enter fullscreen mode Exit fullscreen mode

The main caveat about the extension is that some flags will be handled by git itself. There are probably quite a few of those, but one of them is --help. git xxx --help will try to look up a manual page for git xxx. This is why above a different help flag is defined. And also a usage string, which is helpful when you don't remember the exact shape of the subcommands. In this case, I use git iv as the extension name and as interface to the stuff that needs to be made; but there are subcommands that will do different things. These are implemented later:

my @subcommands = qw(objetivo sube-objetivo);
push( @subcommands, quotemeta $HELP_FLAG);

die( usage_string() ) unless @ARGV;
my $subcommand = shift;

die "No se reconoce el subcomando $subcommand" unless grep( /\Q$subcommand/, @subcommands );

my @args = @ARGV;
Enter fullscreen mode Exit fullscreen mode

I prefer not to include any dependencies; there are powerful command line flag libraries out there, but in this case, a single script is best. So you handle whatever comes after iv uniformly, be it a subcommand or a flag. But the issue with the flag is that it includes a dash -, so we wrap it so that it can be used safely in regexes. Like the one, for instance, 4 lines below: in case the subcommand at the front of the command line is not part of the list, it will bail out showing the usage string.

Anything after the subcommand will be gobbled into @args.

if ( $subcommand eq $HELP_FLAG ) {
  say $USAGE_STRING;
} else {

  my $repo;

  eval {
    $repo = Git->repository;
  } or die "Aparentemente, no estás en un repositorio";

  if ( $subcommand eq "objetivo" ) {
    die $USAGE_STRING unless @args;
    $repo->command( "checkout", "-b", "Objetivo-" . $args[0]);
  }

  if ( $subcommand eq "sube-objetivo" ) {
    my $branch = $repo->command( "rev-parse", "--abbrev-ref", "HEAD" );
    chomp($branch);
    $repo->command ( "push", "-u", "origin", $branch );
  }
}
Enter fullscreen mode Exit fullscreen mode

Now it's a matter of processing the subcommand. If it's the flag -h, print the usage string; if it's any of the other subcommands, we need to work with the git repository.

$repo = Git->repository; creates an object out of the Git library we mentioned before that we will use to issue the different plumbing or high level commands. One of the subcommands will do a checkout: $repo->command( "checkout", "-b", "Objetivo-" . $args[0]); will convert itself to the equivalent command. You can even work with plumbing commands such as rev-parse to check the branch you're in and create that branch remotely, ad the other command does.

Concluding

Perl saves you a whole lot of trouble when writing this kind of thing. Besides, the fact that it will be most probably be installed in any system you use to develop (Mac, Linux or WSL) will save you trouble asking for prerequisites for this script.

Top comments (0)