Git Course 0%

HEAD, refs, origin, upstream, tracking, detached HEAD

Beginner Core Git Git CLI ≈ 12 min

Before this lesson

What you will learn

  • That branches and tags are labels pointing at commits, and what HEAD points at
  • What HEAD~1 and HEAD^ mean
  • What origin, origin/main, upstream and tracking branch mean, and how they differ from main
  • What detached HEAD is, how you get there, and how you get out

After this lesson you can

  • I can read the parentheses in git log --decorate and git status and know what each name refers to
  • I am not scared by the detached HEAD message

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

The names Git uses: HEAD, branches, tags, origin A chain of commits C1 to C4 on main, with a feature branch of one commit C5 starting from C3. Labels: the tag v1.2.0 points at C2; origin/main, drawn dashed, points at C3; main points at C4; HEAD points at main; feature/21 points at C5. Annotations show that HEAD~1 is C3 and HEAD~2 is C2. C1 C2 C3 C4 C5 v1.2.0 tag: a name that never moves origin/main where the remote was at your last fetch main HEAD "you are here" feature/21 HEAD HEAD~1 (HEAD^) HEAD~2 HEAD~3 main is 1 ahead of origin/main (C4 not pushed yet). feature/21 branched from C3.
Branches, tags, remote-tracking names and HEAD are all labels pointing at commits.

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/
Terminal
$ git log --oneline --decorate
97773ae (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 trailguide

Read 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.

Terminal
$ cat .git/HEAD
ref: refs/heads/main

Relative 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:

  • origin is the nickname of the remote repository you cloned from: a name for a URL. git push origin main means "push main to that URL". Almost every project has exactly one remote and it is called origin.
  • origin/main is a remote-tracking branch: your local bookmark of where the remote's main was the last time you fetched or pushed. It is not the remote itself; it is a snapshot of your knowledge of it. Only git fetch, git pull and git push move it.
  • Tracking branch (or upstream): a local branch that is linked to a remote branch, so that git status can report ahead/behind, and git pull and git push know where to go without arguments. main tracks origin/main automatically after a clone. A new branch tracks nothing until you git push -u origin <branch> (the -u sets the upstream).
Terminal
$ git branch -a
* main
  remotes/origin/main

git 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:

Terminal
$ git checkout HEAD~1
Note: 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)
Terminal
$ git status
HEAD detached at 0d9eb38
nothing to commit, working tree clean

Nothing 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.
Terminal
$ 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/main as "the remote right now". It is the remote as of your last fetch. git fetch updates it.
  • Committing in detached HEAD and switching away. The commit becomes an orphan. If it happens, git reflog finds it (lesson 12.1); prevention is git switch -c before committing.
  • Expecting git push to 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 HEAD with main. They usually point at the same commit, but HEAD is "where I am" and main is a branch; after git 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.

  1. In the playground (with at least two commits), run git log --oneline --decorate and note where HEAD -> main is.
  2. Run git checkout HEAD~1 and read the message. Run git status.
  3. Run git log --oneline --decorate -1: which labels are shown now?
  4. Return with git switch - and confirm with git 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.

Check yourself

1. In git log --decorate, a line reads 0d9eb38 (tag: v1.2.0, origin/main). Which is true?
2. What is HEAD~1?
3. You see "You are in 'detached HEAD' state". What is the safe way to get back to normal work without losing anything?

Key terms

HEAD Branch main origin Remote