How do I find the next commit in Git? (child/children of ref)

GitVersion Control

Git Problem Overview


ref^ refers to the commit before ref. What about getting the commit after ref?

For example, if I git checkout 12345, how do I check out the next commit?

PS: Yes, Git's a DAG node pointer struct tree whatever. How do I find the commit after this one?

Git Solutions


Solution 1 - Git

To list all the commits, starting from the current one, and then its child, and so on - basically standard git log, but going the other way in time, use something like

git log --reverse --ancestry-path 894e8b4e93d8f3^..master

where 894e8b4e93d8f3 is the first commit you want to show.

Solution 2 - Git

The creator of Hudson (now Jenkins), Kohsuke Kawaguchi published (November 2013): kohsuke / git-children-of:

> Given a commit, find immediate children of that commit.

#!/bin/bash -e
# given a commit, find immediate children of that commit.
for arg in "$@"; do
  for commit in $(git rev-parse $arg^0); do
    for child in $(git log --format='%H %P' --all | grep -F " $commit" | cut -f1 -d' '); do
      git describe $child
    done
  done
done

As illustrated by this thread, in a VCS based on history represented by a DAG (Directed Acyclic Graph), there is not "one parent" or "one child".

        C1 -> C2 -> C3
      /               \
A -> B                  E -> F
      \               /
        D1 -> D2 ----/

The ordering of commits is done by "topo-order" or "date-order" (see the GitPro book).

But since Git 1.6.0, you can list the children of a commit.

git rev-list --children
git log --children

Note: for parent commits, you have the same issue, with the suffix ^ to a revision parameter meaning the first parent of that commit object. ^<n> means the <n>th parent (i.e. rev^ is equivalent to rev^1).

If you are on branch foo and issue "git merge bar" then foo will be the first parent.
I.e.: The first parent is the branch you were on when you merged, and the second is the commit on the branch that you merged in.

Solution 3 - Git

I found:

git rev-list --ancestry-path commit1..commit2

Where I set commit1 as the current commit and commit2 to the current head. This returns me a list of all commits which build a path between commit1 and commit2.

The last line of the output is the child of commit1 (on the path to commit2).

Solution 4 - Git

I know what you mean. It's frustrating to have plentiful syntax for going to previous commits, but none to go to the next ones. In a complex history, the problem of "what is the next commit" becomes rather hard, but then in complex merging the same hardness emerges with 'previous' commits as well. In the simple case, inside a single branch with a linear history (even just locally for some limited number of commits) it would be nice and make sense to go forward and backward.

The real problem with this, however, is that the children commits are not referenced; it's a backwards-linked list only. Finding the child commit takes a search, which isn't too bad, but it is probably not something Git wants to put into the refspec logic.

At any rate, I came upon this question, because I simply want to step forward in the history one commit at a time, doing tests, and sometimes you have to step forward and not backward. Well, with some more thought I came up with this solution:

Pick a commit ahead of where you're at. This could probably be a branch head. If you're at branch10, "git checkout branch9", "git checkout branch8" to get the next after that, "git checkout branch7" and so on.

Decrementing the number should be really easy in a script if you need it. A lot easier than parsing the Git rev-list.

Solution 5 - Git

Two practical answers:

One Child

Based on @Michael's answer, I hacked up the child alias in my .gitconfig.

It works as expected in the default case, and is also versatile.

# Get the child commit of the current commit.
# Use $1 instead of 'HEAD' if given. Use $2 instead of curent branch if given.
child = "!bash -c 'git log --format=%H --reverse --ancestry-path ${1:-HEAD}..${2:\"$(git rev-parse --abbrev-ref HEAD)\"} | head -1' -"

It defaults to giving the child of HEAD (unless another commit-ish argument is given) by following the ancestry one step toward the tip of the current branch (unless another commit-ish is given as second argument).

Use %h instead of %H if you want the short hash form.

Multiple children

With a detached HEAD (there is no branch) or to get all children regardless of branches:

# For the current (or specified) commit-ish, get the all children, print the first child 
children = "!bash -c 'c=${1:-HEAD}; set -- $(git rev-list --all --not \"$c\"^@ --children | grep $(git rev-parse \"$c\") ); shift; echo $1' -"

Change the $1 to $* to print all the children.

You can also change --all to a commit-ish to display only the children which are ancestors of that commit—in other words, to display only the children “in the direction of” the given commit. This may help you narrow the output down from many children to just one.

Solution 6 - Git

All of Git's internal arrows are one-way, pointing backwards. There is therefore no short convenient syntax for moving forwards: it's just not possible.

It is possible to "move against the arrows", but the way to do it is surprising if you haven't seen it before, and then obvious afterward. Let's say we have:

A <-B <-C <-D <-E   <-- last
        ^
        |
         \--------- middle

Using middle~2 follows the arrows twice from C back to A. So how do we move from C to D? The answer is: we start at E, using the name last, and work backwards until we get to middle, recording the points we visit along the way. Then we just move as far as we want in the direction of last: move one step to D, or two to E.

This is particularly important when we have branches:

          D--E   <-- feature1
         /
...--B--C   <-- master
         \
          F--G   <-- feature2

Which commit is one step after C? There's no correct answer until you add to the question: in the direction of feature___ (fill in the blank).

To enumerate the commits between C (excluding C) itself and, say, G, we use:

git rev-list --topo-order --ancestry-path master..feature2

The --topo-order makes sure that even in the presence of complex branching-and-merging, the commits come out in topologically-sorted order. This is only required if the chain isn't linear. The --ancestry-path constraint means that when we work backwards from feature2, we only list commits that have commit C as one of their own ancestors. That is, if the graph—or the relevant chunk of it anyway—actually looks like this:

A--B--C   <-- master
 \     \
  \     F--G--J   <-- feature2
   \         /
    H-------I   <-- feature3

Then a simple request of the form feature2..master enumerates commits J, G and I, and F and H in some order. With --ancestry-path we knock out H and I: they are not descendants of C, only of A. With --topo-order we make sure that the actual enumeration order is J, then G, then F.

The git rev-list command spills these hash IDs out on its standard output, one per line. To move one step forward in the direction of feature2, then, we just want the last line.

It's possible (and tempting and can be useful) to add --reverse so that git rev-list prints the commits in reversed order after generating them. This does work, but if you use it in a pipeline like this:

git rev-list --topo-order --ancestry-path --reverse <id1>...<id2> | head -1

to just get the "next commit in the direction of id2", and there is a very long list of commits, the git rev-list command can get a broken pipe when it tries to write to head which has stopped reading its input and exited. Since broken-pipe errors are normally ignored by the shell, this mostly works. Just make sure they're ignored in your usage.

It's also tempting to add -n 1 to the git rev-list command, along with --reverse. Don't do it! That makes git rev-list stop after walking one step back, and then reverse the (one-entry) list of commits visited. So this just produces <id2> every time.

Important side note

Note that with "diamond" or "benzene ring" graph fragments:

       I--J
      /    \
...--H      M--...  <-- last
      \    /
       K--L

moving one commit "forward" from H towards last will get you either I or K. There is nothing you can do about that: both commits are one step forward! If you then start from the resulting commit and go another step, you're now committed to whichever path you started on.

The cure for this is to avoid moving one step at a time and getting locked into path-dependent chains. Instead, if you plan to visit an entire ancestry-path chain, before doing anything else, make a complete list of all commits in the chain:

git rev-list --topo-order --reverse --ancestry-path A..B > /tmp/list-of-commits

Then, visit each commit in this list, one at a time, and you'll get the entire chain. The --topo-order will make sure you hit I-and-J in that order, and K-and-L in that order (though there's no easy way to predict whether you'll do the I-J pair before or after the K-L pair).

Solution 7 - Git

In the case where you don't have a particular "destination" commit in mind, but instead want to see child commits that might be on any branch, you can use this command:

git rev-list --children --all | grep ^${COMMIT}

If you want to see all children and grand-children, you have to use rev-list --children recursively, like so:

git rev-list --children --all | \
egrep ^\($(git rev-list --children --all | \
           grep ^${COMMIT} | \
           sed 's/ /|/g')\)

(The version that gives only grand-children would use a more complex sed and/or cut.)

Finally, you can feed that into a log --graph command to see the tree structure, like so:

git log --graph --oneline --decorate \
\^${COMMIT}^@ \
$(git rev-list --children --all | \
  egrep ^\($(git rev-list --children --all | \
             grep ^${COMMIT} | \
             sed 's/ /|/g')\))

Note: the above commands all assume that you've set the shell variable ${COMMIT} to some reference (branch, tag, sha1) of the commit whose children you're interested in.

Solution 8 - Git

I've tried many different solutions and none worked for me. I had to come up with my own.

Find the next commit

function n() {
    git log --reverse --pretty=%H master | grep -A 1 $(git rev-parse HEAD) | tail -n1 | xargs git checkout
}

Find the previous commit

function p() {
    git checkout HEAD^1
}

Solution 9 - Git

I have this alias in ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"

Solution 10 - Git

If the child commits are all on some branch, you can use gitk --all commit^.., where "commit" is something identifying the commit. For example, if the commit's abbreviated SHA-1 hash value is c6661c5, then type gitk --all c6661c5^..

You will probably need to enter the full SHA-1 hash value into gitk's "SHA1 ID:" cell. You will need the full SHA-1 hash value, which for this example can be obtained via git rev-parse c6661c5.

Alternatively, git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d' will produce a line containing all the children of this commit, presumably whether or not there is a branch involved.

Solution 11 - Git

On the terminal:

$ git log --format='%H %P' --all --reflog | grep -F " [commit-hash]" | cut -f1 -d' '

Or in .gitconfig, section [alias]:

children = "!f() { git log --format='%H %P' --all --reflog | grep -F \" $1\" | cut -f1 -d' '; }; f"

Solution 12 - Git

I managed to find the next child the following way:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)

Solution 13 - Git

This shows a list of all children of current HEAD in separate lines:

git rev-list --parents --all | awk -v h="$(git rev-parse HEAD)" 'index($0,h)>1{print$1}'

It just prints all commits that have HEAD as their parent.

You can speed it up a little bit, by putting ^HEAD before |, then the ancestors of HEAD will not be searched.

If you want to print children of another commit or branch, just put it in the place of HEAD (in the faster version in both HEAD places).

Solution 14 - Git

Each commit stores a pointer to its parent (parents, in case of a merge (standard) commit).

So, there isn't any way to point to a child commit (if there is one) from the parent.

Solution 15 - Git

Tomas Lycken's response in Using Git commits to drive a live coding session shows a neat way of doing it if you can create a well-defined tag at the end of your commit stack. Essentially

git config --global alias.next '!git checkout `git rev-list HEAD..demo-end | tail -1`'

where "demo-end" is the last tag.

Solution 16 - Git

Existing answers assume that you have a branch containing the commit you're looking for.

In my case, the commit I was looking for wasn't on git rev-list --all since no branch contained that.

I ended up looking through gitk --reflog manually.

If you can't find your commit even in the reflog, try either:

Solution 17 - Git

I needed a command to rapidly checkout next commit.
I ended up with this alias:

[alias]
next = "!f() { CMIT=$(git log --ancestry-path --format=%H ${commit}..${1} | tail -1) && git checkout $CMIT; }; f"

You need to specify the branch you want to traverse, eg.:

git next master

You can add it to your .gitconfig file by directly editing it.

An example of its output:

~/home/myRepo | dd9e66ee  git next master
Previous HEAD position was dd9e66e Update README
HEAD is now at d71c74b Rename project; update to JUnit5

By running this command you go in detached head mode, of course :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSchwernView Question on Stackoverflow
Solution 1 - GitTim HuntView Answer on Stackoverflow
Solution 2 - GitVonCView Answer on Stackoverflow
Solution 3 - Gitwhite_geckoView Answer on Stackoverflow
Solution 4 - GitvontrappView Answer on Stackoverflow
Solution 5 - GitTom HaleView Answer on Stackoverflow
Solution 6 - GittorekView Answer on Stackoverflow
Solution 7 - GitMatt McHenryView Answer on Stackoverflow
Solution 8 - GitM KView Answer on Stackoverflow
Solution 9 - GitweakishView Answer on Stackoverflow
Solution 10 - Gituser339589View Answer on Stackoverflow
Solution 11 - GitTamaMcGlinnView Answer on Stackoverflow
Solution 12 - GitDevRQView Answer on Stackoverflow
Solution 13 - GitmikView Answer on Stackoverflow
Solution 14 - GitlprsdView Answer on Stackoverflow
Solution 15 - GitAlex DreskoView Answer on Stackoverflow
Solution 16 - GitsnipsnipsnipView Answer on Stackoverflow
Solution 17 - GitFerdinando SantacroceView Answer on Stackoverflow