{"id":50941814,"url":"https://github.com/blhsing/pure-python-git","last_synced_at":"2026-06-17T15:36:11.616Z","repository":{"id":360736705,"uuid":"1251473796","full_name":"blhsing/pure-python-git","owner":"blhsing","description":"Pure-Python reimplementation of git. All built-in subcommands; on-disk compatible with the C implementation; installs a drop-in git console script.","archived":false,"fork":false,"pushed_at":"2026-06-14T12:34:58.000Z","size":702,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T15:36:09.658Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/blhsing.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-27T16:01:47.000Z","updated_at":"2026-06-14T12:35:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/blhsing/pure-python-git","commit_stats":null,"previous_names":["blhsing/pythongit"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/blhsing/pure-python-git","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blhsing%2Fpure-python-git","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blhsing%2Fpure-python-git/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blhsing%2Fpure-python-git/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blhsing%2Fpure-python-git/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blhsing","download_url":"https://codeload.github.com/blhsing/pure-python-git/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blhsing%2Fpure-python-git/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34453440,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-17T15:36:10.064Z","updated_at":"2026-06-17T15:36:11.600Z","avatar_url":"https://github.com/blhsing.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pure-python-git\n\n[![CI](https://github.com/blhsing/pure-python-git/actions/workflows/ci.yml/badge.svg)](https://github.com/blhsing/pure-python-git/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/pure-python-git.svg)](https://pypi.org/project/pure-python-git/)\n\nA pure-Python reimplementation of `git`. No external runtime dependencies — just\nthe Python standard library. It implements a broad set of git built-in\nsubcommands, aliases, and pythongit-specific helpers. The on-disk format is\nbyte-for-byte compatible with real `git`, and the package optionally installs a\ndrop-in `git` console script.\n\n## Why does this exist?\n\nSometimes you need `git` on a machine where you can't install a real `git`\nbinary — locked-down CI workers, restricted containers, environments where the\nonly thing you can `pip install` is wheels. `pythongit` ships as a single\npure-Python wheel and exposes a `git` command. Most everyday workflows just\nwork.\n\nThis is also a reasonable reference implementation if you want to understand\ngit's on-disk formats and protocols. The code in this repo cross-references\ngit's own `Documentation/gitformat-*.adoc` specs for the wire formats it\nimplements.\n\n## Install\n\n```bash\npip install pure-python-git\n```\n\nBy default this installs **one console script**: `pygit`. The system `git`\nbinary on your PATH is **not** shadowed unless you explicitly opt in.\n\n### Opt-in `git` drop-in\n\nThe `git` command name is **not** installed by default. You can opt in two ways:\n\n**1. The standard extras syntax — recommended:**\n\n```bash\npip install \"pure-python-git[git]\"\n```\n\nThis pulls in the tiny companion package `pure-python-git-shim`, which exists\nonly to register a `git` console-script. Uninstall it with\n`pip uninstall pure-python-git-shim` to remove the `git` command without\ntouching the rest of pythongit.\n\n**2. After-the-fact, without reinstalling:**\n\n```bash\npygit install-git-shim\n```\n\nThis copies `pygit` to a sibling `git` (or `git.exe` on Windows) in the same\nscripts directory. Reverse with `pygit uninstall-git-shim`. Useful when you\nalready have pythongit installed and don't want to touch the pip metadata.\n\nWhichever way you choose, whether `git` resolves to pythongit depends on PATH\norder — both commands warn if a different `git` is earlier on PATH.\n\nYou can also run pythongit from a checkout without installing:\n\n```bash\npython -m pythongit \u003ccommand\u003e [args...]\n```\n\n### Why is the `git` name opt-in?\n\nSilently shadowing `git` on every install is a footgun: scripts that shell\nout to `git` start invoking pythongit instead the next time you\n`pip install pure-python-git` into a venv, without warning. Making it opt-in\nturns it into a deliberate choice you make per-environment.\n\n## Tutorial\n\n```bash\nmkdir demo \u0026\u0026 cd demo\npygit init .\npygit config user.name \"You\"\npygit config user.email \"you@example.com\"\n\necho \"hello\" \u003e a.txt\npygit add a.txt\npygit commit -m \"first commit\"\n\necho \"world\" \u003e\u003e a.txt\npygit diff\npygit add a.txt\npygit commit -m \"append world\"\n\npygit log --oneline\npygit tag v1\npygit branch feature\npygit checkout feature\necho \"feature work\" \u003e f.txt\npygit add f.txt\npygit commit -m \"feature commit\"\n\npygit checkout main\npygit merge feature\n```\n\nCloning over HTTPS:\n\n```bash\npygit clone https://github.com/some/repo.git\n```\n\nCloning or converting across object formats:\n\n```bash\npygit clone --object-format=sha256 ./sha1-repo ./sha256-copy\npygit convert-object-format --object-format=sha1 ./sha256-copy ./sha1-copy\n```\n\n## Supported commands\n\nThe command surface covers the 147 built-ins from C Git 2.54.0 plus\npythongit-specific helpers. Runtime registration is checked against the\nsource-derived manifest in\n`tests/git_parity/manifest/git-2.54.0.json`, so upstream built-in names such as\n`stage`, `pickaxe`, `fsck-objects`, `checkout--worker`,\n`credential-cache--daemon`, and `submodule--helper` remain available under the\nsame spellings as C Git. Pythongit-only helpers are kept separate from the\ncompatibility surface.\n\nCLI parity is tracked against C Git 2.54.0 from the upstream implementation\nsource: `tools/git_cli_manifest.py` regenerates the manifest, and a dedicated\nCI job builds a `git version 2.54.0` oracle and runs:\n\n```bash\nPYGIT_PARITY_GIT=/path/to/git-2.54.0 \\\n  python -m pytest tests/git_parity --require-git-254-oracle\n```\n\nWithout `--require-git-254-oracle`, local runs skip behavior checks when the\noracle binary is unavailable. Selected highlights:\n\n**Plumbing.** `hash-object`, `cat-file`, `ls-tree`, `write-tree`, `read-tree`,\n`commit-tree`, `mktree`, `mktag`, `update-ref`, `symbolic-ref`, `rev-parse`,\n`rev-list`, `ls-files`, `diff-tree`, `diff-index`, `diff-files`, `diff-pairs`,\n`pack-objects`, `unpack-objects`, `index-pack`, `verify-pack`, `show-index`,\n`unpack-file`, `merge-index`, `merge-file`, `update-index`, `update-server-info`,\n`check-ref-format`, `check-attr`, `check-mailmap`, `check-ignore`, `for-each-ref`,\n`show-ref`, `pack-refs`, `prune-packed`, `pack-redundant`, `multi-pack-index`,\n`fetch-pack`, `send-pack`, `upload-pack`, `receive-pack`, `upload-archive`,\n`http-fetch`, `http-backend`, `fmt-merge-msg`, `mailinfo`, `mailsplit`,\n`patch-id`, `commit-graph`, `var`, `stripspace`.\n\n**Porcelain.** `init`, `clone`, `add`, `rm`, `mv`, `status`, `commit`, `log`,\n`show`, `diff`, `branch`, `tag`, `checkout`, `switch`, `restore`, `reset`,\n`merge`, `merge-tree`, `cherry-pick`, `revert`, `rebase`, `replay`, `cherry`,\n`range-diff`, `stash`, `reflog`, `notes`, `bisect`, `blame`, `annotate`,\n`describe`, `name-rev`, `shortlog`, `whatchanged`, `clean`, `archive`,\n`bundle`, `format-patch`, `am`, `apply`, `grep`, `show-branch`, `worktree`,\n`submodule`, `sparse-checkout`, `request-pull`, `interpret-trailers`,\n`verify-commit`, `verify-tag`, `rerere`, `replace`, `gc`, `repack`, `prune`,\n`count-objects`, `fsck`, `pull`, `fetch`, `push`, `remote`, `ls-remote`,\n`config`, `refs`, `convert-object-format`, `repo`, `diagnose`, `bugreport`,\n`last-modified`, `history`, `url-parse`, `maintenance`.\n\n**Bridges (orchestrate other binaries / protocols).** `send-email` (via\n`smtplib`), `daemon` (TCP git:// server), `instaweb`/`gitweb` (`http.server`-based\nbrowser), `gitk`/`gui` (Tk log viewer), `cvsimport`/`cvsexportcommit`/`cvsserver`\n(shell out to `cvs`), `svn` (shell out to `svn`), `difftool`/`mergetool`\n(invoke configured external tool), `credential`/`credential-store`/\n`credential-cache`/`credential-cache-daemon`, `remote-helper`/`remote-ext`/\n`remote-fd`, `fsmonitor`/`fsmonitor-daemon`, `shell` (restricted ssh\ndispatcher), `init-db`, `submodule-helper`, `checkout-worker`, `backfill`.\n\nTo see the full list:\n\n```bash\npygit help\n```\n\n## Interop with real git\n\nThe on-disk format is byte-for-byte compatible with the git C implementation.\nThe test suite verifies this against the real `git` binary:\n\n| pythongit writes... | ...real `git` validates |\n|---|---|\n| loose objects | `git fsck` |\n| tree / commit objects | `git cat-file -p` |\n| index v2 with stages | `git ls-files --stage` |\n| pack v2 + idx v2 (with deltas) | `git verify-pack -v` |\n| pack and MIDX bitmap indexes | `git rev-list --test-bitmap` |\n| binary commit-graph file with changed-path Bloom filters | `git commit-graph verify` |\n| SHA-1/SHA-256 object-format repos | `git fsck`, `git rev-parse --show-object-format` |\n| refs / packed-refs / reflog | `git log --all` |\n| smart HTTPS push payload | `git receive-pack` |\n\nThe reverse also holds: pythongit reads packs and indexes produced by real\n`git` clones.\n\n## Architecture\n\n### Object storage\n\nLoose objects under `.git/objects/\u003coid[:2]\u003e/\u003coid[2:]\u003e`, zlib-compressed. SHA-1\nand SHA-256 repositories are selected by `extensions.objectformat`. Loose-object\nenumeration uses a persistent `.git/objects/info/pygit-loose-cache-v1` cache\nvalidated by fanout directory mtimes/sizes, so repeated `count-objects`,\nabbreviated-OID resolution, and pruning commands do not rewalk every loose\nobject directory when nothing changed.\n\nPack objects live in `.git/objects/pack/pack-*.{pack,idx}`. The pack reader\nmmaps pack files, binary-searches `.idx` tables, and handles both `REF_DELTA`\n(delta against a hex object-id base) and `OFS_DELTA` (delta against an earlier\noffset in the same pack). Pack creation has two paths: `pack.build_pack` is the\nsmall in-memory builder used by tests and helper code, while CLI repacks,\nbundles, `pack-objects --stdout`, push requests, and upload-pack responses use\na bounded-memory streaming writer that still emits OFS deltas against recent\nsame-type bases.\n\n`pack-objects --all` and `repack` write pack `.bitmap` indexes for full\nreachable packs. `multi-pack-index write --bitmap` writes `RIDX`/`BTMP` chunks\nplus the companion `multi-pack-index-\u003chash\u003e.bitmap` file. Reachability queries,\n`rev-list --count`, pruning, and maintenance paths use pack/MIDX bitmaps when\navailable. The bitmaps use Git's v1 `BITM` format and EWAH containers; the\nfirst implementation emits literal EWAH words rather than XOR-compressed\nchains, prioritizing compatibility and simple verification over minimum file\nsize.\n\n`translate.ObjectTranslator` converts complete reachable object graphs between\nSHA-1 and SHA-256 by rehashing blobs and rewriting embedded object IDs in\ntrees, commits, and annotated tags. `clone --object-format=...` uses this when\nthe requested target format differs from the source format.\n\n### Index\n\nDIRC v2 with full stage support (bits 14-13 of the flags field). When a merge\nor cherry-pick conflicts, stages 1 (base), 2 (ours), 3 (theirs) are written to\nthe index while the merged-with-markers blob is left in the worktree.\n`pygit commit` refuses to commit while any stage \u003e 0 exists; `pygit add`\nclears the conflict stages on resolution. `pygit merge-index -o \u003ctool\u003e` walks\nconflicted entries and invokes the driver with `(path, base-tmp, ours-tmp,\ntheirs-tmp)`.\n\n### Refs \u0026 reflog\n\n`refs.update_ref` is the single chokepoint for all ref updates; it\nautomatically appends to `.git/logs/\u003cref\u003e` and (when the updated ref is what\nHEAD points at symbolically) to `.git/logs/HEAD`. This means `reflog`, `stash`\n(via `refs/stash`), and `notes` (via `refs/notes/commits`) all share one\nmechanism.\n\n### Merge\n\n`merge.merge_bases` is a faithful port of `commit-reach.c`'s\n`paint_down_to_common`: a date-ordered priority walk with PARENT1/PARENT2/STALE\nflags and insertion-order tie-breaking, followed by `remove_redundant`, so the\nmerge bases come back in the **same order** C Git returns them (which the\nrecursive merge below depends on).\n\nHigh-level three-way merges run a pure-Python port of Git's own `ort` engine —\nno `git` binary and no fallback engine. The port lives in four modules and\nreproduces `git merge-tree --write-tree` byte-for-byte (result tree oid,\nconflicted blobs with markers, and conflicted index stages):\n\n* `xdiff.py` — Git's xdiff library: record classification, the **histogram**\n  diff that `ort` hardcodes for content merges (with the classic Myers\n  algorithm as its documented fallback), change compaction, and the zealous\n  three-way `xdl_merge` that emits `\u003c\u003c\u003c\u003c\u003c\u003c\u003c` / `=======` / `\u003e\u003e\u003e\u003e\u003e\u003e\u003e` markers\n  (merge / diff3 / zdiff3 styles, configurable marker size).\n* `diffcore.py` — rename detection: the `diffcore-delta` spanhash similarity\n  estimator plus exact, basename-driven, and inexact NxM matrix matching from\n  `diffcore-rename.c`, with `relevant_sources` source-culling.\n* `mergeort.py` — the `merge-ort.c` tree engine: the recursive three-way tree\n  walk (`collect_merge_info`, tracking `dir_rename_mask` and rename-source\n  relevance), file and **directory** rename detection/resolution\n  (`process_renames`, dir-rename counting with RELEVANT_FOR_SELF/ANCESTOR\n  gating), per-path resolution (`process_entry`, including the `call_depth`\n  virtual-ancestor behaviors), submodule fast-forward, `.gitattributes`\n  `merge`/`conflict-marker-size` handling (built-in text/binary/union plus\n  shell-executed custom drivers), and streamed result-tree assembly.\n* `ort.py` — adapter exposing `merge_tree(repo, merge_base, ours, theirs)`\n  (explicit base, like `git merge-tree --merge-base`) and `merge_commits(repo,\n  ours, theirs)` (computes all merge bases and **recursively** merges them into\n  a virtual ancestor, like `git merge-tree \u003ca\u003e \u003cb\u003e`). The tree-ish arguments\n  double as conflict-marker labels, exactly as the matching `git merge-tree`\n  arguments do; `merge.conflictStyle` is honored.\n\n### Rerere\n\nWhen a conflict is produced, the file (with markers) is hashed after\nnormalization (branch labels stripped) and stored under\n`.git/rr-cache/\u003chash\u003e/preimage` plus a line in `_pending.txt`. When the user\nresolves the conflict and runs `commit`, the post-image is recorded next to\nit. The next time the *same* logical conflict appears, the merge replays the\npost-image automatically.\n\n### Bisect\n\n`bisect_step` follows git's `best_bisection`: for each candidate commit,\ncompute `min(reachable_from_it, n - reachable_from_it)` and pick the maximum;\nthat is, the commit that splits the candidate DAG as evenly as possible. Parent\nlookups use the commit-graph when present. The scorer mirrors Git's `bisect.c`\nshape: single-parent chains inherit parent weights, while merge commits get an\nexact distance walk so shared ancestors are counted once.\n\n### Pack writer (delta compression)\n\n`pack._compute_delta` builds a hash table of every 16-byte block in the base,\nthen sweeps the target looking for matches \u003e= 4 bytes long. Matches become\n`COPY` ops; misses are accumulated into `INSERT` ops capped at 127 bytes each.\nThe encoder is conservative: it accepts a delta only when it's at most 50% of\nraw size, keeping the chain length sensible. The streaming writer processes\nbounded batches sorted by type/size and keeps only a small recent-base window,\nso large pack creation no longer requires all object contents or the final pack\nbytes in memory. Incoming fetch/receive packs are streamed to a temporary file,\nmmap-indexed from disk, and installed as pack/idx pairs; thin packs are fixed by\nappending missing bases before the final index is written.\n\n### Binary commit-graph\n\nImplements the format from `gitformat-commit-graph.adoc`:\n\n```text\nHEADER  (8 bytes)   CGPH + ver(1) + hashver(1) + chunk_count + base_count\nTOC     ((C+1)*12)  per-chunk (id, offset_uint64) + terminator\nOIDF    (256*4)     fanout: cumulative counts indexed by first byte of OID\nOIDL    (N*H)       sorted object IDs\nCDAT    (N*(H+16))  tree(H) + parent1_pos(4) + parent2_pos(4) + gen+time(8)\nEDGE    (optional)  octopus extra parents\nBIDX    (N*4)       cumulative byte offsets for changed-path Bloom filters\nBDAT    (optional)  Bloom settings + concatenated changed-path filters\nTRAILER (H)         repository hash of all preceding bytes\n```\n\nGeneration numbers count topological level (1 for roots). The on-disk file is\nverifiable by real `git commit-graph verify`. `pygit` also reads and caches the\ncommit-graph for parent/tree lookups during history walks. Changed-path Bloom\nfilters use Git's default settings: hash version 1, seven hashes, and ten bits\nper changed path; parent directories are included so path-limited history can\ntest both `dir` and `dir/file`. `blame` uses those filters to avoid tree/blob\nwork for commits that definitely did not touch the requested path.\n\n### Smart HTTPS\n\n`protocol.discover_refs` calls `GET /info/refs?service=git-upload-pack`,\nstrips the pkt-line framing, and returns the ref map. Fetch/clone stream the\nside-band-encoded pack response directly into the pack indexer instead of\nbuilding one large response buffer. `protocol.push` does the receive-pack flow\nincluding streaming a non-thin pack of only-new objects from a temporary pack\nfile and parsing `ok/ng` lines.\n\nThe `daemon` command serves the same flow over a raw TCP socket (git:// at\nport 9418), implemented with `socketserver.ThreadingTCPServer`. Upload-pack\nresponses stream side-band pack chunks instead of assembling the full response\nbody. `http-backend` is an in-process variant used by `instaweb`; the web server\nuses the streaming backend for upload-pack responses and receive-pack request\nbodies.\n\n## Testing\n\n```bash\npip install -e \".[test]\"\npython -m pytest\n```\n\nThe suite passes:\n\n| File                    | Coverage |\n|-------------------------|----------|\n| `unit_objects.py`       | hash, encode/decode, signatures, gitlinks |\n| `unit_refs.py`          | symbolic refs, reflog, packed-refs, abbrev SHA |\n| `unit_index.py`         | DIRC v2 roundtrip, conflict stages, long paths |\n| `unit_pack.py`          | delta apply, idx v2, build_pack, inbound pack indexing, pack/MIDX bitmaps, binary MIDX, SHA-256 interop |\n| `unit_modules.py`       | diff/merge/patch/ignore/rerere/SMTP/XOAUTH2/fsmonitor/bisect unit-level |\n| `unit_integration.py`   | end-to-end CLI flows incl. ort-backed conflicts, rename-aware merge, rerere replay, SHA-256 translation, loose cache, streaming upload-pack, recursive tree diff |\n| `test_ort_parity.py`    | byte-for-byte `ort` parity vs `git merge-tree --write-tree` across every conflict type (content, modify/delete, add/add, rename/rename, rename/delete, directory rename, distinct-types, exec-bit) |\n| `unit_phase_scripts.py` | wraps the script-style phase tests |\n| `tests/git_parity`      | C Git 2.54.0 manifest coverage, exact built-in registry coverage, and oracle behavior comparisons |\n\nTests that require the real `git` binary are silently skipped when it's not on\nPATH, so the normal suite runs cleanly in containers without one. The dedicated\n`git-254-parity` CI job is stricter: it builds C Git 2.54.0 from the pinned\nupstream tarball, verifies the archive SHA-256, sets `PYGIT_PARITY_GIT`, and\nruns the parity tests with `--require-git-254-oracle`.\n\nTo run the same required parity gate locally, build or install a `git version\n2.54.0` binary and run:\n\n```bash\nPYGIT_PARITY_GIT=/path/to/git-2.54.0 \\\n  python -m pytest tests/git_parity --require-git-254-oracle\n```\n\nWithout `--require-git-254-oracle`, the behavior-comparison tests skip when the\noracle is unavailable, while manifest and registry coverage still run.\n\nThe pure-Python `ort` engine is additionally cross-checked against C Git with\nthe differential fuzzers in `tests/diff_xdiff_harness.py` (blob-level 3-way\nmerges vs `git merge-file`) and `tests/diff_ort_harness.py` (whole-tree merges\nvs `git merge-tree`); both compare results byte-for-byte over thousands of\nrandomized cases.\n\n## What's intentionally NOT implemented\n\n* `git filter-repo` (it's a separate Python tool anyway, not a git built-in).\n\n## Limitations to know about\n\n* Big repos: packed repositories now use mmap-backed pack reads, binary MIDX\n  lookup, pack/MIDX bitmaps, commit-graph parent/tree lookup, changed-path\n  Bloom filters, cached loose-object enumeration, and bounded-memory streaming\n  pack generation/indexing. Tree-diff commands skip identical subtrees. The\n  remaining scale-sensitive cases are commands whose output inherently requires\n  inspecting every path or blob.\n* The `ort` merge engine is a pure-Python reimplementation (no `git` binary,\n  no fallback), validated for byte-for-byte parity against\n  `git merge-tree --write-tree` across content merges, file and directory\n  renames (including deeply-nested simultaneous renames), recursive merges\n  (criss-cross histories with a virtual ancestor), submodule fast-forwards,\n  conflict styles (merge/diff3/zdiff3), and `.gitattributes` merge handling —\n  the `merge`/`conflict-marker-size` attributes, the built-in text/binary/union\n  drivers, and **custom external merge drivers** (`merge.\u003cname\u003e.driver`), which\n  are executed through a POSIX shell exactly as Git does. Custom drivers are\n  the user's own configured tool, not a `git` dependency; on Windows they run\n  via the same `sh` Git for Windows uses.\n* `fsmonitor-daemon run` uses native filesystem notifications on Windows and\n  Linux (`ReadDirectoryChangesW` / inotify). One-shot `fsmonitor` calls and\n  unsupported platforms fall back to configurable polling.\n* `send-email` uses `smtplib` with plain SMTP, STARTTLS/TLS, SMTP-over-SSL,\n  XOAUTH2 bearer tokens, `~/.git-credentials`, and configured `git credential`\n  helpers. Browser-based provider OAuth consent flows are still external.\n* `gitk` / `gui` use Tk when available and fall back to a text log in headless\n  Python installs.\n\n## Contributing\n\nThe project tries to follow git's published wire and on-disk format specs\n(`Documentation/gitformat-*.adoc`, `Documentation/technical/*.adoc`) and C Git\n2.54.0's implementation behavior. When adding or changing CLI behavior:\n\n1. Find the matching C implementation in the Git 2.54.0 source and compare its\n   parser, setup flags, output, exit codes, and repository mutations.\n2. Regenerate or inspect `tests/git_parity/manifest/git-2.54.0.json` with\n   `tools/git_cli_manifest.py` when the upstream source target changes.\n3. Keep every C Git built-in registered under its exact 2.54.0 name; classify\n   any pythongit-only command in `_PYGIT_EXTENSION_COMMANDS`.\n4. Add focused unit/integration tests plus a `tests/git_parity` oracle case for\n   user-visible CLI behavior whenever the command can be exercised\n   deterministically.\n5. Run `python -m pytest`; when a Git 2.54.0 binary is available, also run the\n   required parity gate shown above.\n\n## License\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblhsing%2Fpure-python-git","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblhsing%2Fpure-python-git","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblhsing%2Fpure-python-git/lists"}