<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/migration-files-and-git-do-not-mix-diffypick-blog-iop2y0qf3" -->

---
title: Migration files and Git do not mix — DiffyPick Blog
description: Migration directories look like ordinary files in a Git repo but don&#x27;t get any of Git&#x27;s real protections: diffs show deltas rather than resulting schema state,...
canonical: https://daily.dev/posts/migration-files-and-git-do-not-mix-diffypick-blog-iop2y0qf3
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Migration files and Git do not mix — DiffyPick Blog | daily.dev
og:description: Migration directories look like ordinary files in a Git repo but don&#x27;t get any of Git&#x27;s real protections: diffs show deltas rather than resulting schema state,...
og:url: https://daily.dev/posts/migration-files-and-git-do-not-mix-diffypick-blog-iop2y0qf3
og:image: https://api.daily.dev/og/posts/IOp2y0qF3.png
og:image:alt: Migration files and Git do not mix — DiffyPick Blog
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Migration files and Git do not mix — DiffyPick Blog

**[Planet PostgreSQL](https://daily.dev/sources/planet-postgresql)** · 7 min read · 21 upvotes · 10 comments

## Summary

Migration directories look like ordinary files in a Git repo but don't get any of Git's real protections: diffs show deltas rather than resulting schema state, concurrent conflicting migrations merge cleanly because they live in separate files, merge order can disagree with apply order, git revert only deletes a file without undoing the database change, and checkout doesn't roll back local database state. Declarative schema files (schema.rb, schema.prisma) cooperate with Git because they're mutable and stateful, which is why Rails and Prisma layer a declarative snapshot on top of migrations — though that snapshot only reflects intended state, not actual drift between databases. The argument concludes that real safety for schema changes has to come from comparing live databases directly, not from Git.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://postgr.es/p/9tE>

## Questions this post answers

### Why don't two conflicting database migrations cause a Git merge conflict?

Each developer creates a separate, uniquely named migration file for their change, so textually there is nothing for Git to compare or flag. Both branches merge cleanly and both migrations apply, even though the schema changes are semantically incompatible. The migration file format guarantees concurrent changes never land in the same file, so Git's conflict detection never activates, unlike with declarative schema files where colliding edits to the same lines are caught.

_Anyone debugging a schema clash after a clean merge can compare this pattern against real migration workflows on daily.dev._

### Does git revert actually undo a database migration?

No, running git revert on a migration commit only deletes the migration file from the repository; it does not touch the database or its migration history table. The database keeps the applied change and its tracking table still records the migration as applied, so the repo now claims a migration exists that the database's own history says was already run, creating a new inconsistency rather than fixing one.

_Developers weighing safe rollback strategies for schema changes can dig deeper into database tooling on daily.dev._

### How do Rails schema.rb and Prisma's schema file help with migration drift, and what do they not fix?

Rails maintains schema.rb/structure.sql and Prisma treats its declarative schema as the source migrations generate from, turning intended state back into a file Git can diff and conflict-check normally. However, this snapshot only reflects what the migration files claim, not what any live database actually contains, so it cannot detect drift between the repo's declared schema and the real state of running databases.

_Teams choosing between migration tooling and drift-detection approaches can track these tradeoffs via daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@pdfopsdev** · 2 upvotes

> had this bite us with a rails migration revert. we deleted the file but schema_migrations still had the version marked as applied, so two envs silently diverged. tools like atlas or sqlc's diff step catch that class of drift because they compare live state, not migration history.

**@aliceinlimbo** · 2 upvotes

> the article feels all over the place to me...git was never meant to handle database states; its there to handle & version textual changes between files. that there are external services or systems was never part of the design. which is why you need a separate migratino tool to track during runtime the state of the database & what changed. Alembic can be one such tool to check drifts etc.

**@gaburayondev** · 2 upvotes

> The "event log wearing a file system costume" framing is the sharpest line in this — it explains exactly why every Git operation degrades once it touches a migration directory: Git's protections assume mutable, comparable state, and migrations are explicitly append-only by convention. The `git revert` point is the one that trips people up in practice — deleting the migration file does nothing to the database that already ran it, so revert silently lies about what it accomplished. Worth flagging this is also the vendor's own pitch for their db-diffing tool, so the "compare live databases"...

**@mikhailmakeev** · 0 upvotes

> "Real safety has to come from comparing live databases" is where I landed too, from the deploy side rather than the schema side. Two machines built frontend images off the same stale base an hour apart, and each roll silently reverted the other's feature. Git stayed clean throughout: every commit present, no conflict, nothing to revert.
>
>
> My first write-up of it said the fix was to fetch and check you aren't behind before building. That's wrong, and the incident is its own counterexample. When the second machine built, the other commit hadn't been pushed yet, so a freshness check would have...

**@agustinbarrientos** · 0 upvotes

> Try running the merged schema against a disposable database in CI. A DAG check won't catch an engine rejecting the final DDL.

## Similar posts on daily.dev

- [Delete your old migrations, today](https://daily.dev/posts/delete-your-old-migrations-today-qktxpcdkb) · RUBYLAND · 0 upvotes · 0 comments

---

Tags: [#database](https://daily.dev/tags/database), [#postgresql](https://daily.dev/tags/postgresql), [#git](https://daily.dev/tags/git)

[View this post on daily.dev](https://daily.dev/posts/migration-files-and-git-do-not-mix-diffypick-blog-iop2y0qf3)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Migration files and Git do not mix — DiffyPick Blog","url":"https://daily.dev/posts/migration-files-and-git-do-not-mix-diffypick-blog-iop2y0qf3","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/migration-files-and-git-do-not-mix-diffypick-blog-iop2y0qf3"},"datePublished":"2026-09-01T16:31:20.410Z","dateModified":"2026-09-01T16:42:01.327Z","description":"Migration directories look like ordinary files in a Git repo but don't get any of Git's real protections: diffs show deltas rather than resulting schema state,...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/c2cc8e15868c974351e579fa1c8e1dec?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/c2cc8e15868c974351e579fa1c8e1dec?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Planet PostgreSQL","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Planet PostgreSQL","logo":"https://media.daily.dev/image/upload/logos/placeholder.jpg","url":"https://daily.dev/sources/planet-postgresql"},"commentCount":10,"discussionUrl":"https://daily.dev/posts/migration-files-and-git-do-not-mix-diffypick-blog-iop2y0qf3","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":21},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":10}],"keywords":"database,postgresql,git","timeRequired":"PT7M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Planet PostgreSQL","item":"https://daily.dev/sources/planet-postgresql"},{"@type":"ListItem","position":3,"name":"Migration files and Git do not mix — DiffyPick Blog"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/migration-files-and-git-do-not-mix-diffypick-blog-iop2y0qf3","comment":[{"@type":"Comment","text":"had this bite us with a rails migration revert. we deleted the file but schema_migrations still had the version marked as applied, so two envs silently diverged. tools like atlas or sqlc’s diff step catch that class of drift because they compare live state, not migration history.","datePublished":"2026-09-01T18:03:11.988Z","url":"https://daily.dev/posts/IOp2y0qF3#c-YiNPdFdOO","author":{"@type":"Person","name":"PDFops","url":"https://daily.dev/pdfopsdev","image":"https://media.daily.dev/image/upload/s---8isRBKc--/f_auto/v1782922291/avatars/avatar_orjMeK8QKaaVZwGq7ScPz?_a=BAMAMicg0"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2}},{"@type":"Comment","text":"the article feels all over the place to me…git was never meant to handle database states; its there to handle &amp; version textual changes between files. that there are external services or systems was never part of the design. which is why you need a separate migratino tool to track during runtime the state of the database &amp; what changed. Alembic can be one such tool to check drifts etc.","datePublished":"2026-09-01T20:11:23.588Z","url":"https://daily.dev/posts/IOp2y0qF3#c-e3ec1BsxC","author":{"@type":"Person","name":"Ikram","url":"https://daily.dev/aliceinlimbo","image":"https://media.daily.dev/image/upload/s--FzqkRD6G--/f_auto/v1734873785/avatars/avatar_YORtdHRwRyH5xdHAdrw7H"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2}},{"@type":"Comment","text":"The “event log wearing a file system costume” framing is the sharpest line in this — it explains exactly why every Git operation degrades once it touches a migration directory: Git’s protections assume mutable, comparable state, and migrations are explicitly append-only by convention. The git revert point is the one that trips people up in practice — deleting the migration file does nothing to the database that already ran it, so revert silently lies about what it accomplished. Worth flagging this is also the vendor’s own pitch for their db-diffing tool, so the “compare live databases” conclusion isn’t neutral — but the underlying diagnosis holds regardless of what tool you reach for after it.","datePublished":"2026-09-07T09:09:56.775Z","url":"https://daily.dev/posts/IOp2y0qF3#c-gME5zJ7vL","author":{"@type":"Person","name":"Gabu Rayon Dev","url":"https://daily.dev/gaburayondev","image":"https://media.daily.dev/image/upload/s--5hPC52y_--/f_auto/v1773412842/avatars/avatar_8fkdziGASnSSBES6c4Ml9?_a=BAMAMiiu0"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2}},{"@type":"Comment","text":"“Real safety has to come from comparing live databases” is where I landed too, from the deploy side rather than the schema side. Two machines built frontend images off the same stale base an hour apart, and each roll silently reverted the other’s feature. Git stayed clean throughout: every commit present, no conflict, nothing to revert.\nMy first write-up of it said the fix was to fetch and check you aren’t behind before building. That’s wrong, and the incident is its own counterexample. When the second machine built, the other commit hadn’t been pushed yet, so a freshness check would have gone green and destroyed the same work.\nThe invariant that survives is about prod, not about origin: never move a service to an image that isn’t a descendant of what’s currently running. Which I still can’t check, because our images carry no provenance label at all.","datePublished":"2026-09-03T07:40:20.899Z","url":"https://daily.dev/posts/IOp2y0qF3#c-j6OiXBdmP","author":{"@type":"Person","name":"Mikhail Makeev","url":"https://daily.dev/mikhailmakeev","image":"https://lh3.googleusercontent.com/a/ACg8ocIjcvDU1gmJ9-KJDeTYqY2rMdl5QxcWYtLTGSXrj8ubF4nwar_w=s96-c"}},{"@type":"Comment","text":"Try running the merged schema against a disposable database in CI. A DAG check won’t catch an engine rejecting the final DDL.","datePublished":"2026-09-07T23:53:34.057Z","url":"https://daily.dev/posts/IOp2y0qF3#c-M8HeUuIFz","author":{"@type":"Person","name":"Agustin Barrientos","url":"https://daily.dev/agustinbarrientos","image":"https://media.daily.dev/image/upload/s--5ayxQnqn--/f_auto/v1788281802/avatars/avatar_wQYYVe5Tbj0NJ7C7qPoa8?_a=BAMAMicg0"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/migration-files-and-git-do-not-mix-diffypick-blog-iop2y0qf3#faq","mainEntity":[{"@type":"Question","name":"Why don't two conflicting database migrations cause a Git merge conflict?","acceptedAnswer":{"@type":"Answer","text":"Each developer creates a separate, uniquely named migration file for their change, so textually there is nothing for Git to compare or flag. Both branches merge cleanly and both migrations apply, even though the schema changes are semantically incompatible. The migration file format guarantees concurrent changes never land in the same file, so Git's conflict detection never activates, unlike with declarative schema files where colliding edits to the same lines are caught. Anyone debugging a schema clash after a clean merge can compare this pattern against real migration workflows on daily.dev."}},{"@type":"Question","name":"Does git revert actually undo a database migration?","acceptedAnswer":{"@type":"Answer","text":"No, running git revert on a migration commit only deletes the migration file from the repository; it does not touch the database or its migration history table. The database keeps the applied change and its tracking table still records the migration as applied, so the repo now claims a migration exists that the database's own history says was already run, creating a new inconsistency rather than fixing one. Developers weighing safe rollback strategies for schema changes can dig deeper into database tooling on daily.dev."}},{"@type":"Question","name":"How do Rails schema.rb and Prisma's schema file help with migration drift, and what do they not fix?","acceptedAnswer":{"@type":"Answer","text":"Rails maintains schema.rb/structure.sql and Prisma treats its declarative schema as the source migrations generate from, turning intended state back into a file Git can diff and conflict-check normally. However, this snapshot only reflects what the migration files claim, not what any live database actually contains, so it cannot detect drift between the repo's declared schema and the real state of running databases. Teams choosing between migration tooling and drift-detection approaches can track these tradeoffs via daily.dev."}}]}
```

