Cloud Explainers
Home › Git

The 7 Levels of Git

Git11 min videoPublished Oct 28, 2026

Git for beginners, in 7 levels: commits, undo (restore and revert), branches, merging and merge conflicts, remotes (push, clone, pull), teamwork when your push is rejected (pull --rebase, never --force), and rescuing lost commits with the reflog. One small project, a kettle shop's website, from an empty folder to a team of two, every command a real run.

The 7 Levels of Git: the full diagram
The whole picture, from the video's recap.

The 7 levels of Git

Your website worked yesterday. Today it's broken, and you can't remember what you changed. Git can tell you. git diff shows the one line that changed: the blue kettle's price went from 25 dollars to 2. And one more command, git restore, brings back the version that worked.

Ana's laptop
git diffdiff --git a/index.html b/index.html
index 6dfa3e4..9686fa9 100644
--- a/index.html
+++ b/index.html
@@ -1,3 +1,3 @@
 <h1>Kettle shop</h1>
-<p>Blue kettle: $25</p>
+<p>Blue kettle: $2</p>
 <p>Red kettle: $30</p>
Ana's laptop
git restore index.html

That's one level of Git. In this video we climb all seven, from your first commit to rescuing work you thought was gone: commits, undo, branches, merging, remotes, teamwork, and rescue. We follow one small project, Ana's kettle shop website, and every command is a real run, on a lab of three computers built for this video.

Level 1: Commits

Level one: commits. Git is a version control system. It keeps every saved version of your project, so you can compare them, and bring any of them back. Each saved version is called a commit: a snapshot of all your files at one moment.

So far, the shop's website is one file, index.html, with two kettles and their prices. git init turns the folder into a repository. It creates a hidden folder called dot git, where Git keeps the whole history. git status says index.html is untracked: Git can see the file, but it isn't saving it yet.

Ana's laptop
cat index.html<h1>Kettle shop</h1>
<p>Blue kettle: $25</p>
<p>Red kettle: $30</p>
Ana's laptop
git initInitialized empty Git repository in /home/ana/shop/.git/
Ana's laptop
ls -a.  ..  .git  index.html
Ana's laptop
git statusOn branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	index.html

nothing added to commit but untracked files present (use "git add" to track)

Git has three places. The working folder holds the files you edit. The staging area holds what will go into the next commit. And the repository holds every commit ever made. git add copies a change from the working folder to the staging area. git commit saves everything staged as a new snapshot.

Level 1: Commits: diagram from the video

So Ana runs git add, and status now lists index.html under changes to be committed. git commit, with a short message, saves the snapshot. git log shows it: a long ID, the author, the date, and the message. Git computes that ID from the commit's contents, so every commit gets its own.

Ana's laptop
git add index.html
Ana's laptop
git statusOn branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   index.html
Ana's laptop
git commit -m "Create the shop homepage"[main (root-commit) 299d314] Create the shop homepage
 1 file changed, 3 insertions(+)
 create mode 100644 index.html
Ana's laptop
git logcommit 299d31416d2f47efd2dfad9c1effa6a31f650e3f (HEAD -> main)
Author: Ana <ana@example.com>
Date:   Wed Oct 28 17:03:16 2026 +0000

    Create the shop homepage

Level 2: Undo

Level two: undo. There are two kinds of mistake: a change you haven't committed yet, and a change you already committed. Git has a different command for each.

Back to the broken page from the start. The short form of git status marks index.html with an M: modified since the last commit. git diff compares the file with that commit. A line starting with minus was removed; a line starting with plus was added. Then the restore command throws the change away. Careful: that change is gone for good.

Ana's laptop
git status --short M index.html
Ana's laptop
cat index.html<h1>Kettle shop</h1>
<p>Blue kettle: $25</p>
<p>Red kettle: $30</p>

Now a mistake that did get committed. Ana opens the page in an editor, and adds a sale banner that says every kettle is free. Dash a stages every changed file Git already tracks, so add and commit happen in one step. To undo it: git revert HEAD. HEAD means where you are now: the newest commit on your branch. Dash dash no-edit keeps Git's usual message, so no editor opens. Revert doesn't delete anything. It makes a new commit that does the opposite. The log has all three commits, and the page is back to normal.

Ana's laptop
git commit -am "Add a sale banner"[main bf7e308] Add a sale banner
 1 file changed, 1 insertion(+)
Ana's laptop
git revert HEAD --no-edit[main df18c22] Revert "Add a sale banner"
 Date: Wed Oct 28 17:03:16 2026 +0000
 1 file changed, 1 deletion(-)
Ana's laptop
git log --onelinedf18c22 (HEAD -> main) Revert "Add a sale banner"
bf7e308 Add a sale banner
299d314 Create the shop homepage
Ana's laptop
cat index.html<h1>Kettle shop</h1>
<p>Blue kettle: $25</p>
<p>Red kettle: $30</p>

Here's that history as a picture. Each box is a commit, and each one points back to the commit before it. The mistake is still in the history, and the revert commit undoes it. That matters once other people have copies of the project: revert never rewrites the history they already have.

Level 2: Undo: diagram from the video

Level 3: Branches

Level three: branches. Ana wants to build a login page without breaking the live shop while she works on it. A branch is a separate line of work. And in Git, a branch is just a label that points at one commit, and moves forward with each new commit.

Here's the history so far, with main pointing at the newest commit. git switch dash c login-page creates a new label, and moves Ana onto it. HEAD always marks the branch you're on. Her next commit moves login-page forward. main stays where it was.

Level 3: Branches: diagram from the video

On the laptop: switch dash c, add the new file, and commit. git branch lists the branches; the star marks the one you're on. Now switch back to main, and list the files: login.html is gone. It isn't deleted. It lives on the other branch. Switch back, and it's there again.

Ana's laptop
git switch -c login-pageSwitched to a new branch 'login-page'
Ana's laptop
git add login.html
Ana's laptop
git commit -m "Add a login page"[login-page 84ef6ec] Add a login page
 1 file changed, 4 insertions(+)
 create mode 100644 login.html
Ana's laptop
git branch* login-page
  main
Ana's laptop
git switch mainSwitched to branch 'main'
Ana's laptop
lsindex.html
Ana's laptop
git switch login-pageSwitched to branch 'login-page'
Ana's laptop
lsindex.html  login.html

Level 4: Merging

Level four: merging. The login page works, so it should go into main. Merging brings the commits of one branch into another.

Ana switches to main and runs git merge login-page. Git says fast-forward. main had no new commits of its own, so Git simply moved the main label forward, onto the login page commit. No new commit needed.

Ana's laptop
git switch mainSwitched to branch 'main'
Ana's laptop
git merge login-pageUpdating df18c22..84ef6ec
Fast-forward
 login.html | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 login.html

That's the easy case. The other case is when both branches have new commits. Ana starts a summer-sale branch, and changes the blue kettle's price to 20 dollars. Back on main, she marks the same kettle as in stock. Now the two lines of work have split, and Git has to combine them with a merge: a new commit with two parents.

Level 4: Merging: diagram from the video

Here are those steps for real. And the merge stops: a conflict in index.html. Both branches changed the same line, and Git won't guess which change is right. So it writes both into the file. Above the equals signs is HEAD, Ana's main branch. Below them is the summer sale.

Ana's laptop
git switch -c summer-saleSwitched to a new branch 'summer-sale'
Ana's laptop
git commit -am "Summer sale price for the blue kettle"[summer-sale b613458] Summer sale price for the blue kettle
 1 file changed, 1 insertion(+), 1 deletion(-)
Ana's laptop
git switch mainSwitched to branch 'main'
Ana's laptop
git commit -am "Show that the blue kettle is in stock"[main fdab313] Show that the blue kettle is in stock
 1 file changed, 1 insertion(+), 1 deletion(-)
Ana's laptop
git merge summer-saleAuto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
Ana's laptop
cat index.html<h1>Kettle shop</h1>
<<<<<<< HEAD
<p>Blue kettle: $25 (in stock)</p>
=======
<p>Blue kettle: $20</p>
>>>>>>> summer-sale
<p>Red kettle: $30</p>

Ana opens the file in an editor, keeps both changes on that line, 20 dollars and in stock, and deletes the marker lines. git add tells Git the conflict is solved, and git commit finishes the merge. Then git log draws it as a graph: two lines of work, joined again by the merge commit.

Ana's laptop
cat index.html<h1>Kettle shop</h1>
<p>Blue kettle: $20 (in stock)</p>
<p>Red kettle: $30</p>
Ana's laptop
git add index.html
Ana's laptop
git commit -m "Merge the summer sale"[main 2193693] Merge the summer sale
Ana's laptop
git log --oneline --graph*   2193693 (HEAD -> main) Merge the summer sale
|\
| * b613458 (summer-sale) Summer sale price for the blue kettle
* | fdab313 Show that the blue kettle is in stock
|/
* 84ef6ec (login-page) Add a login page
* df18c22 Revert "Add a sale banner"
* bf7e308 Add a sale banner
* 299d314 Create the shop homepage

Level 5: Remotes

Level five: remotes. So far, everything lives on Ana's laptop. If the laptop dies, the shop's history dies with it. A remote is a copy of the repository on another computer, usually a hosting service like GitHub or GitLab. In our lab, a small Git server plays that part.

git push sends Ana's commits to the server. Then Ben joins the shop. git clone copies the whole repository to his laptop, every commit included. Ben makes a commit, and pushes it. And git pull brings his commit down to Ana.

Level 5: Remotes: diagram from the video

First, Ana tells her repository where the server is. origin is the usual name for your main remote. Then git push dash u origin main. Dash u remembers the pairing, so from now on, plain git push and git pull know where to go.

Ana's laptop
git remote add origin git@git-server:shop.git
Ana's laptop
git push -u origin mainEnumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 10 threads
Compressing objects: 100% (17/17), done.
Writing objects: 100% (19/19), 1.77 KiB | 908.00 KiB/s, done.
Total 19 (delta 2), reused 0 (delta 0), pack-reused 0
To git-server:shop.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

On Ben's laptop: git clone. His log has all 7 of Ana's commits, and the label origin slash main: where the server's main branch was the last time they talked. Ben adds a contact email to the page, commits, and pushes.

Ben's laptop
git clone git@git-server:shop.gitCloning into 'shop'...
remote: Enumerating objects: 19, done.
remote: Counting objects: 100% (19/19), done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 19 (delta 2), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (19/19), done.
Resolving deltas: 100% (2/2), done.
Ben's laptop
cd shop
Ben's laptop
git log --oneline2193693 (HEAD -> main, origin/main, origin/HEAD) Merge the summer sale
fdab313 Show that the blue kettle is in stock
b613458 Summer sale price for the blue kettle
84ef6ec Add a login page
df18c22 Revert "Add a sale banner"
bf7e308 Add a sale banner
299d314 Create the shop homepage
Ben's laptop
git commit -am "Add a contact email"[main 6bd81a2] Add a contact email
 1 file changed, 1 insertion(+)
Ben's laptop
git pushEnumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 10 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 362 bytes | 362.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To git-server:shop.git
   2193693..6bd81a2  main -> main

Back on Ana's laptop: git pull. It's a fast-forward again, and the last line of the page is now Ben's contact email.

Ana's laptop
git pullremote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 342 bytes | 342.00 KiB/s, done.
From git-server:shop
   2193693..6bd81a2  main       -> origin/main
Updating 2193693..6bd81a2
Fast-forward
 index.html | 1 +
 1 file changed, 1 insertion(+)
Ana's laptop
tail -n 1 index.html<p>Questions? hello@kettleshop.example</p>

Level 6: Teamwork

Level six: teamwork, and the day your push is rejected. It happens to every beginner, and it looks scary, but it's Git protecting someone's work.

Ben adds an FAQ page, commits it, and pushes it to the server.

Ben's laptop
git add faq.html
Ben's laptop
git commit -m "Add an FAQ page"[main 73c7b85] Add an FAQ page
 1 file changed, 2 insertions(+)
 create mode 100644 faq.html
Ben's laptop
git pushEnumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 10 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 371 bytes | 371.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To git-server:shop.git
   6bd81a2..73c7b85  main -> main

Meanwhile, Ana commits a log-in button, and runs git push. Rejected. The server has a commit she doesn't have yet: Ben's FAQ page. If her push went through, Ben's commit would be thrown away, so Git refuses, and tells her to pull first.

Ana's laptop
git commit -am "Add a log-in button"[main bf26074] Add a log-in button
 1 file changed, 1 insertion(+)
Ana's laptop
git pushTo git-server:shop.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'git-server:shop.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Here's what happened. Both of them built on the contact email commit. Ben's FAQ page is on the server, and Ana's button is only on her laptop. The history has split. Ana has to bring Ben's commit in first. A merge would join them with a merge commit, like level four. A rebase replays Ana's commit on top of Ben's instead, so the history stays one straight line.

Level 6: Teamwork: diagram from the video

Ana runs git pull, and it stops: you have divergent branches, and you need to say how to combine them. Both sides have new commits, so Git won't choose for you. git pull dash dash rebase picks the rebase. Now the push works, and the log is one straight line: Ana's button on top of Ben's FAQ page.

Ana's laptop
git pullremote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 351 bytes | 351.00 KiB/s, done.
From git-server:shop
   6bd81a2..73c7b85  main       -> origin/main
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.
Ana's laptop
git pull --rebaseSuccessfully rebased and updated refs/heads/main.
Ana's laptop
git pushEnumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 10 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 330 bytes | 330.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
To git-server:shop.git
   73c7b85..b30ff49  main -> main
Ana's laptop
git log --oneline -4b30ff49 (HEAD -> main, origin/main) Add a log-in button
73c7b85 Add an FAQ page
6bd81a2 Add a contact email
2193693 Merge the summer sale

And never fix a rejected push with dash dash force. It replaces the server's branch with yours, and Ben's commit would be thrown away. Pull first, then push. We have a whole video on the safer way to force push.

Level 6: Teamwork: diagram from the video

Level 7: Rescue

Level seven: rescue. Ana wants to undo her last commit. HEAD tilde one means one commit back from HEAD. She types HEAD tilde three instead, three commits back, and adds dash dash hard.

git reset dash dash hard moves the branch back three commits, and makes the files match. The log now ends at the summer sale merge, so her log-in button, Ben's FAQ page and the contact email are gone from the history.

Ana's laptop
git reset --hard HEAD~3HEAD is now at 2193693 Merge the summer sale
Ana's laptop
git log --oneline -42193693 (HEAD -> main) Merge the summer sale
fdab313 Show that the blue kettle is in stock
b613458 (summer-sale) Summer sale price for the blue kettle
84ef6ec (login-page) Add a login page

But Git keeps a second record: the reflog. Every time HEAD moves, after a commit, a switch, a pull, or a reset, Git writes down where it was. Those three commits aren't deleted. Ana's main branch doesn't point at them any more, but the reflog still does. Here they're on the server too, because she pushed them; the reflog also saves commits you never pushed.

Level 7: Rescue: diagram from the video

git reflog lists those moves, newest first. HEAD@{0} is the reset. HEAD@{1} is where Ana was just before it. git reset dash dash hard HEAD@{1} goes back there, and all three commits are back.

Ana's laptop
git reflog -52193693 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
b30ff49 (origin/main) HEAD@{1}: pull --rebase (finish): returning to refs/heads/main
b30ff49 (origin/main) HEAD@{2}: pull --rebase (pick): Add a log-in button
73c7b85 HEAD@{3}: pull --rebase (start): checkout 73c7b85ed3b6f20750b3b5251e0d7baa037ea573
bf26074 HEAD@{4}: commit: Add a log-in button
Ana's laptop
git reset --hard HEAD@{1}HEAD is now at b30ff49 Add a log-in button
Ana's laptop
git log --oneline -4b30ff49 (HEAD -> main, origin/main) Add a log-in button
73c7b85 Add an FAQ page
6bd81a2 Add a contact email
2193693 Merge the summer sale

Two warnings. The reflog only rescues commits: a change you never committed is gone after a hard reset. And the reflog lives only on your own computer, for a limited time. By default, entries for commits that no branch points at are kept for 30 days.

Level 7: Rescue: diagram from the video

Recap

So, the seven levels of Git. One: commits, snapshots of your project. Two: restore throws away a change you haven't committed, and revert undoes a commit. Three: branches, labels for separate lines of work. Four: merge them, and solve conflicts by hand. Five: push, clone and pull, to share through a remote. Six: a rejected push means pull first, never force. Seven: the reflog brings back lost commits.

A question for the comments: which Git mistake has cost you the most time? Tell me in the comments.

Recap: diagram from the video

Every command in this video was a real run, on a lab built for this video.

Sources

Demos: real runs in a small lab of three Docker containers on one machine: a Git server standing in for GitHub, and two laptops. Git is a trademark of the Software Freedom Conservancy; this video is not made or endorsed by the Git project. Watch the full video on YouTube.

More videos