diff --git a/doc/howto/patching.rst b/doc/howto/patching.rst index f2d484f6..508336ce 100644 --- a/doc/howto/patching.rst +++ b/doc/howto/patching.rst @@ -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: @@ -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 @@ -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 @@ -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** @@ -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. @@ -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 @@ -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 @@ -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:: @@ -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`.