Patches
Nest, and its forks, depend on the use of patch files.
A patch file is a text file that contains the same kind of information that exists in a single Git commit.
Nest defines a fork by patch files because Nest assumes that every fork wants to stay up to date with Flutter on a continuous basis. It's much easier to rebase a fork on top of Flutter each day, using patch files, than trying to wrestle with Git history, directly.
This page describes what a patch file is, how they're applied on top of Flutter, and how you can generate more patch files, to contribute to a Flutter fork.
Tooling
To work with patch files, patch manifests, importing, and exporting, use the
nest_patches Dart package, which sits in the nest repository.
The nest_patches package implements reading and writing of patch files.
Patch Protocol
A .patch file uses Git's mail patch format. It contains one or more consecutive
mail messages. Each message describes one commit and includes its metadata,
optional description, and one or more file diffs.
Message Structure
This is a complete patch that changes one line in README.md:
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Nest Example <contributor@example.invalid>
Date: Wed, 4 Nov 2026 12:00:00 +0000
Subject: Update README
Describe the fork.
diff --git a/README.md b/README.md
index 4098c542aab2cc3ac848c838784438ec2ba5eac9..d2eadd095fa4eb9ab69c5eb80a0901a8e5de9e7d 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-Old text.
+New text.
From <commit-id> ...starts a message. It is an envelope marker, separate from the author header. Nest-generated files use 40 zeroes for this ID to keep patches stable across rebases; ordinary Git patches can contain a real commit ID.From:identifies the author by name and email.Date:records the author's timestamp, including the timezone.Subject:supplies the commit's subject.- A blank line ends the headers. An optional commit description follows, separated from the file diffs by a blank line.
A standard Git patch can also include a --- separator and diff statistics before
the file diffs. They are not required in Nest's generated files. To represent
another commit in the same file, append another complete message beginning with
its own From <commit-id> ... envelope.
File Diffs
Each file diff begins with diff --git a/<old-path> b/<new-path>.
index <old-blob>..<new-blob> [mode]identifies file contents, not commits.---and+++identify the old and new file paths./dev/nullrepresents a missing side when adding or deleting a file.@@ -<start>,<count> +<start>,<count> @@gives the old and new line ranges for a text hunk. A count of one can be omitted.- Within a hunk, a leading space marks unchanged context,
-removes a line, and+adds a line.
Standard Git headers represent new files, deleted files, renames, and mode
changes. Modes include 100644 for regular files, 100755 for executable files,
and 120000 for symbolic links. Binary changes use Git's GIT binary patch
payload. Preserve diff contents and line endings, including binary payloads.
Whole-File Replacements
Nest adds one extended diff header: replace file mode <mode>. It means
“use this complete file,” regardless of the file's current upstream contents.
Inside a mail message, a replacement diff looks like this:
diff --git a/README.md b/README.md
replace file mode 100644
index 0000000000000000000000000000000000000000..3d0798163f65baa27af3fbe50f89c4b05b5081fb
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+Entire replacement.
The replacement format has these rules:
- Include exactly one
replace file modeheader, before the payload. The mode must be100644,100755, or120000. - If an
indexheader is present, its old blob ID must contain only zeroes. The replacement must not depend on the previous file's contents. - A nonempty text replacement uses
--- /dev/nulland one complete addition hunk:@@ -0,0 +1,N @@, followed by exactlyNadded lines. It has no removed or context lines. An empty file needs no text hunk; binary replacements use a Git binary patch containing the complete replacement image. - Do not combine this header with separate add, delete, rename, copy, or mode-change headers. Do not include another diff for the same destination in the same message.
- Replacement paths are literal repository-relative paths using forward slashes.
Absolute paths, empty segments,
.,..,.gitsegments, backslashes, colons, control characters, and leading or trailing whitespace are invalid.
A message can mix ordinary diffs and replacement diffs for different files. A replacement overwrites upstream edits to that file rather than merging them.
Reserved Metadata
Patch-Dir:, Patch-Filename:, and Patch-Replace-File: are Nest's internal
commit-message metadata. They are omitted from generated patch files. In
particular, Patch-Replace-File: is not an on-disk replacement header: a patch
file must use replace file mode instead.
Patch Manifests
A Flutter fork is defined by a series of patch files that are played back on top of Flutter. The list of patches that constitute a Flutter fork are defined in a text file called a patch manifest.
A manifest lists one patch filename per line, in the order to apply them. Patch files live in the same directory as the patch manifest file.
For example, a patch collection can contain three channel manifests and their patch files:
patches/
├── .patches-master
├── .patches-beta
├── .patches-stable
├── fix_build.patch
├── add_feature.patch
└── add_feature_2026-09-08.patch
The .patches-stable file might contain:
fix_build.patch
add_feature.patch
This applies fix_build.patch first, then add_feature.patch. A patch file is
only applied when it is listed in the selected manifest.
Channels
Use .patches-master, .patches-beta, or .patches-stable to select patches for
each Flutter channel. Legacy collections use a single .patches manifest.
Channels can share the same patch file or select different adaptations.
Variants
A baseline uses <name>.patch. A compatibility adaptation uses
<name>_YYYY-MM-DD.patch, dated with the UTC date of the upstream change that
required it. Each manifest selects at most one variant of a given change.
In the example above, .patches-master could list
add_feature_2026-09-08.patch instead of add_feature.patch, while beta and stable
continue using the baseline. The manifest selects the variant; the date does not
automatically determine which channel uses it.
Importing
Check out the upstream Flutter revision you want to build on, then import the selected collection:
dart run nest_patches/bin/git_import_patches.dart \
--flutter /path/to/flutter \
--patches-dir /path/to/patches \
--channel stable \
-3
Import applies the patches in manifest order as Git commits. It records the
starting revision at refs/patches/upstream-head so export can distinguish your
patches from upstream history. -3 enables Git's three-way merging.
Use --manifest .patches-stable instead of --channel stable to select a manifest
directly. Collections with channel manifests require an explicit selection;
legacy .patches collections do not.
If git am stops on a conflict, resolve it, stage the affected files, and run
git -C /path/to/flutter am --continue. Use git -C /path/to/flutter am --abort
to cancel that import.
Exporting
Commit your changes in the fork, then check what an export would change:
dart run nest_patches/bin/git_export_patches.dart \
--flutter /path/to/flutter \
--patches-dir /path/to/patches \
--channel stable \
--dry-run
Remove --dry-run to write the patch files and selected manifest. A dry run writes
nothing and exits with a nonzero status when changes would be needed.
By default, export selects commits after the upstream revision recorded during import. Without that reference, it falls back to the nearest reachable Git tag. Pass a Git revision or range as the positional argument to choose a different starting point.
To mark a committed file as a whole-file replacement, add this trailer to its commit message. Repeat it for multiple files:
Patch-Replace-File: path/to/file
Export turns the trailer into the replace file mode diff format described above.
Export updates only the selected manifest. It preserves other manifests and unlisted files, and removes an old patch file only when no manifest still uses it.
- Use a dated adaptation when a shared patch needs different implementations
for different channels. Set its commit's
Patch-Filename:line to the new filename. - Use
--update-sharedto deliberately change a shared file for every channel that selects it. Export otherwise refuses to overwrite changed shared patches. - Use
--grep "text"to export matching patch files while retaining the other selected patches and their order. If a file contains multiple commits, export includes the whole file.
Review the exported diff and validate every affected channel before committing the patch repository changes.