Monday, April 19, 2010

No more select-and-middle-click for git (or how unix saves the day, again)

[Update 2011-07-28: You can get the code at github:]

I use git and rely on its 'git status' output to select the filenames that I want to operate on whatever git command that I wanted to do *now*.

I used to do this (a lot):

$ git status
[git shows the status here]
$ git [add|add -p|rm|checkout] *I reach for the mouse with my right hand, double click on the filename(s) shown in the status (so that it is selected and copied into the X cut selection) and middle click on the terminal (so that the filename is pasted here), and either hit the space bar to prepare for the next 'select-and-middle-click' another filename or press enter at this point to execute this action*

At first this felt like a quick and speedy way of selecting files to operate on but over time it gets old. I don't like reaching for the mouse and prefer that my hand stays on the keyboard. Somehow this breaks my concentration somewhat and that is not a good thing, you know, focus and all... and somehow I never get comfortable with the command line completion offered by the shell (when it comes to git) and wondered if there's a quicker way of telling git which file I want it to operate on ...

So I devised a way that I think I'll be happy using:

$ gt
# On branch master
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#1 modified: fa
#2 modified: mF
#3 modified:
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#4 6a
#5 6c
#6 6cov
#7 6g

'gt' is a perl script that parses the output of git status and adds cute numbers before the tab that precedes the filenames in git status' original output. The numbers are cute because they let me know that I no longer have to reach for the mouse to tell git (or some other shell script) which file I wanted to operate on. You can think of it as the primary key that uniquely identify the file listed by git status. gt also saves its output in a hidden file, overwriting the previous content on every run.

So with this in place I can have another script, ga (which stands for git add) that accepts either the full filename or the cuter numbers as its argument so I can pretty much not go through 'select-and-middle-click' for git anymore:

$ ga -p 1 3

Which have the same effect as if I had typed "git add -p fa" - this is *big* win for me. Yay!

What ga does is go through its argument and convert those numbers to its equivalent filename by looking at that hidden file I mentioned earlier. This is done by the following small perl library:

use strict;
use warnings;
package MyGitList;

my $number_pattern;
my $cmd_opt = '';

sub resolve_args {
my (@argv) = @_;
my @resolved;

foreach my $arg (@argv) {
if ($arg =~ /^[0-9]+$/) {
if (defined $number_pattern) {
$number_pattern .= '|'.$arg
else {
$number_pattern = $arg;
elsif ($arg =~ /^-/) {
$cmd_opt .= $arg;
$cmd_opt .= ' ';
else {
push @resolved, $arg;

my $resolved = join(' ', @resolved);

my $gitlist = '';
if (defined $number_pattern) {
$gitlist = `gl '$number_pattern'`;
$gitlist =~ s/\n/ /g;
$gitlist =~ s/\s*$//g;
$gitlist =~ s/^\s//g;
my $arg_list = "$gitlist $resolved";

return ($cmd_opt, $arg_list);



And ga uses like so:

use strict;
use warnings;
use lib($ENV{HOME} . '/bin');
use MyGitList;

if (scalar @ARGV == 0) {
print "Usage: ga <number_pattern|filename>\n";
exit 1;

my ($cmd_opt, $arg_list) = MyGitList::resolve_args(@ARGV);
my $cmd = "git add $cmd_opt $arg_list";
print "$cmd\n";

I love Unix.

Not too long ago I modified git status, git add, git rm, git diff, and a few more of its siblings to handle this cute numbering scheme but gave up on that because when I think about it more it seems like git is not the proper place to do it - it feels like modifying your car so that it can tell you where you stashed the coins that you had with you yesterday (there goes another not-so-useful car-computer analogy).


Post a Comment

<< Home