Skip to main content

🗂️ 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 add and pull them back with git restore --staged
  • Write a .gitignore so build output, secrets, and node_modules never 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 remote family

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.

The three trees of Git: working directory, staging area, and repository, connected by add and commit Working Directory files you edit on disk right now Staging Area the "index" next commit preview Repository .git history committed snapshots git add git commit git restore / git checkout
Changes flow rightward with add and commit, and can be pulled back leftward with restore.
TreeWhat it holdsCommand that fills it
Working directoryThe actual files you edit, exactly as they sit on diskYour editor (Save)
Staging area (index)A preview of your next commit — the changes you've marked to includegit add
RepositoryEvery committed snapshot, stored permanently in .gitgit 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).

graph LR A["Working directory"] -->|git add| B["Staging area"] B -->|git restore --staged| A C["Last commit"] -->|git restore file| A

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.

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 .gitignore before your first commit, so junk never enters history
  • Start from GitHub's official template for your language, then customize
  • Stage selectively with git add -p to keep commits focused
  • Use git restore --staged to un-stage and git restore <file> to discard — the clear modern verbs
  • git clone projects you'll work on; never rely on "Download ZIP"

❌ Don't

  • Commit node_modules/, build output, or .env files
  • Run git restore <file> on changes you might still want — it's irreversible
  • Assume adding a file to .gitignore removes 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 add moves changes into staging; git restore --staged pulls them back out
  • git restore <file> discards uncommitted edits — powerful and irreversible
  • A .gitignore keeps dependencies, build output, and secrets out of version control
  • git clone downloads a full repo and wires up origin; the git remote family manages those connections

📚 Additional Resources

🚀 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.