tags:

views:

187

answers:

1

How can I get a list of branches that are first-level descendants of the current HEAD?

I can get a list of the whole tree with:

git log --graph --abbrev-commit --pretty=decorate --branches

which gives

* 2eff4a7... (refs/heads/issue-8351) Added a factory factory factory.
* 2e387aa... Refactored all of the factory factories.
| * b3fad52... (refs/heads/issue-8354) Moved the baz out of the constructor.
|/
| * f4cf3fe... (refs/heads/experimental) quuz looking good
| * 1d276b9... Risky fix to replace the quux with the quuz.
| * d6b0222... (refs/heads/issue-7559) Fixed the quux in the foo.
|/
| * 3f4cfff... (refs/heads/dev) wip
|/
* 76f493f... (refs/heads/master) SVN 30192

but I'd like to just get a plain list of the first-level children of the current branch, like this example for master:

dev
issue-7559
issue-8354
issue-8351

and if I was on branch issue-7559 I'd only see:

experimental

How might I go about this?

+3  A: 

You could do it as follows.

First the usual preamble:

#! /usr/bin/perl

use warnings;
use strict;

Use git for-each-ref to gather the SHA-1 and name for each ref:

sub refs {
  open my $fh, "-|", "git", "for-each-ref",
                            "--format=%(objectname)\t%(refname:short)"
    or die "$0: failed to run git for-each-ref";

  my %ref2sha;
  while (<$fh>) {
    chomp;
    my($sha,$ref) = split /\t/;
    $ref2sha{$ref} = $sha;
  }

  \%ref2sha;
}

If a commit is a child of HEAD, the set of commits reachable from HEAD excluding everything reachable from the commit in question is the empty set. We can check this relationship with git rev-list.

sub is_child {
  my($ref) = @_;

  # git rev-list ^dev master
  my $refs = `git rev-list ^$ref HEAD -- 2>&1`;
  die "$0: git rev-list-failed.\n$refs" if $?;

  $refs !~ /\S/;
}

For each ref that is a descendant of HEAD but it not equivalent to HEAD, we examine the path from HEAD to that reference using git log. If the path contains the tip of another branch, the ref cannot be a first-level child.

All survivors of this gauntlet are first-level children.

chomp(my $head = `git rev-parse HEAD 2>&1`);
die "$0: git rev-parse failed.\n$head" if $?;

my $ref2sha = refs;
my %headsha = reverse %$ref2sha;

REF:
foreach my $ref (keys %$ref2sha) {
  my $refsha = $ref2sha->{$ref};

  next if $refsha eq $head || !is_child $ref;

  my @log = `git log --pretty=format:%H ..$ref 2>&1`;
  die "$0: git log failed.\n@log" if $?;
  for (@log) {
    chomp;
    next if $_ eq $refsha;
    next REF if exists $headsha{$_};
  }

  print $ref, "\n";
}
Greg Bacon
I was hoping to avoid writing code to accomplish this, and you've helped me accomplish that. :)
mskfisher
@mskfisher I'm glad it's of benefit. Enjoy!
Greg Bacon