From efe0f9db3695eecd6cf76ad4c4c3f9f08b7f4f50 Mon Sep 17 00:00:00 2001 From: Julia Evans Date: Tue, 6 Oct 2026 16:08:19 -0400 Subject: [PATCH] doc: checkout: rewrite detached HEAD state explanation The current explanation has the following issues: - Says detached HEAD state is useful but doesn't explain why - Takes many paragraphs before explaining what detached HEAD state is - It's common for users to accidentally end up in detached HEAD state, but it doesn't explain why that might happen - One of the UI improvements in `git switch` is to make it harder to detach accidentally, but that isn't advertised. See 7968bef06b (switch: only allow explicit detached HEAD, 2019-03-29) - Too many confusing diagrams Write a new explanation addressing these issues, and put it in a standalone guide so that we can easily reference it from advice ("see `git help detachedhead`"). The result is a shorter guide that covers more material. The framing that "Git considers commits that aren't on a branch/other reference to be garbage" is taken from Steve Klabnik's tutorial https://steveklabnik.github.io/jujutsu-tutorial/branching-merging-and-conflicts/anonymous-branches.html. It's funny and it's consistent with the way Git uses the term "garbage collection". Co-authored-by: Marie Claire LeBlanc Flanagan Signed-off-by: Julia Evans --- Documentation/detached-head.adoc | 43 ++++++++++ Documentation/git-checkout.adoc | 129 +---------------------------- Documentation/gitdetachedhead.adoc | 14 ++++ advice.c | 1 + command-list.txt | 1 + 5 files changed, 60 insertions(+), 128 deletions(-) create mode 100644 Documentation/detached-head.adoc create mode 100644 Documentation/gitdetachedhead.adoc diff --git a/Documentation/detached-head.adoc b/Documentation/detached-head.adoc new file mode 100644 index 00000000000000..1e00712124f9c7 --- /dev/null +++ b/Documentation/detached-head.adoc @@ -0,0 +1,43 @@ +`HEAD` is where Git stores your current branch. `HEAD` can either be: + +1. A branch, which is your current branch. +2. A commit ID, when you don't have a current branch. + This is called "detached HEAD state". + +It can sometimes be useful for `HEAD` to be a commit ID. +For example, it lets you look at an old version of your code +(with `git checkout COMMIT_ID`). + +The only problem is that if you create new commits while in detached +HEAD state, those commits won't be on a branch. This makes those new +commits much harder to find later. Also, Git considers commits that +aren't on any branch (or a tag or other reference) to be garbage. +Git will eventually permanently delete those "garbage" commits during +garbage collection. + +There are 3 main ways you can end up in detached HEAD state +unintentionally: + +1. `git checkout COMMIT_ID`, where `COMMIT_ID` is a commit ID +2. `git checkout v1.3`, where v1.3 is a tag name +3. `git checkout origin/main`, where `origin/main` is + a remote-tracking branch + +Checking out a tag puts you in detached HEAD state because `HEAD` can +only be a branch or a commit, not a tag or any other reference. +So `git checkout TAG` will set HEAD to the commit for that tag. + +The easiest way to avoid accidentally ending up in detached HEAD state +is to use linkgit:git-switch[1] instead of linkgit:git-checkout[1] to +switch branches. `git switch` won't let you detach unless you explicitly +pass the `--detach` argument. + +To get back onto a branch, you can: + +1. Switch to the branch you want to be on, with `git switch BRANCHNAME`. +2. Create a new branch at the current commit, with `git switch -c BRANCHNAME`. + You might want to do this if you've created new commits, so that you can + find the commit later and so that it won't be garbage collected. + +If you create commits in detached HEAD state that aren't on a branch, +you can find them later using linkgit:git-reflog[1]. diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc index 2aefea02287456..4e9e94e24ddd0c 100644 --- a/Documentation/git-checkout.adoc +++ b/Documentation/git-checkout.adoc @@ -376,135 +376,8 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7]. [[DETACHED_HEAD]] DETACHED HEAD ------------- -`HEAD` normally refers to a named branch (e.g. `master`). Meanwhile, each -branch refers to a specific commit. Let's look at a repo with three -commits, one of them tagged, and with branch `master` checked out: ------------- - HEAD (refers to branch 'master') - | - v -a---b---c branch 'master' (refers to commit 'c') - ^ - | - tag 'v2.0' (refers to commit 'b') ------------- - -When a commit is created in this state, the branch is updated to refer to -the new commit. Specifically, `git commit` creates a new commit `d`, whose -parent is commit `c`, and then updates branch `master` to refer to new -commit `d`. `HEAD` still refers to branch `master` and so indirectly now refers -to commit `d`: - ------------- -$ edit; git add; git commit - - HEAD (refers to branch 'master') - | - v -a---b---c---d branch 'master' (refers to commit 'd') - ^ - | - tag 'v2.0' (refers to commit 'b') ------------- - -It is sometimes useful to be able to checkout a commit that is not at -the tip of any named branch, or even to create a new commit that is not -referenced by a named branch. Let's look at what happens when we -checkout commit `b` (here we show two ways this may be done): - ------------- -$ git checkout v2.0 # or -$ git checkout master^^ - - HEAD (refers to commit 'b') - | - v -a---b---c---d branch 'master' (refers to commit 'd') - ^ - | - tag 'v2.0' (refers to commit 'b') ------------- - -Notice that regardless of which checkout command we use, `HEAD` now refers -directly to commit `b`. This is known as being in detached `HEAD` state. -It means simply that `HEAD` refers to a specific commit, as opposed to -referring to a named branch. Let's see what happens when we create a commit: - ------------- -$ edit; git add; git commit - - HEAD (refers to commit 'e') - | - v - e - / -a---b---c---d branch 'master' (refers to commit 'd') - ^ - | - tag 'v2.0' (refers to commit 'b') ------------- - -There is now a new commit `e`, but it is referenced only by `HEAD`. We can -of course add yet another commit in this state: - ------------- -$ edit; git add; git commit - - HEAD (refers to commit 'f') - | - v - e---f - / -a---b---c---d branch 'master' (refers to commit 'd') - ^ - | - tag 'v2.0' (refers to commit 'b') ------------- - -In fact, we can perform all the normal Git operations. But, let's look -at what happens when we then checkout `master`: - ------------- -$ git checkout master - - HEAD (refers to branch 'master') - e---f | - / v -a---b---c---d branch 'master' (refers to commit 'd') - ^ - | - tag 'v2.0' (refers to commit 'b') ------------- - -It is important to realize that at this point nothing refers to commit -`f`. Eventually commit `f` (and by extension commit `e`) will be deleted -by the routine Git garbage collection process, unless we create a reference -before that happens. If we have not yet moved away from commit `f`, -any of these will create a reference to it: - ------------- -$ git checkout -b foo # or "git switch -c foo" <1> -$ git branch foo <2> -$ git tag foo <3> ------------- -<1> creates a new branch `foo`, which refers to commit `f`, and then - updates `HEAD` to refer to branch `foo`. In other words, we'll no longer - be in detached `HEAD` state after this command. -<2> similarly creates a new branch `foo`, which refers to commit `f`, - but leaves `HEAD` detached. -<3> creates a new tag `foo`, which refers to commit `f`, - leaving `HEAD` detached. - -If we have moved away from commit `f`, then we must first recover its object -name (typically by using git reflog), and then we can create a reference to -it. For example, to see the last two commits to which `HEAD` referred, we -can use either of these commands: - ------------- -$ git reflog -2 HEAD # or -$ git log -g -2 HEAD ------------- +include::detached-head.adoc[] [[ARGUMENT_DISAMBIGUATION]] ARGUMENT DISAMBIGUATION diff --git a/Documentation/gitdetachedhead.adoc b/Documentation/gitdetachedhead.adoc new file mode 100644 index 00000000000000..0aeecd159c9317 --- /dev/null +++ b/Documentation/gitdetachedhead.adoc @@ -0,0 +1,14 @@ +gitdetachedhead(7) +=============== + +NAME +---- +gitdetachedhead - How detached HEAD state works + +DESCRIPTION +----------- +include::detached-head.adoc[] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/advice.c b/advice.c index 401d0473917cd9..43f86c2eaf7046 100644 --- a/advice.c +++ b/advice.c @@ -291,6 +291,7 @@ void detach_advice(const char *new_name) "\n" " git switch -\n" "\n" + "Run `git help detachedhead` to learn more.\n" "Turn off this advice by setting config variable advice.detachedHead to false\n\n"); fprintf(stderr, fmt, new_name); diff --git a/command-list.txt b/command-list.txt index 63ae2a67c94055..313689335f07e2 100644 --- a/command-list.txt +++ b/command-list.txt @@ -218,6 +218,7 @@ gitcore-tutorial guide gitcredentials guide gitcvs-migration guide gitdatamodel guide +gitdetachedhead guide gitdiffcore guide giteveryday guide gitfaq guide