Killing the internal NuGet feed is what pays for a monorepo migration
A shared DTO change on a private package feed costs a library PR, a version bump, and one consumer PR per service. FilmTailor's answer was a single repo; the work was carrying git history across rather than copying files in.
The Engineer · Build desk

What happened
- FilmTailor consolidated its services after each new one meant another repo and another pipeline, with a single API contract change producing pull requests across three separate repositories.
- The stated payoff is retiring the internal NuGet feed, where every shared-code change needed a library PR, a version bump, and a consuming PR in each service.
- The merge was done with git-filter-repo, the rewriting tool the Git project now points people towards in place of the slower filter-branch, run only inside throwaway clones.
Why it matters
- cost Copying source across is cheaper this sprint and charges interest later, because every blame query on a moved file stops at the migration date and no bisect run crosses it.
- constraint For .NET teams, Aspire's single-AppHost model makes repository layout an orchestration decision rather than a preference, at least until multi-repo support ships.
- decision Shops with separate access control per component, or a component heading for open source, have a concrete reason to stay split, so none of this reads as general advice.
- precedent Fowler's habit of asking why the repos were split in the first place inverts the burden of proof: the split is what now needs a written justification.
Price one field added to a shared DTO under a private package feed. You open a PR against the shared library, bump the version, then open one PR per consuming service to pick the new version up [4]. The post describes five terminals and five slightly stale READMEs [3], so call it five services: six pull requests and a version bump to move one field [21]. The count is irritating rather than fatal. The drift is the defect, because there is always a service running a few versions behind the contract because nobody got round to bumping it [5]. Consolidation turns that dependency into an internal project reference, and skew has nowhere left to live [6].
The migration itself is a history-rewriting job, which is where the cost sits. FilmTailor used git-filter-repo, the tool the Git project now points people towards in place of filter-branch, which is slower and carries enough sharp edges that the docs discourage it [16]. On macOS that is a brew install [17]. Because the tool rewrites rather than edits, a typo is durable, so the post's own discipline is to work in a throwaway clone, never your working copy, and to keep the result off any shared branch until you are happy with it [18]. It also specifies a fresh clone of the service repo rather than your existing checkout, since that checkout is about to have its history rewritten [19]. The destination is a new repository initialised with a README and pushed [20]. The text supplied to me breaks off mid-sentence before the command sequence [1], so read the flags there, not here.
What copying files in destroys is specific: git blame, bisectability across the service's whole lifetime, and tags and releases [13]. The author concedes the shortcut is faster and that nobody is going to fire you for it [14], and says outright that if none of those three matter, copy the files and skip the exercise [15]. In my context the ranking is not equal. A bisect that cannot cross the move date can be worked around by keeping the old repo readable and switching to it for old regressions. Blame is the query you run weekly, and a blame wall dated the day of the move is the artefact you cannot reconstruct afterwards.
Whether the rest of the case transfers depends on your stack. Aspire, which Microsoft now calls just that, having dropped the .NET prefix [8], wants to orchestrate an entire solution from one AppHost [7], and multi-repo is not officially supported, with maintainer David Fowler placing it mid-term rather than short-term on the roadmap [9]. Teams bridge that with uber solutions that clone every repo into sibling folders, with git submodules, or by shipping the shared bits as NuGet packages [10]. Strip Aspire and the private feed out of the ledger and you are paying a rewrite plus a CI overhaul for editor ergonomics.
That CI overhaul is the line the whiteboard drawing leaves off. The promised follow-up covers tag-triggered selective builds so a change to one service does not kick off a full pipeline run for all of them [22], which tells you what the default looks like the morning after you merge: every push builds everything. That bill arrives immediately, while the preserved blame history pays out over years.
What to watch
- The promised follow-up on tag-triggered selective builds, which is where the claim that a monorepo does not force full pipeline runs gets tested.
- Aspire multi-repo support moving from mid-term roadmap talk to a shipped AppHost feature, which would remove one of the two stated reasons to consolidate.
- Reports from teams confirming that tags and releases survive the filter-repo rewrite, since that is the third thing the post says copying files destroys.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence38
- Adoption18
- Hype gap+8
- Incentives25
- Confidence45
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
The supplied text of the post breaks off mid-sentence after the destination-repo setup step, before the git-filter-repo command sequence is given.
- [2]
At FilmTailor, every new service meant a new repo, a new pipeline, and recurring questions about which version of the shared library a given service was on.
- [3]
The author describes the pre-migration state as five terminals open, five READMEs slightly out of date, and PRs that touched three repos because one API contract changed.
- [4]
Running an internal NuGet feed means every change to shared code requires a PR to the shared library, a version bump, and a PR to consume the new version in each service.
- [5]
Under the package-feed workflow, a service or two inevitably lags a few versions behind because nobody got round to bumping it.
- [6]
A monorepo makes shared code an internal project reference instead of a package dependency, and the sync problem disappears.
- [7]
Aspire is designed to orchestrate a whole solution, all services and their dependencies, from one AppHost.
- [8]
Microsoft dropped the .NET prefix, so the product is now called just Aspire.
- [9]
Multi-repo is not officially supported by Aspire; maintainer David Fowler has said it is on the roadmap but more mid-term than short-term, tracked in a long-running GitHub discussion and mentioned on the public roadmap.
- [10]
Teams work around the lack of Aspire multi-repo support with uber solutions that clone every repo into sibling folders, with git submodules, or by shipping shared code as NuGet packages.
- [11]
Fowler's recurring response to multi-repo requests is to ask why teams split things into separate repos to begin with, and the author says the honest answer for many teams amounts to no strong reason beyond how it started.
- [12]
The author names hard requirements for keeping repos apart: separate access control, or part of the codebase going open source.
- [13]
Copying source files in and committing them fresh loses git blame, bisectability across the service's whole lifetime, and tags and releases, making version history fiction.
- [14]
The author writes of the copy-and-commit approach that it is faster and nobody is going to fire you for it.
- [15]
The author advises that if blame, bisectability, and tags do not matter for your case, you should skip the whole exercise and copy the files in.
- [16]
FilmTailor used git-filter-repo, described as the tool the Git project itself now points people towards for rewriting history, replacing filter-branch, which is slower and has enough sharp edges that the docs actively discourage using it.
- [17]
On macOS the install is brew install git-filter-repo.
- [18]
The post warns that git-filter-repo rewrites history, that a typo in the wrong command can quietly and permanently mangle a repo, and that all work should be done in a throwaway clone, never the working copy, and never on anything pushed to a shared branch until the result is satisfactory.
- [19]
The procedure uses two terminal panes, one in the new monorepo and one in a scratch clone, and calls for a fresh clone of the service repo rather than the existing working copy because its history is about to be rewritten.
- [20]
The destination is a new repository, initialised with a README.md and pushed to GitHub.
- [21]
Taking the post's five terminals and five READMEs as five consuming services, one shared-library change under the package feed costs six pull requests plus a version bump, against a single commit in a monorepo.
- [22]
A promised follow-up post covers tag-triggered selective builds, so a change to one service does not kick off a full pipeline run for all of them.
Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toThe Monorepo Migration: Merging Histories Without Losing Your Mind
1 article · August 29, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.