Patch a project¶
Dfetch has a first-class patch workflow. When you need to fix a bug or apply a customisation to a vendored dependency, you can track that change as a patch file that is automatically re-applied on every dfetch update. When the fix is ready to share, Format patch converts it into a contributor-ready unified diff that upstream maintainers can apply directly.
The full lifecycle looks like this:
Capturing local changes — capture local edits as a
.patchfile withdfetch diffAdding the patch to the manifest — reference the patch from the manifest so it is applied on every fetch
Refreshing a patch — refresh the patch as your edits evolve with
dfetch update-patchContributing the patch upstream — reformat the patch for upstream use with
dfetch format-patch
Capturing local changes¶
After fetching a project with dfetch update, make your edits directly in the vendored source tree. Once you are happy with the changes, run:
$ dfetch diff some-project
Dfetch compares the working tree against the revision recorded in the
metadata file and writes a patch file named some-project.patch (or
some-project-N.patch if multiple patches already exist).
Controlling which revisions are compared
By default, Dfetch uses the revision stored in the project’s metadata as the base. You can override this:
Single base revision:
dfetch diff some-project --revs 23864ef2Explicit range:
dfetch diff some-project --revs 23864ef2:4a9cb18
Example: A patch file is generated
Given "SomeProject/README.md" in MyProject is changed and committed with
"""
An important sentence for the README!
"""
When I run "dfetch diff SomeProject"
Then the patch file 'MyProject/SomeProject.patch' is generated
"""
diff --git a/README.md b/README.md
index 1e65bd6..faa3b21 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
Generated file for SomeProject.git
+An important sentence for the README!
"""
Example: New files are part of the patch
Given files as '*.tmp' are ignored in git in MyProject
And "SomeProject/NEWFILE.md" in MyProject is created and committed with
"""
A completely new tracked file.
"""
And "SomeProject/NEW_UNCOMMITTED_FILE.md" in MyProject is created
And "SomeProject/IGNORE_ME.tmp" in MyProject is created
When I run "dfetch diff SomeProject"
Then the patch file 'MyProject/SomeProject.patch' is generated
"""
diff --git a/NEWFILE.md b/NEWFILE.md
new file mode 100644
index 0000000..a2d8605
--- /dev/null
+++ b/NEWFILE.md
@@ -0,0 +1 @@
+A completely new tracked file.
diff --git a/NEW_UNCOMMITTED_FILE.md b/NEW_UNCOMMITTED_FILE.md
new file mode 100644
index 0000000..0ee3895
--- /dev/null
+++ NEW_UNCOMMITTED_FILE.md
@@ -0,0 +1,1 @@
+Some content
"""
Example: No change is present
When I run "dfetch diff SomeProject"
Then the output shows
"""
Dfetch (0.13.0)
SomeProject:
> No diffs found since 59efb91396fd369eb113b43382783294dc8ed6d2
"""
Example: Diff is generated on uncommitted changes
Given "SomeProject/README.md" in MyProject is changed with
"""
An important sentence for the README!
"""
When I run "dfetch diff SomeProject"
Then the patch file 'MyProject/SomeProject.patch' is generated
"""
diff --git a/README.md b/README.md
index 1e65bd6..faa3b21 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
Generated file for SomeProject.git
+An important sentence for the README!
"""
Example: Metadata is not part of diff
Given the metadata file ".dfetch_data.yaml" of "MyProject/SomeProject" is changed
When I run "dfetch diff SomeProject"
Then the output shows
"""
Dfetch (0.13.0)
SomeProject:
> No diffs found since 59efb91396fd369eb113b43382783294dc8ed6d2
"""
Example: A patch file is generated
Given "SomeProject/README.md" in MySvnProject is changed, added and committed with
"""
An important sentence for the README!
"""
When I run "dfetch diff SomeProject" in MySvnProject
Then the patch file 'MySvnProject/SomeProject.patch' is generated
"""
Index: README.md
===================================================================
--- README.md
+++ README.md
@@ -1,1 +1,2 @@
Generated file for SomeProject
+An important sentence for the README!
"""
Example: New files are part of the patch
Given files as '*.tmp' are ignored in 'MySvnProject/SomeProject' in svn
And "SomeProject/NEWFILE.md" in MySvnProject is changed, added and committed with
"""
A completely new tracked file.
"""
And "SomeProject/NEW_UNCOMMITTED_FILE.md" in MySvnProject is created
And "SomeProject/IGNORE_ME.tmp" in MySvnProject is created
When I run "dfetch diff SomeProject" in MySvnProject
Then the patch file 'MySvnProject/SomeProject.patch' is generated
"""
Index: NEWFILE.md
===================================================================
--- NEWFILE.md
+++ NEWFILE.md
@@ -0,0 +1,1 @@
+A completely new tracked file.
Index: NEW_UNCOMMITTED_FILE.md
===================================================================
--- /dev/null
+++ NEW_UNCOMMITTED_FILE.md
@@ -0,0 +1,1 @@
+Some content
"""
Example: No change is present
When I run "dfetch diff SomeProject" in MySvnProject
Then the output shows
"""
Dfetch (0.13.0)
SomeProject:
> No diffs found since 1
"""
Example: A patch file is generated on uncommitted changes
Given "SomeProject/README.md" in MySvnProject is changed with
"""
An important sentence for the README!
"""
When I run "dfetch diff SomeProject" in MySvnProject
Then the patch file 'MySvnProject/SomeProject.patch' is generated
"""
Index: README.md
===================================================================
--- README.md
+++ README.md
@@ -1,1 +1,2 @@
Generated file for SomeProject
+An important sentence for the README!
"""
Example: Metadata is not part of diff
Given the metadata file ".dfetch_data.yaml" of "MySvnProject/SomeProject" is changed
When I run "dfetch diff SomeProject" in MySvnProject
Then the output shows
"""
Dfetch (0.13.0)
SomeProject:
> No diffs found since 1
"""
Adding the patch to the manifest¶
Once you have a patch file, reference it from the project entry in
dfetch.yaml using the Patch attribute:
manifest:
version: '0.0'
projects:
- name: some-project
url: https://github.com/example/some-project
tag: v1.2.3
patch: some-project.patch
From this point on, every dfetch update will fetch the upstream source and
re-apply the patch on top. See Patch in the manifest reference for the
full attribute syntax.
Refreshing a patch¶
As your local edits evolve — or when the upstream version changes — the existing patch file may no longer apply cleanly. Instead of manually regenerating it, run:
$ dfetch update-patch some-project
This regenerates the last patch for some-project from the current working
tree, keeping the upstream revision unchanged. It is safe to run repeatedly
as you iterate on the fix.
Example: Patch is updated with new local changes
Given "SomeProject/README.md" in MyProject is changed and committed with
"""
Update to patched file for SomeProject.git
"""
When I run "dfetch update-patch SomeProject" in MyProject
Then the patch file 'MyProject/patches/SomeProject.patch' is updated
"""
diff --git a/README.md b/README.md
index 1e65bd6..925b8c4 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
-Generated file for SomeProject.git
+Patched file for SomeProject.git
+Update to patched file for SomeProject.git
"""
And the output shows
"""
Dfetch (0.13.0)
SomeProject:
> Fetched master - f9b88b8259d9a7fb48327bf23beabe40c150d474
> Updating patch "patches/SomeProject.patch"
> Fetched master - f9b88b8259d9a7fb48327bf23beabe40c150d474
> Applying patch "patches/SomeProject.patch"
successfully patched 1/1: b'README.md'
"""
Example: Patch is updated with new but not ignored files
Given files as '*.tmp' are ignored in git in MyProject
And "SomeProject/IGNORE_ME.tmp" in MyProject is created
And "SomeProject/NEWFILE.md" in MyProject is created
And all files in MyProject are committed
When I run "dfetch update-patch SomeProject" in MyProject
Then the patch file 'MyProject/patches/SomeProject.patch' is updated
"""
diff --git a/NEWFILE.md b/NEWFILE.md
new file mode 100644
index 0000000..0ee3895
--- /dev/null
+++ b/NEWFILE.md
@@ -0,0 +1 @@
+Some content
diff --git a/README.md b/README.md
index 1e65bd6..38c1a65 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-Generated file for SomeProject.git
+Patched file for SomeProject.git
"""
Example: Patch is updated with new local changes
Given "SomeProject/README.md" in MySvnProject is changed, added and committed with
"""
Update to patched file for SomeProject
"""
When I run "dfetch update-patch SomeProject" in MySvnProject
Then the patch file 'MySvnProject/patches/SomeProject.patch' is updated
"""
Index: README.md
===================================================================
--- README.md
+++ README.md
@@ -1,1 +1,2 @@
-Generated file for SomeProject
+Patched file for SomeProject
+Update to patched file for SomeProject
"""
And the output shows
"""
Dfetch (0.13.0)
Update patch is only fully supported in git superprojects!
SomeProject:
> Fetched trunk - 1
> Updating patch "patches/SomeProject.patch"
> Fetched trunk - 1
> Applying patch "patches/SomeProject.patch"
successfully patched 1/1: b'README.md'
"""
Example: Patch is updated with new but not ignored files
Given files as '*.tmp' are ignored in 'MySvnProject/SomeProject' in svn
And "SomeProject/IGNORE_ME.tmp" in MySvnProject is created
And all files in MySvnProject are added and committed
When I run "dfetch update-patch SomeProject" in MySvnProject
Then the patch file 'MySvnProject/patches/SomeProject.patch' is updated
"""
Index: README.md
===================================================================
--- README.md
+++ README.md
@@ -1,1 +1,1 @@
-Generated file for SomeProject
+Patched file for SomeProject
"""
Contributing the patch upstream¶
Patches generated by dfetch diff are relative to the project’s vendored
directory inside your repository. Most upstream projects expect patches to be
relative to their own root, which is a different path. To reformat all patches
for a project:
$ dfetch format-patch some-project
This writes a formatted-some-project.patch file (or one file per patch if
there are several) that is ready to attach to a pull request or send by email.
You can verify the formatted patch applies cleanly before submitting:
$ git apply --check formatted-some-project.patch
Example: All patch files are formatted
Given the manifest 'dfetch.yaml'
"""
manifest:
version: '0.0'
remotes:
- name: github-com-dfetch-org
url-base: https://github.com/dfetch-org/test-repo
projects:
- name: ext/test-repo-tag
tag: v2.0
dst: ext/test-repo-tag
patch:
- 001-diff.patch
- 002-diff.patch
- 003-new-file.patch
"""
And the patch file '001-diff.patch'
"""
diff --git a/README.md b/README.md
index 32d9fad..62248b7 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
# Test-repo
-A test repo for testing dfetch.
+A test repo for testing patch.
"""
And the patch file '002-diff.patch'
"""
diff --git a/README.md b/README.md
index 32d9fad..62248b7 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
# Test-repo
-A test repo for testing patch.
+A test repo for testing formatting patches.
"""
And the patch file '003-new-file.patch'
"""
diff --git a/NEWFILE.md b/NEWFILE.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWFILE.md
@@ -0,0 +1 @@
+This is a new file.
"""
And all projects are updated
When I run "dfetch format-patch ext/test-repo-tag --output-directory patches"
Then the patch file 'patches/001-diff.patch' is generated
"""
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: John Doe <john@dfetch.io>
Date: Mon, 02 Feb 2026 21:02:42 +0000
Subject: [PATCH 1/3] Patch for ext/test-repo-tag
Patch for ext/test-repo-tag
diff --git a/README.md b/README.md
index 32d9fad..62248b7 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
# Test-repo
-A test repo for testing dfetch.
+A test repo for testing patch.
"""
And the patch file 'patches/002-diff.patch' is generated
"""
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: John Doe <john@dfetch.io>
Date: Mon, 02 Feb 2026 21:02:42 +0000
Subject: [PATCH 2/3] Patch for ext/test-repo-tag
Patch for ext/test-repo-tag
diff --git a/README.md b/README.md
index 32d9fad..62248b7 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
# Test-repo
-A test repo for testing patch.
+A test repo for testing formatting patches.
"""
And the patch file 'patches/003-new-file.patch' is generated
"""
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: John Doe <john@dfetch.io>
Date: Mon, 02 Feb 2026 21:02:42 +0000
Subject: [PATCH 3/3] Patch for ext/test-repo-tag
Patch for ext/test-repo-tag
diff --git a/NEWFILE.md b/NEWFILE.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWFILE.md
@@ -0,0 +1,1 @@
+This is a new file.
"""
Example: Svn subproject in Git superproject gives a svn patch
Given a svn-server "SomeProject" with the files
| path |
| SomeFolder/SomeSubFolder/README.md |
And the patch file 'MyProject/patches/001-diff.patch'
"""
diff --git a/README.md b/README.md
index 32d9fad..62248b7 100644
--- a/README.md
+++ b/README.md
@@ -1,1 +1,1 @@
-Generated file for SomeProject
+Patched file for SomeProject
"""
And a fetched and committed git-repo "MyProject" with the manifest:
"""
manifest:
version: 0.0
projects:
- name: SomeProject
url: some-remote-server/SomeProject
src: SomeFolder/SomeSubFolder
patch:
- patches/001-diff.patch
vcs: svn
"""
When I run "dfetch format-patch SomeProject --output-directory MyProject/patches"
Then the patch file 'MyProject/patches/001-diff.patch' is generated
"""
Index: SomeFolder/SomeSubFolder/README.md
===================================================================
--- SomeFolder/SomeSubFolder/README.md
+++ SomeFolder/SomeSubFolder/README.md
@@ -1,1 +1,1 @@
-Generated file for SomeProject
+Patched file for SomeProject
"""
$ svn patch formatted-some-project.patch
Example: All patch files are formatted
Given a svn-server "SomeProject" with the files
| path |
| SomeFolder/SomeSubFolder/README.md |
And the patch file 'MySvnProject/patches/001-diff.patch'
"""
Index: README.md
===================================================================
--- README.md
+++ README.md
@@ -1,1 +1,1 @@
-Generated file for SomeProject
+Patched file for SomeProject
"""
And the patch file 'MySvnProject/patches/002-diff.patch'
"""
Index: README.md
===================================================================
--- README.md
+++ README.md
@@ -1,1 +1,1 @@
-Generated file for SomeProject
+Patched file for formatted patch of SomeProject
"""
And a fetched and committed MySvnProject with the manifest
"""
manifest:
version: 0.0
projects:
- name: SomeProject
url: some-remote-server/SomeProject
src: SomeFolder/SomeSubFolder
patch:
- patches/001-diff.patch
- patches/002-diff.patch
vcs: svn
"""
And all projects are updated
When I run "dfetch format-patch SomeProject --output-directory formatted-patches" in MySvnProject
Then the patch file 'MySvnProject/formatted-patches/001-diff.patch' is generated
"""
Index: SomeFolder/SomeSubFolder/README.md
===================================================================
--- SomeFolder/SomeSubFolder/README.md
+++ SomeFolder/SomeSubFolder/README.md
@@ -1,1 +1,1 @@
-Generated file for SomeProject
+Patched file for SomeProject
"""
And the patch file 'MySvnProject/formatted-patches/002-diff.patch' is generated
"""
Index: SomeFolder/SomeSubFolder/README.md
===================================================================
--- SomeFolder/SomeSubFolder/README.md
+++ SomeFolder/SomeSubFolder/README.md
@@ -1,1 +1,1 @@
-Generated file for SomeProject
+Patched file for formatted patch of SomeProject
"""
Example: Git subproject in Svn superproject gives a git patch
Given a git repository "SomeProject.git"
And the patch file 'MySvnProject/patches/001-diff.patch'
"""
Index: README.md
===================================================================
--- README.md
+++ README.md
@@ -1,1 +1,1 @@
-Generated file for SomeProject
+Patched file for SomeProject
"""
And a fetched and committed MySvnProject with the manifest
"""
manifest:
version: 0.0
projects:
- name: some-subproject
url: some-remote-server/SomeProject.git
patch:
- patches/001-diff.patch
"""
When I run "dfetch format-patch some-subproject --output-directory MySvnProject/patches"
Then the patch file 'MySvnProject/patches/001-diff.patch' is generated
"""
From ce0f26a0ef7924942debe7285af89337bac26ddf Mon Sep 17 00:00:00 2001
From: ben <ben@example.com>
Date: Sat, 07 Feb 2026 16:23:34 +0000
Subject: [PATCH] Patch for some-subproject
Patch for some-subproject
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,1 +1,1 @@
-Generated file for SomeProject
+Patched file for SomeProject
"""