Sebastiano Druda

← Back to writing

Essay / Tools, Infrastructure & Workflows

Git History Is Part of the Product

Clear commits preserve the reasoning behind software changes. Make history useful for review, debugging and recovery without burying behavior in noise.

A bug appears after a release. The relevant commit is called “updates” and contains a dependency upgrade, a renamed component, a new feature and a repository-wide formatting pass. The code may be readable, but its history provides little help in identifying which change introduced the problem or which part can be reversed safely.

Git history is an interface for future maintenance. Reviewers use it to understand a proposal. Maintainers use it to investigate behavior. Operators use it to identify a release. A history that preserves meaningful boundaries can reduce the work required by all three.

A useful commit explains one change well enough to undo it safely.

Choose a boundary a reviewer can understand

A coherent commit has a reason to exist as a unit. It might add one article collection, fix a chronology rule or change a deployment safeguard. The files can span several directories if they serve the same behavior. Small does not mean one file, and large does not automatically mean incoherent.

The useful question is whether a reviewer can explain the result and its consequences without separating unrelated work. If a change combines a visible feature with an optional refactor, ask whether both are necessary for the same outcome. If not, separate them so each can be assessed on its own.

Keep tests with the behavior they protect when that produces a working, understandable commit. A test-only commit can be useful to demonstrate a regression, but a shared branch should not accumulate unexplained broken intermediate states merely to make the history look granular.

For content work, grouping a coherent collection may be clearer than creating one commit per paragraph. The boundary should support review and recovery, not optimize for an arbitrary commit count.

Explain why the behavior changed

A commit message should name the concrete result. “Make archive chronology oldest-first” tells a future reader more than “fix sorting.” The body can explain the previous behavior, the intended invariant and any trade-off that is not obvious from the diff.

Do not use the message as a transcript of the working session. Failed experiments and temporary approaches belong only when they explain the final choice. A reviewer who never saw the conversation should still understand the change.

Include validation that supports the claim. “Tests pass” is less informative than naming the relevant checks and their scope when the change is consequential. Avoid claiming that a live deployment succeeded in a commit created before that deployment happened.

The message and the code should agree. If the scope changes during implementation, rewrite the description around the final result. An accurate short message is more useful than a detailed description of a plan the code no longer follows.

Keep mechanical noise separate

Formatting can improve consistency while obscuring behavior changes in the same diff. A repository-wide formatter run attached to a small bug fix forces reviewers to distinguish relevant edits from mechanical churn. It also makes future line-history searches less informative.

Apply formatting required by the repository to changed files, and separate broad mechanical changes when they are genuinely needed. Avoid using a feature request as an opportunity to restyle unrelated code. The benefit rarely justifies the additional review surface.

Generated output deserves a deliberate policy. This static site builds deployment artifacts from source and does not commit the generated dist/ tree. The source, lockfile and build configuration explain how output is produced, while CI verifies the process.

Other projects may intentionally commit generated files. The important point is to document the boundary and keep changes reproducible. A reviewer should know whether a generated difference reflects a source change, a tool version change or an accidental local environment difference.

Revert behavior without rewriting shared history

Git’s revert command creates new commits that reverse changes introduced by earlier commits. This supports correction on shared history without pretending the original change never occurred. It is different from moving a branch pointer or discarding local work.

A revert is easier to reason about when the original commit has a coherent scope. If it contains unrelated changes, reversing one defect may also remove a valid feature or a dependency fix. That is an operational cost created by the earlier commit boundary.

Even a clean revert requires judgment. Later commits may depend on the behavior being removed, and data changes may have effects outside Git. Reverting source does not automatically undo a published email, a database migration or an external action.

For this publication, the release still needs to be rebuilt and deployed after a source correction. Git identifies the intended state; it does not by itself establish what the public host currently serves.

Make debugging history trustworthy

A useful history lets a maintainer ask when behavior changed and why. Focused commits and meaningful messages reduce the search space. Tests that accompany the change provide additional evidence of the intended contract.

When investigating, avoid assuming the most recent edit to a line caused the defect. A formatting pass may be the last textual change, while the underlying assumption originated elsewhere. Follow the behavior and its dependencies as well as the line history.

A reproducible failing case makes history more valuable. It can be checked against earlier revisions to locate the transition from working to failing behavior. Without a stable observation, the investigation risks attributing the defect to whichever change looks suspicious.

History becomes useful when it preserves causes, not just snapshots.

That is also why unexplained generated churn is costly. It adds snapshots without clarifying intent, making it harder to distinguish a meaningful change from the side effect of running a different local toolchain.

Review deletions as product changes

Removing code can simplify a system, but a deletion should have evidence behind it. Search imports, routes, runtime references, tests and documentation. A public asset may have no import because it is requested directly by URL. A demo file may support an intentional preview suite.

Record the reason for non-obvious deletions in the commit or technical documentation. “Unused” should mean that the relevant use paths were checked, not that one text search returned no result.

Consider external references before removing a public route. An internal navigation change does not prove that nobody has bookmarked the URL. Depending on the site’s history and hosting capabilities, a redirect or a deliberate retirement may be appropriate. The release should not leave a stale page accidentally reachable simply because old files were never cleaned up.

The deployment guide discusses why source deletion and remote cleanup are separate steps. History records the intended removal; the deployment process must apply it within a safe ownership boundary.

Before pushing, inspect the exact staged diff. The working directory can contain a correct final implementation while the index contains only part of it. Staging explicit paths and reviewing the staged result reduces that mismatch. Confirm the destination repository and branch as well, especially when an older project with a similar name remains available. A coherent commit sent to the wrong repository is still an incorrect release action.

Preserve security without hiding useful evidence

Never commit credentials to make a workflow easier to reproduce. Reproducibility should identify the required secret names and configuration boundary without storing the secret values. Logs and temporary reports deserve the same care before they enter history.

A clean working tree review should include untracked files as well as the diff. Screenshots, local caches and copied input datasets can contain information that the source changes do not reveal. Ignore rules help, but they do not replace inspecting what is about to be committed.

Dependency changes should include the lockfile and a reason. A broad upgrade attached to an unrelated content edit expands the risk without helping the requested outcome. When a security finding requires an update, explain the affected boundary and validate the relevant behavior.

The final history should let another person identify what changed, why it changed and how to recover if necessary. That makes Git part of the product’s maintainability rather than a storage location for whatever happened to be in the working directory at the end of the day.

Sources

  1. Git revert documentation — checked 2026-10-14

Topics

37 / Essay

Own the Source, Rent the Infrastructure

Control can live in source, content and a portable build. A practical look at static hosting, independent recovery and the limits of infrastructure ownership.

self hosting · hosting · 7 min

34 / Guide

GitHub Actions Became My Publishing System

Versioned articles, validation, static builds and search indexing form a publishing pipeline. Here is how this site connects editorial changes to delivery.

ci cd · github · 7 min