How can I find out the git branch my current branch is based on?

If git-gc(1) hasn't kicked in and removed the information, create a shell script in your $PATH, name it git-fork-point with the following:

#!/bin/sh

if [ $# -gt 0 ]
then
    target="$1"
else
    target=$(git branch|sed -ne 's,^\* ,,p')
fi

git reflog |sed -nE \
    "s,^([a-f0-9]+).*: checkout: moving from ([^[:space:]]+) to ${target}\$,Forked on commit \1 from \2.,p" |tail -1

It's now useable as git fork-point and defaults to current branch.

This is based on the assumption, that the first occurence of a branch name associated with a checkout, creates a new branch (reflog is in reverse chronological, hence the tail -1).

There's two things wrong with that:

  • re-use of branch name after discovering one used the wrong parent, indeed lists the wrong parent
  • if history is missing this just lists the first time the switch is made.

It works for 80% case though, which is why I use it.


There really is no such notion in git in the first place.

This is perhaps best seen by example. Consider the following commit graph fragment:

        o    <-- branchA
       /
o--o--o
    \
     o--o    <-- branchB

Here, "obviously" branchB comes from branchA. But wait, there's more, there's a bit I left out:

        o    <-- branchA
       /
o--o--o--o   <-- branchC
    \
     o--o    <-- branchB

Now, does branchB comes off branchA or does it come off branchC?

The trickiest part is that any of these branches can be created in any order;1 and furthermore, the labels—the branch names—can also be moved or removed at any time.2 So if you decide that branchB is "based off" branchA, but then someone deletes branchA entirely, you're left with this:

o--o--o--o   <-- branchC
    \
     o--o    <-- branchB

and now branchB is clearly based off branchC, not branchA.

[Edit: if you want to identify the specific commit where two branches first "split apart", use git merge-base. Having found that commit, you can see what other branch names might also be interesting with git branch --contains, and so on. The general rule here is that the commit graph is all you really have: labels like branch names are only good until you or someone else changes them later. Tag labels should generally stay where they are but even those can be moved, it's just generally more of a pain.]


1Well, almost any: the parent commit(s) of a new commit must exist before you can create the child commits, unless you have a way of breaking the SHA-1 crypographic hash.

2Removing a label means that the commits found by starting at that label and working "backwards" (left-ward, and maybe up or down as well as long as you keep moving leftward) in these drawings) become "unreachable", unless they're found by starting from some other label. Unreachable commits in the commit-graph are eventually3 removed entirely, but typically you have about 30 days to get them back.

3This is achieved with git's "reflogs". Each reflog entry works like a regular label, in terms of making a commit "reachable" and therefore retaining it. It's actually the reflog entries that are expired.

Tags:

Branch

Git