In normal circumstances, HEAD
either points to a SHA1 (in which case it's called detached) or it points to an existing branch reference (in which case the named branch is considered to be checked out).
When you check out new-branch
(HEAD
points to refs/heads/new-branch
) and then somehow manage to delete the new-branch
branch, Git simply deletes the branch's ref file (.git/refs/heads/new-branch
) and the branch's reflog file (.git/logs/refs/heads/new-branch
). Git does not delete HEAD
, nor does it update it to point somewhere else (such as the SHA1 that new-branch
used to point to), because there shouldn't be a need—you're not supposed to be able to delete the current branch. So HEAD
still references the now-deleted branch, which means that HEAD
no longer points to a valid commit.
If you then do git checkout -f master
, Git updates HEAD
to point to refs/heads/master
, a new entry is added to HEAD
's reflog file (.git/logs/HEAD
), the files are checked out, and the index is updated. All of this is normal—this is what Git always does when you check out another branch.
The issue you encountered arises from how the reflog file is updated and how git reflog
processes the updated reflog file. Each reflog entry contains a "from" and "to" SHA1. When you switch from the non-existent new-branch
branch to master
, Git doesn't know what the "from" SHA1 is. Rather than error out, it uses the all-zeros SHA1 (0000000000000000000000000000000000000000
). The all-zeros SHA1 is also used when a ref is created, so this most recent reflog entry makes it look like HEAD
was just created, when in fact it was never deleted. Apparently the git reflog
porcelain command stops walking the reflog when it encounters the all-zeros SHA1 even if there are more entries, which is why git reflog
only prints one entry.
The following illustrates this:
$ git init test
Initialized empty Git repository in /home/example/test/.git/
$ cd test
$ echo hi >file1
$ git add file1
$ git commit -m "test commit 1"
[master (root-commit) 3c79ff8] test commit 1
1 file changed, 1 insertion(+)
create mode 100644 file1
$ git checkout -b new-branch
Switched to a new branch 'new-branch'
$ echo test2 >file2
$ git add file2
$ git commit -m "test commit 2"
[new-branch f828d50] test commit 2
1 file changed, 1 insertion(+)
create mode 100644 file2
$ cat .git/HEAD
ref: refs/heads/new-branch
$ cat .git/refs/heads/new-branch
f828d50ce633918f2fcaaaad5a52ac1ffa1c81b1
$ git update-ref -d refs/heads/new-branch
$ cat .git/HEAD
ref: refs/heads/new-branch
$ cat .git/refs/heads/new-branch
cat: .git/refs/heads/new-branch: No such file or directory
$ cat .git/logs/HEAD
0000000000000000000000000000000000000000 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400 commit (initial): test commit 1
3c79ff8fc5a55d7c143765b7f749db4dd8526266 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400 checkout: moving from master to new-branch
3c79ff8fc5a55d7c143765b7f749db4dd8526266 f828d50ce633918f2fcaaaad5a52ac1ffa1c81b1 Your Name <email@example.com> 1411018898 -0400 commit: test commit 2
$ git checkout -f master
Switched to branch 'master'
$ cat .git/logs/HEAD
0000000000000000000000000000000000000000 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400 commit (initial): test commit 1
3c79ff8fc5a55d7c143765b7f749db4dd8526266 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400 checkout: moving from master to new-branch
3c79ff8fc5a55d7c143765b7f749db4dd8526266 f828d50ce633918f2fcaaaad5a52ac1ffa1c81b1 Your Name <email@example.com> 1411018898 -0400 commit: test commit 2
0000000000000000000000000000000000000000 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400 checkout: moving from new-branch to master
$ git reflog
3c79ff8 HEAD@{0}: checkout: moving from new-branch to master
As you can see, HEAD
's reflog still has all of the old entries—they're just not shown by git reflog
. I consider this to be a bug in Git.
Side note: When you delete a ref, the corresponding log is also deleted. I consider this to be a bug, as there's no way to completely undo an accidental ref deletion unless you have a backup of the log.