|
| 1 | +# pygit2 Agent Guide |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +**pygit2** is a Python library providing bindings to [libgit2](https://libgit2.org/), the shared C library that implements Git plumbing operations. It exposes both a low-level API (direct libgit2 wrappers) and a high-level Pythonic API for repository manipulation. |
| 6 | + |
| 7 | +- **Version**: 1.19.2 |
| 8 | +- **License**: GPLv2 with linking exception |
| 9 | +- **Maintainer**: J. David Ibáñez |
| 10 | +- **Python Support**: 3.11 – 3.14 and PyPy3 7.3+ |
| 11 | +- **libgit2 Version**: 1.9.4 |
| 12 | +- **Homepage**: <https://www.pygit2.org/> |
| 13 | +- **Repository**: <https://github.com/libgit2/pygit2> |
| 14 | + |
| 15 | +## Architecture |
| 16 | + |
| 17 | +The project uses a **hybrid C/Python** architecture with two compiled extension modules: |
| 18 | + |
| 19 | +- **`src/`** — C11 source files that compile into the `_pygit2` C extension module. Each file generally maps to a libgit2 concept: |
| 20 | + - Core objects: `blob.c`, `commit.c`, `object.c`, `tag.c`, `tree.c` |
| 21 | + - Repository and refs: `repository.c`, `branch.c`, `reference.c`, `refdb.c`, `refdb_backend.c`, `revspec.c` |
| 22 | + - Diff and patch: `diff.c`, `patch.c` |
| 23 | + - ODB and backend: `odb.c`, `odb_backend.c` |
| 24 | + - Filters and mailmap: `filter.c`, `mailmap.c` |
| 25 | + - Notes and signatures: `note.c`, `signature.c` |
| 26 | + - Main module init: `pygit2.c` |
| 27 | + |
| 28 | +- **`pygit2/`** — The main Python package. |
| 29 | + - **`_pygit2*.so`** — Compiled C extension (built from `src/`). |
| 30 | + - **`_libgit2.abi3.so`** — CFFI-generated ABI module built from `pygit2/_run.py`. |
| 31 | + - **`decl/`** — C header stub files used by CFFI to define the libgit2 API surface (e.g., `types.h`, `repository.h`, `callbacks.h`, `diff.h`, `remote.h`). |
| 32 | + - **`_build.py`** — Build-time helpers and the canonical `__version__` string. Also used at runtime to locate libgit2. Must remain importable without the rest of the package being built because `setup.py` imports it. |
| 33 | + - **`_run.py`** — CFFI build script that aggregates `decl/*.h` and compiles `pygit2._libgit2`. |
| 34 | + - **`ffi.py`** — Runtime import of the CFFI `ffi` and `lib` (`C`) objects. |
| 35 | + - **`_pygit2.pyi`** — Type stubs for the C extension. Keep it in sync when adding or changing low-level APIs. |
| 36 | + - **`py.typed`** — PEP 561 marker indicating the package is typed. |
| 37 | + - **High-level modules** — Pure-Python wrappers that sit on top of the C extension: |
| 38 | + `repository.py`, `callbacks.py`, `config.py`, `index.py`, `remotes.py`, `settings.py`, `submodules.py`, `transaction.py`, `filter.py`, `blob.py`, `blame.py`, `credentials.py`, `errors.py`, `options.py`, `packbuilder.py`, `references.py`, `refspec.py`, `utils.py`, `enums.py`, `legacyenums.py`. |
| 39 | + |
| 40 | +- **`test/`** — pytest suite with fixture-based repository handling. |
| 41 | +- **`docs/`** — Sphinx documentation (RTD theme). |
| 42 | + |
| 43 | +## Key Configuration Files |
| 44 | + |
| 45 | +- **`setup.py`** — setuptools entry point. Builds both the C extension (`src/*.c`) and the CFFI extension (`pygit2/_run.py:ffi`). |
| 46 | +- **`pyproject.toml`** — Build system requirements, `cibuildwheel` configuration, `ruff` settings, and `codespell` settings. |
| 47 | +- **`setup.cfg`** — Legacy pycodestyle configuration. |
| 48 | +- **`pytest.ini`** — pytest configuration (`--capture=no -ra --verbose`, `testpaths = test/`). |
| 49 | +- **`mypy.ini`** — mypy configuration with strict settings. |
| 50 | +- **`mypy-stubtest.ini`** — mypy configuration for `stubtest` against `_pygit2.pyi`. |
| 51 | +- **`requirements.txt`** — Runtime/build requirements (`cffi>=2.0`, `setuptools` for Python >= 3.12). |
| 52 | +- **`requirements-test.txt`** — Test requirements (`pytest`, `pytest-cov`). |
| 53 | +- **`requirements-typing.txt`** — Typing requirements (`mypy`, `types-cffi`). |
| 54 | +- **`Makefile`** — Convenience targets: `make` builds deps + extension inplace; `make html` builds docs. |
| 55 | + |
| 56 | +## Build and Test Commands |
| 57 | + |
| 58 | +### Quick Development Build (inplace) |
| 59 | + |
| 60 | +Requires libgit2 development headers and library to be installed on the system or pointed to via the `LIBGIT2` environment variable. |
| 61 | + |
| 62 | +```bash |
| 63 | +python setup.py build_ext --inplace |
| 64 | +pytest |
| 65 | +``` |
| 66 | + |
| 67 | +### Full Build with Dependencies |
| 68 | + |
| 69 | +The `build.sh` script can download, compile, and bundle libgit2 (and optionally libssh2 and OpenSSL) into a local prefix. On Windows, `build.ps1` handles libgit2 compilation via CMake. |
| 70 | + |
| 71 | +```bash |
| 72 | +# Build inplace with bundled libgit2/libssh2/OpenSSL |
| 73 | +make |
| 74 | + |
| 75 | +# Or manually: |
| 76 | +LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.4 sh build.sh |
| 77 | + |
| 78 | +# Build inplace + run tests |
| 79 | +sh build.sh test |
| 80 | + |
| 81 | +# Build a wheel, install it, and run tests |
| 82 | +sh build.sh wheel |
| 83 | + |
| 84 | +# Run tests with coverage |
| 85 | +sh build.sh test # (build.sh already adds --cov=pygit2) |
| 86 | + |
| 87 | +# Run mypy type checking |
| 88 | +sh build.sh mypy |
| 89 | + |
| 90 | +# Run stubtest against the .pyi file |
| 91 | +sh build.sh stubtest |
| 92 | +``` |
| 93 | + |
| 94 | +### Environment Variables |
| 95 | + |
| 96 | +- `LIBGIT2` — Base path where libgit2 is installed (default: `/usr/local` or `%ProgramFiles%\libgit2` on Windows). |
| 97 | +- `LIBGIT2_LIB` — Override the library directory specifically. |
| 98 | +- `LIBGIT2_VERSION` — If set, `build.sh` downloads and builds this libgit2 version. |
| 99 | +- `LIBSSH2_VERSION` — If set, `build.sh` downloads and builds libssh2 with SSH support. |
| 100 | +- `OPENSSL_VERSION` — If set, `build.sh` downloads and builds OpenSSL (mainly used for macOS universal builds on CI). |
| 101 | + |
| 102 | +### Documentation Build |
| 103 | + |
| 104 | +```bash |
| 105 | +# Build the extension first, then docs |
| 106 | +make # builds deps + extension |
| 107 | +make -C docs html # requires sphinx-rtd-theme |
| 108 | +``` |
| 109 | + |
| 110 | +## Code Style Guidelines |
| 111 | + |
| 112 | +### Python |
| 113 | + |
| 114 | +- **Formatter / Linter**: [ruff](https://docs.astral.sh/ruff/) |
| 115 | + - Target Python: 3.11+ |
| 116 | + - Quote style: single quotes |
| 117 | + - Selected rules: `E4`, `E7`, `E9`, `F`, `I`, `UP035`, `UP007` |
| 118 | +- **Type checker**: mypy (strict settings enabled; see `mypy.ini`) |
| 119 | +- All Python source files must include the standard GPLv2 copyright header. |
| 120 | + |
| 121 | +### C |
| 122 | + |
| 123 | +- Standard: C11 |
| 124 | +- All C source files must include the standard GPLv2 copyright header. |
| 125 | +- The `.vimrc` at repo root configures ALE with `-std=c11 -Wall` and includes the Python headers and `/usr/local/include`. |
| 126 | + |
| 127 | +### Docstrings |
| 128 | + |
| 129 | +Use the following style (from `docs/development.rst`): |
| 130 | + |
| 131 | +```python |
| 132 | +def f(a, b): |
| 133 | + """ |
| 134 | + The general description goes here. |
| 135 | +
|
| 136 | + Returns: bla bla. |
| 137 | +
|
| 138 | + Parameters: |
| 139 | +
|
| 140 | + a : <type> |
| 141 | + Bla bla. |
| 142 | +
|
| 143 | + b : <type> |
| 144 | + Bla bla. |
| 145 | +
|
| 146 | + Examples:: |
| 147 | +
|
| 148 | + >>> f(...) |
| 149 | + """ |
| 150 | +``` |
| 151 | + |
| 152 | +## Testing Instructions |
| 153 | + |
| 154 | +- **Runner**: pytest |
| 155 | +- **Configuration**: `pytest.ini` |
| 156 | + ```ini |
| 157 | + [pytest] |
| 158 | + addopts = --capture=no -ra --verbose |
| 159 | + testpaths = test/ |
| 160 | + ``` |
| 161 | +- **Fixtures**: Defined in `test/conftest.py`. They yield `pygit2.Repository` instances extracted from zipped sample repos in `test/data/` (e.g., `testrepo.zip`, `barerepo.zip`). |
| 162 | +- **Test utilities**: `test/utils.py` provides helpers such as `TemporaryRepository`, network/proxy/SSH skip markers, and `diff_safeiter`. |
| 163 | +- **Isolation**: The session-scoped `global_git_config` fixture clears `GLOBAL`, `XDG`, and `SYSTEM` config search paths to ensure reproducibility. |
| 164 | +- **Coverage**: `pytest-cov` is used; run via `sh build.sh test`. |
| 165 | + |
| 166 | +## CI / Deployment |
| 167 | + |
| 168 | +GitHub Actions workflows live in `.github/workflows/`: |
| 169 | + |
| 170 | +- **`tests.yml`** — Runs on s390x via QEMU (allowed to fail; see issue #812). |
| 171 | +- **`lint.yml`** — Runs `ruff format --diff`, `ruff check`, and `sh build.sh mypy`. |
| 172 | +- **`wheels.yml`** — Uses `cibuildwheel` to build wheels for Linux (amd64, arm64, ppc64le, musl), macOS (intel, arm64, PyPy), and Windows (x64, x86, arm64). Publishes to PyPI and creates GitHub Releases on version tags (`v*`). |
| 173 | +- **`codespell.yml`** — Spell checking. |
| 174 | + |
| 175 | +## Security Considerations |
| 176 | + |
| 177 | +- The project links against OpenSSL and libssh2. CI pins specific versions of these libraries when building wheels. |
| 178 | +- Wheel repair commands (`auditwheel`, `delocate-wheel`) bundle shared libraries so wheels are self-contained. |
| 179 | +- Credentials callbacks (`RemoteCallbacks`, `get_credentials`) are the primary interface for supplying secrets; never hardcode credentials in tests. |
| 180 | +- Valgrind support: see `docs/development.rst` and `misc/valgrind-python.supp` for memory-leak debugging instructions. |
| 181 | + |
| 182 | +## Useful Notes for Agents |
| 183 | + |
| 184 | +- **Do not assume libgit2 is installed globally.** Check for `LIBGIT2` or use `build.sh`. |
| 185 | +- **`pygit2/_build.py`** is imported by `setup.py`; it must remain importable without the rest of the package being built. |
| 186 | +- **CFFI and setuptools extensions are both built from `setup.py`.** `ext_modules` builds the C extension from `src/*.c`; `cffi_modules` triggers the CFFI build via `pygit2/_run.py:ffi`. |
| 187 | +- **`.pyi` stub file**: `pygit2/_pygit2.pyi` provides type stubs for the C extension. Keep it in sync when adding or changing low-level APIs. |
| 188 | +- **`pygit2/__init__.py`** is large because it re-exports a vast surface of constants and classes. Follow existing patterns when adding new public symbols. |
0 commit comments