🗂️ The Staging Model, .gitignore & Remotes
Once you can commit and push, the next leap is understanding how Git actually moves your changes around. Git has three "trees" — your files, the staging area, and your history — and almost every command is really about shuffling changes between them. Master that mental model and the rest of Git stops feeling like magic incantations and starts feeling like a map you can read.
Reference & Extra Tutorials · Resources · Git Survival Kit
🎯 What This Covers
By the end of this reference, you will be able to:
- Describe Git's three trees — working directory, staging area (index), and repository
- Move changes precisely with
git addand pull them back withgit restore --staged - Write a
.gitignoreso build output, secrets, andnode_modulesnever get committed - Undo working-directory mistakes safely with
git restore - Clone an existing project and understand what cloning sets up for you
- Add, inspect, rename, and remove remotes with the
git remotefamily
Estimated Time: 45 minutes
Practice: Build a .gitignore for a Node project and practice staging then un-staging files.
In This Reference
The Three Trees
The single most useful thing you can learn about Git is that it manages three trees — three snapshots of your project that Git constantly compares. When you understand what each holds, git status reads like plain English and confusing commands click into place.
add and commit, and can be pulled back leftward with restore.| Tree | What it holds | Command that fills it |
|---|---|---|
| Working directory | The actual files you edit, exactly as they sit on disk | Your editor (Save) |
| Staging area (index) | A preview of your next commit — the changes you've marked to include | git add |
| Repository | Every committed snapshot, stored permanently in .git | git commit |
When git status says "Changes not staged for commit," it means the working directory differs from the staging area. "Changes to be committed" means the staging area differs from the last commit. It's always comparing two of the three trees.
Staging Precisely
Beginners reach for git add . every time, but the staging area's real power is selectivity — building a commit out of exactly the changes that belong together, even if you touched a dozen files.
git add index.html # stage one file
git add css/ js/ # stage whole folders
git add "*.js" # stage all JavaScript files
git add . # stage everything changed below the current folder
git add -p # review each change chunk and choose y/n interactively
The -p (patch) flag is a hidden gem. It walks you through each hunk of changes and asks whether to stage it. That lets you split a messy editing session into clean, single-purpose commits — stage the bug fix now, leave the half-finished feature for later.
💡 Why care about clean commits?
Six months from now, when a bug appears, you'll run git log looking for the change that caused it. If each commit does exactly one thing with a clear message, you'll find it in seconds. If every commit is "misc updates," your history is useless. Staging is how you keep it readable.
Un-staging & Restoring
Staged the wrong file? Edited something you wish you hadn't? Modern Git gives you two clear, purpose-built commands: git restore for undoing changes and git restore --staged for un-staging. (Older tutorials use git checkout and git reset for these; the newer verbs are less error-prone and are what you should reach for.)
Un-stage a file (keep your edits)
# Oops — added a file I didn't mean to include in this commit
git restore --staged secrets.env
# The edits are safe; the file is just no longer staged
Explanation: This moves a change from the staging area back to the working directory. Your file's contents are untouched — you've only changed Git's mind about the next commit.
Discard uncommitted edits (throw the changes away)
# Discard changes to a file, reverting it to the last commit
git restore index.html
⚠️ git restore <file> is destructive
Discarding working-directory changes cannot be undone — those edits were never committed, so Git has no copy to bring back. Only run it when you're sure you want the file reset to its last committed state. When in doubt, commit first (commits are always recoverable).
Reading the diagram: add pushes right, restore --staged pulls a change back out of staging, and restore <file> overwrites your working file with the committed version.
Keeping Junk Out: .gitignore
Not every file belongs in version control. Dependency folders, build output, editor settings, and — critically — secret credentials should never be committed. A .gitignore file tells Git which paths to pretend it can't see.
Create a file literally named .gitignore in your project root and list patterns, one per line:
# Dependencies — huge and re-installable, never commit these
node_modules/
# Build output
dist/
build/
# Secrets and environment variables
.env
.env.local
*.key
# OS and editor cruft
.DS_Store
.vscode/
Thumbs.db
# Logs
*.log
npm-debug.log*
Why it matters: committing node_modules/ bloats your repo with thousands of files anyone can reinstall in seconds with npm install. Committing a .env can leak API keys and database passwords to the entire internet the moment you push. The .gitignore is your first line of defense.
⚠️ .gitignore only ignores untracked files
If you already committed a file, adding it to .gitignore won't remove it — Git is already tracking it. Stop tracking it (while keeping it on disk) with:
git rm --cached .env
git commit -m "Stop tracking .env and ignore it"
And if that .env was ever pushed with real secrets, rotate those credentials — they must be treated as compromised.
✅ Don't write it from scratch
GitHub maintains battle-tested templates for every language at github.com/github/gitignore. When you create a repo on GitHub you can also pick a .gitignore template from a dropdown. Start from Node.gitignore and add your own lines.
Cloning an Existing Project
So far you've created repos from scratch. Just as often, you'll clone — download a complete copy of a repository that already exists on GitHub, including its entire history and its remote connection, all in one command.
git clone https://github.com/yourname/portfolio-site.git
# Clone into a folder with a different name
git clone https://github.com/yourname/portfolio-site.git my-copy
What cloning sets up for you: a new folder containing the project, a full .git history, and — importantly — a remote called origin already pointing at the URL you cloned from. So after a clone you can immediately git pull and git push with no git remote add needed. Cloning is init plus remote add plus a full download, bundled together.
💡 Clone vs. download ZIP
GitHub also offers a "Download ZIP" button, but that gives you a dead snapshot with no history and no remote — you can't commit, pull, or push. Always git clone when you intend to work on a project.
Managing Remotes
A remote is a named URL pointing at a hosted copy of your repo. Most projects have exactly one, called origin, but the git remote family lets you inspect and manage them.
git remote -v # list remotes with their URLs
git remote add origin URL # add a new remote named origin
git remote rename origin upstream # rename a remote
git remote remove origin # remove a remote
git remote set-url origin URL # point an existing remote at a new URL
Output — git remote -v
origin https://github.com/yourname/portfolio-site.git (fetch)
origin https://github.com/yourname/portfolio-site.git (push)
The set-url command is the one you'll want when switching a repo from HTTPS to SSH, or after transferring a repository to a new owner:
# Switch from HTTPS to SSH so you stop being prompted for credentials
git remote set-url origin git@github.com:yourname/portfolio-site.git
📖 What is "upstream"?
When you contribute to someone else's open-source project, you fork it (make your own GitHub copy) and clone that. Your fork becomes origin; the original project is conventionally added as a second remote called upstream, so you can pull in the maintainers' latest changes with git pull upstream main.
Linking a Folder to an Existing Repo
A common real situation: you already have an empty (or matching) repository on GitHub, and a local project folder you want to connect to it. The sequence is the same building blocks you now know:
# 1. Initialize Git in the project folder (if not already a repo)
git init
# 2. Stage and commit the project's current state
git add .
git commit -m "Initial project setup"
# 3. Point it at the existing GitHub repository
git remote add origin https://github.com/yourname/existing-repo.git
# 4. Push, setting the upstream link
git push -u origin main
If the remote already had commits (say a README you created on GitHub), step 4 is rejected. Reconcile the histories first:
git pull origin main --allow-unrelated-histories
# resolve any conflicts, then:
git push -u origin main
The --allow-unrelated-histories flag tells Git it's fine to merge two histories that started independently — exactly the case when both sides had their own first commit.
Practice & Quiz
🏋️ Exercise 1: Ignore, then un-stage
Goal: Prove to yourself that .gitignore works and that you can pull a file back out of staging.
💡 Hint
Create a .env and a node_modules/ folder, add both to .gitignore, and confirm git status no longer lists them. Then stage a real file with git add and un-stage it with git restore --staged.
✅ Solution
# Create files that should be ignored
echo "SECRET=abc123" > .env
mkdir node_modules && touch node_modules/lib.js
# Ignore them
printf ".env\nnode_modules/\n" > .gitignore
git status
# .env and node_modules/ do NOT appear — only .gitignore is untracked
# Stage a file, then change your mind
git add .gitignore
git restore --staged .gitignore # back to untracked, edits intact
git status
🏋️ Exercise 2: Split one mess into two clean commits
Goal: Use selective staging to turn two unrelated edits into two focused commits.
✅ Solution
# Suppose you edited both a bug in app.js and styles in style.css
git add app.js
git commit -m "Fix null check in cart total"
git add style.css
git commit -m "Increase heading contrast"
git log --oneline # two separate, self-describing commits
Each commit now does exactly one thing — future-you will thank present-you.
🎯 Quick Quiz
Question 1: What does the staging area (index) represent?
Question 2: You already committed .env by mistake. Adding it to .gitignore now will…
Question 3: After git clone, which remote is already set up for you?
Best Practices & Pitfalls
✅ Do
- Add a
.gitignorebefore your first commit, so junk never enters history - Start from GitHub's official template for your language, then customize
- Stage selectively with
git add -pto keep commits focused - Use
git restore --stagedto un-stage andgit restore <file>to discard — the clear modern verbs git cloneprojects you'll work on; never rely on "Download ZIP"
❌ Don't
- Commit
node_modules/, build output, or.envfiles - Run
git restore <file>on changes you might still want — it's irreversible - Assume adding a file to
.gitignoreremoves an already-tracked file - Leave every commit as "update" — a useless history is barely better than none
⚠️ A leaked secret is compromised forever
If you push a real password or API key, deleting it in a later commit is not enough — it remains in the history and in every clone. Rotate the credential (generate a new one) immediately, then remove the file from tracking. Prevention via .gitignore is far easier than the cleanup.
Summary
🎉 Key Takeaways
- Git manages three trees: working directory, staging area (index), and repository
git addmoves changes into staging;git restore --stagedpulls them back outgit restore <file>discards uncommitted edits — powerful and irreversible- A
.gitignorekeeps dependencies, build output, and secrets out of version control git clonedownloads a full repo and wires uporigin; thegit remotefamily manages those connections
📚 Additional Resources
- Pro Git — Recording Changes to the Repository
- git-scm.com — gitignore documentation
- GitHub Docs — Ignoring files
- git-scm.com — git restore documentation
🚀 What's Next?
You now understand how changes flow between Git's trees and how to keep your repo clean. Next we unlock Git's real superpower: branching and collaboration — creating parallel lines of work, merging them back together, and using GitHub pull requests to build software as a team.
🎉 Git stopped being magic
Three trees, one staging area, and a good .gitignore — that's the mental model every fluent Git user carries around.