AttishOculus
AttishOculus

Reputation: 1489

Referencing the child of a commit in Git

If you want to move the HEAD to the parent of the current HEAD, that's easy:

git reset --hard HEAD^

But is there any simple way to do the exact opposite of this operation, that is, set the head to the current head's first child commit?

Right now, I use gitk as a workaround (alt-tab, up-arrow, alt-tab, middle-click), but I would like a more elegant solution, one that can also be used when gitk is not available.

Upvotes: 51

Views: 16811

Answers (11)

Alex Dresko
Alex Dresko

Reputation: 5203

This post 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.

Upvotes: 0

Rainer Blome
Rainer Blome

Reputation: 571

AttishOculus's method using git rev-list --all considers all available commits, which can be a lot and is often not necessary. If the interesting child commits are reachable from some branch, the number of commits that a script interested in child commits needs to process can be reduced:

branches=$(git branch --contains $commit | grep -v '[*] (' | sed -e 's+^..++')

will determine the set of branches that $commit is an ancestor of. With a modern Git, at least version 2.21+, this should do the same without the need for sed (untested):

branches=$(git branch --format='%(refname:short)' --contains $commit | grep -v '[*] (')

Using this set, git rev-list --parents ^$commit $branches should yield exactly the set of all parent-child relationships between $commit and all branch heads that it is an ancestor of.

Upvotes: 13

Phil
Phil

Reputation: 1219

This is not an absolute answer but it is very useful for quick research.

  • git log from the beginning
  • each line is a abbrev commit with --oneline
  • add optionally --graph for multiple ancestors.
  • pipe |
  • step back of n (here: 3) ancestries with --before-context=n (same as -B n) of grep searching an abbrev commit (7 characters)
git log --oneline HEAD | grep -B 3 <an-abbrev-commit>

Upvotes: 2

AttishOculus
AttishOculus

Reputation: 1489

Very probably not the fastest possible solution, but it does what I need:

#!/bin/bash

REV=$1

if [[ -z "$REV" ]]; then
    echo "Usage: git-get-child <refspec> [<child-number>]"
    exit
fi

HASH=$(git rev-parse $REV)

NUM=$2

if [[ -z "$NUM" ]]; then
    NUM=1
fi

git rev-list --all --parents | grep " $HASH" | sed -n "${NUM}s/\([^ ]*\) .*$/\\1/p"

The git rev-list --all --parents does exactly what I need: it iterates over all reachable commits, and prints the following line for each:

SHA1_commit SHA1_parent1 SHA1_parent2 etc.

The space in the grep expression ensures that only those lines are found where the SHA1 in question is a parent. Then we get the nth line for the nth child and get the child's SHA1.

Upvotes: 18

Tom Hale
Tom Hale

Reputation: 46963

To just move HEAD (as asked - this doesn't update the index or working tree), use:

git reset --soft $(git child)

You'll need to use the configuration listed below.

Explanation

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.

With a detached head, there is no branch, but getting the first child can still be achieved with this alias:

# 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

Upvotes: 4

Michael come lately
Michael come lately

Reputation: 9382

Based partly on Paul Wagland's answer and partly on his source, I am using the following:

git log --ancestry-path --format=%H ${commit}..master | tail -1

I found that Paul's answer gave me the wrong output for older commits (possibly due to merging?), where the primary difference is the --ancestry-path flag.

Upvotes: 6

VonC
VonC

Reputation: 1327304

You can use the gist of the creator for Hudson (now Jenkins) Kohsuke Kawaguchi (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

Put that script in a folder referenced by your $PATH, and simply type:

git children-of <a-commit>

Upvotes: 2

Paul Wagland
Paul Wagland

Reputation: 29129

Based on the answer given in How do I find the next commit in git?, I have another solution that works for me.

Assuming that you want to find the next revision on the "master" branch, then you can do:

git log --reverse ${commit}..master | sed 's/commit //; q'

This also assumes that there is one next revision, but that is kind of assumed by the question anyway.

Upvotes: 4

Dustin
Dustin

Reputation: 91010

It depends on what you're asking. There could be an infinite number of children of the current head in an infinite number of branches, some local, some remote, and many that have been rebased away and are in your repository, but not part of a history you intend to publish.

For a simple case, if you have just done a reset to HEAD^, you can get back the child you just threw away as HEAD@{1}.

Upvotes: 2

u0b34a0f6ae
u0b34a0f6ae

Reputation: 49813

It is strictly not possible to give a good answer -- since git is distributed, most of the children of the commit you ask about might be in repositories that you don't have on your local machine! That's of course a silly answer, but something to think about. Git rarely implements operations that it can't implement correctly.

Upvotes: -2

tanascius
tanascius

Reputation: 53944

You can use gitk ... since there can be more than one child there is probably no easy way like HEAD^.

If you want to undo your whole operation you can use the reflog, too. Use git reflog to find your commit’s pointer, which you can use for the reset command. See here.

Upvotes: 4

Related Questions