Previously, in Part 9, we rescued a deleted branch from the security-camera footage and let git bisect corner a bug that an innocent-looking commit message tried to hide.

Welcome to the finale. Today we open the hood — and I promise it is the friendliest engine you will ever look at. One idea in this post reorganizes everything you have learned so far, so let's start with it.

Commits are snapshots, not diffs

For ten parts you have watched Git show you diffs — little plus and minus lines — so it is natural to assume that is what Git stores. It is not. Every commit is a complete snapshot of your entire project. The diffs you see are computed on the fly by comparing two snapshots. Here is what actually lives inside your .git folder:

Commit e4f9… message · author parent: 2fc6… Tree the folder snapshot Blob Patient.cs contents Blob Home.razor contents Commit 2fc6… the previous snapshot
Every commit is a full snapshot: it points at a tree of your files and at its parent — a chain of photographs, not a pile of diffs.

A commit is a tiny object holding your name, your message, a pointer to its parent commit — that is the chain that makes history — and a pointer to one tree. The tree is a snapshot of a folder: a list of filenames, each pointing to a blob, which is the file's actual content. Trees can point to other trees for subfolders, and that is the whole system. Three kinds of objects, holding hands.

Every object is named by the SHA hash of its contents — a fingerprint. Change one character in one file and its blob gets a new fingerprint, so the tree pointing at it changes, so the commit changes. This has two lovely consequences. First, history is tamper-evident: nobody can quietly edit an old commit, because every fingerprint after it would stop matching. Second, unchanged files are simply shared between snapshots — same content, same fingerprint, stored once. That is why a thousand commits do not take a thousand times the space, and why switching branches is instant: a branch is just a sticky note with a commit's fingerprint on it, and checkout merely moves the note and unpacks that snapshot. Every "aha" from this series — cheap branches, safe rebases, the reflog resurrection — falls out of this one picture.

Tags and releases

You have already met two tags without ceremony: the workshop repo's v0.1 (the moment live search first worked — and our "good" state in Part 9) and v1.0. A tag is a sticky note that never moves — a bookmark saying "this exact snapshot is version 1.0." Run git tag in your clone to list them, and git show v1.0 to see where one points. There are two kinds: a lightweight tag (git tag v0.1) is just the note, while an annotated tag (git tag -a v1.0 -m "First stable release") carries a message, an author, and a date — use annotated for anything you release.

On GitHub, a tag can be dressed up as a Release: a page with release notes, downloadable files, and a friendly changelog. Push a tag, click "Create release from tag", and your project suddenly looks very grown-up.

A robot that tests every push

Here is a glimpse of where Git leads next. GitHub Actions lets you leave a small YAML file in your repo, and from then on a robot builds and tests every single push — yours, your teammates', everyone's. This is called continuous integration, and a taste of it looks like this:

name: Build and test
on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 10.0.x
      - run: dotnet build
      - run: dotnet test

That is the entire file. Break the build and the robot emails you before a human ever notices. The very site you are reading lives on GitHub and gets the same treatment — the cobbler's children have shoes now.

How teams arrange their branches

Once you join a team, someone will name a "branching strategy" in a meeting. Here is the secret: you already know all the moves. The strategies just choose how to combine them.

Branching strategies in plain words
StrategyIn one sentenceReach for it when
GitHub FlowBranch, open a PR, review, merge, deploy — repeatMost projects, most teams. The recommended default, and exactly what you have done since Part 7
GitFlowLong-lived develop and release branches beside masterYou ship numbered versions customers install
Trunk-basedEveryone merges tiny changes into master daily, behind feature flagsBig teams with a strong test robot

Careful: a question every developer eventually asks — "do I put my database in Git?" You do not version the data; you version the shape of it. Table definitions live as code — migration scripts, committed like any other file — so the schema has history, reviews, and rollbacks while the data itself stays in the database. There is a taste of schema-as-code thinking in the PostgreSQL series.

The daily habits that make it all stick

  • Commit small and often. Tiny commits are easy to review, easy to revert, easy to bisect.
  • Make the message say why. The diff already shows what changed — and Part 9 proved messages get read years later.
  • Pull before you push. Start from the latest shared history and conflicts stay small.
  • Branch for everything. Branches are sticky notes — free, instant, disposable.
  • Never force-push a shared branch. The Golden Rule from Part 8, wearing its work clothes.
  • Commit before letting an AI agent loose on your code. Yes, that post again — future you will be very glad there is a restore point.

Look how far you have come

Take a second and look back down the mountain. In Part 1 you were emailing yourself zip files named final-FINAL-v2. In Part 2 you typed git init for the first time and made a photo album out of a folder. By Part 7 you were reviewing code like a teammate, and in Part 9 you resurrected a deleted branch and out-detected a lying commit message. That is not a small journey. You went from zip files to time lord — you can branch timelines, rewrite drafts, rescue the lost, and interrogate history itself. Version control is a superpower precisely because it removes fear, and a fearless programmer learns everything faster.

Tip: the best next step is to use Git on something real, today. Build along with the Blazor series, or containerize a project with Docker — and commit every step of the way, because now you know what those commits are worth.

Thank you for climbing all ten parts with me. If there is a topic you want this site to explain next — Git or anything else — tell me through the contact page; the best posts here started as reader questions. Happy committing, time lord.