Build Systems Keep the Git History While the Teams That Wrote Them Rebase Away

Aug 10, 2026 By Yusuke Tanaka

Every developer has been there: you run git blame on a suspicious line, and the name staring back at you belongs to someone who hasn't worked at the company for two years. The commit message says "fix bug," and the diff shows a magic number that appears nowhere else in the codebase. The original author is gone, the reasoning is gone, and the only thing left is the artifact itself. This is the dirty secret of version control: history is not what you think it is. But there is a place where the truth survives, where decisions are encoded in a form that outlives the people who made them. That place is the build system.

The Lie That Your History Is Your History

Git blame is the first tool most developers reach for when they want to understand why a line of code exists. But blame is a snapshot of a moving target. Every time a branch is rebased, every time a force-push rewrites a series of commits, the original commit IDs are destroyed and replaced with new ones. The author field might stay the same, but the context evaporates. A commit that originally sat next to a dozen related changes gets squashed into a single amorphous blob. The narrative that the git log tells you is a fiction, maintained by the team's willingness to keep the history tidy.

Rebases are the worst offender. They are the standard workflow for teams that want a linear history, a single clean line of commits that reads like a story. But that story is written after the fact, by someone who wasn't there when the original commits were made. The rebase reorders, squashes, and rewrites, and in the process it destroys the intermediate states that might have documented the evolution of an idea. The final commit message says "implement feature X," but the hours of false starts and dead ends are gone. The next developer reads that message and assumes the feature was designed in a vacuum.

Teams know this on some level, but they rarely act on it. They accept the rewritten history as the truth because it is easier than digging through reflogs and old branches. The team's memory, the collective knowledge of why decisions were made, lives in Slack threads and hallway conversations. Those are ephemeral. They evaporate when the team changes. The build system, by contrast, is permanent. It sits in the repository, unchanged, until someone deliberately edits it. And when someone does edit it, the diff shows up in the same git log that everyone pretends is reliable.

Nobody commits the why. They commit the what, and sometimes a half-hearted message that hints at the why. The build script is different. It encodes decisions in a form that is executable. The flags, the ordering, the dependencies, they all say something about what the author believed was necessary to get this code to run. That is a record that survives the departure of the original author, the reorganization of the team, and the next round of rebases.

The Build System Is the Only Authoritative Record

Open any Makefile and you are reading a fossil record. The order of targets, the flags passed to the compiler, the environment variables that are assumed to exist, they all betray the original intent. A Makefile that runs gcc -O2 -Wall is telling you that the author cared about warnings and optimization. A Makefile that uses a specific version of a toolchain is telling you that the author had a bad experience with a newer or older version. These are decisions that were made once, under pressure, and then never revisited because they worked.

Compiler flags are the most honest part of the record. They are rarely added on a whim. Someone had a reason for turning on -fno-strict-aliasing or -Werror. That reason is not in the commit message, but the flag is still there, years later, enforcing a constraint that the original author felt was necessary. The next engineer who reads that flag might not understand why it is there, but they know it is there, and they know removing it could break something. The flag is a warning sign, a piece of institutional memory that outlives the person who planted it.

Dependency pinning is a time capsule. When a build file specifies an exact version of a library or a package, it is capturing a moment in time. The author knew that this version worked, and they were afraid of what would happen if a newer version changed the behavior. That fear is encoded in the build file, and it is more reliable than any README. The README might say "we use the latest version," but the build file says == 1.2.3. The build file is the truth.

Shell scripts are folklore in executable form. They are passed down from developer to developer, each one adding a line to handle a new edge case, and nobody knows why the line is there. But the line is there, and it runs, and it does something. The script is the collective memory of the team, written in a language that the computer can execute. It is the closest thing to a perfect record of the decisions that were made, because it is the decisions themselves, in action.

A broken build complains louder than a deleted comment. When someone removes a line from a Makefile and the build fails, the error message is immediate and unambiguous. That is more effective than any code review comment. The build system is the enforcer of the team's unwritten rules, and it never forgets.

What the Rebase Actually Erases

Force-push wipes the original commit IDs. That is the most obvious damage, but it is not the most important. The commit IDs are just pointers; the content is still there in the reflog for a while. What is truly lost is the context that the original commits provided. A series of commits that showed a feature being built incrementally, with each step building on the last, is replaced by a single squashed commit that hides the intermediate states. The reasoning behind each step, the false starts, the corrections, all of it is gone.

The commit message survives, but the reasoning doesn't. A message that says "fix off-by-one error" does not tell you why the off-by-one error existed in the first place. The original commit might have had a longer message, or a diff that showed the evolution of the logic. After a rebase, all that is left is the final state and a vague description. The next developer who sees the message has to guess at the intent, and guessing is how bugs are introduced.

Blame becomes a game of telephone. When a commit is rewritten, the author field might stay the same, but the blame output for a given line might point to a commit that is not the original. The line might have been moved, or the commit might have been split, or the author might have been changed by a rebase that reassigned authorship. The result is that blame tells you who last touched the line, not who originally wrote it. That is a subtle but crucial difference. The person who last touched the line might have been fixing a bug introduced by someone else, and blame will point to them, making them look responsible for code they never wrote.

Linear history is a fiction we maintain. It is a convenience for reading the log, but it comes at the cost of accuracy. The reflog is the only honest witness. It records every movement of every branch, including the ones that were later rewritten. But the reflog is local, it is not pushed, and it is eventually garbage-collected. The honest witness is silenced, and the fiction becomes the official record.

The Paradox: We Preserve the Artifact, Not the Artist

Version control stores code, not context. The code is a static snapshot, a frozen moment in time. The context, the reasoning, the decisions, they are all in the heads of the developers, and when they leave, the context leaves with them. The build system is the exception. It stores context in the form of executable instructions. The flags, the ordering, the pins, they are all decisions that were made and then recorded in a form that the computer can execute. The build system is the fossil record of the team's thinking.

A CI pipeline is a ritualized memory. The steps in a CI pipeline, the tests that run, the commands that are executed, they are a codified version of the team's beliefs about what constitutes a working build. The pipeline is the team's memory, preserved in a form that runs automatically every time a commit is pushed. It is a ritual that ensures the past is not forgotten, that the decisions made by the first developers are still honored by the current ones.

We document the output, not the decision. The README describes what the software does, the API reference describes how to call it, but the why is left to the imagination. The build system is the only place where the why is encoded. The -march=native flag is a decision about performance. The --no-optional flag is a decision about reliability. The build system is the only document that records these decisions, and it does so in a form that is both executable and readable.

The next engineer reads the Makefile, not the PR. The PR is a transient artifact, reviewed and then archived. The Makefile is a permanent artifact, read and executed every time the code is built. The next engineer will spend more time with the Makefile than with any PR. They will learn more from it. They will understand the constraints and the assumptions that the original author made. The PR is a conversation; the Makefile is a contract.

How the Best Teams Actually Keep the History

The best teams annotate build steps with rationale. They add comments to their Makefiles and Gradle files that explain why a flag is there or why a dependency is pinned. These comments are not just documentation; they are a conversation with the future. They tell the next engineer, "I added this flag because the default behavior caused a segfault on ARM," or "I pinned this version because the newer one changed the API." These comments are the history that the rebase erases, preserved in a place that the rebase cannot touch.

They commit the why in the build script itself. Instead of relying on external docs, they put the reasoning inline. The build script becomes a self-contained narrative, a story that the next engineer can read without having to dig through old PRs or interview departed colleagues. This is not just a matter of good documentation; it is a matter of survival. The build script is the only record that is guaranteed to be there when the next engineer arrives.

They freeze dependency versions with comments. A line that says version: "1.2.3" is good, but a line that says version: "1.2.3" # 1.3.0 breaks the build is better. The comment turns a bare pin into a piece of institutional memory. It tells the next engineer that they should not upgrade without understanding why the pin is there. It is a warning, a cautionary tale, and a piece of history all in one.

They treat the reflog as a backup, not a source. They know that the reflog is local and ephemeral, so they do not rely on it for long-term history. Instead, they make sure that the build system is the source of truth. They write a design doc and link it in the build. The design doc is a living document that explains the high-level decisions, and the build file links to it, so that the next engineer can find it. The build file is the entry point to the history, not the history itself.

Consider the Linux kernel community. They have strict rules about rebasing: once patches are merged into a subsystem tree, they are never rewritten. The build system, with its Kconfig files and Makefiles, carries decades of decisions about architecture support, compiler quirks, and configuration options. When a new developer wants to add support for a new ARM board, they read the existing Makefiles and Kconfig entries to understand the patterns and constraints. The build system is the living memory of the project, and it is why the kernel can be maintained by thousands of contributors who have never met each other.

Closer to home, the Android Open Source Project (AOSP) uses a combination of Soong (a build system) and prebuilt toolchains. The build files specify exact versions of the Clang compiler and the NDK, with comments explaining why a particular version is required. When a developer encounters a cryptic error in the build, the build files often contain the explanation, because someone before them hit the same issue and left a note in the build. This is institutional memory at its finest.

Practical Moves: Make the Build System Tell the Truth

Add a header comment to every Makefile. The header should explain what this build system is for, who maintains it, and what the key decisions are. It should be written for the next engineer, not for the current one. It should say, "This Makefile was written for the embedded Linux target, and it assumes you have the cross-compiler installed. The flags are tuned for the older kernel, so do not update without testing." This is the kind of information that is usually lost, and putting it in the header makes it survive.

Use deterministic builds so outputs match history. A deterministic build produces the same output from the same input, regardless of the environment. This is important because it means that the build system is a reliable record. If the build is deterministic, then the output is a direct result of the input, and the input includes the build files. The build files are the history, and the output is the proof. Without determinism, the build output is a mystery, and the history is unreliable.

Pin toolchains, not just libraries. Libraries are easy to pin, but toolchains are often ignored. A Makefile that uses gcc without a version is a gamble. The next engineer might have a newer version of gcc that produces different code. Pinning the toolchain, whether through a Docker image or a versioned wrapper script, ensures that the build system is a consistent record. It also ensures that the build system is a reliable record, because the same commands will produce the same results.

Record the environment in the build log. When a build runs, log the environment variables, the toolchain versions, and the git commit. This is not just for debugging; it is for history. The build log becomes a record of what was built, with what, and when. It is a time capsule that can be opened later to understand why a particular binary behaves the way it does. Tag builds with the git commit and the builder's name. This makes the build log a reliable witness, and it makes the history of the build system complete.

The Takeaway: Stop Rebasining, Start Preserving

Rebase less, merge more. This is a controversial opinion in some circles, but the evidence is clear: rebases destroy history. Merges preserve it. A merge commit has two parents, and it records the fact that two lines of development were combined. A rebase rewrites the history so that it looks like the development was linear, which is a lie. The lie might be prettier, but it is still a lie. The next engineer who reads the history will have a harder time understanding the actual sequence of events.

History is a feature, not a nuisance. The git log is not just a list of commits; it is the story of the project. It is the story of the decisions that were made, the mistakes that were corrected, and the people who made them. Rebase destroys that story. It is an act of vandalism, even if it is well-intentioned. The next engineer who reads the history will have a harder time understanding the actual sequence of events.

Teach the build system to speak. The build system is the most reliable record of the project's decisions, but it is only useful if it is readable. Add comments, add headers, add links to design docs. Make the build system a document that can be read and understood, not just executed. The next developer is your audience. Write the build system for them, as if you were writing a letter to the future.

Your code will be read long after you're gone. The build system will be there, too, silently ensuring that the code still builds. It will be the only thing that remembers why the code is the way it is. The next engineer will read the build system, and they will learn from it. They will learn about the decisions that were made, the constraints that were honored, and the people who made them. The build system is the true history of the project. Make it a good one.

Recommend Posts
Tech

App Store Review Embargoes Now Timezone-Shift the Release Train’s Whole Crew

By Yusuke Tanaka/Aug 10, 2026

App Store review windows shift with timezones, stalling deploys and burning team hours. Here's how global mobile teams cope with embargoes and scheduling.
Tech

Python Wheelhouse Councils Rewrite Cargo’s License Playbook and the Audit Trail Notices

By Lucas Mendes/Aug 10, 2026

How Python's Wheelhouse councils are reshaping license metadata and audit trails, and what Cargo's declarative approach can learn from them.
Tech

Multi-Year Security Contracts Now Outprice the Zero-Days They Insure

By Lucas Mendes/Aug 10, 2026

Annual security retainers now cost more than the zero-day exploits they cover. Here's why multi-year deals lock in stale defenses and how to negotiate better.
Tech

CDN Peering Deals Now Outlast the Streaming Services That Signed Them

By Lucas Mendes/Aug 10, 2026

Streaming startups die fast, but their CDN contracts live on. Explore how long-term peering deals become stranded assets and what operators can do.
Tech

Firmware Sign-Off Checklists Now Outlive the Boards They Validate

By Yusuke Tanaka/Aug 10, 2026

Firmware sign-off checklists often outlive the boards they validate. Learn how to build checklists that age gracefully, with lessons from a scrapped NPU project.
Tech

Build Systems Keep the Git History While the Teams That Wrote Them Rebase Away

By Yusuke Tanaka/Aug 10, 2026

Build systems preserve decisions better than git blame ever will. Explore how Makefiles, Gradle, and CI pipelines encode the why that rebases erase.
Tech

Xcode’s Debugger Traces Threads Older Than the App Store Review Waiting Room

By Lucas Mendes/Aug 10, 2026

A craft breakdown of mobile app debugging: how thread traces, crash logs, and boring habits catch issues that approval queues never will.
Tech

CI YAML Outlives the Teams That Version It, and the Wire Format Agrees

By Sara Park/Aug 10, 2026

CI pipelines outlast the teams that write them. The YAML wire format stays stable, freezing semantics and forcing backward compatibility. Here's how to build durable configs.
Tech

Browser Vendors Now Charge for the Merge Queue, Not the Rendering

By Deepa Iyer/Aug 10, 2026

Browser vendors are monetizing the merge queue, not the rendering engine. Patch acceptance now costs money, reshaping who gets into the fast lane of web development.
Tech

Service-Level Agreements Now List the Maintainer’s Name Before the Uptime Promise

By Yusuke Tanaka/Aug 10, 2026

Service-level agreements are shifting from uptime promises to naming the humans who keep services alive. A look at bus factor, key-person clauses, and the economics of continuity.
Tech

Interconnect Fees on Undersea Cables Now Dictate Which Streaming Deals Get Signed

By Yusuke Tanaka/Aug 10, 2026

How interconnect fees on undersea cables determine which streaming deals get signed. Explore the economics of submarine cables, peering, and CDNs.
Tech

Maintainer On-Call Rotations Put the Bus Factor on a Salary Schedule

By Sara Park/Aug 10, 2026

Open-source maintainers are adopting on-call rotations, formalizing the bus factor into a salary schedule. This deep dive looks at the tradeoffs and how to do it well.
Tech

Firmware Bugs Surface at 3 A.M. When the Accelerator's Heat Sink Decides to Speak

By Sara Park/Aug 10, 2026

At 3 A.M., the heat sink crackles and firmware logs go silent. A look at what breaks at the hardware–software boundary, and how on-call engineers cope.
Tech

Maintainer Retirement Plans Now Name Their Successors in the LICENSE File

By Deepa Iyer/Aug 10, 2026

A quiet shift is turning LICENSE files into succession documents. Maintainers now name successors to combat the bus factor, burnout, and project abandonment.
Tech

Queue Depth Overtakes CPU Load as the First Signal That a Service Is Dying

By Lucas Mendes/Aug 10, 2026

CPU load has long been the go-to health metric, but queue depth often reveals trouble earlier. Learn why queues matter, how to instrument them, and how to alert on them.
Tech

Dependency Lockfiles Now Outnumber the READMEs That Explain Their Failures

By Lucas Mendes/Aug 10, 2026

Lockfiles now outnumber READMEs. Why dependency pinning became the default, what it hides, and how teams can bridge the gap between automation and understanding.
Tech

App Store Prices Hide the Cloud Bill That Cross-Platform Toolchains Write

By Deepa Iyer/Aug 10, 2026

App store prices hide the real cost of mobile development. Cross-platform toolchains shift compute burdens to developers, inflating cloud bills.
Tech

Edge Cities Route Around the CDN and Build Their Own Last Mile

By Deepa Iyer/Aug 10, 2026

CDNs were built for static assets, but dynamic traffic breaks their model. Here's how a new wave of edge cities build their own last mile—and when to stay put.
Tech

Edge Engineers Commute Between PoPs and Learn Latency’s Real Salary

By Sara Park/Aug 10, 2026

Edge engineers who live near network nodes trade travel and on-call for a latency-focused career. Here's what the commute really pays.
Tech

Undersea Cable Repair Ships Now Outbid Streaming Bundles for Bandwidth

By Sara Park/Aug 10, 2026

Repair ships for undersea cables now command premium rates rivaling CDN fees. A look at who owns seafloor bandwidth, the cost of a cable break, and what it means for buyers.