Patching
Overview
IVPM can apply one or more patch files to a dependency after it is fetched,
without forking the upstream repository. You declare the patches in
ivpm.yaml next to the dependency; IVPM applies them deterministically and
records exactly what it did so that re-running ivpm update is idempotent.
Note
Patching is currently supported for git dependencies (both cached and
editable). Archive sources (http, gh-rls, tgz/txz/zip/
jar) declare patch capability but are not yet wired to the patch
pipeline – patches: on those sources is silently ignored for now. Apply
patches only to git dependencies until archive support lands.
Declaring Patches
Add a patches: list to a dependency. Each entry is either a string (a
path to a patch file) or a mapping with options:
package:
name: consumer
dep-sets:
- name: default
deps:
- name: somelib
src: git
url: https://github.com/foo/somelib.git
patches:
- patches/fix.patch # string form (strip: 1)
- file: patches/feature.patch # mapping form
strip: 1
directory: src
tool: git
Patch-entry options (mapping form):
Option |
Meaning |
|---|---|
|
Path to the patch file, resolved relative to the directory of the
declaring |
|
The |
|
Sub-directory of the package to apply the patch within. Defaults to the package root. |
|
Force the patch engine: |
A patch file that does not exist (at parse time) or an unknown option is a hard,
located error. Declaring patches: on a source type that cannot be patched
(dir, file, …) is also an error.
How Patches Are Applied
Patches are applied in declaration order.
With no explicit
tool, IVPM auto-detects: it triesgit apply -p<strip>first, then falls back to GNUpatch -p<strip>. The first engine that applies cleanly wins.Application is fail-closed: the first patch that does not apply cleanly aborts the update with an error (a partially patched tree is never published).
A path-traversal guard rejects any patch whose targets would escape the package directory before the patch tool runs.
Idempotency and Change Detection
When IVPM patches a tree it writes a manifest, .ivpm/patch-manifest.json,
recording the base version, every applied patch (name, source, MD5, strip,
directory), and a patch-set id.
The patch-set id is the SHA-256 of a canonical description of the patch set –
one entry per patch, in order, carrying each patch file’s MD5 plus its strip
and directory. Consequences:
Editing a patch file’s contents, adding/removing a patch, or reordering patches changes the id.
Renaming a patch file or changing its
tooldoes not change the id (the resulting tree is identical).
On the next ivpm update, IVPM compares the declared patch set against the
manifest: a match is a no-op; a change triggers a re-establish (for a clean
tree) or an error (for a tree with other local modifications – see
The Drift Error).
Caching Patched Dependencies
For a cached dependency (cache: true), patching is base-first:
The pristine base is always cached as its own entry, keyed by its base version (the resolved commit for git).
Each patched variant is cached as a full copy of the base with the patch set applied, keyed by
<base_version>+patch.<id>(the first 16 hex characters of the patch-set id). The variant is symlinked read-only intopackages/exactly like any other cached package.A dependency with an empty patch set resolves to the base version byte-for-byte, so merely being patchable never fragments the cache or invalidates existing pristine entries.
Worked example – two consumers, same base:
Project A pins
somelibat commitabc123with patch set P.Project B pins the same commit with a different patch set Q.
The cache ends up with one base entry (abc123) and two variant entries
(abc123+patch.<idP> and abc123+patch.<idQ>). The base is fetched once
and shared; each distinct patch set materializes one variant, reused by every
consumer that requests the same (base, patch set) pair.
Editable Patched Checkouts
For an editable git dependency (cache: false or cache omitted), IVPM
applies the patches in place in packages/<pkg>: it retains the pristine
base (so it can roll back), applies the patch set, and writes the manifest.
Re-running ivpm update is idempotent:
Same declared patch set, clean tree – no-op.
Changed declared patch set, clean tree – IVPM rolls the tree back to the pristine base and re-applies the new set.
Same declared patch set, you have local edits – IVPM leaves your in-progress work alone (it never reverts a working tree you are editing).
The safety invariant: IVPM mutates the working tree only when the sole deviation from “base + recorded patches” is its own patch application. Any other drift makes it stop rather than risk discarding your work.
The Drift Error
If the declared patch set changes and the working tree has local
modifications beyond the patches IVPM recorded, an unattended ivpm update
cannot safely re-establish the tree without discarding those changes, so it
stops with an error instead:
package somelib has local modifications beyond its applied patches; refusing
to re-establish (this would discard your changes). Your tree is left
untouched.
- to discard the changes and re-establish: remove 'packages/somelib', then
re-run 'ivpm update'
The tree is never modified in this case. To re-establish cleanly, remove the
package directory and re-run ivpm update; IVPM will re-fetch the base and
apply the current declared patch set. (This is the intended escape hatch under
the current CLI; a destructive ivpm update --reset convenience may be added
later.)
Limitations
Patching applies to git dependencies only today; archive sources are not yet wired (see the note at the top).
A patched git dependency is resolved through the patch pipeline and does not consult a deps-source.