13

I have been looking for an answer to this question for a while now, but am still not entirely sure about its answer. Most information I found about deleting branches was no more than a copy of what the manual says (for example here on SO). I think a core problem is that I do not know precisely what a branch in Git is (even though there are many articles that claim to explain that). The only somewhat useful information I found was in this SO answer and the docs installed with git.

My question:

When I run git branch -d BRANCH_NAME,

  1. what happens under the hood?
  2. what changes externally, i.e. how does my interaction with the repository change?
  3. can any of these changes be considered a change in the history?

and additionally the same subquestions for git branch -D BRANCH_NAME.

My current understanding:

First my perception of a branch: depending on the context, the term branch refers to either a pointer to a certain commit (strictly called the branch head), or the list of commits leading up to that

What I think happens (but am quite unsure about), for git branch -d BRANCH_NAME:

  1. the pointer to the branch head is removed, and no more
  2. I can't see the branch in any listing anymore and I cannot switch to it or branch from it anymore (although I guess I could create a new branch at that commit, to effectively achieve those things)
  3. probably not: the commits are still there, they are only not labeled with the name of the branch anymore?

What I think happens, for git branch -D BRANCH_NAME:

  1. the pointer to the branch head is removed, and any commits that are not on an other branch as well are deleted
  2. I can't see the branch in any listing anymore and I cannot switch to it or branch from it anymore, or retrieve the code in any way
  3. yes: the commits in the branch are lost
Community
  • 1
  • 1
Oebele
  • 581
  • 1
  • 4
  • 17

2 Answers2

14

Regardless of whether you delete with git branch -d or git branch -D, git removes not the commits but the branch ref only. Read on to see what that means.

First, we will set up a simple demo history.

$ touch initial ; git add initial ; git commit -m 'Initial commit'
[master (root-commit) 2182bb2] Initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 initial

$ git checkout -b mybranch
Switched to a new branch 'mybranch'

At this point, both master and mybranch are pointing to the same commit, which we can verify in at least two ways.

$ git lola
* 2182bb2 (HEAD -> mybranch, master) Initial commit

Note that git lola is a non-standard but highly useful alias, equivalent to

$ git log --graph --decorate --pretty=oneline --abbrev-commit --all
* 2182bb2 (HEAD -> mybranch, master) Initial commit

The other way we will look at after creating a new commit on mybranch.

$ touch mybranch ; git add mybranch ; git commit -m 'My branch'
[mybranch 7143aa4] My branch
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 mybranch

After doing this, we do indeed have multiple commits on multiple branches.

$ git lola
* 7143aa4 (HEAD -> mybranch) My branch
* 2182bb2 (master) Initial commit

We can now peek at part of how git implements this under the hood.

$ ls -R .git/refs
.git/refs:
heads  tags

.git/refs/heads:
master  mybranch

.git/refs/tags:

Curious that there are files with the same names as our branches. Looking inside them, we see

$ cat .git/refs/heads/master .git/refs/heads/mybranch
2182bb2d5a0a7f57d0b74e95d37e208dac41f95b
7143aa477735382e7a0ed11c9e4b66c1f27583df

So git implements refs as files in a certain location whose names match the branch names and that contain SHA1 hashes of certain commits. Observe that the abbreviated hash in the output from git lola (2182bb2) is the leading prefix of the cat output above.

Think of git refs as simple pointers that give human readable names to specific commits in your repository’s history.

Now if we switch back to master and zap mybranch

$ git checkout master ; git branch -D mybranch
Switched to branch 'master'
Deleted branch mybranch (was 7143aa4).

we see that the ref is gone

$ ls -R .git/refs
.git/refs:
heads  tags

.git/refs/heads:
master

.git/refs/tags:

but the commit is still there.

$ git show --pretty=oneline 7143aa4
7143aa477735382e7a0ed11c9e4b66c1f27583df My branch
diff --git a/mybranch b/mybranch
new file mode 100644
index 0000000..e69de29

If you want mybranch back, you need only run

$ git checkout -b mybranch 7143aa4
Switched to a new branch 'mybranch'

or

$ git branch mybranch 7143aa4

depending on, as the difference in their respective outputs indicates, whether you want to switch to the branch or not. In the latter case, where you stayed on your current branch, git lola looks like

$ git lola
* 7143aa4 (mybranch) My branch
* 2182bb2 (HEAD -> master) Initial commit

Yes, your commits hang around for a short time even after you delete the pointers that keep them alive. This can be tremendously useful in cases of accidental deletion. See also git reflog and git gc.


Note that the SHA1 hashes will be different in your repository because your name and email address, at least, will be different from what I used.

For completeness, the difference between -d and -D is the lowercase version is slightly safer.

-d
--delete

Delete a branch. The branch must be fully merged in its upstream branch, or in HEAD if no upstream was set with --track or --set-upstream.

-D

Shortcut for --delete --force.

-f
--force

Reset branchname to startpoint if branchname exists already. Without -f git branch refuses to change an existing branch. In combination with -d (or --delete), allow deleting the branch irrespective of its merged status …

Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
  • 2
    I can't give a better advice than taking one hour to explore all the folders and open all the files in the .git directory to easily understand how it works. It's really simple and well done. To explore the 'objects' subdirectory, use `git cat-file -p` followed by a sha1. It really help in every day use to understand how git works... – Philippe Jan 19 '16 at 23:31
  • It should be noted that git won't keep the commits forever. Commits only survive indefinitely if there are some how referenced (either directly or indirectly). – PyRulez Jan 20 '16 at 01:05
3

The commits are still there after branch removal, until the next garbage collection. git branch -d (and -D) print the abbreviated commit hash, you can use it as the argument to git log or git checkout or git branch, which gives you the possibility to restore the deleted branch.

The only difference between -d and -D is that git branch -d will not let you delete unmerged branch (i.e. potentially lose commits).