leco
leco

Reputation: 2039

Retrieve the list of child commits of a specific commit in Git

I was wondering whether there is an efficient way to retrieve the children of a given commit. Although a method was discussed in Referencing the child of a commit in Git, it is very inefficient.

I thought this would be a straightforward thing to be done in Git, but apparently, it is not.

Upvotes: 30

Views: 9205

Answers (3)

Mark
Mark

Reputation: 1986

I started out using @Lily Ballard's answer too (and big thanks to Lily and others who edited that answer, it works well!). But after starting there, I hit on a slightly different way to do things that's working better (running faster) for me. Here's what you'd put into the [alias] section of your ~/.gitconfig:

children = "!f() { git branch --contains "$1" --format='%(refname:short)' | git rev-list --not "$1^@" --children --stdin | grep -F "$(git rev-parse $1)" ; }; f"

What this is doing:

  1. The first part of the function is a git branch command that says "find all branches that are descendants of the inputted ref".
  2. Those branch names are piped as input to a git rev-list command. This command is very similar to the one in @Lily Ballard's answer, except that it doesn't use --all, which makes git rev-list consider all commits in the entire repo. Instead it takes the branch names from the first command via stdin, and only considers commits that are ancestors of those branches.
  3. Then it pipes those results to the same grep command as in @Lily Ballard's answer.

In my personal testing, this function is running ~8x faster than the one in @Lily Ballard's answer (~0.07s as opposed to ~0.57s). But obviously, the speed differences will depend entirely on the total size of your repo and size/complexity of the subtree at the commit you're interested in.

Upvotes: 1

Lily Ballard
Lily Ballard

Reputation: 185671

git rev-list can show children, but these children have to be reachable from the commits you provide. Assuming you want to show all children reachable from all branches in your repo, you can use something like

git rev-list --all --not $COMMIT^@ --children | grep "^$COMMIT"

This should output a line that looks like

$COMMIT $child1 $child2 $child3 ...

For convenience, you can add turn the command into a git alias by adding the following line to the [alias] section of your ~/.gitconfig:

children = "!f() { git rev-list --all --not $1^@ --children | grep $(git rev-parse $1); }; f" # reachable children of a ref

The syntax $COMMIT^@ might be confusing, so I'll explain it. Hopefully $COMMIT is self-explanatory. This is then followed by ^@, which expands to all parents of the referenced commit. So $COMMIT^@ means "all parents of $COMMIT". Since this follows the --not flag, this instructs rev-list to stop processing after it hits any parent of $COMMIT. This is basically just an optimization, because any commit reachable from $COMMIT cannot possibly be a child.


Note: a previous version of this answer said tail -1 instead of grep "^$COMMIT". This may work in a simple test repo (which is why I initially said it), but there's no guarantee that git rev-list will emit $COMMIT last, if you have any branches that do not contain $COMMIT.

Upvotes: 38

Tom Hale
Tom Hale

Reputation: 46775

I use the following alias (based on @Lily Ballard's answer:

# Get all children of current or specified commit-ish
children = "!bash -c 'c=${1:-HEAD}; set -- $(git rev-list --all --not \"$c\"^@ --children | grep $(git rev-parse \"$c\") ); shift; echo $*' -"
  • With no argument - gives all children of HEAD
  • With a commit-ish - gives all children of that object

Upvotes: 3

Related Questions