Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 203 additions & 21 deletions doc/howto/patching.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,44 @@ The full lifecycle looks like this:
1. :ref:`patching-create` — capture local edits as a ``.patch`` file with ``dfetch diff``
2. :ref:`patching-wire` — reference the patch from the manifest so it is applied on every fetch
3. :ref:`patching-update` — refresh the patch as your edits evolve with ``dfetch update-patch``
4. :ref:`patching-upstream` — reformat the patch for upstream use with ``dfetch format-patch``
4. :ref:`patching-upstream-bump` — re-apply your patch when you move to a new upstream version
5. :ref:`patching-upstream` — reformat the patch for upstream use with ``dfetch format-patch``

.. _patching-prereq:

Before you begin
----------------

*Dfetch* calculates the diff for a project by comparing the working tree
against the revision recorded in the project's ``.dfetch_data.yaml`` metadata
file. For that comparison to be meaningful, the fetched files should already
be committed to your superproject's VCS — they become the baseline that the
patch is measured against.

After fetching, commit before editing:

.. tabs::

.. tab:: Git

.. code-block:: console

$ dfetch update some-project
$ git add some-project/
$ git commit -m "vendor: add some-project v1.2.3"

.. tab:: SVN

.. code-block:: console

$ dfetch update some-project
$ svn add some-project/
$ svn commit some-project/ -m "vendor: add some-project v1.2.3"

You can then make edits to ``some-project/`` and capture them with
``dfetch diff``. Both committed and uncommitted edits are included in the
generated patch, so you do not need to commit every intermediate step — only
the clean upstream baseline matters.

.. _patching-create:

Expand All @@ -38,11 +75,17 @@ 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).
metadata file and writes a patch file named ``some-project.patch``.

.. asciinema:: ../asciicasts/diff.cast

**What goes into the patch**

The diff captures all tracked modifications and any new untracked files in the
vendored directory. Files ignored by your superproject's VCS (via
``.gitignore`` or ``svn:ignore``) and the ``dfetch`` metadata file itself are
always excluded.

**Controlling which revisions are compared**

By default, *Dfetch* uses the revision stored in the project's metadata as the
Expand All @@ -68,8 +111,8 @@ See :ref:`diff` in the command reference for all options.
Adding the patch to the manifest
---------------------------------

Once you have a patch file, reference it from the project entry in
``dfetch.yaml`` using the :ref:`patch` attribute:
Once you have a patch file, commit it to your repository and reference it from
the project entry in ``dfetch.yaml`` using the :ref:`patch` attribute:

.. code-block:: yaml

Expand All @@ -82,7 +125,23 @@ Once you have a patch file, reference it from the project entry in
patch: some-project.patch

From this point on, every ``dfetch update`` will fetch the upstream source and
re-apply the patch on top.
re-apply the patch on top. You can test the round-trip immediately:

.. code-block:: console

$ dfetch update --force some-project

The ``--force`` flag overwrites the working tree with the freshly fetched and
patched version. Confirm the result looks right, then commit the manifest
change and the patch file together.

**Organizing patch files**

Keep patch files alongside ``dfetch.yaml`` or in a dedicated subdirectory such
as ``patches/``. *Dfetch* resolves patch paths relative to the manifest file,
so as long as the path in the manifest matches the location on disk you have
full flexibility. Committing the patch files to VCS ensures every team member
and every CI run gets the same result.

**Multiple patches**

Expand All @@ -96,8 +155,10 @@ order:
- 002-add-missing-header.patch

Patches are applied in the order listed. A good convention is to prefix each
file name with a zero-padded number so they sort correctly and their purpose is
clear at a glance.
file name with a three-digit, zero-padded number (``001-``, ``002-``, …) so
they sort correctly and their purpose is clear at a glance. The ``dfetch update-patch``
command always updates the **last** patch in the list, so the earlier patches represent
stable, settled changes and the final one accumulates ongoing work.

See :ref:`patch` in the manifest reference for the full attribute syntax.

Expand All @@ -106,22 +167,29 @@ See :ref:`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:
As your local edits evolve, the existing patch file may become stale. Instead
of manually regenerating it, run:

.. code-block:: console

$ 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.
This command:

1. Re-fetches the upstream revision (without applying any patches).
2. Computes the diff between that clean baseline and your current working tree.
3. Overwrites the **last** patch in the manifest list with the new diff.
4. Re-fetches the project and applies all patches so the working tree is left
in the patched state.

It is safe to run repeatedly as you iterate on a fix. The upstream revision
stays unchanged — only the patch file is updated.

.. note::

When the upstream version changes, *Dfetch* applies patches with fuzzy
matching, so a patch can survive minor context changes without needing an
immediate refresh. If the patch no longer applies at all, ``dfetch update``
will report the failure and you can refresh with ``dfetch update-patch``.
``update-patch`` requires the project directory to have **no uncommitted
changes** in the superproject. Commit your work first (Git users can also
``git stash``), then run the command.

.. asciinema:: ../asciicasts/update-patch.cast

Expand All @@ -137,6 +205,77 @@ See :ref:`update-patch` in the command reference for all options.

.. scenario-include:: ../features/update-patch-in-svn.feature

.. _patching-upstream-bump:

Upgrading the upstream version
-------------------------------

When you want to move to a new upstream release, update the ``tag``,
``branch``, or ``revision`` in ``dfetch.yaml`` and then run ``dfetch update``.
*Dfetch* fetches the new version and attempts to re-apply the patch using fuzzy
matching, so patches often survive minor context changes automatically.

.. code-block:: console

$ # 1. Edit dfetch.yaml: change tag v1.2.3 → v1.3.0
$ dfetch update some-project

Three outcomes are possible:

**Patch applies cleanly** — you are done. Review the result, commit the
updated manifest and the updated vendored files.

**Patch applies with fuzz warnings** — the patch applied but the context lines
shifted slightly. The files are in the correct state. Run
``dfetch update-patch some-project`` to refresh the patch against the new
baseline so it stays clean for future upgrades:

.. tabs::

.. tab:: Git

.. code-block:: console

$ git add some-project/
$ git commit -m "vendor: update some-project to v1.3.0"
$ dfetch update-patch some-project
$ git add some-project.patch
$ git commit -m "patches: refresh some-project.patch for v1.3.0"

.. tab:: SVN

.. code-block:: console

$ svn commit some-project/ -m "vendor: update some-project to v1.3.0"
$ dfetch update-patch some-project
$ svn commit some-project.patch -m "patches: refresh some-project.patch for v1.3.0"

**Patch fails to apply** — the upstream changes conflict with the local edits
tracked in the patch. Resolve the conflict manually by editing the vendored
files, then use ``dfetch update-patch`` to record the resolved state:

.. tabs::

.. tab:: Git

.. code-block:: console

$ # Manually resolve conflicts in some-project/
$ git add some-project/
$ git commit -m "vendor: update some-project to v1.3.0 with resolved conflicts"
$ dfetch update-patch some-project
$ git add some-project.patch
$ git commit -m "patches: update some-project.patch for v1.3.0"

.. tab:: SVN

.. code-block:: console

$ # Manually resolve conflicts in some-project/
$ svn commit some-project/ -m "vendor: update some-project to v1.3.0 with resolved conflicts"
$ dfetch update-patch some-project
$ svn commit some-project.patch -m "patches: update some-project.patch for v1.3.0"

.. _patching-upstream:

Contributing the patch upstream
Expand All @@ -152,10 +291,15 @@ 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 share with the upstream project.
there are several) in the current directory. Use ``--output-directory`` to
place the formatted files in a specific location:

.. code-block:: console

Before sending it, do a dry-run check to confirm it applies cleanly to a local
clone of the upstream repository:
$ dfetch format-patch some-project --output-directory patches/upstream

Before sending a patch, do a dry-run check to confirm it applies cleanly to a
local clone of the upstream repository:

.. tabs::

Expand Down Expand Up @@ -188,3 +332,41 @@ clone of the upstream repository:
.. asciinema:: ../asciicasts/format-patch.cast

See :ref:`format-patch` in the command reference for all options.

.. _patching-troubleshooting:

Troubleshooting
---------------

**"No diffs found"**

``dfetch diff`` found no changes between the working tree and the upstream
baseline recorded in ``.dfetch_data.yaml``. If you expected changes, make
sure the edits are in the vendored directory and are not excluded by your
VCS ignore rules. If the metadata file is missing, run
``dfetch update some-project`` first to re-establish the baseline.

**Patch fails to apply after an upstream bump**

The upstream version introduced changes that conflict with the local edits
in the patch. Follow the manual resolution workflow in
:ref:`patching-upstream-bump`: edit the vendored files to the desired
state, commit them, then run ``dfetch update-patch`` to regenerate the
patch from the resolved working tree.

**"skipped - Uncommitted changes"**

``dfetch update-patch`` detected uncommitted changes in the project
directory. Commit those changes first (Git users can also ``git stash``),
then run the command so the patch calculation starts from a clean state.

**"skipped - the project was never fetched before"**

Run ``dfetch update some-project`` first. The project must exist on disk
before a patch can be updated.

**"skipped - there is no patch file"**

The project has no ``patch:`` entry in the manifest. Use
``dfetch diff some-project`` to create the initial patch, then add it to
the manifest as described in :ref:`patching-wire`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading