How do I find the next commit in Git? (child/children of ref)
GitVersion ControlGit 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:
git fsck --full
to list dangling (i.e. not in any branch) commits, orgit fsck --lost-found
to make refs pointing to dangling commits to apply techniques in the other answers.
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 :)