Deleting a merged branch gives an "error: X branch is not fully merged ..."

I was caught in a marinade. And I can't find answers to other SO questions or read the git documentation. I would really appreciate your help.

After merging my branch into Github, I remove the branch from the UI (remote). Periodically, I prune my local branches:

git checkout master git pull origin master git fetch -p git branch -d X Error: error: The branch X is not fully merged. If you are sure you want to delete it, run 'git branch -DX' 

Branch X has been merged. And I can see commits from X when I do git log on master . Why is this happening? I want to use -d rather than -d , as this can lead to a catastrophic removal of branches that really weren't merged.

+5
source share
2 answers

There are several different complex fragments here.

The key question for git branch -d (delete without coercion 1 ) does not mean that the branch is merged? Because this question literally does not answer. Instead, the question is, is the branch merging into <fill something here>?

This test was modified in Git version 1.7 (which means everyone should have one today, but check their version of Git), in commit 99c419c91554e9f60940228006b7d39d42704da7 Junio ​​C Hamano:

branch -d: base "already merged" security on the branch with which it merges

When a branch is marked to be combined with another ref (for example, local 'next', which merges and returns to the original "next", with "branch.next.merge" set to "refs / heads / next"), it makes no sense to base a "branch -d ", a security whose goal is not to lose commits that are not merged with other branches on the current branch. It is much more reasonable to check whether it merges with another branch with which it merges.

Therefore, there are two, and sometimes three, if you look at the above commit questions that you (or Git) should answer to see if -d allowed:

  • What is the ascending name for the branch we are proposing to remove? (If not, see below.)
  • The branch name points to one specific commit (let this T for Tip). The name of the upstream branch also points to one specific commit (let's call it commit U).

    Is T an ancestor of U?

If the answer to question 2 is yes (i.e., T ≤ U), deletion is allowed.

What if there is no upstream? It’s good that where “changed in 1.7” comes: the original test was not T ≤ U, but rather T ≤ HEAD. This test is still present. Right now, if there is no upstream, the HEAD test is used instead of the up test. Meanwhile, for the “transition period," when everyone is adapting to the newfangled behavior of Git 1.7, you can also get a warning or additional explanation when there is an upstream. This warning persists today, in Git 2.10 (almost 7 years later: this is a very long transition period!):

 if ((head_rev != reference_rev) && in_merge_bases(rev, head_rev) != merged) { if (merged) warning("deleting branch ... not yet merged to HEAD."); else warning("not deleting branch ... even though it is merged to HEAD."); } 

(here I cut off a part of the code for display purposes): basically, if HEAD resolves some other commit than the commit we used in the test T ≤, and we would get a different result for T ≤ HEAD than we got for T ≤ U, add an additional warning. (Note that the first part of the test is redundant: if we compare with HEAD due to compatibility with up to 1.7 and missing upstream, we will get the same result if we compare again with HEAD. All we really need is an in_merge_bases test .)

How do you know what happens upstream? Well, there really is an easy command line way:

 $ git rev-parse --abbrev-ref master@ {u} origin/master $ git rev-parse --symbolic-full-name master@ {u} refs/remotes/origin/master 

The --abbrev-ref option gives you Git a typical shortened version, and --symbolic-full-name gives the full name of the link. Both do not work if they run on a branch without setting upstream. Of course, you can also use git branch -vv to view shortened upstream streams (for all branches).

How do you test this whole thing T ≤ U? The git merge-base --is-ancestor does this for shell scripts, therefore:

 $ git merge-base --is-ancestor master origin/master && echo yes || echo no 

will tell you whether master ancestor of origin/master (and for this purpose “points to the same commit” it is considered that “is an ancestor”, i.e. this is the same ≤ test).

Whenever this transition period finally ends, the “use HEAD” test may disappear completely or may be used for branches that do not have an upstream set. In any case, however, the question is always "is the branch to be deleted combined with ____? (Fill in the gap)" and you should see what fills the gap.

The end in the upstream that corresponds to the commit on the branch that you want to delete must be (or at least have in its history) the same commit, by fixing the hash identifier for which the test merges into so that to succeed. If feature/X was merged into origin/develop using the so-called "squash merge" (which is not a merge, although this was done by merging 2 ), this commit identifier will not match, and the "merge into" test will always fail.


1 By the way, with Git 2.3 you can now add --force to git branch -d instead of using git branch -d , although of course -d still works.

2 The difference here is in the “merger” -merge as a noun, which means “committing, which is the fixation of the merger” (which uses the merger as an adjective), and the “merge” -merge as a verb, which means the action combining some sets of changes. The git merge command can:

  • performs fast rewind that does not unite-as-a-verb and does not merge as-noun, so there is no merger at all; or
  • Perform a real merge that combines (verb) some changes to create a merge (noun); or
  • do a "squash merge" that combines (verb) but creates a non-merge (which for some inexplicable reason needs to be done manually).

“Squash merging” occurs only if you request it, while “accelerated unauthorized switching” occurs only if it is possible, and you do not prohibit it.

+5
source

This happens when the contents of the commits are different from the branch you are trying to delete. This may be due to squash fixation or a branch attached to the previous base, when they were combined by someone else on the remote control.

If you are sure that the changes were actually merged (as you said), then branch -D is safe.

Finally,

catastrophic removal of branches

Wrong. Deleting a branch is not a problem. All actions are stored in git reflog , and a commit indicates that the branch has indicated that it should remain for at least 30 days. If you are truly paranoid, you can instead rename the branch. But in practice it’s easier to just remove the branch.

In other words, a branch is just a pointer. Deleting a pointer does not delete the contents. And so you can easily resurrect a branch if you need to.

+3
source

Source: https://habr.com/ru/post/1259523/


All Articles