287

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?

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

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
Schwern
  • 153,029
  • 25
  • 195
  • 336

19 Answers19

231

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.

N.b. When using a DOS command prompt, you must escape the caret:

git log --reverse --ancestry-path 894e8b4e93d8f3^^..master
Lee Hunt
  • 23
  • 4
Tim Hunt
  • 2,571
  • 1
  • 15
  • 7
  • 5
    For the specific case in the original question, just substitute `HEAD^` for `894e8b4e93d8f3^`. – Søren Løvborg Apr 14 '14 at 14:30
  • 2
    Maybe, add --oneline is a better brief output result. – firo Jun 12 '15 at 07:40
  • 1
    I need to use `...`, `^..` fails silently – TankorSmash Oct 05 '15 at 15:44
  • 1
    Getting `fatal: unrecognized argument: --ancestry-path` in git version 1.7.1 – user151841 Oct 26 '15 at 18:30
  • 8
    This will only work if `master` is on the ancestry path of the current commit. See [my answer](http://stackoverflow.com/a/39566420/5353461)'s second code snippet for a solution which will work in all cases. – Tom Hale Sep 20 '16 at 03:45
  • 1
    I don't see the point of adding `--reverse` here. It's just a preference, and an unneeded option as part of an answer that could be simpler. – polynomial_donut Jan 16 '19 at 18:49
  • So if you want to get the actual next commit in one line I used `git rev-list HEAD~$(expr $(git rev-list --ancestry-path .. --count) - 1) --max-count=1` – Brian Apr 08 '21 at 18:30
41

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 7
    `git rev-list --children` sure looks like what I want, but it doesn't DWIM. It appears to list all the parents and their children. I suppose I can list them all and parse through them... bleh, but its something. – Schwern Feb 16 '10 at 22:36
  • @Schwern: true, `git rev-list --children` is not for listing just children, but for listing parents *with their children*... you always need to parse. – VonC Feb 16 '10 at 23:06
  • I tried that code on a commit with two children: `$ git children0of 9dd5932` `fatal: No annotated tags can describe '71d7b5dd89d241072a0a078ff2c7dfec05d52e1f'.` `However, there were unannotated tags: try --tags.` What output do you get? – Tom Hale Sep 19 '16 at 05:54
  • @TomHale I cannot test it right now, but ask a new question (with OS and Git version), that way everyone can test. – VonC Sep 19 '16 at 06:43
  • 3
    The only problem with `git-children-of` is that it uses [git describe](https://git-scm.com/docs/git-describe) that attempts to format the SHA as human-readable, which can fail with @TomHale's error, and gives results like `v1.0.4-14-g2414721` that are confusing if you expected a SHA. Replacing it with a simple `echo` makes this an excellent tool, thanks! – Nickolay May 16 '18 at 13:15
  • @TomHale 's problem can also be fixed with `git describe --all --always $child` so it will fall back to the abbreviated hash if it can't find a ref to use. If you have no tags whatsoever or only started using them recently, `--all --contains` might give more useful names, but since they are relative to branch heads they won't stay valid as new commits occur. – LeBleu Apr 28 '22 at 15:49
  • 1
    @LeBleu Thank you for the feedback. How would you modify the script from the answer to accommodate that particular scenario? – VonC Apr 28 '22 at 15:51
24
git rev-list --ancestry-path commit1..commit2

I set commit1 as the current commit and commit2 to the current head, and this returns 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).

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
white_gecko
  • 4,808
  • 4
  • 55
  • 76
13

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 branch~10, "git checkout branch~9", "git checkout branch~8" to get the next after that, "git checkout branch~7" 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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
vontrapp
  • 649
  • 6
  • 6
  • 1
    While useful, it doesn't find the next commit. It walks towards the current commit. – Schwern Feb 28 '12 at 11:41
  • 1
    Well then I suppose you could do: "`BRANCH=master; git co $BRANCH~$[ $(git rev-list HEAD..$BRANCH | wc -l) - 1 ]`" You've got to go towards a branch, no way around that. – vontrapp Jul 20 '13 at 05:43
12

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).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
torek
  • 448,244
  • 59
  • 642
  • 775
  • 1
    "*There's no correct answer until you add to the question: in the direction of feature___*" This is a very good point. Thanks for your answer. – Schwern Oct 04 '19 at 05:55
10

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.

bdesham
  • 15,430
  • 13
  • 79
  • 123
Tom Hale
  • 40,825
  • 36
  • 187
  • 242
9

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.

Matt McHenry
  • 20,009
  • 8
  • 65
  • 64
6

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
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
M K
  • 9,138
  • 7
  • 43
  • 44
  • What programming language? [Bash](https://en.wikipedia.org/wiki/Bash_%28Unix_shell%29)? [Z shell](https://en.wikipedia.org/wiki/Z_shell)? [C shell](https://en.wikipedia.org/wiki/C_shell)? [PowerShell](https://en.wikipedia.org/wiki/Windows_PowerShell)? – Peter Mortensen Jun 15 '21 at 11:46
4

I have this alias in ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"
weakish
  • 28,682
  • 5
  • 48
  • 60
  • [Useful for Maven developers](https://gist.github.com/jglick/66974651f7c0c3ffed5945b3f2d2ebd4). – Jesse Glick Oct 20 '17 at 20:02
  • what is `f()`? And it needs to be `head -1` to be the first child, otherwise this will simply report the HEAD. – xeruf May 31 '18 at 23:27
  • @Xerus Because it uses some "complex" shell syntax, and git will not recognize it if not wrapped in `f()`. Yes, `head -1` is a brave guess. – weakish Jun 11 '18 at 11:52
  • Nice! Tweaked mine to: ```nextref = "!f() { git log --reverse --ancestry-path --pretty=%H $1..HEAD | head -${2:-1} | tail -1; }; f"``` so you can choose how far ahead, optionally – Bernardo Dal Corno May 20 '19 at 19:39
3

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"
TamaMcGlinn
  • 2,840
  • 23
  • 34
2

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user339589
  • 86
  • 2
1

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).

mik
  • 3,575
  • 3
  • 20
  • 29
0

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)
Perception
  • 79,279
  • 19
  • 185
  • 195
DevRQ
  • 35
  • 3
  • 1
    for me this shows not the first child, it shows the current commit – Radon8472 Mar 24 '19 at 12:00
  • This does not show children of the commit. It shows current commit. If you provide -n2 instead, it will show previous commit and the current one, but no next ones to the current commit. Which renders it useless because you can just look history. – KulaGGin Aug 13 '21 at 14:46
0

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex Dresko
  • 5,179
  • 3
  • 37
  • 57
0

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:

snipsnipsnip
  • 2,268
  • 2
  • 33
  • 34
0

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 :)

Ferdinando Santacroce
  • 1,047
  • 3
  • 12
  • 31
0

I get the next child ref using following way:

git rev-list --children --reverse b3443a2^.. | sed -n 1p | awk '{print $2}'
  1. Get the ref list with children of specific commit to the HEAD in reverse order
  2. Use the first line
  3. Print the next child ref
Markus Weigelt
  • 352
  • 1
  • 3
  • 6
0

If you want to search all commits in your Git object database, not just those referenced by some ref, then it doesn't seem like there is a practical way to do it with just the standard tools. Though it's possible iterate through and inspect every commit object in the database with just Git plumbing commands and bash, that will be too slow to be practical for most repositories.

I wrote a program which performs this task quickly:

https://github.com/CyberShadow/misc/blob/master/git-find-child-commit.d

(To run it, install dub, then execute the source file like a script; the package manager will do the rest.)

Vladimir Panteleev
  • 24,651
  • 6
  • 70
  • 114
-1

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
lprsd
  • 84,407
  • 47
  • 135
  • 168
  • Commit cannot store pointers to its children, as additional child commits (branching points) can be added at later point. – Jakub Narębski Feb 16 '10 at 00:23
  • 1
    @Jakub I don't really follow. They can't be added later? – Schwern Feb 16 '10 at 05:30
  • 1
    Jakub: Thats exactly what I said. 'Each commit stores pointers only to its parent.' – lprsd Feb 16 '10 at 06:22
  • 1
    @Schwern: Commits in git are immutable (which has nice consequence of accountability), so pointers to children cound't "be added later". One of the reasons is that identifier of commit (used e.g. in "parent" links) depends on the contents of the comit; this is the only solution in distributed system, without central numbering authority. Also "children" of commits depends on the branches you have, and this in turn can be different from repository to repository (and commits are the same in each repository). – Jakub Narębski Feb 16 '10 at 11:08
  • @becomingGuru: My comment was about why it is impossible to have pointers to child comits in the commit object. – Jakub Narębski Feb 16 '10 at 11:09
  • 5
    @becomingGuru I down voted it. It may be true, but it does not answer my question. The question is "how do I find the next commit in git?" It is not "does a git commit store a pointer to its children?" – Schwern Feb 16 '10 at 22:32