Reputation: 4677
Is there a way to list the branches that have been merged into the current working tree? The following is close:
git branch -r --merged
However, this also includes empty branches. For example, at some point before the latest commit on the current branch, I ran the following:
git checkout -b empty_branch
git push -u origin empty_branch
Now, the first command includes empty_branch in the resulting list. I found a related question asking how to find empty branches in git, but the accepted answer doesn't work for branches without commits. Is there any way to detect commit-less branches in git, or to otherwise filter such branches out from the results of git branch --merged
?
Upvotes: 4
Views: 6961
Reputation: 488003
You're working off at least one faulty assumption about Git. This is not an unreasonable assumption, as Git is weird, but it throws your whole question off a bit.
In Git, branch names don't really mean as much as most people think they do, at least at first. A branch name simply holds the hash ID of some existing commit. There's an underlying entity that people also call "a branch", and it consists of some or all of the commits that are reachable from the tip commit as defined by the branch name. For (much) more about this, see What exactly do we mean by "branch"? and Think Like (a) Git, but let's try for a quick summary:
What Git is really all about is commits. Each commit is identified by its own unique hash ID. No other commit can ever have this hash ID. The contents of a commit include a snapshot—made from the index, rather than from the work-tree, but I will try not to get into all those hairy details here—and include the name and email address of the author of the commit, the same for the committer (usually the same person), and two date-and-time-stamps: one for author, one for committer. They also include the log message the committer supplied at the time he/she/they/pronoun-of-choice made the commit. Perhaps most importantly, the contents of a commit include the raw hash IDs of any commits that should be considered immediate predecessors of that commit.
In other words, each commit has some set of parent hash IDs, most commonly just one hash ID. These parent IDs make commits form backwards-looking chains: from a last commit, we can go back to a previous commit. From there, we can go back one more step, and so on. So if a branch name like master
holds the hash ID of the last commit we should consider to be part of the branch, the remaining commits are that commit's parent(s), the parent(s) of the parent(s), and so on. If the hash ID of the master
tip commit is H
and its parent is G
and G
's parent is F
and so on, we have:
... <-F <-G <-H <-- master
and that's what a branch is: it's either the name, or the series of commits ending at H
, or both: we tend to have to guess what someone means when they say "the master
branch".
What git branch --merged
does is:
HEAD
).HEAD
)HEAD
?1 If so, print the name B. In any case, move on to the next name.So if part of the graph looks like:
I--J <-- feature2
/
...--F--G--H <-- master (HEAD)
\
K--L <-- feature1
the two commits that git branch --merged
will test are J
and L
, as compared to the current commit H
. If J
is an ancestor of H
—but it's not—this would print feature2
. If L
is an ancestor of H
—but it's not—this would print feature1
. Of course, H
is an ancestor of H
, so this does print master
(with a prefix *
to indicate that it is the current branch).
If there's a fourth name that points to any of F
, G
, H
, or any of the commits "behind" F
, git branch --merged
will print that name, too.
I think what you want is to print all names that point to any of the commits that are truly ancestors, and not any names that point directly to commit H
. The simplest way to do that is probably to let git branch --merged
print everything, then remove from the list any name whose hash ID matches that of HEAD
.
To remove such names, use git rev-parse
on each name. Use git rev-parse HEAD
to find the hash ID of the current branch, i.e., the actual hash ID for H
in the drawing above. Then, using git rev-parse
again on each name from git branch --merged
, if the result is the same as the first git rev-parse
, discard that name. Otherwise, keep that name.
(You will have to write a little bit of code for this. If git for-each-ref
could do --not
and combine some boolean expressions, it could probably be done with that, but it doesn't do --not
and combining.)
1The is-ancestor test that Git uses allows equality, i.e., it's ≤ rather than < (or more precisely, precedes-or-equals, ≼, vs ≺). This is also true for git merge --is-ancestor
.
Upvotes: 2