Git, Branches, and Reviewable Change History

LESSON

Backend Development Foundations

003 25 min beginner

Git, Branches, and Reviewable Change History

By the end of this lesson, you will be able to...

  • Explain how commits, branches, diffs, and pull requests make backend changes inspectable.

  • Split one risky backend task into reviewable units of change.

  • Identify what evidence a reviewer needs before trusting a change to API behavior, database state, startup, or deployment.

Idea in one sentence: Git is not just a save button; it is a way to design change history so future people can see what changed, why it changed, and what risk was accepted.

Core Insight

The orders API now has a request path and a startup contract.

The next problem is not how to run it. The next problem is how to change it without making the system harder to trust.

Imagine the team wants to add delivery_window to each order. The product request sounds small:

Customers should see the delivery window for an order.

The backend change is not one tiny edit. It touches several boundaries:

database schema
  -> application model
  -> API response
  -> tests
  -> deployment order
  -> rollback plan

One developer makes all edits in one long session and opens a pull request:

commit: update orders
files changed: 37

The tests pass. That is good. But the reviewer is still stuck. Which lines change persistent database state? Which lines change the API contract? Which lines are only formatting? Can old clients tolerate the new response? Can the migration deploy before the code? What happens if the app must roll back?

The naive idea is:

Commit when the code works.

That is better than no history. It is not enough for backend work.

Backend changes often affect shared state and external promises. A reviewable history turns one pile of edits into a sequence of decisions that another person can inspect.

The Promise We Need To Keep

Git gives you several pieces:

Those pieces matter because backend work crosses a boundary:

private change -> shared system history

Before a change merges, it belongs mostly to the author. After it merges, teammates, CI, deployment tools, incident responders, and future maintainers treat it as part of the system's record.

Plain meaning:

Reviewable history means the change is recorded in pieces that match the reasons and risks behind the work.

In this scenario:

Adding delivery_window should make the schema change, API change, compatibility evidence, and tests visible enough for a reviewer to trust them.

Technical name:

One useful term is atomic commit. In practical backend work, an atomic commit is a commit with one coherent reason. It may touch multiple files, but those files support the same change.

Atomic does not mean "one line." It means "one reason."

The Naive Design

Start with the unreviewable version:

branch: delivery-window

commit 1: update orders
  - add database migration
  - update order model
  - change API response
  - update serializer
  - adjust validation
  - add tests
  - reformat unrelated files
  - rename a helper

The branch may work. The problem is that the review unit is too large.

The reviewer has to answer many questions at once:

This is where Git becomes more than storage. Git is the tool you use to shape the review surface.

A Better Change Shape

Now design the branch as a sequence of reviewable steps:

branch: delivery-window

commit 1: add nullable delivery_window column
commit 2: read and write delivery_window in the order model
commit 3: include delivery_window in the API response
commit 4: add compatibility tests for old and new orders
commit 5: document rollout and rollback notes in the PR

Each commit asks a smaller question.

Commit 1:

Does the schema change preserve existing rows?

Commit 2:

Does application code handle the field without breaking old data?

Commit 3:

Does the API response change match the intended contract?

Commit 4:

Do tests prove both old and new behavior?

Commit 5:

Can a reviewer understand deployment order and rollback risk?

This design is not about pleasing Git. It is about reducing cognitive load for review. A reviewer can inspect one risk at a time.

Check: A commit changes a database migration and also reformats unrelated files. What should the reviewer ask for first?

Think first, then reveal.

Answer: Ask to split the unrelated formatting from the migration work. The migration changes persistent state, and the reviewer needs to inspect that risk without unrelated noise.

A Worked Review Trace

Follow one pull request from messy to reviewable.

Starting point:

Task:
  Add delivery_window to orders.

Current branch:
  one commit named "update orders"

Naive diff shape:

files changed:
  db/migrations/20260701_add_delivery_window.sql
  app/models/order.py
  app/api/orders.py
  app/serializers/order_json.py
  tests/test_orders_api.py
  tests/test_order_model.py
  app/utils/date.py
  README.md
  many files with formatting only

Intermediate decision:

Separate by reason, not by file count.

Reviewable branch shape:

commit 1: add nullable delivery_window column
  db/migrations/20260701_add_delivery_window.sql
  tests/test_order_migration.py

commit 2: support delivery_window in order model
  app/models/order.py
  tests/test_order_model.py

commit 3: expose delivery_window in orders API
  app/api/orders.py
  app/serializers/order_json.py
  tests/test_orders_api.py

commit 4: remove unrelated formatting from this branch
  no behavior change, or move to a separate PR

Output:

The reviewer can inspect storage risk, application behavior, API contract, and noise separately.

Naive failure contrast:

"The tests pass, so the PR is easy to review."

Better model:

Passing tests are evidence. They are not the whole review.
The branch shape should show what risk each test is proving.

So far, we have turned a pile of edits into a path of decisions. That same habit will matter in later backend lessons when changes affect dependencies, data models, migrations, CI, deployment, and observability.

Reading The Diff Like A Reviewer

The diff is the reviewer's main artifact. It shows what changed. It does not automatically explain why.

For example, this line looks small:

+ ALTER TABLE orders ADD COLUMN delivery_window TEXT;

But it raises real backend questions:

Persistent state changes are special. Code can often be reverted by deploying old code. Database state may not return to its exact previous shape just because one commit is reverted.

A useful pull request description makes this visible:

Change:
  Add optional delivery_window to orders.

Deployment order:
  1. Apply nullable column migration.
  2. Deploy code that reads and writes delivery_window.
  3. Expose delivery_window in the API response.

Compatibility:
  Existing orders have delivery_window = null.
  Existing clients may ignore the extra JSON field.

Evidence:
  Migration test covers existing rows.
  API test covers old and new response shapes.
  Contract note says the field is optional.

Rollback:
  Old code ignores the nullable column.
  Do not drop the column during an emergency rollback.

This is not bureaucracy. It is boundary evidence for a change. The reviewer can now see the state transition, the API promise, and the operational risk.

Branches Are Drafts Of Shared History

A branch gives your work a temporary line of history.

main
  A---B---C
           \
delivery-window
            D---E---F

While exploring, rough commits are normal:

try stuff
fix test
more fixes
oops

Before review, the branch should become a clearer story:

add nullable delivery_window column
support delivery_window in order model
expose delivery_window in API response
add compatibility tests

The exact Git commands are less important than the design goal. Review should inspect deliberate change, not your scratchpad.

There is a boundary here too. Cleaning up your own unpublished review branch is usually fine. Rewriting history that other people have based work on can confuse them. The safe beginner rule is:

Clean your own branch before review.
Be careful with history other people already depend on.

Check: Should every tiny syntax correction be its own commit?

Think first, then reveal.

Answer: No. Reviewability is about coherent reasons, not maximum commit count. A syntax correction can belong with the change it supports. It should become separate when it creates a different risk or distracts from the main review.

What Review Protects

Code review is not only a style check. In backend work, review protects system boundaries.

For the delivery_window change, different reviewers may look for different risks:

API boundary:
  Does the response shape change?
  Can old clients ignore the new field?

Data boundary:
  What happens to existing rows?
  Can old and new code both tolerate the schema during rollout?

Startup boundary:
  Does the change require a new environment variable or file?
  Will the service fail clearly if that input is missing?

Deployment boundary:
  Must migration, code, and client changes happen in a specific order?
  What is safe to roll back?

This connects the first three lessons. The request-path lesson taught you to ask which boundary saw a request. The process lesson taught you to ask which startup input a process received. This lesson adds a social and historical boundary: what evidence crosses from the author's private work into shared history.

The reviewer is not trying to slow the author down for sport. The reviewer is checking whether the change can be trusted by people who were not sitting next to the author while it was written.

Good Git history helps that trust. A commit message can explain intent. A diff can show the exact state transition. Tests can prove the expected behavior. A pull request description can name deployment and rollback limits.

Poor history removes those signals. Months later, during an incident, someone may run git log or git blame and find only:

misc fixes
update stuff
final changes

The code is still there, but the reason is gone. Reviewable history keeps enough reason that future debugging can ask a better question:

What changed, when, why, and what evidence did we have?

Common Confusions

Confusion: "Atomic means one file"

Why it is tempting:

One file feels small, and small feels reviewable.

Better model:

Atomic means one reason. A migration plus its focused migration test can be one atomic commit. One file that mixes three unrelated behavior changes is not atomic.

Confusion: "Passing CI means the change is reviewable"

Why it is tempting:

Green checks are comforting. They prove something useful.

Better model:

CI is evidence, not explanation. Reviewers still need to see the risk shape: state changes, API compatibility, startup behavior, deployment order, and rollback limits.

Confusion: "Git history is only for the team today"

Why it is tempting:

Most Git work happens while the team is trying to merge the current feature.

Better model:

History is also for future debugging. When a production incident happens, people read git log, git blame, commit messages, and pull request notes to understand when a behavior changed and why.

Trade-offs And Limits

The main trade-off is review clarity versus author speed.

Small, coherent commits take more care while authoring. You may need to split changes, write better commit messages, move unrelated cleanup out of the branch, and explain deployment order.

That work buys review clarity. It reduces the chance that a risky backend change hides inside a large diff. It also helps future debugging because the history records intent, not only final text.

This does not mean every branch needs a perfect museum-quality history. Sometimes a small bug fix is one simple commit. Sometimes a team reviews squash-merged pull requests and cares more about the final PR description than individual commits. The principle stays the same:

The unit of review should match the unit of risk.

The signal that you are near the boundary is reviewer confusion. If a reviewer says "I cannot tell what is behavior and what is cleanup," the change shape is probably too noisy.

Practice

Split this backend task into reviewable units:

Task:
  Add archived orders.
  Archived orders should not appear in the default list endpoint.
  Admins can still fetch archived orders by ID.

Write:

A good answer could be:

commit 1: add nullable archived_at column to orders
  evidence: migration test covers existing rows

commit 2: exclude archived orders from the default list endpoint
  evidence: API test proves archived orders are hidden by default

commit 3: preserve admin fetch-by-id behavior for archived orders
  evidence: admin API test proves archived order lookup still works

commit 4: document rollout and compatibility notes
  evidence: PR description names the changed default list behavior

The pull request should warn reviewers that the default list endpoint changes behavior. Existing clients that expect all orders in the default list need to know that archived orders now require a different path or option.

Resources

Key Takeaways

PREVIOUS Command Line, Processes, and Environment Variables NEXT Choosing a Backend Language and Runtime