Module 1 — What Git is and why it exists
Git data model and four file states. ~5 minutes.
Snapshots, not deltas
When you make a commit, Git stores a full snapshot of the entire project at that moment. Not a diff from the previous commit — every file in full (with deduplication inside the store, so it actually takes little space).
History is a tape of snapshots:
commit C → index.html app.js README.md (full snapshot) ↑ commit B → index.html app.js (full snapshot, app.js was different) ↑ commit A → index.html (full snapshot, app.js did not exist yet)
Analogy: a photo album, not an edit diary. Rolling back to any version is simply “pull that snapshot out.”
Four file states
The same file in Git can be in one of four states. Understanding the transitions between them is the key to everything else.
1. Untracked — Git does not know about the file
The file sits in the project folder but has never been added to Git. git status will show it in the red “Untracked files” section.
Example: you created notes.txt and have not yet run git add.
2. Modified — Git knows the file and sees changes
The file was committed at some point; you changed it, but the changes are not in the stage yet. git status will show it under “Changes not staged for commit”.
Example: you edited app.py, which was in the last commit.
3. Staged — in the index, ready to commit
You ran git add: the changes are parked in the stage (also called the index). This is the draft of the next commit. In git status — the “Changes to be committed” section.
Example: git add app.py succeeded; git commit has not run yet.
4. Committed — recorded in history
After git commit the snapshot goes into the repo history. From that moment the state is locked in and you can retrieve it whenever you want.
Lifecycle of a single change:
Untracked → (git add) → Staged → (git commit) → Committed
↑
edited the file → Modified → (git add) → ...
Local and remote
All of Git history lives in a hidden .git/ folder inside the project — that is the local repository. No internet is needed for basic work: commits, branches, history — all local.
A remote repository is a copy of your repo on a server (GitHub, GitLab, Bitbucket, or your own). You talk to it with git push (send local commits) and git pull (bring in other commits). Details in Module 5.
Check yourself
todo.md and saved it. You have not run git add yet. What state is it in?app.py was in the last commit. You edited it, saved, but did not run git add. What state is the file in?git add app.py. It succeeded; you have not run git commit yet. What state is the file in now?Module 2 — Setup and creating a repository
First Git configuration, git init, .gitignore. ~7 minutes.
First setup (once after installing Git)
Git needs to know who is making commits. These are global settings — set them once, and they apply to every project on this machine:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
Check what was recorded:
git config --global --list
Creating a repository: init vs clone
Two ways to start using Git in a project:
git init — a new repository from an existing folder
Go into the project folder (it may already have your code, or nothing yet) and run:
git init
.git/ folder — that is where the entire history will live. Git does not touch the project files.git clone — download an existing repository
git clone <url>
.gitignore — what Git should ignore
Not everything belongs in the repo: build artifacts, caches, secrets, virtual environments. The list of what Git should skip lives in a text file .gitignore at the project root. The file itself is committed.
# dependencies / caches node_modules/ __pycache__/ *.pyc # build dist/ build/ # secrets — never commit .env .env.local # logs *.log # IDE .vscode/ .idea/
One pattern per line. Glob is supported: *.log, build/**. Lines with # are comments. Useful tip: add .gitignore at the very beginning, before the first commit — otherwise junk can slip into history.
Ctrl+Shift+G (or the branching icon in the left sidebar). If there is no repo yet — you will see an Initialize Repository button; it simply runs git init. If a repo exists — you will see a panel with current changes: to the right of each file, + (stage) and ↩ (discard).Try it in the trainer
This terminal supports basic git commands. Two files are already sitting in it: README.md and index.html — but without git so far. Do this in order:
git init— create the repogit status— see what Git sees (there are untracked files already there)git add .— add everything to the stagegit status— notice how the files moved from “Untracked” to “Changes to be committed”git commit -m "init"— lock in the first snapshot in historygit log --oneline— confirm the commit appeared
Module 3 — The basic working cycle: stage → commit
The main everyday loop. ~8 minutes.
Three commands you will type constantly
git status
git add <file>
git add a.py b.py.git add .
git status first.git commit -m "Add login button"
-m flag is a short message. Without it Git opens an editor.Ctrl+Enter) → commit. After a commit the change list clears.Good commit messages: the 50/72 rule
A commit message is a note to your future self and to colleagues. Six months later, when something breaks, you will search “when did we change this and why” via git log. Message quality == search quality.
Rules
- Title ≤ 50 characters. Fits on one line in any UI.
- Imperative mood: “Add user auth”, “Fix race in pool”, “Update README”. Not “Added”, not “Fixing”.
- Meaning, not mechanics: “Fix race in connection pool”, not “Edit pool.go”.
- If you need detail — a blank line after the title, then a body wrapped at 72 characters per line (hence “50/72”).
Good / bad
✓ Good:
Add login button to header Hooks up to existing /auth/login endpoint. Closes ticket #PAY-142.
✗ Bad:
fix update file . WIP asdfasdf
Bad messages turn git log into noise. Better spend 10 seconds on the wording — it saves hours of future searching.
Sort the files by state
Drag each file into the correct state. Each one is labeled with what was just done to it.
Module 4 — Branches
Parallel lines of work. ~10 minutes.
What a branch is
A branch in Git is a movable pointer to a commit. That is all. Not a copy of files, not a separate folder — just a text reference to one commit hash.
When you make a new commit, the branch pointer automatically moves to that new commit. The current branch is stored in a special pointer, HEAD.
Analogy: a branch = a parallel universe of the code. You can start a new line of work, experiment, then either merge it back into main or delete it if it did not work out.
Commands
git branch
git branch feature-x
feature-x branch from the current commit. It does not switch to it — it just creates the pointer.git switch feature-x
feature-x branch. The modern synonym of the old git checkout feature-x.git switch -c feature-x
git checkout -b feature-x.git merge feature-x
feature-x into the current branch. Typical scenario: switch to main, then merge your feature into it.Merge vs Rebase — conceptually
Merge
Creates a merge commit with two parents: your last commit and the last commit of the branch being merged. History is preserved as-is — you can see what ran in parallel. Safe (does not rewrite the past), but the graph gets bushy.
Rebase
Rewrites the branch commits as if they originally grew on top of the target branch. History comes out linear and clean. But the commits are recreated with new hashes — do not rebase something already pushed and used by others.
This course works only with merge — it is simpler and safer to start with.
Play with a branch
Each button below = one git command. The graph redraws after every step. The recommended scenario is top to bottom, so you get a merge commit:
- git commit (on main — it is currently active) — one more commit on main
- git branch feature — create the feature branch (a pointer)
- git switch feature — switch to it
- git commit — a commit now on feature
- git switch main — back to main
- git commit — a commit on main (now main and feature have diverged)
- git merge feature — merge
Module 5 — Working with a remote repository
push, pull, fetch, and why origin/main exists. ~7 minutes.
Linking to a remote repo
First — once — tell Git where the remote copy lives:
git remote add origin <url>
origin is the traditional name for the primary remote. The URL is what GitHub/GitLab shows on the repo page (HTTPS or SSH).Send local commits — push
git push -u origin main
-u flag (or --set-upstream) remembers the link “local main ↔ origin/main”, and after that you can simply:git push
Bring in other commits — pull and fetch
git pull
pull = fetch + merge (or + rebase, depending on settings).git fetch
git log origin/main, then decide what to do.What origin/main is
This is a tracking branch — a local cache of how the main branch looked on the remote at the last fetch or pull. It can lag behind the real origin (until you fetch).
Useful for comparison: “what is new locally compared with origin/main” — git log origin/main..HEAD. Or the other way: “what origin has that you do not” — git log HEAD..origin/main.
↓N ↑M indicator: commits behind / ahead of origin. Click it — sync.Scenarios — what to do?
Module 6 — Conflicts and how to resolve them
When Git cannot merge two branches on its own. ~7 minutes.
Why a conflict happens
A conflict = both sides of a merge changed the same line (or adjacent lines) of the same file. Git can merge many changes automatically — but if two versions edit literally the same place, it will not guess which to keep. Then you have to decide by hand.
It happens on git merge, git rebase, git pull (because pull = fetch + merge), sometimes on cherry-pick and stash pop.
Conflict markers in the file
When Git cannot cope, it leaves special markers in the file:
def hello(): <<<<<<< HEAD print("Hello, world!") ======= print("Hello, universe!") >>>>>>> feature return 0
<<<<<<< HEAD— start of your version (current branch)=======— separator>>>>>>> feature— end of the incoming version (name of the branch you are merging)
You need to: pick one version, or assemble a hybrid by hand, and must delete all three markers. A file with markers is broken code.
Mini merge editor
Click a hunk — it goes into Result. You can “Accept Both”, then both versions stay (and almost always you need a manual edit after that).
VSCode Merge Editor — no hand-editing of markers
After you have resolved the conflict
Markers are gone, the code is assembled. Next:
git add <file>
git add . immediately if you are sure.git commit
-m — Git will fill in a default message “Merge branch ...”; you only need to save. After that — the merge is done.If you decide you do not want to continue the merge — git merge --abort rolls everything back to the state before the merge started.
Done 🎉
Course complete. You can now:
- Explain Git’s model: snapshots, not deltas; four file states
- Configure Git and initialize a repository (CLI and VSCode)
- Run the
git add→git commitcycle and write good messages - Work with branches:
git branch,git switch,merge - Push and pull with a remote via
origin - Resolve merge conflicts in VSCode
What’s next
Going deeper — the Pro Git book: a free book at git-scm.com. Especially useful are the chapters on rebase, reflog, and the finer points of merge strategies.