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:

  1. Capturing local changes — capture local edits as a .patch file with dfetch diff

  2. Adding the patch to the manifest — reference the patch from the manifest so it is applied on every fetch

  3. Refreshing a patch — refresh the patch as your edits evolve with dfetch update-patch

  4. Contributing 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 23864ef2

  • Explicit range: dfetch diff some-project --revs 23864ef2:4a9cb18

Example: A patch file is generated
../features/diff-in-git.feature
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
../features/diff-in-git.feature
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
../features/diff-in-git.feature
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
../features/diff-in-git.feature
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
../features/diff-in-git.feature
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
"""

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
../features/update-patch-in-git.feature
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
../features/update-patch-in-git.feature
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

    """

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
../features/format-patch-in-git.feature
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
../features/format-patch-in-git.feature
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

    """