I put the short answer in a comment (just create the branches again, or later) but there's a fundamental error in the question:
... this will also pick up branches that are created from current one but has no commits on them.
Those branches do contain commits. They just contain the same commits as your current branch.
Remember, a branch name is simply a name—whatever string you like, with some minor constraints like "no consecutive dots" and "no at signs" and so on; see the git check-ref-format
documentation)—that contains one hash ID. That one hash ID is the commit that is the tip of the branch. This is the definition of a branch name in Git: it is
- a name
- containing a hash ID
- of a commit
and that commit, by virtue of having that name that identifies it, is the tip of that branch.
If more than one name hold the same hash ID, that one commit is the tip of multiple branches, all at the same time.
Meanwhile every commit holds a list of hash IDs—usually just one entry long—which are the parent commits of that one commit. These hash IDs are part of the commit itself. The hash IDs are, in a sense, the "true names" of each commit. If we draw the commits, replacing actual hash IDs with single uppercase letters, we get a picture like this:
... <-F <-G <-H <--master
The name master
holds the hash ID of commit H
. Commit H
holds the hash ID of earlier commit G
. G
holds the hash ID of earlier commit F
, which holds another hash ID, and so on. So the name master
points to H
, and H
points to G
which points to F
and so on.
If we have multiple names that point to H
, then all those commits—up through H
—are on all those branches:
...--F--G--H <-- master, develop
\
I--J <-- feature/short
Meanwhile the branch name feature/short
points to commit J
, which points back to I
, which points to H
, which points to G
and so on. So commits through H
are on all three branches, while commits I
and J
are—currently—only on feature/short
.
Git will move a branch name. If we make a new name temp
that points to H
we get:
...--F--G--H <-- master, develop, temp
\
I--J <-- feature/short
If we now git checkout temp
and then make a new commit, the new commit gets a new hash ID—but we'll just call it K
here—and the name temp
moves to point to the new commit:
K <-- temp
/
...--F--G--H <-- master, develop
\
I--J <-- feature/short
To create a branch, we pick any existing commit and make a new name that points to that existing commit. To delete a branch, we simply erase the name. Let's delete temp
(we'll have to git checkout
something else first):
K
/
...--F--G--H <-- master, develop
\
I--J <-- feature/short
Commit K
still exists. You just can't find it now. Git will eventually reclaim it once there is really, truly no way to find it—but Git generally will hang on to some hidden ways to find K
, in case you want it back, typically for at least a month or so. As long as those hidden ways work to find K
, commit K
will stick around. But with no obvious way to find it, we'll just stop drawing it entirely.
Now let's move the name develop
to point to commit J
, perhaps by doing:
git checkout develop && git merge --ff-only feature/short
The picture now looks like this:
...--F--G--H <-- master
\
I--J <-- feature/short, develop (HEAD)
The (HEAD)
annotation is just to remember which branch we have checked-out right now.
What git branch --merged
does is look for names where:
- the given branch name points to a commit (which is always), and
- the commit to which that name points is reachable from your current
HEAD
So if you're on develop
, and commit K
does not exist, the names that point to commits that you can get to by starting at J
and working backwards are:
master
, because H
is reachable by starting at J
and going back two steps, and
feature/short
, because J
is reachable by starting at J
and doing nothing at all.
Should feature/short
move forward one step like this:
...--F--G--H <-- master
\
I--J <-- develop (HEAD)
\
L <-- feature/short
then git branch --merged
will stop listing feature/short
, because we cannot reach commit L
by starting at J
and working backwards. But adding another commit to develop
has no effect on git branch --merged
output:
...--F--G--H <-- master
\
I--J--M <-- develop (HEAD)
\
L <-- feature/short
as we can start at M
, count back three steps to J
, I
, and then H
, and we have arrived at the commit to which master
points, so git branch --merged
still lists master
.
You can, if you wish, get your list of branches where:
- the name points to a commit that is reachable from
HEAD
and
- the name points to a commit that is not the commit that
HEAD
identifies
but this will take multiple steps: first, get the list of reachables via git branch --merged
, then compare the result of:
git rev-parse HEAD
(which is the actual hash ID of the current commit) to the result of git rev-parse
on each branch name (which is the tip commit of each of those branches).
Beware of using this on remote-tracking names like origin/master
Note that all of these results are only valid for your names. If you apply them to remote-tracking names, they tell you about those remote-tracking names as they exist right now in your repository. If the branch names in some other Git are highly active, you'll need to run git fetch
to update your remote-tracking names first. That way your origin/master
, origin/feature
, and so on will be in sync with the other Git.
If that other Git is extremely active, however, in the time it takes you to run git branch -r --merged
and do some more checking, your own remote-tracking names may become out of date. You'll need to run git fetch
again to update them, and try again. By the time you get a result, they may be out of date—and so on. Unless you control the other repository, and can make people stop updating it, you're racing against them to get an accurate picture here. This is generally unwise.