Team workflow with git: branches, conflicts and order
Migrations are files in git, so a team works on them the way it works on code: on branches, through pull requests. Two things are different. Their order comes from their names, and a migration that has run anywhere must never change again. This recipe shows the workflow that respects both.
What you'll have at the end
- a branch per change, with its migration named at the time it is written;
requires:where one migration really depends on another;- clear rules for renaming, rebasing and merge conflicts.
Before you start
- Every developer has a development installation of the site and runs migrations there with
--no-commitor with[RepositorySettings] Push=disabled, so no record leaves the laptop. - The main branch is protected and CI runs
./console exp:migration:checkon every pull request (Testing migrations).
Steps
1. One branch, one change, its own migration
git switch -c feature/news-priority
./console exp:migration:generate --skeleton news_note_priority --description="News notes get a priority (default 0)" --applyThe id starts with the time of now, to the second: 20261101110000_news_note_priority. Two developers who generate
on the same day get different ids, and both files merge without a conflict, because they are different files.
2. Order: timestamps first, requires for dependencies
Migrations run in id order: timestamp, then name. When two branches are merged, their migrations interleave by time. That is right as long as they are independent. When one uses what the other creates, say so:
// requires: 20261101090000_news_note_tablerequires: wins over the timestamps: the migration runs after the one it names, wherever their names put them.
A requirement that names no migration, and a cycle, fail exp:migration:check in CI before the merge.
3. Two migrations with the same timestamp
It happens when a developer copies a file instead of generating it. The ids still differ by name
(20261101110000_news_note_priority and 20261101110000_news_tags) and sort by name; when they must run in a given
order, add requires:. Two files with the same id in different sources (the site and an extension) are an
error: rename the one that has run nowhere yet.
4. Rebasing a long-lived branch
A branch that waited weeks carries a migration with an old timestamp. After git rebase main it sorts before
migrations other developers merged and production already ran. The runner applies it anyway (a pending migration
runs whatever its position), but on a fresh installation it would run before them. If it depends on them, add
requires:. If you want the name to tell the truth, rename it now, before the merge:
git mv migrations/20261015090000_news_tags.php migrations/20261101120500_news_tags.phpRename only a migration that has run on no shared installation. Your own development installation then lists the
old id as an orphan (recorded as done, but its file is not found any more); roll it back before renaming, or leave
the harmless row.
5. After the merge: never edit, always add
Once a migration is on the main branch, staging or production may have run it. A bug fix is a new migration:
FAIL: 20261101110000_news_note_priority changed after it was applied (checksum 3c1f9a0b2d41 -> 8e77d0c45a19); a run with --force --reason=<text> accepts the change
FAIL (exit 1)--force --reason=<text> is only for changes that do not change what the migration does, such as a comment.
6. Conflicts in the status files
The record of each environment is written to migrations/status/<environment>.json and migrations/runs/ by the
runs themselves, never by hand. Each environment writes only its own file, so two environments never conflict. If a
merge does conflict in a status file, take the version of the branch that the environment's runs commit to and let
the next run write it again; the database, not the file, is the record.
Check it
On the merge result:
./console exp:migration:check
./console exp:migration:statusExponential migrations: status
Run 20261101-121530-0a8c3e, environment development, database sqlite (transactions: full)
done 20261101090000_news_note_table site A table for news notes, with a first note
pending 20261101110000_news_note_priority site News notes get a priority (default 0)
pending 20261101120500_news_tags site Tags for news notes
Summary: 3 migration(s): 1 done, 2 pending, 0 not needed, 0 failed, 0 unknown; 2 would-apply
PASSRoll it back
A branch that is abandoned after its migration ran on development: ./console exp:migration:rollback <id> --apply
before you switch back to main, otherwise the record keeps an orphan.
Next step
Ship the merged result: Deploying with migrations.
Related pages
- Migrations: the hub · All recipes
- The guide, the tutorial, PHP migrations
- In the administration, Commands, Settings
- The file format
- When a migration fails, Troubleshooting messages
- Migrations versus migrating a site
- Reviewing a migration, The migrations repository, Develop with git
- Free documentation: this page is free to copy, translate and improve under the GNU FDL 1.3.