Tool for patching third-party dependencies without need to fork them
  • Python 91.5%
  • Nix 8.5%
Find a file
kp2pml30 a6e28ba6ca
Some checks failed
CI / ci (push) Has been cancelled
feat(patches): name patch files by their SHA-3 digest ✨♻️
Patch files were named by their position in the series, so inserting a
patch rewrote every file after it. The name is now the Crockford Base32
SHA-3 digest of the patch bytes and the order lives in the manifest;
the legacy layout migrates in place on the next save or update.

Rename `.git-third-party/config.json` to `manifest.json` in the same
migration: nothing in that file is configuration. The old name is still
read, and dropped once the new one is written -- unless it describes a
repo the manifest does not, which is refused rather than deleted.

`git am` is pinned against local config that would rewrite what a patch
applies (`--keep-cr`, `--whitespace=nowarn`, `--no-3way`), and the tool
now keeps a `.gitattributes` in `.git-third-party` so the enclosing
repository can not normalize the bytes its patch names are derived from.
2026-07-28 16:19:52 +09:00
.github/workflows chore(ci): add github actions, gate on 100% branch coverage 👷✅ 2026-07-13 14:48:53 +09:00
docs/contributing feat(patches): name patch files by their SHA-3 digest ✨♻️ 2026-07-28 16:19:52 +09:00
support/scripts chore: adopt new-repo scaffolding and git-hooks.nix flake 🏗️ 2026-07-13 00:12:00 +09:00
tests feat(patches): name patch files by their SHA-3 digest ✨♻️ 2026-07-28 16:19:52 +09:00
.coveragerc chore(ci): add github actions, gate on 100% branch coverage 👷✅ 2026-07-13 14:48:53 +09:00
.editorconfig chore: adopt new-repo scaffolding and git-hooks.nix flake 🏗️ 2026-07-13 00:12:00 +09:00
.gitattributes chore: adopt new-repo scaffolding and git-hooks.nix flake 🏗️ 2026-07-13 00:12:00 +09:00
.gitignore test: drive the tool in-process for 100% branch coverage ✅ 2026-07-13 00:45:27 +09:00
collect-sources.nix feat(patches): name patch files by their SHA-3 digest ✨♻️ 2026-07-28 16:19:52 +09:00
flake.lock chore: adopt new-repo scaffolding and git-hooks.nix flake 🏗️ 2026-07-13 00:12:00 +09:00
flake.nix chore(ci): add github actions, gate on 100% branch coverage 👷✅ 2026-07-13 14:48:53 +09:00
git-third-party feat(patches): name patch files by their SHA-3 digest ✨♻️ 2026-07-28 16:19:52 +09:00
LICENSE Initial commit 2024-08-20 10:18:19 +04:00
LICENSE.lgpl fix: change license of nix 2025-05-12 16:58:59 +04:00
README.md feat(patches): name patch files by their SHA-3 digest ✨♻️ 2026-07-28 16:19:52 +09:00
ruff.toml chore(cli): make the tool importable behind a main() guard ♻️ 2026-07-13 00:45:26 +09:00

Git third party

Patch third-party libraries without forking them.

standard-readme compliant License: GPL v3

Background

git-third-party is a small zero-dependency utility that is an alternative to git-submodules and git-subtree. It stores only the delta (your changes) inside your tree, not the full vendored source.

It is not oriented for highly concurrent modification of third-party tools by multiple users.

Install

With Nix (flakes):

nix profile install github:kp2pml30/git-third-party

Or just drop the single script on your PATH — it needs only python3 and git:

curl -o ~/.local/bin/git-third-party https://raw.githubusercontent.com/kp2pml30/git-third-party/main/git-third-party
chmod +x ~/.local/bin/git-third-party

Usage

# add a third-party repository
git-third-party add third-party/RustPython https://github.com/RustPython/RustPython.git a13b99642b0bc13ca89d01768a7ddbec18fe8219
# ^ git-third-party add <relative path> <repository url> <commit hash>

# now modify it as a regular repository, and commit your changes

# save patches to push to your origin
git-third-party save third-party/RustPython
# ^ git-third-party save <relative path>

# reapply patches (e.g. on a fresh checkout)
git-third-party update third-party/RustPython
# ^ git-third-party update <relative path>

Without installing, you can run it straight from the flake:

nix run github:kp2pml30/git-third-party -- add third-party/RustPython <url> <commit>

How it works

It stores a manifest under /.git-third-party/manifest.json that describes all third-party repositories and their patches. save updates the patches, update reapplies them. Older versions called that file config.json; it is still read, and renamed the next time the tool writes.

Patch files are content addressed: the file name is the SHA-3 digest of the patch bytes, in Crockford Base32 (case-insensitive and safe in paths). The order of the series lives in manifest.json, so editing history in the middle of a series renames nothing — only the patches that actually changed show up in a diff. Repositories saved by an older version, which numbered patches 1, 2, 3, …, are migrated in place by the next save or update.

The .git-third-party directory must be tracked by git, while the third-party working trees themselves should not be. The tool keeps a .gitattributes and a .gitignore of its own in there: patch bytes are what their names are derived from, so the enclosing repository must not normalize their line endings. Commit those alongside the manifest.

Each managed checkout gets its push url disabled (origin fetches from upstream, but pushing fails), so local patch commits can not accidentally be pushed to the third-party project.

Best effort is made to keep patches deterministic and to strip metadata from them, including:

  • git version
  • commit hash (which depends on the committer)
  • …

Contributing

See docs/contributing/.

License

GPL-3.0 (C) 2024-2026 Kira Prokopenko