Quickly seeing the age of your branches

Posted on Sat 12 December 2015 in Plumbing is awesome

Have you ever asked yourself 'how old are all my branches?'. I have not, and it's not a common question. But recently someone did ask me how to find this out. You can of course dig through history manually, but where's the fun in that? I prefer to use our good friends: the plumbing commands.

The first attempt was a simple for-each-ref invocation

$ git for-each-ref --format '%(authordate) %(refname) %(subject)' refs/heads
Fri Oct 23 12:02:50 2015 -0700 refs/heads/debian-sid debian: new upstream point release
Fri Aug 8 22:24:56 2014 +0200 refs/heads/jk/blame-tree Add (stubby) documentation for blame-tree
Sun Nov 8 21:06:56 2015 +0100 refs/heads/master check-ignore: correct documentation about output
Tue Jan 27 16:38:49 2015 +0100 refs/heads/nd/multiple-work-trees t2026 needs procondition SANITY
Thu Nov 5 15:27:33 2015 -0800 refs/heads/next Sync with master
Mon Oct 19 22:41:32 2015 -0700 refs/heads/pu Merge branch 'jc/mailinfo' into pu
Thu Jun 26 13:48:07 2014 -0700 refs/heads/test Sync with gitk/git-gui submodule fixes on master
Tue May 19 23:58:10 2015 +0200 refs/heads/test-commit-no-tags tests: avoid creating unnecessary tags in test_commit

Not bad, but there are a few easy improvements, all quickly learned from the for-each-ref manpage. First we replace authordate with authordate:iso for a better date format. We also sort by this date with --sort=authordate and finally we remove refs/heads/ everywhere using refname:short.

$ git for-each-ref --sort=authordate --format '%(authordate:iso) %(refname:short) %(subject)' refs/heads
2014-06-26 13:48:07 -0700 test Sync with gitk/git-gui submodule fixes on master
2014-08-08 22:24:56 +0200 jk/blame-tree Add (stubby) documentation for blame-tree
2015-01-27 16:38:49 +0100 nd/multiple-work-trees t2026 needs procondition SANITY
2015-05-19 23:58:10 +0200 test-commit-no-tags tests: avoid creating unnecessary tags in test_commit
2015-10-19 22:41:32 -0700 pu Merge branch 'jc/mailinfo' into pu
2015-10-23 12:02:50 -0700 debian-sid debian: new upstream point release
2015-11-05 15:27:33 -0800 next Sync with master
2015-11-08 21:06:56 +0100 master check-ignore: correct documentation about output

Better, but the subjects aren't quite aligned yet. So let's align those:

$ git for-each-ref --sort=authordate --format '%(authordate:iso) %(align:left,25)%(refname:short)%(end) %(subject)' refs/heads
2014-06-26 13:48:07 -0700 test                      Sync with gitk/git-gui submodule fixes on master
2014-08-08 22:24:56 +0200 jk/blame-tree             Add (stubby) documentation for blame-tree
2015-01-27 16:38:49 +0100 nd/multiple-work-trees    t2026 needs procondition SANITY
2015-05-19 23:58:10 +0200 test-commit-no-tags       tests: avoid creating unnecessary tags in test_commit
2015-10-19 22:41:32 -0700 pu                        Merge branch 'jc/mailinfo' into pu
2015-10-23 12:02:50 -0700 debian-sid                debian: new upstream point release
2015-11-05 15:27:33 -0800 next                      Sync with master
2015-11-08 21:06:56 +0100 master                    check-ignore: correct documentation about output

So now we have a nicely formatted list containing the last commit of each branch. But is that really the proper definition of age? Another definition would be the date of the first commit on the branch. Or the date of the last commit on the current branch that can be reached from the other branch.

The latter is called the merge base, and git has a plumbing command to find it. We can use this command together with for-each-ref to find all merge bases in a "oneliner" that's good fodder for the git haters, as it shows that git commands can be complex. But remember that these are all plumbing commands, they're supposed to make uncommon things possible. The porcelain commands are here to make common things easy.

$ eval "$(
>     git for-each-ref --shell --format \
>     "git --no-pager log -1 --date=iso --format='%%ad '%(align:left,25)%(refname:short)%(end)' %%h %%s' \$(git merge-base %(refname:short) master);" \
>     refs/heads
> )" | sort
2014-06-26 13:46:09 -0700 test                      ea0e524 Merge early parts from git://ozlabs.org/~paulus/gitk.git
2014-08-07 09:44:17 -0700 jk/blame-tree             764c739 Merge branch 'mb/relnotes-2.1'
2015-02-05 13:23:56 -0800 nd/multiple-work-trees    9874fca Git 2.3
2015-05-13 14:34:46 -0700 test-commit-no-tags       1ea28e1 Sync with 2.4.1
2015-10-16 14:40:04 -0700 debian-sid                3c3d3f6 Git 2.6.2
2015-10-19 15:48:15 -0400 pu                        9b680fb t7063: fix flaky untracked-cache test
2015-11-05 12:22:13 -0800 next                      2c78628 Sync with 2.6.3
2015-11-08 21:06:56 +0100 master                    a5e28db check-ignore: correct documentation about output