N
Naveenr.dev
Chapter 01
11 min read2026-08-05
📖 Git SeriesChapter 01 · 11 chapters

Git Fundamentals: Staging, Commits, and the Repository Model

A folder named final_v2_ACTUAL_fixed_USE_THIS is what version control looks like when nobody's using version control. Understanding what init, add, and commit actually do — and why a staging area exists at all — is the difference between typing commands and using the tool.

Git staging area, commits, and the repository model
Git staging area, commits, and the repository model

Before I worked somewhere with a real Git setup, "version control" meant a folder full of report.docx, report_v2.docx, report_v2_final.docx, and report_v2_final_ACTUAL.docx.

Every one of those files is a full copy. Nobody remembers what changed between v2 and v2_final. If two people edit report_v2_final.docx at the same time, whoever saves last wins — the other person's work is just gone.

Git is a direct reaction to that exact problem. Once you see it that way, commands like add and commit stop feeling like rituals and start making obvious sense.

Git stores snapshots, not diffs

The assumption most people carry in from older tools: Git stores a list of diffs — commit A changed line 12, commit B changed line 40 — and reconstructs your file by replaying them forward.

That's how CVS and SVN worked. Git doesn't do this.

Every commit is a full snapshot of the entire project at that moment. If a file didn't change between two commits, Git doesn't store it twice — it points both commits at the same stored content, identified by a hash of that content.

text
Commit A  →  Snapshot of entire project
Commit B  →  Snapshot of entire project (unchanged files reused)
Commit C  →  Snapshot of entire project

The diffs you see in git log -p are computed on the fly by comparing two snapshots. Nothing diff-shaped is actually stored on disk.

Why does this matter? Checking out an old commit or switching branches doesn't mean replaying a sequence of diffs — it means swapping your working directory to match a snapshot. That's why Git can jump around history instantly regardless of how many commits separate two points.


git init — creating a repository

bash
git init

This creates a .git directory inside your project folder. That hidden folder is the repository — every commit, every branch, every bit of history lives inside it.

  • Delete .git and you've deleted the entire history. The rest of your project becomes an ordinary folder again. This is also why you never need to "install" anything in a project — if .git exists, it's already a repository.

The staging area — the part everyone skips past

This is the concept that trips people up the most coming from simpler tools.

Git doesn't go directly from "I edited a file" to "that edit is committed." There's a step in between called the staging area (also called the index).

text
Working Directory  →  Staging Area  →  Commit History
    (your files)       (git add)        (git commit)
text
git add <file>

git add doesn't commit anything. It copies the current state of <file> into the staging area — a separate holding space between your working directory and your commit history.

Why does this exist?

It lets you build a commit out of exactly the changes you want, not just "whatever happens to be different right now."

Real scenario: Say you're halfway through fixing a bug, and along the way you also reformatted an unrelated function because you noticed it was messy. Those are two different changes with two different purposes.

Without a staging area you'd have to commit them together or manually stash one aside. With it:

bash
git add src/payment-service.js   # just the bug fix
git commit -m "Fix null check on payment retry"

git add src/utils/formatting.js  # the unrelated cleanup
git commit -m "Reformat date utility functions"

Two clean, individually-reviewable, individually-revertable commits instead of one messy blob.

That matters a lot six months later when someone runs git blame on the formatting change and doesn't want to also read an unrelated payment fix.

Staging part of a file

bash
git add -p src/payment-service.js

This walks through each changed "hunk" in the file and asks whether to stage it. Genuinely useful when you've made two logically separate edits inside the same function and want them in separate commits.


Committing

bash
git commit -m "Fix null check on payment retry"

Takes whatever is currently staged and records it as a permanent snapshot, referenced by a commit hash. Most repositories use SHA-1 object IDs; Git also supports SHA-256 repositories.

It's not a sequential ID like "commit #47." Each commit stores:

  • A pointer to its parent commit(s) — this is what makes history a chain
  • Author, committer, and timestamp
  • The commit message
  • A reference to the full project snapshot

The most common beginner mistake: Editing three files, staging one, committing, and wondering later why the other two aren't in history. They were never staged. Anything you didn't git add first isn't in the commit, no matter how important it felt while you were writing it.


Three commands you'll run every single day

git status

bash
git status

Shows which files are staged, which are modified-but-not-staged, and which are untracked (Git has never seen them before).

Run this before every commit. It takes two seconds and catches "oh, I forgot to stage that file" every single time.

git log

bash
git log
git log --oneline   # one line per commit — use this one

Shows commit history. --oneline is what you actually want once the project has more than a handful of commits.

git diff

bash
git diff            # unstaged changes
git diff --staged   # what's about to be committed

I run git diff --staged right before git commit on anything I'm not 100% sure about. It shows exactly what's going in — no surprises.


Who are you, actually

bash
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

Every commit records a name and email. GitHub reads the email to match commits to a profile — get it wrong and your commits show up as an anonymous gray silhouette with correct code but no credit.

💡 If you work across personal and professional GitHub accounts on one machine, the email config is half the fix. SSH key routing is the other half — covered in the multiple accounts post.


.gitignore — things Git should never see

Not everything belongs in version control: build output, dependency folders, local environment files, IDE settings.

gitignore
node_modules/
dist/
.env
*.log
.DS_Store

Committing node_modules bloats the repo with megabytes of regeneratable files and creates diff noise on every dependency update.

Committing a .env with real credentials is a security incident. Once something's in Git history, deleting the file in a later commit does not remove it from history. Anyone who checks out an old commit can still read it.

If a secret gets committed:

  1. Rotate the credential immediately — treat it as compromised
  2. Scrub it from history using git filter-repo or BFG Repo-Cleaner
  3. Force-push the rewritten history and have everyone re-clone

Deleting the file going forward is not enough.


Putting it all together

Your files sit in the working directory. git add moves them into the staging area. git commit takes everything in the staging area and saves it as a permanent snapshot in Git history. That's the whole flow — three places, two commands to move between them.

The staging area is the concept that clicks everything else into place. Once you see it as "a place to assemble your next commit before you finalize it," git add stops being a mysterious required step and becomes obviously the right design.


Branching is next — everything in this post has been a single, linear line of commits. Real projects branch, and understanding what git branch actually creates (spoiler: it's not a copy of your project) is where Git stops feeling like magic.

Enjoyed this chapter?

Get an email when I publish the next chapter. No spam — just new technical deep-dives.

Comments

Share feedback or questions about this blog post.

No comments yet. Be the first to share your thoughts.