Git Refresher: The Foundation You're Already Standing On
Git Refresher: The Foundation You're Already Standing On
Before you can sprint, you need to walk. Before you can perform surgery, you need to understand anatomy. And before you can master advanced branching strategies—the meat of this book—you need a rock-solid understanding of how Git actually works under the hood. Not just the commands you'll type, but the mental model that makes those commands predictable and powerful.
Here's the uncomfortable truth: most developers use Git without fully understanding it. They know enough to get by—commit, push, pull, maybe merge when forced to—and then they're baffled when branches behave unexpectedly, when history becomes a tangled mess, or when `git revert` doesn't do what they "expected." The problem isn't intelligence; it's that Git's surface simplicity masks remarkable depth.
This chapter fixes that. We're going to rebuild your understanding of Git from the ground up, focusing on the concepts that matter most for branching. By the end, you'll have an intuitive grasp of how Git thinks, which will make every branching decision feel obvious rather than arbitrary.
Let's start with the three trees—Git's most fundamental concept.
Understanding the Three Trees: Git's Architecture
When you interact with Git, you're actually manipulating three distinct "trees," and understanding how changes flow between them is the key to understanding everything else.
A tree in Git terms is simply a collection of files and directories at a specific point in time. Git manages three of these:
The Working Directory: Your Sandbox
The working directory is where you do your actual work. It's the folder on your filesystem containing your project files—the code you're editing, the files you're creating, the documents you're writing. When you clone a repository or checkout a branch, Git populates this directory with the files from that specific point in history.
Think of the working directory as your sandbox. You can do anything here: break things, experiment wildly, write terrible code. Nothing in the working directory is "official" yet. Git isn't even really paying attention to most of your changes until you tell it to.
The Staging Area: Your Preparation Table
Between your working directory and Git's internal storage sits something called the staging area (technically called the "index" in Git's internals). This is where you put things before they're committed—hence the common command `git add`, which moves changes from the working directory into the staging area.
The staging area is Git's preparation table. You might edit five files, but decide that only three of them are ready for commit. Maybe you want to split your changes into multiple commits with separate purposes. The staging area lets you be deliberate about what goes into each commit.
This is also why the staging area confuses many developers. In most version control systems, there's no middle step—you edit files and they're simply "modified." The staging area is unique to Git and surprisingly powerful once you understand it.
The Repository: The Archive
The repository (or more precisely, the `.git` directory within your project) is where Git stores everything. It's the archive—the permanent record of your project's history. When you run `git commit`, you're taking the contents of the staging area and creating a permanent snapshot, linked to all the snapshots that came before it.
The repository is designed to be append-only. Once a commit exists, it cannot be changed. This immutability is what makes Git reliable and enables features like branching, merging, and history rewriting (which we'll discuss in depth).
Here's how these three trees interact:
```mermaid flowchart TD A[Working Directory<br/>Your sandbox<br/>Editable files] -->|git add| B[Staging Area<br/>The index<br/>Preparation table] B -->|git commit| C[Repository<br/>.git directory<br/>Permanent archive] C -->|git checkout / git switch| A C -->|git checkout / git switch| B
style A fill:#e1f5fe,stroke:#01579b style B fill:#fff3e0,stroke:#e65100 style C fill:#e8f5e9,stroke:#2e7d32 ```
Understanding this flow isn't academic—it directly impacts how branching works. When you switch branches, Git manipulates all three trees simultaneously, and understanding which tree you're affecting determines whether your changes survive the switch.
How Commits Create an Immutable Chain of Snapshots
Let's dive deeper into what actually happens during a commit, because commits are the atoms of Git's universe. Everything—branches, tags, history—revolves around commits.
What a Commit Actually Contains
A commit is more than just "the state of my files at this moment." It's a snapshot with ancestry. Each commit contains:
1. A snapshot of all tracked files at that moment (stored efficiently—unchanged files aren't duplicated) 2. Metadata: the author, the committer, the timestamp, the commit message 3. Pointers to its parent commits: one for a normal commit, two or more for a merge commit
This last piece—parent pointers—is crucial. Each commit knows which commit came before it. This creates a chain stretching back to the beginning of your repository. When you run `git log`, you're walking backward through this chain, seeing each snapshot in sequence.
``` commit a1b2c3d4 Author: Jane Developer <jane@example.com> Date: Thu Mar 13 14:32:01 2025
Add user authentication middleware
- Implement JWT validation
- Add rate limiting per endpoint
- Include request logging
commit e5f6g7h8 Author: Jane Developer <jane@example.com> Date: Wed Mar 12 09:15:42 2025
Set up Express server structure
- Configure middleware stack
- Add error handling
- Create health check endpoint
commit i9j0k1l2 Author: Jane Developer <jane@example.com> Date: Tue Mar 11 16:45:22 2025
Initial project setup
- Initialize npm project
- Add TypeScript configuration
- Create .gitignore
```
Notice how each commit points backward. The "Add user authentication" commit knows it came after "Set up Express server," which came after "Initial project setup."
The Power of Immutability
Once a commit exists, it cannot be changed. Not through normal operations, anyway. Git computes a hash (called a SHA or commit hash) based on the commit's contents, and that hash becomes the commit's identifier. Change any single bit in the commit—alter a character in the message, tweak a file—recalculate the hash, and you have an entirely different commit.
This immutability enables remarkable features:
- **Safe history**: You can always return to any previous state with confidence
- **Branching is cheap**: A branch is just a pointer to a commit; creating one costs virtually nothing
- **Merging is reliable**: Git can compare commits reliably because they're permanent records
- **Collaboration works**: Multiple people can work simultaneously because commits are shared facts
When we discuss branching strategies later, this immutability becomes the foundation. Branches are just pointers to commits, and commits are immutable. The entire structure of your project's history emerges from this simple design.
The Snapshot Mental Model
Most developers first encounter Git through a "diff" metaphor—they think of version control as recording changes, file-by-file, line-by-line. This mental model will trip you up when branching gets complex.
Instead, embrace the snapshot mental model: Git records complete snapshots of your project at each commit. Yes, internally Git is clever about storage—it doesn't duplicate unchanged files—but that's an optimization, not the fundamental operation.
When you branch, you're saying "I want to develop a new feature from this snapshot." When you merge, you're saying "I want to combine the changes from this snapshot and that snapshot." When you revert, you're saying "I want to create a new sn