Git & GitHub Notes — Backbencher Codes
← Back to Backbencher Codes Compiler
These notes cover the basics and advanced Git workflows. Each section includes explanations and terminal examples you can try in your own practice repository.
Table of Contents
- Git & GitHub Basics
- Install & Configure Git
- Repository Basics
- The Git Workflow
- Core Commands
- File States & .gitignore
- Push, Pull & Clone
- Branching
- Merging
- Stashing
- Collaboration
- Raising a Pull Request
- Reviewing a Pull Request
- Merging a PR & Handling Rejection
- Merge Conflicts
- Undo Changes
- Rebase & Interactive Rebase
- Reflog — Recovering Lost Work
- Cherry-Picking Commits
- Forking & Branch Rulesets
- More Power Tools
- README & Documentation
- Best Practices
Topic 1: Git & GitHub Basics {#topic-1}
What they are, why they exist, and how they're different
Say you're building a personal portfolio site. Every day you tweak the CSS, add a new section, maybe break something by accident. If you're not careful, you end up with a folder like this:
portfolio-final
↓
portfolio-final-v2
↓
portfolio-final-REAL-final
↓
nobody knows which one actually works 😵
Key Concept
Git is a tool that saves the history of every change you make to your project, so you never need duplicate folders again. GitHub is a website that stores a backup of that history online, so you can share it and work with other people.
Think of Git like the "Version History" feature in Google Docs — every edit is remembered, and you can jump back to any earlier version instantly. GitHub is like uploading that document to Google Drive so your teammates can open it too.
Git vs GitHub — the classic interview question
| What | Git | GitHub |
|---|---|---|
| What is it? | A tool installed on your computer | A website that hosts Git projects online |
| Needs internet? | No — works fully offline | Yes — it lives on the web |
| Made by | Linus Torvalds (2005) | Owned by Microsoft |
| Cost | Free & open source | Free plan + paid plans |
Easy way to remember it: Git = Camera (takes the snapshot). GitHub = Google Photos (stores it online so others can see it).
Why does this matter for a web developer?
Imagine three of you are building a React app together — one person builds the navbar, another the login page, another the footer. Without Git, you'd be emailing zip files back and forth and constantly overwriting each other's work. Git lets everyone edit the same project safely, and GitHub gives you one shared "source of truth" online.
Topic 2: Install & Configure Git {#topic-2}
Getting your machine ready before your first commit
Before Git can track anything, it needs two things installed and one thing configured: your identity (name + email). Every change you save gets stamped with "who did this."
Step 1 — Install
Download Git from git-scm.com for Windows, or use your terminal on Mac/Linux:
Example (macOS):
brew install git
Example (Ubuntu / Debian):
sudo apt install git
Step 2 — Verify
Example (Terminal):
git --version
# git version 2.51.0 — you're good to go
Step 3 — Configure your identity
Example (Terminal):
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
| Flag | Value Used | What it does |
|---|---|---|
| --global | — | Applies this name/email to every project on your computer, not just one. |
| user.name | "Your Name" | Shows up as the author on every commit you make. |
| user.email | you@email.com | Use the same one as your GitHub account so commits link back to your profile. |
If your Git email doesn't match your GitHub email, your commits won't show up on your GitHub contribution graph — a common beginner surprise.
Check what's configured anytime with git config --list, and create a free account at github.com if you haven't already — you'll need it from Section 7 onward.
Topic 3: Repository Basics {#topic-3}
Turning an ordinary folder into a Git project
A repository (or "repo") is just a project folder that Git is watching. Say you have a folder for a to-do list app:
todo-app
├── index.html
├── style.css
└── script.js
Right now, Git knows nothing about it. To start tracking it:
Example (Terminal):
cd todo-app
git init
# Initialized empty Git repository
This creates a hidden .git folder inside your project. That's where Git quietly stores every commit, branch, and bit of history — your actual files (index.html, etc.) never move or change.
Key Concept
.git is Git's brain for this project. Deleting it doesn't delete your code — it just makes Git forget every version you ever saved. Never edit its contents by hand.
Run git init only once per project — right when you start it, or right when you decide to start tracking it.
Topic 4: The Git Workflow {#topic-4}
The four stages every change passes through
Every file you touch moves through four areas before it's safely part of your project's history:
- Working Directory: Your actual project folder — where you edit index.html, add a new component, etc.
- Staging Area: A waiting room. You pick exactly which changed files you want saved next.
- Local Repository: A "commit" locks in a permanent snapshot — saved on your computer.
- Remote Repository (GitHub): A push uploads your commits online so others (and future you) can access them.
Key Concept
Think of the Staging Area like an online shopping cart. Browsing the store = editing files. Adding to cart = git add. Checkout = git commit. Nothing is "bought" (saved forever) until you check out.
Example: building a navbar over one day
9 AM — write navbar HTML
↓
git add . → git commit -m "Add navbar markup"
↓
2 PM — style the navbar with CSS
↓
git add . → git commit -m "Style navbar"
↓
6 PM — push everything to GitHub
↓
git push ✅
Commits happen completely offline — you don't need internet until you're ready to push. So you can commit freely on a flight, a train, or with bad Wi-Fi.
Topic 5: Core Commands {#topic-5}
The six commands you'll type every single day
| Flag | Value Used | What it does |
|---|---|---|
| git init | — | Starts tracking the current folder as a Git repo. |
| git status | — | Shows what's changed, staged, or untracked right now. |
| git add | file / . | Moves changes into the Staging Area. |
| git commit -m | "message" | Saves a permanent snapshot with a message describing it. |
| git log --oneline | — | Shows your commit history, one line per commit. |
| git diff | — | Shows exactly what lines changed, before you commit. |
A real example: fixing the background color
Example (Terminal):
git status
# modified: style.css
git diff
- background: white;
+ background: #111827;
git add style.css
git commit -m "Switch hero section to dark background"
Good commit message: "Fix mobile navbar overlap". Bad commit message: "update" or "final". Describe what changed, not that something changed.
Topic 6: File States & .gitignore {#topic-6}
Untracked, modified, staged — and what Git should never touch
- Untracked: A brand new file Git has never seen, e.g. a new about.html you just created.
- Modified: Git already knows this file, but you've changed it since the last commit.
- Staged → Committed: You've run git add, then git commit — the change is now locked in.
.gitignore — telling Git what to skip
In a React or Node project, some folders/files should never be uploaded to GitHub — like your node_modules folder (can be 100,000+ files!) or your .env file (which holds secret API keys).
Example (.gitignore):
node_modules/
.env
dist/
build/
*.log
Never commit a .env file. It usually contains database passwords, API keys, or secret tokens — leaking these on a public GitHub repo is a real security risk.
Create your .gitignore before your first commit. Once Git is already tracking a file, adding it to .gitignore later won't make Git stop watching it automatically.
Topic 7: Push, Pull & Clone {#topic-7}
Connecting your local project to GitHub
So far, all your commits live only on your laptop. To back them up (and let others see them), connect your project to a GitHub repository.
Step 1 — Connect
Example (Terminal):
git remote add origin https://github.com/you/todo-app.git
origin is just a nickname for "the GitHub URL I'm connected to."
Step 2 — Push (first time)
Example (Terminal):
git push -u origin main
After this, every future push is as simple as git push — Git remembers where to send it.
Cloning someone else's project
Example (Terminal):
git clone https://github.com/friend/react-blog.git
This downloads the entire project — files, branches, and full commit history — as if you'd been there from day one.
Pull vs Fetch
| What | git fetch | git pull |
|---|---|---|
| Downloads new changes? | Yes | Yes |
| Updates your files immediately? | No — you review first | Yes — merges right away |
Think WhatsApp: fetch is seeing "5 new messages" without opening them. pull is opening and reading them — your view updates instantly.
Topic 8: Branching {#topic-8}
Working on new features without breaking what already works
Say your React app is live and working. Now you want to add a dark mode toggle — but what if it breaks the whole app? You don't experiment directly on main. Instead, you create a branch: an independent copy of your project where you can safely try things.
Example (Terminal):
git switch -c dark-mode
# creates AND switches to the new branch in one step
| Flag | Value Used | What it does |
|---|---|---|
| git branch | — | Lists all branches; the current one has a *. |
| git switch | name | Moves you into an existing branch. |
| git switch -c | name | Creates a new branch and jumps into it immediately. |
| git branch -d | name | Deletes a branch once its work is merged in. |
Key Concept
Changes made on dark-mode stay invisible on main until you merge them. Switch back to main and your dark mode code simply isn't there — safely tucked away in its own branch.
Good branch names
feature/dark-mode
·
bugfix/navbar-overlap
·
docs/readme-update
test
·
temp
·
abc
← avoid these
Topic 9: Merging {#topic-9}
Bringing a finished feature branch back into main
Once dark mode works perfectly on its own branch, it's time to combine it back into main.
Example (Terminal):
git switch main
git merge dark-mode
Always switch to the branch you want to merge into first (usually main), then run merge with the feature branch's name.
Fast-forward vs Three-way merge
| What | Fast-forward | Three-way |
|---|---|---|
| When it happens | main hasn't changed since you branched | Both branches got new commits |
| Extra "merge commit"? | No — pointer just moves forward | Yes — Git creates one to combine histories |
After merging, delete the now-unneeded branch:
Example (Terminal):
git branch -d dark-mode
Don't worry — your dark mode code stays in main's history. Only the branch label disappears.
Topic 10: Stashing {#topic-10}
Temporarily setting aside unfinished work
Imagine you're halfway through building a search bar on the feature/search branch — messy, half-working code — when your teammate says "quick, switch to main, there's a bug in production!" You can't commit unfinished, broken code. That's exactly what stashing is for.
Key Concept
git stash takes your uncommitted changes and tucks them away in a hidden "shelf," giving you back a clean working directory — without creating a commit.
Step 1 — Save your unfinished work
Example (Terminal):
git stash
# Saved working directory state WIP on feature/search: ...
Step 2 — Do the urgent fix
Example (Terminal):
git switch main
# fix the bug, commit, push...
Step 3 — Bring your work back
Example (Terminal):
git switch feature/search
git stash pop
| Flag | Value Used | What it does |
|---|---|---|
| git stash | — | Shelves your current uncommitted changes. |
| git stash list | — | Shows every stash you've saved, most recent first. |
| git stash pop | — | Restores the most recent stash and removes it from the shelf. |
| git stash apply | — | Restores the stash but keeps a copy on the shelf too. |
| git stash drop | — | Deletes a stash you no longer need. |
| git stash save | "message" | Same as stash, but with a note so you remember what it was. |
Pop vs Apply: pop = "take it off the shelf and use it" (removes it from the stash list). apply = "make a copy from the shelf but leave the original there too" — handy if you want to apply the same stash to two different branches.
Stashes aren't tied to a specific branch — you can stash on one branch and pop it on another. This is powerful, but can also cause confusing conflicts if you're not paying attention to which branch you're on.
Topic 11: Collaboration {#topic-11}
How multiple developers safely share one project
Say four of you are building an e-commerce site: one on login, one on the cart, one on payments, one on the admin dashboard. GitHub gives you two main ways to work together.
| What | Collaborator Access | Fork & PR |
|---|---|---|
| Used for | Company / team projects | Open-source contributions |
| How it works | Owner invites you directly to the repo | You copy the repo to your own account, then propose changes back |
Key Concept
A fork is your own personal copy of someone else's repository. You can experiment freely there — the original project stays untouched until you formally ask them to include your changes.
The golden rule of team collaboration: never build features directly on main. Each feature gets its own branch, keeping everyone's work isolated until it's reviewed and ready.
Topic 12: Raising a Pull Request {#topic-12}
Asking the team to review your work before it joins the main project
You've finished the login feature on your feature/login branch. Should you merge it into main yourself? No. Professional teams never let developers merge directly into important branches. Instead, they open a Pull Request (PR) — a formal request that says: "I've finished my work, please review it before it becomes part of the main project."
Key Concept
Think of it like a group assignment. You finish your section, but instead of inserting it straight into the final document, you send it to your teacher first. They review it, suggest corrections, approve it — and only then does it get added. A Pull Request works exactly the same way.
Why do we even need Pull Requests?
Imagine 20 developers on one team. If everyone pushed directly into main, bugs would pile up fast, code quality would drop, and a single mistake could break the live site. PRs fix this by forcing every change through code review, discussion, testing, and approval before it's safely merged.
The full Pull Request workflow
- Create branch: git switch -c feature/login
- Develop the feature & commit: Build login, commit in small logical chunks.
- Push the branch: git push origin feature/login
- Create Pull Request: login-feature → main, with title + description.
- Code review: A teammate reads, tests, and comments on it.
- Approve → Merge → Delete branch: Once approved, it's merged into main and the feature branch is cleaned up.
This exact cycle is what almost every software company — from small startups to Google and Netflix — runs, every single day.
Step-by-step: creating your PR
Step 1 — Push your branch to GitHub
Example (Terminal):
git push origin feature/login
Step 2 — Open GitHub
GitHub will usually display a banner that says "Compare & pull request" right after your push. Click it.
Step 3 — Fill in the details, then submit
Fill in a Title and a Description, then click "Create Pull Request." Done — your PR is now waiting for review.
Writing a good PR title
A good title clearly describes the feature — not just that "something" changed. Many teams also prefix the title with the type of change, similar to commit message conventions, so reviewers know what they're looking at before opening it.
✓ Add User Authentication
✓ Fix Navbar Alignment
✓ Implement Dark Mode
✓ Create Payment API
✗ Update
·
✗ Changes
·
✗ Final
·
✗ Done
| Flag | Value Used | What it does |
|---|---|---|
| feat: | feat: add user authentication | A new feature is being introduced. |
| fix: | fix: navbar overlap on mobile | A bug fix, nothing new added. |
| refactor: | refactor: simplify auth middleware | Code restructured, behavior unchanged. |
| docs: | docs: update setup instructions | Documentation-only changes. |
| chore: | chore: bump dependencies | Maintenance work — no feature or fix. |
Writing a good PR description
A solid description answers: what changed, why it changed, and how you tested it (screenshots help too). Rather than one paragraph, most teams use a structured, sectioned template — this keeps every PR consistent and makes reviewers' jobs much faster, since they know exactly where to look for each piece of information.
Key Concept
Reviewers are busy. A templated description lets them scan straight to "Related Issue" or "Testing" instead of hunting through a wall of text. Many teams save this exact template as a .github/PULL_REQUEST_TEMPLATE.md file, so GitHub auto-fills it into every new PR's description box.
Example (PR Description Template):
## Description
Briefly describe what this PR changes.
## Changes Made
- Added ...
- Fixed ...
- Updated ...
## Why
Explain why these changes are needed.
## Testing
- Tested locally
- No errors found
## Screenshots
(Add if applicable)
## Related Issue
Closes #123
## Checklist
- [x] Code follows project guidelines
- [x] Tested locally
- [x] Documentation updated (if needed)
- [x] Ready for review
What each section is for
| Flag | Value Used | What it does |
|---|---|---|
| ## Description | 1–2 sentences | A quick summary a reviewer can read in five seconds to know what this PR is about. |
| ## Changes Made | bullet list | The concrete list of what was added, fixed, or updated — the "diff" in plain English. |
| ## Why | short paragraph | The reasoning or motivation — the bug it solves, the feature it enables, or the issue it addresses. |
| ## Testing | bullet list | How you verified it works — manual testing, automated tests run, edge cases checked. |
| ## Screenshots | image / gif | Visual proof for UI changes — drag-and-drop an image straight into GitHub's PR description box. |
| ## Related Issue | Closes #123 | Links back to the tracked issue — GitHub auto-closes it when this PR merges. |
| ## Checklist | - [x] item | Self-review checkboxes confirming the PR is actually ready, not still a work in progress. |
In ## Related Issue, keywords like Closes, Fixes, or Resolves followed by an issue number (e.g. Closes #123) aren't just for readability — GitHub detects them and automatically closes that issue the moment this PR is merged.
Before you open a PR — checklist
- Does the project build successfully?
- Did I test my code myself first?
- Are my commit messages meaningful?
- Is my branch up to date with
main? - Did I remove unnecessary/leftover code (console.logs, commented-out blocks)?
If your feature isn't finished yet but you want early feedback, open a Draft Pull Request instead. It clearly tells reviewers "still in progress, please don't merge yet."
Two common mistakes when raising a PR: opening it from the wrong source/target branch (always double-check which branch merges into which), and submitting a huge PR with hundreds of unrelated file changes — keep each PR focused on one feature.
Topic 13: Reviewing a Pull Request {#topic-13}
How teams check code quality before it ships
Now flip roles — you're the reviewer, checking a teammate's feature/cart PR before it becomes part of the shared codebase. One of the biggest advantages of Pull Requests is Code Review: another developer checks your code before it's merged. Even experienced developers make mistakes — a second pair of eyes regularly catches bugs, typos, security issues, and performance problems that the original author missed.
What happens after a PR is created?
- Reviewer reads the code: Goes through every changed file in the "Files changed" tab.
- Reviewer tests the feature: Pulls the branch locally or checks CI results to confirm it actually works.
- Reviewer gives feedback: Leaves inline comments directly on the relevant lines of code.
- Developer updates the code: Commits fixes and pushes to the same branch.
- Reviewer approves → Merge: Once satisfied, the reviewer approves and the PR is merged.
What a good reviewer checks
| Flag | Value Used | What it does | Why it matters |
|---|---|---|---|
| Code Quality | clean, simple logic | Is the implementation as simple as it can reasonably be? | Simpler code has fewer places for bugs to hide. |
| Readability | clear names | Can the next developer understand this in 6 months? | Code is read far more often than it's written. |
| Performance | no unnecessary work | Are there obvious slowdowns — e.g. loops inside loops, unneeded re-renders? | Keeps the app fast as it grows. |
| Bugs / Logic | edge cases | Does it handle an empty cart, a failed API call, invalid input? | Prevents issues from reaching production. |
| Security | no secrets/leaks | No hard-coded API keys, no unsafe handling of user input. | One leaked key can compromise the whole app. |
| Best Practices | matches team style | Follows the project's existing conventions and patterns. | Keeps the codebase maintainable long-term. |
The three review outcomes
- Approve
Everything looks good. The reviewer clicks Approve — the PR is now ready to merge.
- Request Changes
The reviewer found real issues that must be fixed before merging. For example: "Improve variable names," "Fix responsive layout," "Add error handling." The developer updates the branch, and the Pull Request updates automatically.
- Comment
The reviewer is simply asking questions or suggesting ideas, without blocking the merge — for example: "Why did you choose this approach?" or "Can we simplify this function?"
Updating a Pull Request after feedback
Key Concept
A common beginner misconception: "I have to create a new Pull Request after every change." Wrong. Simply make changes, commit, and push to the same branch — the existing PR updates automatically.
Example (Terminal — author fixing review comments):
git add .
git commit -m "Fix review comments"
git push
Approving and merging
When everything looks good, the reviewer clicks Approve. Then, to actually combine the code, someone clicks "Merge Pull Request" — GitHub combines the feature branch into the target branch (usually feature/cart → main).
| What | Merge | Close |
|---|---|---|
| Result | Code becomes part of the project | Code is rejected or postponed — no code is merged |
| Branch | Usually deleted after merging | May still exist for later use |
Every PR also keeps a full conversation — comments, suggestions, reviews, commit history, and changed files — so the whole discussion around a feature lives in one place.
Good review culture: be specific and kind. Instead of "this is wrong," try "This might break if items is undefined — could we add a default value?" Never approve code blindly — and always address review comments before requesting another look.
Topic 14: Merging a PR & Handling Rejection {#topic-14}
How teams finish the review cycle — and what to do if your PR gets turned down
Merging, at industry level
Once a PR is approved (often requiring 1–2 approvals, plus passing automated tests/CI), it's ready to merge. GitHub usually offers three merge styles:
| Flag | Value Used | What it does | Why it matters |
|---|---|---|---|
| Merge commit | keeps all commits | Adds every commit from the branch, plus one merge commit. | Full history preserved, but can get noisy. |
| Squash & merge | one clean commit | Combines all the branch's commits into a single commit on main. | Keeps main's history clean — most companies default to this. |
| Rebase & merge | linear history | Replays your commits on top of main, one by one, no merge commit. | Very clean history, used by teams wanting a straight-line log. |
Most companies use Squash & Merge by default for feature branches — it keeps main's history readable: one commit per feature, instead of "wip", "fix typo", "oops" scattered everywhere.
After merging, the standard next step:
Example (Terminal):
git switch main
git pull
git branch -d feature/login
What if your PR gets rejected?
It happens to everyone — even senior engineers. A "rejected" PR usually means one of two things: the reviewer requested changes, or the PR was closed without merging altogether (maybe the feature isn't needed anymore, or the approach needs rethinking).
Step 1 — Don't take it personally
Code review feedback is about the code, not you. Every experienced developer has had PRs sent back for changes — it's a normal, healthy part of shipping good software.
Step 2 — Read the feedback carefully
Understand why it was rejected. If it's unclear, it's completely fine to ask: "Could you clarify what you mean by this comment?"
Step 3 — Fix and push again
Example (Terminal):
git switch feature/login
# make the requested changes
git add .
git commit -m "Address review feedback: validate email format"
git push
Then re-request a review on GitHub instead of opening a brand new PR — this keeps the whole discussion in one place.
If the whole PR was closed (not just changes requested)
Talk to your team about the reasoning first. Sometimes the feature direction changed, or a different approach is preferred. If you still want to pursue it, you can branch off the latest main and rebuild with the new feedback in mind:
Example (Terminal):
git switch main
git pull
git switch -c feature/login-v2
Avoid force-pushing over a reviewer's comments or arguing in the PR thread. If you disagree with feedback, discuss it calmly, or loop in a lead/senior developer to help decide the right approach.
Key Concept
A rejected or "changes requested" PR isn't the end of your work — it's the review process doing its job. The goal is always a better main branch, not a perfect first attempt.
Topic 15: Merge Conflicts {#topic-15}
What to do when Git can't decide for you
Say you changed the header's background to black, and your teammate changed the very same line to blue — on a different branch. When merging, Git has no idea which one you actually want. That's a merge conflict.
Example (style.css — conflict markers):
<<<<<<< HEAD
background: black;
=======
background: blue;
>>>>>>> feature/header
Edit the file, delete the markers, and keep whichever version (or combination) you actually want:
Example (Terminal):
git add style.css
git commit
Conflicts are completely normal — even experienced developers hit them regularly. They just mean two people touched the same line; Git needs a human to pick the final answer.
Topic 16: Undo Changes {#topic-16}
Because mistakes should never be permanent
| Flag | Value Used | What it does |
|---|---|---|
| git restore | file | Discards uncommitted changes, back to the last commit. Very safe. |
| git reset --soft | HEAD~1 | Undoes the last commit but keeps your changes staged (great for fixing a bad commit message). |
| git reset --hard | HEAD~1 | Deletes the last commit AND all its changes completely. Use with caution. |
| git revert | HEAD | Creates a new commit that undoes an earlier one — safe for shared branches. |
Never use git reset --hard on a branch others have already pulled — it rewrites history. On shared branches, always prefer git revert, which preserves history instead of erasing it.
Topic 17: Rebase & Interactive Rebase {#topic-17}
Rewriting history to keep it clean and linear
Rebase takes the commits from your branch and replays them one-by-one on top of another branch, instead of creating a merge commit that ties the two histories together. The end result looks as if you'd branched off much later — a straight line, not a web of merges.
Key Concept
Merge preserves exactly what happened (including the "join point"). Rebase rewrites what happened, so it looks like your work started from the latest code. Neither is "wrong" — they trade off honesty of history against readability of history.
Rebase vs merge, visually
git merge
feature into main
main: A---B---C-------
M
\_____/
feature: D---E
git rebase
feature onto main
main: A---B---C
feature: \---
D'
---
E'
(new commit IDs)
Basic rebase
Example (Terminal):
git checkout feature/navbar
git rebase main # replays feature/navbar's commits on top of latest main
Interactive rebase — editing history itself
git rebase -i opens an editable list of your last commits, letting you reorder, combine, rename, or drop them before they're finalized. It's the tool for turning "wip", "fix typo", "actually fix typo" into one clean commit.
Example (Terminal):
git rebase -i HEAD~3 # open the last 3 commits for editing
| Flag | Value Used | What it does |
|---|---|---|
| pick | (default) | Keep this commit exactly as it is. |
| reword | r | Keep the changes, but let you edit the commit message. |
| squash | s | Merge this commit into the one above it, combining their messages. |
| fixup | f | Like squash, but silently discards this commit's message. |
| drop | d | Delete this commit entirely, as if it never happened. |
| edit | e | Pause at this commit so you can amend its content, then continue. |
After changing a line, save and close the editor. If you marked a commit edit, make your changes and run:
Example (Terminal):
git add .
git rebase --continue
If a rebase hits a conflict
Example (Terminal):
# fix the conflicted file(s), then:
git add <file>
git rebase --continue
# or bail out completely and go back to how things were:
git rebase --abort
The golden rule of rebasing: never rebase commits that have already been pushed and that other people have pulled. Rebase rewrites commit hashes — if teammates already built on the old ones, you'll create a confusing, duplicated history for everyone. Rebase your own local/feature work; merge (or git pull --rebase only on branches solely yours) once it's shared.
Topic 18: Reflog — Recovering Lost Work {#topic-18}
Git's private safety net that almost nothing can defeat
Every time HEAD moves — a commit, a checkout, a reset, a rebase, even an aborted merge — Git quietly logs it in the reference log (reflog). It's local-only (never pushed, never shared) and it's the reason "I accidentally deleted my commit" is almost never fatal.
Key Concept
A "deleted" commit is rarely actually gone — Git just stops pointing at it. As long as it's still in the reflog (default ~90 days for reachable commits, ~30 days for unreachable ones), you can point something back at it and get your work back.
Example (Terminal):
git reflog
# example output:
a1b2c3d HEAD@{0}: rebase (finish): returning to refs/heads/main
e4f5g6h HEAD@{1}: commit: Add payment validation
i7j8k9l HEAD@{2}: reset: moving to HEAD~1
m0n1o2p HEAD@{3}: commit: Fix login bug
Recovering a "lost" commit
Say a git reset --hard just wiped out a commit you actually needed. Find it in the reflog, then reset back onto it:
Example (Terminal):
git reflog # find the entry right before the mistake, e.g. HEAD@{2}
git reset --hard HEAD@{2}
# or, if you'd rather not touch your current branch:
git checkout -b recovered-work HEAD@{2}
Reflog is your panic button for rebases-gone-wrong, accidental hard resets, deleted branches, and detached-HEAD confusion. Whenever something feels like it "disappeared," check git reflog before assuming it's actually lost.
Reflog is local to your machine only — it isn't pushed to GitHub and won't help recover a teammate's history, and it does eventually expire and get garbage-collected. It's a safety net, not permanent storage.
Topic 19: Cherry-Picking Commits {#topic-19}
Grabbing one specific commit without merging everything else
git cherry-pick copies the changes from one specific commit on another branch and applies them as a new commit on your current branch — without pulling in anything else that branch has. Useful for backporting a hotfix, or grabbing just one teammate's commit out of a much bigger feature branch.
Example (Terminal):
git checkout main
git cherry-pick a1b2c3d # applies just this one commit onto main
| Flag | Value Used | What it does |
|---|---|---|
| git cherry-pick | Applies that single commit's changes as a new commit here. | |
| git cherry-pick | A..B | Applies a whole range of commits (exclusive of A), in order. |
| git cherry-pick | --no-commit |
Applies the changes to your working directory, but doesn't commit — lets you review or combine first. |
| git cherry-pick | --continue | Resumes after you've resolved a conflict from a cherry-pick. |
| git cherry-pick | --abort | Cancels the cherry-pick and returns to how things were before. |
Because cherry-pick replays a commit's changes elsewhere, it can hit merge conflicts just like a merge or rebase can — resolve the file, git add it, then continue:
Example (Terminal):
git add <file>
git cherry-pick --continue
Cherry-picked commits get a brand-new commit hash on the destination branch, even though the change is identical — Git treats it as its own, separate commit.
Topic 20: Forking & Branch Rulesets {#topic-20}
Contributing to repos you don't own, and protecting the ones you do
The full fork workflow
A fork is your own personal copy of someone else's repository, sitting on your own GitHub account. It's how you contribute to projects you don't have write access to — like most open-source repositories.
- Fork: Click "Fork" on GitHub — creates yourname/project, a full copy under your account.
- Clone your fork: git clone your fork down to your machine.
- Branch, commit, push: Do your work on a feature branch, then push it to your fork.
- Open a PR: Open a Pull Request from your fork's branch into the original project's main .
- Keep your fork in sync: The original project keeps moving — regularly pull its changes into your fork.
Keeping a fork up to date with the original ("upstream")
Example (Terminal):
git remote add upstream https://github.com/original-owner/project.git
git fetch upstream
git checkout main
git merge upstream/main # or: git rebase upstream/main
git push origin main # origin = your fork
Key Concept
origin is a nickname for your fork (where you push); upstream is a nickname you add yourself for the original repo (where you pull updates from). This is what lets you stay current without ever needing write access to the source project.
Branch protection rules & Rulesets
On repos you do control, GitHub lets you stop force-pushes, accidental deletions, and unreviewed merges straight into important branches. This started as Branch Protection Rules and has since been extended by Rulesets — a newer, more flexible layer that can also target tags and apply across multiple branches at once by pattern.
| What | Branch Protection Rules | Rulesets |
|---|---|---|
| Scope | One exact branch name or pattern | Multiple branches/tags, combinable, with include/exclude patterns |
| Layering | One rule set applies at a time | Multiple rulesets can stack on the same branch |
| Visibility | Only visible to admins | Can be marked visible to all contributors, so people see why a push was blocked |
| Where it lives | Settings → Branches | Settings → Rules → Rulesets |
Common rules teams turn on
- Require a pull request before merging — nobody pushes straight to
main, not even admins. - Require approvals — e.g. at least 1–2 reviewers must approve before a PR can merge.
- Require status checks to pass — CI tests, linters, or build pipelines must succeed first.
- Require conversation resolution — every review comment thread must be marked resolved.
- Block force pushes — stops anyone from rewriting the shared branch's history.
- Restrict deletions — the branch can't be deleted, even by mistake.
- Require signed commits — every commit must be cryptographically signed to prove who really authored it.
Protecting main is one of the highest-value, lowest-effort things a team can set up on day one — it turns "please don't push directly to main" from a polite request into something GitHub actually enforces.
Topic 21: More Power Tools {#topic-21}
Lesser-known commands that pay off once projects get bigger
git bisect — binary-searching for the commit that broke things
When something used to work and now doesn't, but you don't know which of 50 commits caused it, bisect does a binary search through history for you, halving the search space each time.
Example (Terminal):
git bisect start
git bisect bad # current commit is broken
git bisect good v1.2.0 # this old tag was fine
# Git checks out a commit halfway between — test it, then say:
git bisect good # or: git bisect bad
# repeat until Git names the exact first-bad commit
git bisect reset # done — return to where you started
git tag — marking release points
Example (Terminal):
git tag v1.0.0 # lightweight tag — just a name on a commit
git tag -a v1.0.0 -m "First stable release" # annotated — stores author, date, message
git push origin v1.0.0 # tags aren't pushed automatically
git worktree — multiple branches checked out at once
Normally you can only have one branch checked out per repo folder. worktree gives you a second working directory linked to the same repo, so you can have two branches open side-by-side without stashing or cloning twice.
Example (Terminal):
git worktree add ../hotfix-dir hotfix/urgent-bug
git submodule — a repo inside a repo
Lets one Git repository include another repository as a subdirectory, pinned to a specific commit — common for shared libraries kept in their own repo.
Example (Terminal):
git submodule add https://github.com/org/shared-ui.git libs/shared-ui
git clone --recurse-submodules https://github.com/org/project.git # clone with submodules included
Git hooks — automation on every commit or push
Hooks are scripts stored in .git/hooks/ that Git runs automatically at points like pre-commit or pre-push — commonly used to run linters or tests before a commit is allowed through.
| Flag | Value Used | What it does |
|---|---|---|
| git bisect | good / bad | Binary-searches commit history to find exactly where a bug was introduced. |
| git tag | -a v1.0.0 | Marks a specific commit as a named release point. |
| git worktree | add |
Checks out another branch into a separate folder at the same time. |
| git submodule | add |
Nests another Git repo inside this one, pinned to a commit. |
| git blame | file | Shows who last changed each line of a file, and in which commit. |
| git log | --graph --oneline --all | Draws a compact ASCII graph of all branches and how they connect. |
You won't need most of these day-to-day — but knowing they exist means that when a specific hard problem shows up (which commit broke this? which branch touches this bug? I need two branches open at once), you'll recognize the right tool instead of reaching for a workaround.
Topic 22: README & Documentation {#topic-22}
The first thing anyone sees when they open your repo
A recruiter opens your GitHub repo. If all they see is index.html, style.css, script.js with no explanation, they'll likely close the tab in 10 seconds. A good README.md changes that completely — it's usually the first file people read, and GitHub automatically displays it on your repository's homepage.
Key Concept
Historically, developers named this file README to literally tell people "read me first." Today the standard filename is README.md — the .md extension stands for Markdown, a simple text format GitHub automatically turns into nicely styled documentation.
Think of it like buying a new washing machine — the manual inside the box explains setup, features, usage, and troubleshooting. Your README is that manual, but for your project.
How to add a README to your repo
Step 1 — Create the file
Example (Terminal):
touch README.md
Place it in the root of your project folder — the same level as package.json or index.html.
Step 2 — Write your content using Markdown
Fill it in using the Markdown syntax below (see the reference table), then save it.
Step 3 — Commit and push it like any other file
Example (Terminal):
git add README.md
git commit -m "Add project README"
git push
GitHub will now automatically render it on your repository's homepage — no extra setup needed.
Basic Markdown syntax reference
| Flag | Value Used | What it does |
|---|---|---|
| Headings | # ## ### | One # for the biggest heading, more #s for smaller ones — e.g. # Heading 1, ## Heading 2. |
| Bold | **text** | Renders as text. |
| Italic | *text* | Renders as text. |
| Bold + Italic | ***text*** | Renders bold and italic together. |
| Bullet List | - item | One dash per line creates an unordered list. |
| Numbered List | 1. item | Numbers followed by a period create an ordered list. |
| Code Block | ```javascript | Triple backticks (optionally with a language name) create a formatted code block. |
| Inline Code | git clone |
Single backticks highlight a short code snippet inline with text. |
| Link | text | Creates a clickable hyperlink. |
| Image | Displays an image inline — same as a link, with a ! in front. |
|
| Horizontal Line | --- | Three dashes on their own line create a divider between sections. |
Example (Markdown syntax examples):
# Heading 1
## Heading 2
**GitHub** # bold
*Git* # italic
***Git*** # bold + italic
- HTML
- CSS
- JavaScript
1. Install
2. Run
3. Enjoy
`git clone` # inline code
[GitHub](https://github.com)

---
Structure of a professional README
Project Title
↓
Description
↓
Features
↓
Tech Stack
↓
Installation
↓
Usage
↓
Folder Structure
↓
Screenshots / Live Demo
↓
Author & License
A complete example
Example (README.md):
# Portfolio Website
A personal portfolio website built using HTML, CSS and JavaScript.
## Features
- Responsive Design
- Dark Mode
- Smooth Animations
## Tech Stack
- HTML
- CSS
- JavaScript
## Installation
git clone https://github.com/you/portfolio.git
## Author
Your Name
Simple, clean, professional — and exactly the kind of thing a recruiter (or teammate) can understand in ten seconds.
README vs Wiki vs Documentation
| What | README | Wiki / Docs |
|---|---|---|
| Length | One file, quick overview | Multiple pages, in-depth |
| Where it lives | Repository homepage | Separate documentation section |
| Best for | First impressions, setup steps | API references, deployment guides, FAQs |
A good README should always include: project name, description, features, tech stack, installation steps, usage instructions, and author info. Screenshots, a live demo link, and a license are optional but make a strong extra impression.
Avoid a one-line README like "My Project" — it tells readers nothing. And remember to update it whenever the project changes significantly; an outdated README can be more confusing than none at all.
Topic 23: Best Practices {#topic-23}
The habits that separate professional Git usage from chaos
- Write commit messages that describe what changed —
"Fix mobile navbar overlap", not"update". - Keep
mainstable — always build features on a separate branch. git pullbefore you start working, to avoid unnecessary conflicts.- Keep Pull Requests small and focused — one feature at a time.
- Never commit
.env, API keys, or passwords. - Delete branches once they've been merged in.
- Keep your
README.mdup to date as the project grows.
Key Concept
Git rewards small, frequent, well-described commits and branches. The habits in this section are exactly what separates a messy solo project from a codebase a whole team can work in together.
About Backbencher Codes
Backbencher Codes shares coding notes and tools to help learners practise programming and understand development topics.