Easily clean up a team's remote git branches

There's a problem that I've seen on every team I've worked with that uses git. Because at Tau Station we're fairly merciless about technical debt--which makes the code base pretty sweet to work with--we take all technical debt issues seriously on the theory that once we launch, it may be too late to clean up (a silly idea in theory, but a prevalent one in practice).

The following technical debt issue is actually causing us a problem, though it's more of a process problem than a technical one:

05:35:02 (master) ~/veure $ git branch -a | wc -l
     272

Wow! We have 272 branches? Most of those are are long merged or abandoned. We've discovered that there are a couple of them which got overlooked (I'm tempted to blame github's poor PM tooling, but it's a lousy craftsman who blames his tools). We tried asking devs to find all of their remote branches and review them and delete them, but that turned out to be a rather daunting task and they're still missing branches.

Now, with a simple Perl script, we have a solution.

The following is named branch-authors (many thanks to friends on Twitter who answered my initial git question):

#!/usr/bin/env perl

use 5.24.0;
use warnings;
use Term::ANSIColor;
chomp( my @branches
    = qx/git show -s --pretty='%cn|%ci %D' \$(git rev-parse --branches --remotes)/
);

my %branches_for;

for my $branch (@branches) {
    my ( $author, $branch_info ) = split /\|/ => $branch, 2;
    push @{ $branches_for{$author} //= [] } => $branch_info;
}

my @authors = sort keys %branches_for;

my $author;
do {
    my $current = 1;
    foreach (@authors) {
        say "[$current] $_";
        $current++;
    }
    $current--;
    print "Choose an author ('q' to quit): ";
    chomp( my $response = <STDIN> );
    if ( $response =~ /^([0-9]+)$/ ) {
        if ( $response < 1 || $response > $current ) {
            say colored(['bright_red on_black'], "Response '$response' is out of range");
        }
        else {
            $author = $authors[ $response - 1 ];
        }
    }
    elsif ( $response =~ /^\s*[qQ]/ ) {
        exit;
    }
    else {
        say colored(['bright_red on_black'], "Please enter an integer from 1 to $current");
    }
} while not $author;

my $branches = $branches_for{$author};

# due to iso date format, we sort on dates for free
foreach my $branch (sort @$branches) {
    say $branch;
}

__END__

=head1 NAME

branch-authors

=head1 SYNOPSIS

    perl bin/branch-authors

=head1 DESCRIPTION

Prints a list of branch authors and prompts you to choose one. Once chosen,
displays a list of all branches believed to be pushed by them, along with the
date. This should allow you to easily track down your remote branches and
remove them after they've been merged.

The syntax for deleting a remote branch is:

git push origin :branchname

Note that there is a space after C<origin>. This does not delete your local copy.

See also: http://gitready.com/beginner/2009/02/02/push-and-delete-branches.html

=head1 BRANCH DETECTIONS

Currently we guess that the name on the last commit is the person responsible
for a given branch. It's up to them to verify that a branch may be safely
deleted.

It's a hack, but running the script has an output like this:

[1] Alice
[2] Bob
[3] Charlie
[4] Ovid
Choose an author ('q' to quit):

You choose the number of the author in question and you get a full list of all branches found for them, listed from oldest to newest. Here's a truncated list for me:

2016-08-26 12:58:04 +0200 cleanup-666
2016-11-03 15:36:17 +0100 origin/tasks-to-actions-1205
2016-11-09 16:29:08 +0100 origin/character-impairment-1227
2016-11-18 11:33:08 +0100 average-stats-per-level-827
2016-11-20 08:28:52 +0100 origin/average-stats-per-level-827
2016-11-20 17:25:48 +0100 HEAD -> master, origin/master, origin/HEAD

With that, all team members can now easily find their branches and decide what they should do with it. Of course, git doesn't actually track who pushed a branch, so I made the executive decision that whoever pushed a branch last is responsible for said branch. In practice, with our workflow, it's spot on.

3 Comments

At work the rule is that when you do merge someone's branch after code review you also delete it. We use GitHub Enterprise, which makes this incredibly trivial, but it shouldn't be that hard with any workflow.

That doesn't fix the problem of abandoned branches, but it does at least cut down on some of the noise.

With Ovid's permission i have added this to my git-tools repo on github: https://github.com/leejo/git-tools/commit/2eea65690f7ace95ccf1cc9aa514371f69f8500d

This seems to list all local branches, not remote ones -- so if one has a lot of local branches, but only a few of them exist on the remote, it doesn't isolate those ones that need to be cleaned up on the remote.

Put another way -- if I go down the list of branches provided by this script, and run git push origin :branchname on all of them, the script will still keep listing all the branches, so I can't tell which ones still remain to be cleaned up.

I would suggest taking a remote name (defaulting to origin), and only looking at remote-tracking branches.

Leave a comment

About Ovid

user-pic Freelance Perl/Testing/Agile consultant and trainer. See http://www.allaroundtheworld.fr/ for our services. If you have a problem with Perl, we will solve it for you. And don't forget to buy my book! http://www.amazon.com/Beginning-Perl-Curtis-Poe/dp/1118013840/