HEAD, refs, origin, upstream, tracking, detached HEAD
Beginner Core Git Git CLI
Why this matters
Git's output is full of names: HEAD -> main, origin/main, HEAD~1, detached HEAD. They are not jargon for its own sake; each one is a pointer to a commit, and once you know where each points, git log and git status read like a map with labels on it.
Everything is a label on a commit
A commit is identified by its hash. Nobody wants to remember hashes, so Git keeps refs: named pointers to commits, stored as tiny files inside .git/refs. There are three kinds:
| Kind | Example | Moves? | Lives in |
|---|---|---|---|
| Branch | main, feature/21-difficulty-filter |
Yes: forward to each new commit you make on it | .git/refs/heads/ |
| Tag | v1.2.0 |
No: a permanent name for one commit | .git/refs/tags/ |
| Remote-tracking branch | origin/main |
Only when you fetch or push: it records where the remote's branch was | .git/refs/remotes/origin/ |
$ git log --oneline --decorate97773ae (HEAD -> main) docs: add Windows steps to installation guide (#12)
0d9eb38 (tag: v1.2.0, origin/main) docs: add map question to FAQ (#9)
8fa922e Initial import of trailguideRead the parentheses: main points at 97773ae; HEAD points at main; the tag v1.2.0 and the remote-tracking name origin/main both point at 0d9eb38. So the local main is one commit ahead of the remote, exactly what git status says.
HEAD: where you are
HEAD is the pointer that answers "where am I?". Normally it points at a branch (HEAD -> main), which in turn points at a commit. When you commit, the branch moves, and HEAD follows because it is attached to the branch.
$ cat .git/HEADref: refs/heads/mainRelative names count backwards from HEAD along parent links:
| Name | Means | In the log above |
|---|---|---|
HEAD |
the commit you are on | 97773ae |
HEAD~1 or HEAD^ |
its parent | 0d9eb38 |
HEAD~2 |
the grandparent | 8fa922e |
HEAD~3 |
three back | (does not exist here) |
~ and ^ mean the same for ordinary commits; they differ only on merge commits, which have two parents (^2 picks the second). You can use these names anywhere a hash is accepted: git show HEAD~1, git diff HEAD~2.
origin, origin/main, upstream, tracking
Four names that beginners hear as one:
originis the nickname of the remote repository you cloned from: a name for a URL.git push origin mainmeans "push main to that URL". Almost every project has exactly one remote and it is called origin.origin/mainis a remote-tracking branch: your local bookmark of where the remote'smainwas the last time you fetched or pushed. It is not the remote itself; it is a snapshot of your knowledge of it. Onlygit fetch,git pullandgit pushmove it.- Tracking branch (or upstream): a local branch that is linked to a remote branch, so that
git statuscan report ahead/behind, andgit pullandgit pushknow where to go without arguments.maintracksorigin/mainautomatically after a clone. A new branch tracks nothing until yougit push -u origin <branch>(the-usets the upstream).
$ git branch -a* main
remotes/origin/maingit branch -a lists local branches (the * marks the current one) and the remote-tracking ones under remotes/.
Detached HEAD
Sometimes HEAD points directly at a commit instead of at a branch. That is detached HEAD, and Git warns you loudly when it happens, for example after checking out an old commit to look at it:
$ git checkout HEAD~1Note: switching to 'HEAD~1'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 0d9eb38 docs: add map question to FAQ (#9)$ git statusHEAD detached at 0d9eb38
nothing to commit, working tree cleanNothing is broken. Your files now show the project as it was at that commit. The danger is only this: if you commit while detached, no branch label moves to your new commit, so when you switch back to main the commit is left behind and easy to lose. The message tells you both exits:
- To look and leave:
git switch -(back to the previous branch). - To keep new work:
git switch -c new-branch-name, which creates a branch at the current commit and attaches HEAD to it.
$ git switch -Previous HEAD position was 0d9eb38 docs: add map question to FAQ (#9)
Switched to branch 'main'
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)You also land in detached HEAD when checking out a tag (git checkout v1.2.0) or a remote-tracking branch (git checkout origin/main), and pipelines run in that state on purpose. Lesson 12.5 shows how to rescue commits made while detached.
Common mistakes
- Reading
origin/mainas "the remote right now". It is the remote as of your last fetch.git fetchupdates it. - Committing in detached HEAD and switching away. The commit becomes an orphan. If it happens,
git reflogfinds it (lesson 12.1); prevention isgit switch -cbefore committing. - Expecting
git pushto work on a new branch. The first push needs-u origin <branch>to set the upstream; Git prints the exact command when you forget. - Confusing
HEADwithmain. They usually point at the same commit, but HEAD is "where I am" and main is a branch; aftergit switch feature/21, HEAD points at feature/21.
Try it yourself
Goal: visit an old commit in detached HEAD, read the message, and return safely.
- In the playground (with at least two commits), run
git log --oneline --decorateand note whereHEAD -> mainis. - Run
git checkout HEAD~1and read the message. Rungit status. - Run
git log --oneline --decorate -1: which labels are shown now? - Return with
git switch -and confirm withgit status.
Expected result: step 2 shows the detached HEAD note and HEAD detached at <hash>; step 3 shows the older commit with HEAD alone (no -> main); step 4 says Switched to branch 'main'.
Show solution
In detached state, HEAD points straight at the older commit, so the decoration shows (HEAD) without an arrow to a branch; main still points at the newest commit, which is not in this one-line view. git switch - reattaches HEAD to main. Nothing in the repository changed during the whole visit.