Master Git & GitHub, properly.
From “why version control” to rebasing, pull requests, branching strategies, and the interview questions that trip people up β one resource, followed start to finish.
Jump to a chapter
Why version control?
Before you learn Git’s commands, it helps to feel the pain it removes. Every developer who has named a file report_final_v2_ACTUALLY_final.doc already understands the problem.
Software changes constantly β and rarely by one person alone. Without a system to track those changes, you hit walls fast: you overwrite a teammate’s work, you can’t tell what changed or why, you break something and can’t get back to the version that worked, and “who touched this line and when” becomes a mystery. A version control system (VCS) is the tool that records every change to your code over time, so you can review it, undo it, branch off it, and collaborate on it safely.
What a VCS gives you
blame shows exactly which change introduced a line β invaluable for debugging and reviews.Three generations of version control
Understanding how we got to Git makes its design click:
- Local VCS β early tools tracked versions only on your own machine. Fine solo, useless for teams.
- Centralized VCS (CVCS) β tools like Subversion (SVN) and CVS keep history on one central server. Everyone checks code out from it. Better for teams, but the server is a single point of failure, and you need a connection to do almost anything.
- Distributed VCS (DVCS) β Git (and Mercurial) give every developer a full copy of the entire repository, history and all. You can commit, branch, and view history completely offline, and there’s no single point of failure. This is why Git won.
Git vs GitHub β the confusion, cleared
This is the single most common beginner mix-up, and it comes up in interviews constantly. They’re related but completely different things.
Put simply: Git is the engine; GitHub is one of the garages you can park it in. You can use Git entirely on your own with no GitHub at all. And GitHub isn’t the only option β its main alternatives are GitLab and Bitbucket, which host Git repositories too and add their own features.
| Git | GitHub | |
|---|---|---|
| What it is | A version control tool | A hosting & collaboration platform |
| Runs | Locally on your machine | In the cloud (a website) |
| Needs internet? | No | Yes |
| Needs an account? | No | Yes |
| Adds | Commits, branches, merges, history | Pull requests, issues, Actions, wikis, teams |
| Alternatives | Mercurial, SVN (different model) | GitLab, Bitbucket, Azure DevOps |
How Git works: architecture & internals
Most Git confusion disappears the moment you understand its three areas and what a commit really is. This is the mental model that makes every command make sense.
The three areas
Git organizes your work into three “areas.” Files move between them as you work, and the two commands you’ll use most β git add and git commit β are simply how you move changes from one area to the next.
β¦and git push sends committed snapshots to a remote like GitHub Β· git pull brings theirs back
- Working directory β the real folder of files on your disk that you edit.
- Staging area (the index) β a preview of your next commit. You
git addthe specific changes you want to include, which lets you craft clean, focused commits instead of dumping everything at once. - Repository β the hidden
.gitdirectory where Git permanently stores your committed history. When yougit commit, the staged snapshot is saved here forever.
What is a commit, really?
A crucial idea that surprises people: Git does not store diffs β it stores snapshots. Each commit is a complete snapshot of what all your tracked files looked like at that moment. (Under the hood Git is smart about not duplicating unchanged files, but conceptually, think snapshots, not deltas.)
Every commit is identified by a unique SHA-1 hash (a 40-character fingerprint like a1b2c3dβ¦) computed from its contents. Change anything and the hash changes. Each commit also records its parent commit, which is how Git chains commits into a history you can walk backward through.
The object model
Internally, Git is a simple content-addressable database built from just a few object types:
So a commit points to a tree (your whole project’s folder structure), which points to blobs (file contents) and sub-trees. Everything is addressed by the hash of its content, which is what makes Git so fast and tamper-evident.
Refs and HEAD
Commit hashes are unwieldy, so Git uses friendly pointers called refs:
- A branch (like
main) is just a lightweight, movable pointer to a commit. That’s the whole secret β this is why branching in Git is instant and cheap. - HEAD is a pointer to where you are right now β usually the branch you currently have checked out. When you commit, HEAD’s branch moves forward to the new commit.
- A tag is a fixed pointer to a specific commit, typically used to mark releases like
v1.0.
Terminology & glossary
The words you’ll meet everywhere in Git. Learn these and the documentation β and the interviews β read easily.
.git history.Install & first-time setup
A five-minute setup you do once per machine. Getting your identity and SSH keys right now saves headaches later.
Install Git
- Windows β download “Git for Windows” from git-scm.com; it includes Git Bash, a handy terminal.
- macOS β run
git --versionto trigger the developer tools install, or use Homebrew:brew install git. - Linux β
sudo apt install git(Debian/Ubuntu) orsudo dnf install git(Fedora).
Tell Git who you are
Every commit is stamped with a name and email. Set them once, globally:
git config --global user.name "Your Name" git config --global user.email "you@example.com" # Make "main" the default branch name for new repos git config --global init.defaultBranch main # Set your preferred editor (e.g. VS Code) git config --global core.editor "code --wait" # Check everything git config --list
Connect to GitHub with SSH
SSH keys let you push and pull without typing a password every time. Generate a key, then add the public half to GitHub (Settings β SSH and GPG keys).
# Generate a new key ssh-keygen -t ed25519 -C "you@example.com" # Copy the PUBLIC key, then paste it into GitHub settings cat ~/.ssh/id_ed25519.pub # Test the connection ssh -T git@github.com
https:// instead and authenticate with a Personal Access Token (PAT) or the GitHub CLI. SSH is just less typing day to day.The everyday workflow
Ninety percent of your Git life is one short loop: change files, stage them, commit them, push them. Master this and you’re productive.
The lifecycle of a file
Every file Git tracks moves through a few states. Knowing which state a file is in tells you what to do next:
Start a repo β two ways
# A) Turn an existing folder into a repo git init # B) Copy an existing repo from GitHub git clone git@github.com:user/project.git
The core loop
git status # what's changed? (run this constantly) git add index.html # stage one file... git add . # ...or everything that changed git commit -m "Add contact form" # save a snapshot git push # send commits to the remote
Seeing what’s going on
git status # current state of working tree & staging git diff # unstaged changes, line by line git diff --staged # what you're about to commit git log --oneline --graph --all # compact, visual history git show HEAD # details of the latest commit
git status before and after almost everything. It’s free, it’s fast, and it stops most mistakes before they happen.Branching & merging
Branching is Git’s superpower. Because a branch is just a movable pointer to a commit, creating one is instant β and it changes how you work.
The idea: keep your stable code on main, and do every new feature or fix on its own branch. You can experiment freely; if it works, you merge it back; if it doesn’t, you throw the branch away and main was never touched.
# Create a branch and switch to it (modern syntax) git switch -c feature/login # (older equivalent: git checkout -b feature/login) # ...do your work, add and commit as usual... git switch main # go back to main git merge feature/login # bring the feature in git branch -d feature/login # clean up the merged branch
Two kinds of merge
- Fast-forward merge β if
mainhasn’t moved since you branched, Git simply slidesmain‘s pointer forward to your branch’s latest commit. No merge commit, perfectly linear history. - Three-way merge β if both branches have new commits, Git combines them and creates a new merge commit with two parents, tying the histories together.
git branch # list local branches git branch -a # include remote branches git switch main # switch branches git branch -m old new # rename a branch git branch -D feature/x # force-delete (even if unmerged)
Merge conflicts, demystified
Conflicts scare beginners, but they’re just Git being honest: two branches changed the same lines, and Git won’t guess which one you meant. You decide.
When a merge can’t be done automatically, Git pauses and marks the clashing spot inside the file with conflict markers:
<<<<<<< HEAD const greeting = "Hello there!"; // your current branch's version ======= const greeting = "Hi, welcome!"; // the incoming branch's version >>>>>>> feature/login
How to resolve one, step by step
- Run
git statusto see which files conflict. - Open each file and find the
<<<<<<<markers. - Edit the file to the version you actually want β keep one side, the other, or blend them β then delete all three marker lines.
- Stage the resolved file with
git add. - Complete the merge with
git commit(Git pre-fills a message).
git status # lists "both modified" files # ...edit files, remove markers, keep what you want... git add greeting.js # mark it resolved git commit # finish the merge # Changed your mind mid-merge? Abort and start over: git merge --abort
main for weeks.Rebasing & rewriting history
This is where Git gets powerful β and where you can hurt yourself if you skip the one golden rule. Take it slow; it’s worth it and it’s a favorite interview topic.
Merge vs rebase
Both integrate changes from one branch into another; they differ in the history they leave behind.
- Merge keeps history exactly as it happened and adds a merge commit. Honest and safe, but can get tangled with many small merges.
- Rebase re-plays your branch’s commits on top of another branch, as if you’d started from its latest commit. Result: a clean, linear history β but the commits are rewritten (they get new hashes).
git switch feature/login git rebase main # replay my commits on top of main # resolve any conflicts, then: git rebase --continue
Interactive rebase β polish your commits
Interactive rebase lets you rewrite a series of your own commits before sharing them β squash several into one, reword messages, reorder, or drop them entirely.
git rebase -i HEAD~3 # An editor opens; change "pick" to: # squash (s) β fold this commit into the one above # reword (r) β keep the commit, edit its message # drop (d) β delete this commit entirely # edit / reorder lines to reorder commits
Cherry-pick & amend
# Copy one specific commit onto your current branch git cherry-pick a1b2c3d # Fix the most recent commit (message or forgotten file) git add forgotten-file.js git commit --amend
git reflog records everywhere HEAD has been, so you can find the old commit’s hash and recover it. Git rarely deletes anything for ~30 days.Undoing mistakes
Everyone makes them. This is the section you’ll bookmark and return to. The key skill is knowing which undo to reach for.
Before you’ve committed
git restore file.js # discard unstaged edits to a file git restore --staged file.js # unstage (keep the edits) git restore . # discard ALL unstaged changes (careful!) git clean -fd # delete untracked files & folders
Fixing commits
# Fix the LAST commit's message or contents git commit --amend # Undo last commit, KEEP changes staged git reset --soft HEAD~1 # Undo last commit, KEEP changes but unstage them (default) git reset HEAD~1 # Undo last commit and THROW AWAY the changes (dangerous) git reset --hard HEAD~1 # Safely undo a commit by making a NEW opposite commit # (the right choice for shared/pushed history) git revert a1b2c3d
reset vs revert β the interview classic
| git reset | git revert | |
|---|---|---|
| What it does | Moves the branch pointer back, rewriting history | Adds a new commit that undoes an old one |
| History | Rewritten β old commits disappear from the branch | Preserved β the undo is part of history |
| Safe on shared branches? | No β never on pushed history | Yes β designed for it |
| Use when | Cleaning up local, unpushed work | Undoing something already public |
git reflog, find the hash of the commit you want back, and git reset --hard <hash> (or git checkout <hash>) to recover it. This has saved countless careers.Remotes & collaboration
A remote is just another copy of your repo living somewhere else β usually GitHub. Three verbs keep you in sync: fetch, pull, and push.
fetch + merge in one step β grabs remote changes and integrates them into your branch.git remote -v # list remotes and their URLs git remote add origin git@github.com:you/repo.git git push -u origin main # push & set upstream (first time) git fetch origin # see remote changes safely git pull # fetch + merge into current branch
git fetch followed by reviewing changes, rather than a blind git pull. It’s the same result with a moment to look before you leap. Add git pull --rebase to keep history linear.The forking workflow (open source)
You usually can’t push directly to someone else’s repo. Instead you fork it (make your own copy on GitHub), clone your fork, push changes to it, and open a pull request back to the original. Keep your fork current by adding the original as an upstream remote:
git remote add upstream git@github.com:original/repo.git git fetch upstream git switch main git merge upstream/main # pull the original's latest into your fork git push origin main
Pull requests & code review
The pull request (PR) is the heart of collaboration on GitHub. It’s not a Git feature β it’s a GitHub one β and it’s how teams review and discuss changes before they land.
A pull request is a proposal: “here are the commits on my branch β please review them and merge them into main.” It opens a dedicated page where teammates can read the diff, comment on specific lines, request changes, approve, and watch automated checks (tests, linters) run before anything merges.
The PR lifecycle
- Create a branch, commit your work, and push it to GitHub.
- Open a pull request from your branch into
main(GitHub usually shows a prompt). - Write a clear title and description β what changed and why. Link any related issue (e.g. “Closes #42”).
- Reviewers comment, request changes, or approve. You push follow-up commits to address feedback β the PR updates automatically.
- Once approved and checks pass, someone merges it. Delete the branch.
Three ways to merge a PR
| Merge type | What it does | Best for |
|---|---|---|
| Merge commit | Keeps every commit and adds a merge commit | Preserving full, honest history |
| Squash & merge | Combines all PR commits into one clean commit | A tidy, one-commit-per-feature main branch |
| Rebase & merge | Replays commits onto main with no merge commit | A strictly linear history |
Do it from the terminal (GitHub CLI)
gh pr create --title "Add login form" --body "Closes #42" gh pr status # see your PRs gh pr checkout 123 # check out someone's PR to test it gh pr merge 123 --squash
Issues & project management
Issues are how GitHub turns a code repository into a place to plan work, track bugs, and have conversations β no separate tool required.
An issue is a tracked item: a bug report, a feature request, a question, or a task. Each has a title, a description, a comment thread, and a set of tools to organize it:
bug, enhancement, or good first issue to categorize and filter.Link issues to code
The magic connection: mention an issue number in a commit or PR and GitHub links them automatically. Use a closing keyword and merging the PR will close the issue for you.
git commit -m "Fix null crash on empty cart" # In the PR description, write: # Closes #42 (also: Fixes #42, Resolves #42) # β merging the PR auto-closes issue #42
Branching strategies
Once a team shares a repo, they need an agreed way to use branches. There’s no single “right” answer β the best strategy depends on how you ship.
GitHub Flow
The simplest modern approach: main is always deployable. For any change, branch off main, open a pull request, get it reviewed, and merge. Deploy continuously. That’s it.
Git Flow
A structured model with long-lived main (production) and develop (integration) branches, plus dedicated feature, release, and hotfix branches. Powerful but heavier, with more ceremony around each release.
Trunk-Based Development
Developers integrate into a single main (“the trunk”) at least daily, using very short-lived branches and feature flags to hide unfinished work. Minimizes merge pain and maximizes integration.
Command cheat sheet
The commands worth keeping within reach, grouped by what you’re trying to do. Bookmark this section.
-u sets the upstream on first push.Real-world scenarios
The situations you’ll actually face, and exactly what to type. From your first push to untangling a mistake under pressure.
Start a project and push it to GitHub
Create the repo on GitHub (empty), then connect your local folder to it.
git init git add . git commit -m "Initial commit" git remote add origin git@github.com:you/project.git git push -u origin main
Work on a feature without breaking main
Branch, build, merge back. Your stable code is never at risk.
git switch -c feature/search # ...work, add, commit... git push -u origin feature/search # open a Pull Request on GitHub, get it reviewed & merged
Contribute to an open-source project
Fork it, clone your fork, branch, push, and open a PR to the original.
# after clicking "Fork" on GitHub: git clone git@github.com:you/their-project.git git switch -c fix/typo-in-readme # ...make the change, commit... git push -u origin fix/typo-in-readme # open a PR from your fork β the original repo
You committed to the wrong branch
Move the last commit to where it belongs.
# you're on main but meant to commit to a feature branch git switch -c feature/right-place # bring the commit with you git switch main git reset --hard HEAD~1 # remove it from main
Undo a bad change that’s already public
Never rewrite shared history β revert instead, creating a clean, honest undo.
git revert a1b2c3d # makes a new commit that undoes a1b2c3d git push
Interview questions
The Git & GitHub questions that come up again and again, with concise answers you can say out loud. Tap any question to reveal the answer.
What’s the difference between Git and GitHub?
Explain the three areas / states in Git.
git add), and the repository (committed history in .git, written with git commit). Staging lets you craft focused commits.Does Git store diffs or snapshots?
What is HEAD? What’s a detached HEAD?
Merge vs rebase β what’s the difference?
git fetch vs git pull?
fetch downloads remote changes but doesn’t touch your working files. pull is fetch + merge β it downloads and integrates in one step. Fetch is the safer “look first” option.git reset vs git revert?
reset moves the branch pointer back and rewrites history (use only on local, unpushed work). revert creates a new commit that undoes a previous one, preserving history β the safe choice for shared branches.What do soft, mixed, and hard reset do?
--soft moves HEAD but keeps changes staged. --mixed (default) keeps changes but unstages them. --hard discards the changes entirely. Hard is the destructive one.How do you undo the last commit?
git reset --soft HEAD~1. To just fix it: git commit --amend. If already pushed: git revert <hash> to add a safe undo commit.What is a fast-forward merge?
What is git cherry-pick?
What does git stash do?
git stash pop brings them back. Handy when you’re mid-change and need to jump to something urgent.How do you resolve a merge conflict?
<<<<<<<, =======, >>>>>>>. Open the file, edit it to the version you want, delete the markers, then git add the file and git commit to complete the merge.Forking vs cloning?
What is “origin”?
upstream).What is a pull request?
What goes in a .gitignore file?
node_modules/), logs, environment/secret files, and OS/editor junk. It keeps the repo clean and prevents accidentally committing secrets.How would you recover a “lost” commit?
git reflog to find the commit’s hash in HEAD’s history, then git reset --hard <hash> or git checkout <hash>. Git keeps unreferenced commits for about 30 days, so they’re rarely truly gone.What is git squash and why use it?
git rebase -i) or “Squash & merge” on a PR. It produces a cleaner history β one tidy commit per feature instead of many “wip” commits.Explain a branching strategy you know.
main is always deployable; every change goes on a short-lived branch, through a reviewed pull request, then merges and deploys. Simple and continuous. Alternatives include the more structured Git Flow and integration-heavy trunk-based development.This is a sensitive-free technical topic β but if you’re preparing for a real interview, the best follow-up is to actually run each command on a throwaway repo. Muscle memory beats memorization.