Learn the theory
Git Commands, HEAD & Refs (Visual): how it works
5 min read · Updated
Git feels mysterious until you see its three moving parts: three areas that files travel between, a graph of commits, and a handful of pointers — branches, tags and HEAD — that label spots on that graph. Every Git command either moves files between the areas, adds commits to the graph, or moves a pointer. This lab lets you run the commands and watch exactly which of those three things happens.
In short
- Files flow working directory → staging area (index) → repository with
git addandgit commit. - A commit is an immutable snapshot with a pointer to its parent(s). History is a graph of commits.
- A branch is just a movable label on a commit. HEAD is a pointer to the branch (or commit) you're on.
- Committing moves the current branch label forward — and HEAD follows because it points at the branch.
resetmoves a label (and optionally the index/files);revertadds a new undoing commit;fetchonly movesorigin/*labels.
The three areas
Git keeps your work in three places. Knowing which one a command touches explains almost every Git surprise.
| Area | What it is | Moves with |
|---|---|---|
| Working directory | The actual files on disk that you edit. | Your editor, git switch, git restore, git reset --hard |
| Staging area (index) | A draft of your next commit: exactly what git commit will record. | git add (files in), git reset (files out) |
| Repository | The permanent database of commits (the .git folder). | git commit, git fetch, git push |
edit app.js # working directory changed (not staged)
git add app.js # copy it into the staging area
git commit -m "Add" # snapshot the staging area as a new commitCommits form a graph
Each commit stores a full snapshot of your files, a message, and a pointer to its parent commit. The commit's ID is a hash of all that content, so a commit can never be changed — only replaced by a new one. In the lab, arrows run from each commit to its parent: history reads right to left.
A normal commit has one parent. A merge commit has two. The first commit of a repository has none.
Branches, tags and HEAD are just pointers
A ref is a named pointer to a commit, stored as a tiny file containing a commit ID.
| Ref | Behavior | Example |
|---|---|---|
| Branch | Moves forward automatically when you commit on it. | main, feature |
| Tag | Never moves — marks a fixed point such as a release. | v1.0 |
| Remote-tracking branch | Your local bookmark of where the remote's branch was at the last fetch. Only fetch/push/pull move it. | origin/main |
| HEAD | Points at the branch you have checked out (or directly at a commit when detached). New commits get HEAD's current commit as their parent. | HEAD -> main |
Branching and merging
git branch topiccreates a label at the current commit. It doesn't switch.git switch topic(orcheckout) moves HEAD to that branch and rewrites your working files.git merge topicbrings its commits into your branch: a fast-forward if your branch has nothing new (just slide the label), otherwise a merge commit with two parents.git rebase mainreplays your branch's commits on top ofmain, creating new commits with new IDs.
For a deeper comparison of merge, rebase and squash, try the Git Merge vs Rebase lab.
Undoing things: reset, revert, restore
| Command | Moves the branch? | Changes the index? | Changes your files? |
|---|---|---|---|
git reset --soft HEAD~1 | Yes | No | No — undone changes stay staged |
git reset --mixed HEAD~1 (default) | Yes | Yes (reset to the target commit) | No — changes stay but unstaged |
git reset --hard HEAD~1 | Yes | Yes | Yes — uncommitted work is lost |
git revert <commit> | No (adds a commit) | — | A new commit undoes the changes |
Commits you reset away aren't deleted immediately: git reflog lists where HEAD has been, so you can recover them for a few weeks.
Remotes: push, fetch and pull
git pushuploads commits the remote lacks and moves the remote's branch label (and yourorigin/*bookmark). It's rejected if it isn't a fast-forward.git fetchdownloads new commits and moves only yourorigin/*labels. It never touches your branches or files, so it's always safe.git pull=git fetch+git merge origin/<branch>(or a rebase with--rebase).
git fetch # see what teammates did: origin/main moves
git log main..origin/main # inspect it before integrating
git merge origin/main # now integrateFrequently asked questions
- What is HEAD in Git?
- HEAD is a pointer to your current position: usually to a branch (which in turn points to a commit). When you commit, the branch HEAD points at moves forward to the new commit.
- Is a Git branch a copy of the code?
- No. A branch is just a small pointer (a label) to one commit. Creating a branch is instant and costs almost nothing; commits aren't copied.
- What is the difference between git reset --soft, --mixed and --hard?
- All three move the branch to another commit.
--softleaves the staging area and files alone,--mixedalso resets the staging area but keeps your files, and--hardresets the staging area and overwrites your working files. - What is the difference between git reset and git revert?
resetmoves a branch pointer, effectively rewriting history.revertcreates a new commit that undoes an earlier one and leaves history intact, which is safe for shared branches.- What is the difference between git fetch and git pull?
git fetchonly downloads new commits and updates yourorigin/*references.git pullfetches and then merges (or rebases) them into your current branch.- How do I recover a commit after reset --hard?
- Run
git reflogto find the commit ID where HEAD used to be, thengit reset --hard <id>orgit switch -c rescue <id>. Uncommitted changes that were never committed cannot be recovered.