Cloud Tech by Victor
DevOpsBeginner

Git Fundamentals

How the working directory, staging area, and commit history actually relate to each other, and why understanding that three-stage model makes branching, rebasing, and undoing changes stop feeling arbitrary.

Updated 2026-07-247 min read

Overview

Git tracks a project's history as a graph of immutable commits, each one a full snapshot of the project plus a pointer back to its parent, rather than storing changes as a linear list of diffs. Getting from a change you've made on disk into that graph goes through three distinct areas: the working directory (the files as you're editing them), the staging area (what you've explicitly told Git to include in the next commit, via git add), and the commit history (what git commit actually records). Almost every command that confuses newcomers - why git status shows a file as both staged and modified, why git restore and git restore --staged do different things, why a rebase "loses" commits that a revert wouldn't - makes sense once that three-stage model is the lens you're looking through, rather than treating Git as a single "save my changes" button.

Quick Reference

AreaWhat it holdsCommand that moves things into it
Working directoryFiles as they exist on disk right now(editing them directly)
Staging area (index)Exactly what the next commit will containgit add
Local repositoryPermanent, immutable commit historygit commit
RemoteThe shared copy other people push to and pull fromgit push / git pull

The commands below are grouped by what you're trying to do, not an exhaustive reference (see the official Git reference docs for every flag), but the set that covers nearly all day-to-day Git work.

CommandDescriptionCopy
git initInitialize a new, empty repository in the current directory.
git clone <url>Clone a remote repository into a new local directory.
git statusShow staged, unstaged, and untracked changes in the working directory.
git add <file>Stage a file's changes for the next commit.
git add .Stage every changed and new file in the current directory.
git commit -m "message"Commit staged changes to history.
git commit -am "message"Stage all changes to already-tracked files and commit, in one step.

Syntax

bash
git commit -m "<type>: <short summary>"
# e.g.
git commit -m "fix: handle empty response body in retry logic"
bash
git switch -c feature/checkout-retry
# ... make changes, commit ...
git push -u origin feature/checkout-retry

Examples

bash
git switch -c feature/rate-limit
git add lib/rate-limit.ts
git commit -m "feat: add token-bucket rate limiter"
git push -u origin feature/rate-limit
git log --oneline --graph --all

A compact, visual view of every branch's commit history and where they diverged - the fastest way to actually see what a merge or rebase is about to do.

git log --oneline --graph --all

Visual Diagram

Common Mistakes

  • Running git add . without checking git status first, accidentally staging generated files, secrets, or unrelated work-in-progress.
  • Rebasing a branch that other people have already pulled, which rewrites commit hashes and forces everyone else's local copy to diverge.
  • Using git reset --hard to "clean up" without realizing any uncommitted or unstashed work is discarded permanently, not moved anywhere recoverable.
  • Committing directly to a shared default branch (main) instead of a feature branch, making it impossible to review or roll back a change independently of everything else.
  • Force-pushing (git push --force) to a shared branch to "fix" a mistake, silently overwriting commits a teammate may have already based work on; --force-with-lease at least fails safely if the remote has moved since you last fetched.

Performance

  • git status and git add on a very large repository can be slow if the working directory contains many untracked files Git has to scan; a well-maintained .gitignore keeps that scan fast.
  • Shallow clones (git clone --depth 1) fetch only the most recent history, dramatically speeding up CI checkouts of large repositories where full history isn't needed.
  • Repository size grows with every large binary ever committed, even after the file is deleted, because history is immutable; binaries belong in Git LFS or object storage, not the repository itself.

Best Practices

  • Commit small, focused changes with descriptive messages; a commit should represent one logical change, not "end of day checkpoint."
  • Use feature branches for anything beyond a trivial fix, and merge or rebase into the default branch through a reviewed pull/merge request.
  • Prefer git revert over git reset --hard for undoing anything that has already been pushed and could be shared by others.
  • Keep a .gitignore current for build artifacts, dependencies, and local environment files, so git status/git add . only ever surfaces real changes.
  • Write commit messages that explain why, not just what - the diff already shows what changed; the message is where the reasoning that won't survive in the code belongs.

Interview questions

What is the difference between the working directory, the staging area, and a commit in Git?

The working directory is the actual files on disk, whatever state you've left them in. The staging area (the "index") is a snapshot of exactly what will go into the next commit; `git add` copies changes from the working directory into it, one file or hunk at a time, which is why you can commit only part of what you've changed. A commit is a permanent, immutable snapshot of the staging area at the moment you ran `git commit`, plus a pointer to its parent commit, which is what forms the project's history graph. Understanding that staging is a separate, explicit step - not just "what's changed" - explains why `git status` shows both staged and unstaged changes for the same file.

What is the practical difference between `git merge` and `git rebase`?

Both bring one branch's commits into another, but they produce different history shapes. `git merge` creates a new merge commit with two parents, preserving exactly how the branches diverged and came back together; nothing is rewritten, which is why merge is safe on shared/public branches. `git rebase` replays your branch's commits one by one on top of the target branch's tip, producing a linear history with no merge commit, but every replayed commit gets a new hash. That rewriting is why rebase should be avoided on branches other people have already pulled; their history and yours will diverge as soon as they fetch the rewritten commits.

What is the difference between `git reset` and `git revert`, and when should you use each?

git reset moves the current branch pointer (and optionally the staging area and working directory) to a different commit, effectively rewriting history as if the reset-past commits never happened on this branch - fine for commits that only exist locally and haven't been pushed. git revert creates a brand new commit that applies the inverse of a previous commit's changes, leaving history intact and additive. Because it doesn't rewrite anything, revert is the safe choice for undoing a commit that's already been pushed and pulled by others; reset --hard on shared history causes exactly the same divergence problem as a rebase on a shared branch.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement