Git for Beginners: The Stuff That Actually Matters
A practical introduction to Git covering the commands you'll use daily, the mental model that makes it click, and how to fix common mistakes.
Git is confusing when you first start. The terminology is weird, the commands feel arbitrary, and half the explanations online assume you already know what a "remote tracking branch" is. Here's Git explained the way I wish it had been explained to me.
The Mental Model: Three Boxes
Think of Git as having three "boxes" on your machine:
- Working directory -- your actual files on disk. This is what you see in your editor.
- Staging area (also called "index") -- a holding area for changes you're about to commit. Think of it as a packing box.
- Repository -- the permanent history of all your commits.
git add), and then you commit them to history (git commit). This two-step process feels annoying at first but it's actually powerful -- it lets you commit only some of your changes while leaving others for later.
The Commands You Need
Starting a project
git init # Turn current folder into a git repo
git clone <url> # Download an existing repo
The daily workflow
git status # What's changed? What's staged?
git add file.js # Stage a specific file
git add . # Stage everything (use carefully)
git commit -m "Fix login bug" # Commit staged changes
git status is your best friend. Run it constantly. It tells you exactly what state your files are in.
Connecting to GitHub / remote
git remote add origin <url> # Link your local repo to a remote
git push -u origin main # Push commits to remote (first time)
git push # Push commits (after first time)
git pull # Download and merge remote changes
Branching
git branch feature-login # Create a new branch
git checkout feature-login # Switch to it
git checkout -b feature-login # Create AND switch (shortcut)
git branch # List all local branches
Branches are Git's killer feature. They let you work on something without messing up the main codebase. When you're done, you merge it back.
git checkout main # Switch back to main
git merge feature-login # Merge your branch into main
git branch -d feature-login # Delete the branch (it's merged, you're done)
Seeing history
git log --oneline # Compact commit history
git diff # What changed but isn't staged yet?
git diff --staged # What's staged but not committed?
Writing Good Commit Messages
Bad: "fixed stuff", "update", "asdfgh", "WIP"
Good: "Fix password reset email not sending", "Add dark mode toggle to settings page", "Remove deprecated API endpoint"
The convention is to write in the imperative mood -- "Fix bug" not "Fixed bug." Think of it as completing the sentence "This commit will ___."
Keep the first line under 72 characters. If you need more detail, leave a blank line and write a longer description:
git commit -m "Fix password reset for OAuth users
OAuth users don't have a password hash, so the reset flow was
crashing when trying to compare hashes. Now it checks the auth
method first and redirects OAuth users to their provider."
Common Mistakes and Fixes
"I committed to the wrong branch"
You made changes on main that should've been on a feature branch. If you haven't pushed yet:
git branch feature-oops # Create a branch from current state
git reset --hard HEAD~1 # Move main back one commit
git checkout feature-oops # Your commit is safe here
"I need to undo my last commit but keep the changes"
git reset --soft HEAD~1 # Uncommit, but changes stay staged
"I want to completely throw away uncommitted changes"
git checkout -- file.js # Discard changes to one file
git checkout -- . # Discard ALL uncommitted changes (careful!)
"I accidentally committed a secret / API key"
This is the big one. If you committed a .env file or an API key:
- If you haven't pushed:
git reset --soft HEAD~1, remove the secret from the file, add it to.gitignore, recommit. - If you already pushed: The secret is compromised. Rotate the key immediately. Then look into
git filter-branchor BFG Repo-Cleaner to scrub it from history.
.gitignore file before your first commit. At minimum:
.env
node_modules/
*.log
.DS_Store
"Merge conflict -- now what?"
Merge conflicts happen when two branches changed the same lines. Git marks the conflicting sections:
<<<<<<< HEAD
your version of the code
=======
their version of the code
>>>>>>> feature-branch
You manually edit the file to keep what you want, remove the markers, then:
git add resolved-file.js
git commit -m "Resolve merge conflict in resolved-file.js"
It's not as scary as it looks. Most conflicts are straightforward once you read both versions.
The .gitignore File
Create this in your project root. It tells Git which files to never track:
# Dependencies
node_modules/
venv/
__pycache__/
# Environment
.env
.env.local
# Build output
dist/
build/
out/
# OS junk
.DS_Store
Thumbs.db
Add this before your first commit. Retroactively ignoring files that are already tracked is annoying (you have to git rm --cached them).
What's Next
These commands cover probably 95% of what you'll do with Git day to day. The remaining 5% -- rebase, cherry-pick, stash, bisect -- you can learn as you need them.
The best way to get comfortable is to use Git on real projects. If you're working through coding exercises on CodeUp, keep your solutions in a Git repo. Every time you finish an exercise, commit it. You'll build Git muscle memory without even thinking about it, and you'll have a history of your progress to look back on.
One last thing: don't be afraid of Git. The whole point of version control is that you can always go back. As long as you've committed your work, it's very hard to permanently lose anything.