I have a git repo with mainline
(equivalent to master
) and some local feature branches. For example:
$ git branch
* mainline
feature1
feature2
feature3
When I do the following, I am able to squash merge all of my edits in a feature branch into one commit to mainline
:
$ git checkout mainline
$ git pull
$ git checkout feature1
$ git rebase mainline
$ git checkout mainline
$ git merge --squash feature1
$ git commit
$ git push
My question is, at this point, when I try to delete the feature1
branch, it tells me it is not fully merged:
$ git branch -d feature1
error: The branch 'feature1' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature1'.
What causes this error? I thought git merge --squash feature1
merged feature1
into mainline
.
This happens because Git doesn't know that the squash merge is "equivalent to" the various branch-specific commits. You must forcibly delete the branch, with
git branch -D
instead ofgit branch -d
.(The rest of this is merely about why this is the case.)
Draw the commit graph
Let's draw (part of) the commit graph (this step is appropriate for so many things in Git...). In fact, let's step back one more step, so that we start before your
git rebase
, with something like this:A branch name, like
mainline
orfeature1
, points only to one specific commit. That commit points back (leftward) to a previous commit, and so on, and it's these backward pointers that form the actual branches.The top row of commits, all just called
o
here, are kind of boring, so we didn't give them letter-names. The bottom row of commits,A-B-C
, are only on branchfeature1
.C
is the newest such commit; it leads back toB
, which leads back toA
, which leads back to one of the boringo
commits. (As an aside: the leftmosto
commit, along with all earlier commits in the...
section, is on both branches.)When you ran
git rebase
, the threeA-B-C
commits were copied to new commits appended to the tip ofmainline
, giving us:The new
A'-B'-C'
commits are mostly the same as the original three, but they are moved in the graph. (Note that all three boringo
commits are on both branches now.) Abandoning the original three means that Git usually doesn't have to compare the copies to the originals. (If the originals had been reachable by some other name—a branch that appended to the oldfeature1
, for instance—Git can figure this out, at least in most cases. The precise details of how Git figures this out are not particularly important here.)Anyway, now you go on to run
git checkout mainline; git merge --squash feature1
. This makes one new commit that is a "squash copy" of the three—or however many—commits that are onfeature1
. I will stop drawing the old abandoned ones, and call the new squash-commitS
for Squash:"Delete safety" is determined entirely by commit history
When you ask Git to delete
feature1
, it performs a safety check: "isfeature1
merged intomainline
?" This "is merged into" test is based purely on the graph connectivity. The namemainline
points to commitS
; commitS
points back to the first boringo
commit, which leads back to more boringo
commits. CommitC'
, the tip offeature1
, is not reachable fromS
: we're not allowed to move rightward, only leftward.Contrast this with making a "normal" merge
M
:Using the same test—"is the tip commit of
feature1
reachable from the tip commit ofmainline
?"—the answer is now yes, because commitM
has a down-and-left link to commitC'
. (CommitC'
is, in Git internal parlance, the second parent of merge commitM
.)Since squash merges are not actually merges, though, there is no connection from
S
back toC'
.Again, Git doesn't even try to see if
S
is "the same as"A'
,B'
, orC'
. If it did, though, it would say "not the same", becauseS
is only the same as the sum of all three commits. The only way to getS
to match the squashed commits is to have only one such commit (and in this case, there's no need to squash in the first place).