You have a couple of good answers so far, but I think it's worth pointing out that given your question formulation, the command would have to print multiple branch names for commits B
and C
for instance.
If you are willing to live within the limitations of Git reflogs (essentially, 90 days by default), there is an alternative forumlation that I think will do what you want.
Let me re-draw your graph the way I prefer, which is left (earlier commits) to right (later commits) with names at the right hand edges:
D--F--G--I <-- branch1
/
A--B--C--E--H--M--N <-- master
\
J--K--L--O <-- branch2
When you created branch1
, you probably did so like this:
$ git checkout master # which resolves to commit `C`, because ...
... because at this point the graph looks like this:
A--B--C <-- master (HEAD)
Now you run:
$ git checkout -b branch1
which makes the graph look like this:
A--B--C <-- master, branch1 (HEAD)
That is, now two branch-names exist, both of which identify commit C
. The name HEAD
is currently attached to branch1
, due to the use of git checkout -b
.
At this point you:
... do some work ...
$ git commit -m message # which creates commit `D`
giving:
D <-- branch1 (HEAD)
/
A--B--C <-- master
As you make more commits, they are added wherever HEAD
is attached, so this causes branch1
to accumulate commits.
Your question includes this phrasing:
Is there any method to only list the branches that have the given commit at or after the branch creation?
which falls afoul of two problems: branches don't really have a "creation", and commits have—or contain, really—every commit that is reachable from their tip backwards.
What we could instead ask is: For each commit, which branch reflogs have entries that refer directly to that commit? Assuming no reflog value has expired and been removed, we will find that, at this point—where branch1
exists and points to commit D
—commit D
is in only branch1
's reflog, commit C
is in both branch1
and master
's reflog, commit B
is only in master
's reflog, and commit A
is only in master
's reflog.
Later, when we have commits A
through O
inclusive, we will find that H
is like C
: it's in two different reflogs, for master
and branch2
. The remaining commits are in only one reflog.
Hence we would start by asking the reflog-based question. Then, for commits that appear in multiple reflogs, we would declare that such a commit is "more in tune with" whichever reflog was created later. That's because the reflog entry that has C
as a commit on master
was created at the time commit C
itself was created, so that reflog entry has an earlier date; but when branch1
was created, pointing to commit C
, that, in effect, "takes" commit C
into branch1
.
Note that this formulation also has a flaw—besides, of course, the one that occurs when the reflog entry expires. Suppose we now run:
git checkout -b branch3 <hash-of-C>
and make a new commit on branch3
? Our graph is now:
,--P <-- branch3 (HEAD)
/
| D--F--G--I <-- branch1
|/
A--B--C--E--H--M--N <-- master
\
J--K--L--O <-- branch2
Commit C
is now in three reflogs: one for master
, one for branch1
, and one for branch3
. (The full reflog for branch3
consists of "commit P
, then commit C
".) The latest is branch3
: by attaching C
as the base of branch3
we've effectively "stolen" commit C
for branch3
.
If you dislike this, the obvious alternative is to assign commit C
to the earliest reflog that contains it, i.e., to master
. But now:
git who-owns <hash-of-C>
says master
, not branch1
, which is not what you asked for. It's easy enough to do either one though; here's the untested pseudocode framework to get you started:
#! /bin/sh
# git-who-owns: pick a branch to "own" a commit
. git-sh-setup
case $# in
0) die "usage: git who-owns <commit> ...";;
esac
who-owns() {
local b hash
hash=$(git rev-parse $1^{commit}) || return 1
for b in $(git for-each-ref --format='%(refname:short) refs/heads); do
git reflog --no-abbrev $b | awk '$1 == "$hash"'
done
}
status=0
for arg do who-owns "$arg" || status=1; done
exit $status
If this script is in your $PATH
as git-who-owns
, running git who-owns <commit-specifier>
will print out all the matching reflogs.
There's another bigger flaw: if a commit could belong to one or more branches, but was brought in via an en-masse operations like git fetch
followed by git merge
, its hash ID will not be in any reflog. You can hybridize the above along with --contains
operations to assign such "in-between" commits to branches.
(The idea in general is not terribly workable, though. I would not spend a lot of time on this. Instead, drop a marker, such as a tag, on a boundary commit when you want to generate bounded lists of commits. Then git rev-list branch-name ^marker
will get you the commit IDs you want. This is more conventionally spelled git rev-list marker..branch-name
, e.g., git log orgin/master..master
.)