Version control is the professional developer's time machine, safety net, and teamwork engine — learn it once, use it every day.
| Git | GitHub |
|---|---|
| Installed software | A website |
| Works offline | Needs the internet |
| Tracks changes in your local folders | Lets you share & review code with others |
| Created by Linus Torvalds, 2005 | Launched separately, acquired by Microsoft, 2018 |
| Free and open-source | Free for public repos, paid plans for private/teams |
| Like a diary you keep at home | Like a library you share with the world |
The problem version control solves
init, add, commit, log
Isolate work, merge safely
push, pull, fetch, clone
Repos, auth, README, Markdown
Read, resolve, prevent
Four of the six topics end in a runnable terminal simulation + a 15-question test (8 command + 7 concept) on a 15-minute timer. Then a 30-question final exam.
$ # Check Git is installed (download from git-scm.com if not) $ git --version >> git version 2.43.0 $ # Configure your identity — required ONCE per computer $ git config --global user.name "Priya Sharma" $ git config --global user.email "priya@example.com" $ # Use VS Code as the editor, and 'main' as the default branch $ git config --global core.editor "code --wait" $ git config --global init.defaultBranch main $ # Verify $ git config --list >> user.name=Priya Sharma >> user.email=priya@example.com >> init.defaultBranch=main
Before any command — the problem every developer hits without it, and what Git actually buys you.
assignment.docx assignment-v2.docx assignment-v2-edited.docx assignment-v2-edited-FINAL.docx assignment-v2-edited-FINAL-ACTUALLY-FINAL.docx assignment-v2-edited-FINAL-ACTUALLY-FINAL-2.docx $ # ...which one did the professor grade? $ # ...which one has your partner's latest changes? $ # ...where did the paragraph you wrote at 2am go?
WITHOUT GIT WITH GIT
------------------------------ ------------------------------
project-v1/ project/
project-v2/ .git/ (full history)
project-v2-fixed/ index.html
project-FINAL/ style.css
project-FINAL-USE-THIS/ app.js
project-FINAL-USE-THIS-2/
$ git log --oneline
$ # which folder is "current"? >> a1b2c3d Fix navbar bug
$ # copy-paste merges by hand >> 9f8e7d6 Add contact form
$ # no record of WHO changed WHAT >> 4c5b6a7 Initial commit
One folder, one .git history. Every past version is one command away — nothing is ever truly lost or renamed into confusion.
init, status, add, commit, log, and the one file you should write before any of them: .gitignore.
Working Directory --(git add)--> Staging Area --(git commit)--> Repository (your edits) (what's next) (saved forever)
git init$ mkdir my-portfolio $ cd my-portfolio $ git init >> Initialised empty Git repository in /home/priya/my-portfolio/.git/
git status & git add$ git status >> Untracked files: >> (use "git add <file>..." to include in what will be committed) >> index.html <-- shown in red $ git add index.html $ git status >> Changes to be committed: >> new file: index.html <-- now shown in green $ git add . # stage every changed file in this folder $ git add *.js # stage only JavaScript files $ git restore --staged index.html # oops — unstage it (keeps your edits)
git status is the command you should run constantly.git commit$ git add . $ git commit -m "Add initial project files" >> [main (root-commit) a1b2c3d] Add initial project files >> 3 files changed, 42 insertions(+) $ git commit -am "Fix navbar bug" # stage all TRACKED changes + commit, in one step $ git commit --amend -m "Add initial project files (typo fixed)" # rewrite the last commit
fix, wip, update — tell nobody anything. GOOD messages: Add user authentication, fix(api): handle null response — specific about what changed.git log$ git log >> commit a1b2c3d4e5f6... >> Author: Priya Sharma <priya@example.com> >> Date: Wed Jul 8 10:22:04 2026 >> >> Add initial project files $ git log --oneline >> a1b2c3d Add initial project files $ git log --oneline --graph --all >> * a1b2c3d (HEAD -> main) Add initial project files $ git show a1b2c3d >> # full diff of exactly what changed in that one commit
--oneline compresses each commit to a hash + message; --graph --all draws the branch structure; git show <hash> shows the full diff of one commit.
.gitignore$ touch .gitignore # --- Python / Django --- venv/ __pycache__/ *.pyc .env # --- Node / MERN --- node_modules/ dist/ build/ # --- OS / editor --- .DS_Store Thumbs.db .vscode/
.gitignore FIRST — before anything else — so secrets and huge folders never enter history.$ mkdir devcard && cd devcard $ git init >> Initialised empty Git repository $ echo 'node_modules/' >> .gitignore $ touch index.html style.css README.md $ git status >> Untracked files: .gitignore index.html style.css README.md $ git add . $ git commit -m "Initial commit: project setup" >> [main (root-commit) a1b2c3d] Initial commit: project setup >> 4 files changed $ git log --oneline >> a1b2c3d (HEAD -> main) Initial commit: project setup
Every real project starts this way: init, ignore the junk, stage, commit, verify with log. Run it yourself before the test.
Isolate risky work on its own timeline, then bring it back together safely.
Step 1 — main has three commits:
main: A---B---C
Step 2 — git checkout -b feature/login
(a new pointer "feature/login" is created at C; HEAD moves to it)
main: A---B---C
feature/login: C (HEAD)
Step 3 — two commits made while on feature/login:
main: A---B---C
feature/login: \
D---E (HEAD)
Step 4 — merge feature/login back into main:
main: A---B---C-------F
\ /
feature/login: D---E
git branch / git switch$ git branch >> * main $ git branch feature/login >> # creates the pointer — does NOT switch to it $ git checkout feature/login >> Switched to branch 'feature/login' $ git switch feature/login >> Switched to branch 'feature/login' # switch = modern, safer alternative to checkout $ git branch -m old-name new-name >> # rename a branch $ git branch -d feature/login >> # delete — SAFE, refuses if not yet merged $ git branch -D feature/login >> # force-delete, even if unmerged — use with care $ git branch -a >> # list every branch, including remote-tracking ones
git branch <name> only creates a pointer. git checkout/git switch is the command that actually moves HEAD onto it.checkout -b
$ git checkout -b feature/contact-form
>> Switched to a new branch 'feature/contact-form'
$ git switch -c feature/contact-form
>> Switched to a new branch 'feature/contact-form' # -c = create; identical result to checkout -b
$ touch contact.html
$ git add . && git commit -m "Add contact form markup"
>> [feature/contact-form 7f8e9d0] Add contact form markup
$ echo "form { border: 1px solid #ccc; }" >> style.css
$ git add . && git commit -m "Style the contact form"
>> [feature/contact-form 9a1b2c3] Style the contact form
-b/-c creates the branch AND checks it out immediately, so you never accidentally commit on main.git merge
Fast-forward (main hasn't moved since branching):
main: A---B---C
feature: \
D---E
after "git merge feature" on main:
main: A---B---C---D---E (main's pointer just slides forward)
Three-way merge (main moved on too — needs a new commit):
main: A---B---C-------F
\ /
feature: D---E
F is a new "merge commit" — it has TWO parents (C and E)
$ git checkout main
>> Switched to branch 'main'
$ git merge feature/contact-form
>> Updating a1b2c3d..9a1b2c3
>> Fast-forward
>> contact.html | 1 +
>> style.css | 1 +
$ git branch -d feature/contact-form
>> Deleted branch feature/contact-form (was 9a1b2c3).
| Prefix | Used for | Example |
|---|---|---|
feature/ | New functionality | feature/login |
fix/ | Non-urgent bug fix | fix/navbar-overlap |
hotfix/ | Urgent production fix | hotfix/payment-crash |
release/ | Preparing a release | release/v2.1.0 |
chore/ | Maintenance, deps, config | chore/update-dependencies |
docs/ | Documentation only | docs/api-readme |
$ git checkout -b feature/about >> Switched to a new branch 'feature/about' $ touch about.html $ git add . && git commit -m "Add about page" >> [feature/about d4e5f6a] Add about page $ git checkout main >> Switched to branch 'main' $ git merge feature/about >> Updating a1b2c3d..d4e5f6a >> Fast-forward $ git branch -d feature/about >> Deleted branch feature/about (was d4e5f6a).
Branch, commit, switch back, merge, clean up — the exact rhythm you'll repeat for every feature. Run it yourself before the test.
Your local history is safe on your machine — a remote is how it reaches GitHub, your teammates, and a backup that survives a dead laptop.
Your laptop --(git push)--> GitHub --(git clone)--> Colleague's laptop LOCAL REPO <--(git pull)--- REMOTE REPO LOCAL REPO (their own copy)
push sends your commits there; pull brings theirs to you; clone gives a whole new person their own full copy.git remote$ git remote add origin https://github.com/priya/devcard.git >> # links your local repo to a remote, nicknamed "origin" $ git remote -v >> origin https://github.com/priya/devcard.git (fetch) >> origin https://github.com/priya/devcard.git (push) $ git remote set-url origin https://github.com/priya/devcard-renamed.git >> # updates the URL "origin" points to, e.g. after a repo rename $ git remote remove origin >> # unlinks the remote — your local history is untouched
origin is just a convention, not a keyword — it's the default nickname Git gives the first remote you add.git push$ git push -u origin main >> Enumerating objects: 6, done. >> Writing objects: 100% (6/6), 1.02 KiB | 1.02 MiB/s, done. >> To https://github.com/priya/devcard.git >> * [new branch] main -> main >> Branch 'main' set up to track 'origin/main'. $ git push >> # -u only needed ONCE — after that, plain "git push" remembers where $ git push origin feature/login >> # push a specific branch explicitly
git push --force overwrites remote history and can destroy your team's work. Never force-push shared branches; use git revert to undo pushed commits.git pull & git fetch$ git pull origin main >> Updating a1b2c3d..9f8e7d6 >> Fast-forward >> README.md | 3 +++ >> 1 file changed, 3 insertions(+) $ git fetch origin >> # downloads new commits — does NOT touch your working files yet $ git diff main origin/main >> # review exactly what the remote has that you don't $ git merge origin/main >> # now bring those reviewed changes into your local main
pull = fetch + merge. Pros fetch first, review, then merge.
git clone$ git clone https://github.com/priya/devcard.git >> Cloning into 'devcard'... >> remote: Enumerating objects: 6, done. >> Receiving objects: 100% (6/6), done. $ git clone https://github.com/priya/devcard.git my-folder-name >> # clone into a custom folder name instead of the repo's default name $ cd devcard $ # install dependencies for your stack, then you're ready to work
| # | Step |
|---|---|
| 1 | git pull origin main — start from the latest code |
| 2 | git checkout -b feature/x — create your branch |
| 3 | Code the change |
| 4 | git add . — stage it |
| 5 | git commit -m "..." — save a snapshot |
| 6 | Repeat steps 3–5 as needed |
| 7 | git push -u origin feature/x — send it to GitHub |
| 8 | Open a Pull Request for review |
| 9 | Merge the PR into main |
| 10 | Delete the feature branch — locally and on GitHub |
Creating repos, connecting them to your local machine, authenticating safely, and writing a README that makes a good first impression.
| Step | What to do |
|---|---|
| 1 | Click "New" on GitHub to start a repository |
| 2 | Name it lowercase-with-hyphens, e.g. my-portfolio |
| 3 | Add a short description |
| 4 | Choose Public or Private visibility |
| 5 | Do NOT initialize with a README (if a local repo already exists) |
| 6 | Click "Create repository", then copy the HTTPS URL |
Scenario A — you already have a local repo: $ git remote add origin https://github.com/priya/devcard.git $ git branch -M main $ git push -u origin main >> Branch 'main' set up to track 'origin/main'. Scenario B — starting fresh from GitHub: $ git clone https://github.com/priya/devcard.git >> Cloning into 'devcard'... $ cd devcard $ # "origin" is already configured — just add files and commit
| HTTPS + token | SSH key | |
|---|---|---|
| Setup | Generate a Personal Access Token on GitHub | Generate a key pair once with ssh-keygen |
| Each push | Username + token (unless cached) | Nothing — the key handles it silently |
| Best for | Quick start, shared/lab computers | Your own machine, daily use |
| Remote URL | https://github.com/you/repo.git | git@github.com:you/repo.git |
$ ssh-keygen -t ed25519 -C "you@example.com" >> Generating public/private ed25519 key pair... $ cat ~/.ssh/id_ed25519.pub >> ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... you@example.com $ # paste that key into GitHub Settings -> SSH and GPG keys -> New SSH key $ ssh -T git@github.com >> Hi priya! You've successfully authenticated, but GitHub does not provide shell access. $ git remote set-url origin git@github.com:priya/devcard.git
README.md# DevCard > A one-page developer portfolio built while learning Git & GitHub. ## Features - Responsive layout - Dark / light theme toggle - Contact form ## Tech Stack - HTML5, CSS3, JavaScript ## Getting Started ``` $ git clone https://github.com/you/devcard.git $ cd devcard $ # open index.html in your browser ``` ## Author Your Name — [GitHub](https://github.com/you)
| Syntax | Meaning |
|---|---|
# / ## | Heading / subheading |
**bold** | Bold text |
*italic* | Italic text |
`code` | Inline code |
``` fenced ``` | Multi-line code block |
[link](url) | Hyperlink |
 | Image |
- item | Bullet list |
> quote | Blockquote |
--- | Horizontal rule |
$ git remote add origin https://github.com/priya/devcard.git $ git remote -v >> origin https://github.com/priya/devcard.git (fetch) >> origin https://github.com/priya/devcard.git (push) $ git branch -M main $ git push -u origin main >> Writing objects: 100% (6/6), done. >> To https://github.com/priya/devcard.git >> * [new branch] main -> main >> Branch 'main' set up to track 'origin/main'.
Link the remote, rename to main, push with -u to set the tracking relationship — the exact sequence every new project needs once. Run it yourself before the test.
The one moment Git can't decide for you — read the markers, choose the code, and move on with confidence.
git merge to bring the two histories together.Conflicts are normal. Resolving them is a skill.
$ git checkout main $ echo 'nav background: blue;' > style.css $ git add . && git commit -m "Blue navbar" $ git checkout -b feature/green $ echo 'nav background: green;' > style.css $ git add . && git commit -m "Green navbar" $ git checkout main $ echo 'nav background: purple;' > style.css $ git add . && git commit -m "Purple navbar" $ git merge feature/green !! Auto-merging style.css !! CONFLICT (content): Merge conflict in style.css
Both main and feature/green changed the SAME line of style.css after they split apart — Git has no way to guess which colour you actually want.
<<<<<<< HEAD nav background: purple; (your current branch — main) ======= nav background: green; (incoming branch — feature/green) >>>>>>> feature/green
| # | Step |
|---|---|
| 1 | git status — see which file(s) are in conflict |
| 2 | Open the conflicted file in your editor |
| 3 | Find the <<<<<<< / ======= / >>>>>>> marker blocks |
| 4 | Decide which side to keep — yours, theirs, or a rewrite |
| 5 | Edit the file — delete ALL marker lines, keep only the code you chose |
| 6 | Save the file |
| 7 | git add <file> — mark this file as resolved |
| 8 | Repeat steps 2–7 for every other conflicted file |
| 9 | git commit — completes the merge |
$ git status >> both modified: style.css $ code style.css $ # delete the markers, keep the colour you want, save the file $ git add style.css $ git commit >> [main e7f8a9b] Merge branch 'feature/green'
VS Code shows these four buttons right above every marker block it detects — click one, then save → git add → git commit, same as the terminal method.
$ git merge --abort >> # backs the repo out of the merge completely >> # your files return to exactly how they were before "git merge" ran
Too messy to resolve right now? Abort, breathe, and try again later — nothing is lost.
| Prevention practice | Why it helps |
|---|---|
| Pull before you start work | Your base is already up to date |
| Keep branches small & short-lived | Less code overlapping with everyone else's |
| Communicate with your team | Two people rarely touch the same lines if they talk first |
| Push often | Small diffs are easy for everyone to merge |
| Merge main into your branch often | Conflicts show up early, in small batches |
| Use Pull Requests with review | A second pair of eyes catches risky overlaps before merge |
$ git status >> Unmerged paths: >> both modified: style.css $ # Edit style.css: delete the markers, choose a new value $ echo 'nav background: #1D4ED8;' > style.css $ git add style.css $ git status >> All conflicts fixed but you are still merging. $ git commit >> [main e7f8a9b] Merge branch 'feature/green'
Every conflict resolves the same way: find the markers, choose the code, stage it, commit. Run this yourself before the test.
| Command | Does |
|---|---|
git config --global | set name/email |
git init | start tracking |
git clone <url> | copy a remote repo |
| Command | Does |
|---|---|
git status | what's changed |
git add . | stage changes |
git commit -m | save a snapshot |
git push / pull | sync with GitHub |
| Command | Does |
|---|---|
git log --oneline | compact history |
git diff | unstaged changes |
git restore <file> | discard edits |
| Command | Does |
|---|---|
git branch | list branches |
git checkout -b name | create + switch |
git merge name | bring a branch in |
git branch -d name | delete a merged branch |
| Command | Does |
|---|---|
git remote add origin <url> | link a remote |
git push -u origin main | first push, sets tracking |
git merge --abort | bail out of a conflict |
You can init a repo, stage & commit, branch & merge, push & pull to GitHub, and resolve conflicts — the everyday workflow of every professional developer.
init → add → commit → branch → merge → push → pull → conflict resolved
Take the final test → ask your instructor for the link.