pax_global_header00006660000000000000000000000064147374402410014520gustar00rootroot0000000000000052 comment=a011e2610684703e85ccee2b929858630e2887c8 libgit2-pygit2-a011e26/000077500000000000000000000000001473744024100146115ustar00rootroot00000000000000libgit2-pygit2-a011e26/.gitattributes000066400000000000000000000000201473744024100174740ustar00rootroot00000000000000*.h text eol=lf libgit2-pygit2-a011e26/.github/000077500000000000000000000000001473744024100161515ustar00rootroot00000000000000libgit2-pygit2-a011e26/.github/FUNDING.yml000066400000000000000000000000171473744024100177640ustar00rootroot00000000000000github: jdavid libgit2-pygit2-a011e26/.github/workflows/000077500000000000000000000000001473744024100202065ustar00rootroot00000000000000libgit2-pygit2-a011e26/.github/workflows/lint.yml000066400000000000000000000006411473744024100217000ustar00rootroot00000000000000name: Lints on: pull_request: push: paths-ignore: - '**.rst' jobs: ruff: runs-on: ubuntu-24.04 steps: - name: Checkout pygit2 uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install ruff run: pip install ruff - name: Check code style with ruff run: ruff format --diff libgit2-pygit2-a011e26/.github/workflows/tests.yml000066400000000000000000000040731473744024100220770ustar00rootroot00000000000000name: Tests on: pull_request: push: paths-ignore: - '**.rst' jobs: linux-x86_64: runs-on: ${{ matrix.os }} strategy: matrix: include: - os: ubuntu-24.04 python-version: '3.10' - os: ubuntu-24.04 python-version: '3.13' - os: ubuntu-24.04 python-version: 'pypy3.10' steps: - name: Checkout pygit2 uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Linux run: | sudo apt install tinyproxy LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 /bin/sh build.sh test linux-arm64: runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v4 - name: Build & test uses: uraimo/run-on-arch-action@v2 with: arch: aarch64 distro: ubuntu22.04 install: | apt-get update -q -y apt-get install -q -y cmake libssl-dev python3-dev python3-venv wget run: | LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 /bin/sh build.sh test linux-s390x: runs-on: ubuntu-24.04 if: github.ref == 'refs/heads/master' steps: - name: Checkout uses: actions/checkout@v4 - name: Build & test uses: uraimo/run-on-arch-action@v2 with: arch: s390x distro: ubuntu22.04 install: | apt-get update -q -y apt-get install -q -y cmake libssl-dev python3-dev python3-venv wget run: | LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 /bin/sh build.sh test continue-on-error: true # Tests are expected to fail, see issue #812 macos-arm64: runs-on: macos-latest steps: - name: Checkout pygit2 uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.13' - name: macOS run: | export OPENSSL_PREFIX=`brew --prefix openssl@3` LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 /bin/sh build.sh test libgit2-pygit2-a011e26/.github/workflows/wheels.yml000066400000000000000000000025141473744024100222220ustar00rootroot00000000000000name: Wheels on: push: branches: - master tags: - 'v*' jobs: build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: include: - name: linux os: ubuntu-24.04 - name: macos os: macos-13 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - uses: docker/setup-qemu-action@v3 if: runner.os == 'Linux' with: platforms: all - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.22.0 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 with: name: wheels-${{ matrix.name }} path: ./wheelhouse/*.whl pypi: if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') needs: [build_wheels] runs-on: ubuntu-24.04 steps: - uses: actions/download-artifact@v4 with: path: dist pattern: wheels-* merge-multiple: true - name: Display structure of downloaded files run: ls -lh dist - uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} libgit2-pygit2-a011e26/.gitignore000066400000000000000000000002021473744024100165730ustar00rootroot00000000000000/.cache/ /.coverage /.eggs/ /.envrc /.tox/ /build/ /dist/ /docs/_build/ /MANIFEST /venv* __pycache__/ *.egg-info *.pyc *.so *.swp libgit2-pygit2-a011e26/.mailmap000066400000000000000000000035411473744024100162350ustar00rootroot00000000000000Alexander Bayandin Alexander Linne Anatoly Techtonik Bob Carroll Brandon Milton Carlos Martín Nieto Christian Boos Grégory Herrero Guillermo Pérez Gustavo Di Pietro J. David Ibáñez Jeremy Westwood Jose Plana Kaarel Kitsemets Lukas Fleischer Martin Lenders Matthew Duggan Matthew Gamble Matthias Bartelmeß Mikhail Yushkovskiy Nabijacz Leweli Óscar San José Petr Hosek Phil Schleihauf Richo Healey Robert Hölzl Saugat Pachhai Sriram Raghu Sukhman Bhuller Tamir Bahar Tamir Bahar Victor Garcia Victor Florea Vlad Temian Wim Jeantine-Glenn Xavier Delannoy Xu Tao Xu Tao libgit2-pygit2-a011e26/AUTHORS.md000066400000000000000000000103361473744024100162630ustar00rootroot00000000000000Authors: J. David Ibáñez Carlos Martín Nieto Nico von Geyso Iliyas Jorio Sviatoslav Sydorenko Matthias Bartelmeß Robert Coup W. Trevor King Drew DeVault Dave Borowitz Brandon Milton Daniel Rodríguez Troitiño Peter Rowlands Richo Healey Christian Boos Julien Miotte Nick Hynes Richard Möhn Xu Tao Matthew Duggan Matthew Gamble Jeremy Westwood Jose Plana Martin Lenders Sriram Raghu Victor Garcia Yonggang Luo Patrick Steinhardt Petr Hosek Tamir Bahar Valentin Haenel Xavier Delannoy Michael Jones Saugat Pachhai Bernardo Heynemann John Szakmeister Nabijacz Leweli Simon Cozens Vlad Temian Brodie Rao Chad Dombrova Lukas Fleischer Mathias Leppich Nicolas Dandrimont Raphael Medaer (Escaux) Anatoly Techtonik Andrew Olsen Dan Sully David Versmisse Grégory Herrero Michał Kępień Mikhail Yushkovskiy Robin Stocker Rohit Sanjay Rémi Duraffort Santiago Perez De Rosso Sebastian Thiel Thom Wiggers William Manley Alexander Linne Alok Singhal Assaf Nativ Bob Carroll Christian Häggström Erik Johnson Filip Rindler Fraser Tweedale Grégoire ROCHER Han-Wen Nienhuys Helio Machado Jason Ziglar Leonardo Rhodes Mark Adams Nika Layzell Peter-Yi Zhang Petr Viktorin Robert Hölzl Ron Cohen Sebastian Böhm Sukhman Bhuller Thomas Kluyver Tyler Cipriani WANG Xuerui Alex Chamberlain Alexander Bayandin Amit Bakshi Andrey Devyatkin Arno van Lumig Ben Davis Colin Watson Dan Yeaw Dustin Raimondi Eric Schrijver Greg Fitzgerald Guillermo Pérez Hervé Cauwelier Hong Minhee Huang Huang Ian P. McCullough Igor Gnatenko Insomnia Jack O'Connor Jared Flatow Jeremy Heiner Jesse P. Johnson Jiunn Haur Lim Jorge C. Leitao Jun Omae Kaarel Kitsemets Ken Dreyer Kevin KIN-FOO Kyle Gottfried Marcel Waldvogel Masud Rahman Mathieu Parent Michael Sondergaard Natanael Arndt Ondřej Nový Sarath Lakshman Steve Kieffer Szucs Krisztian Vicent Marti Zbigniew Jędrzejewski-Szmek Zoran Zaric nikitalita Adam Gausmann Adam Spiers Albin Söderström Alexandru Fikl Andrew Chin Andrey Trubachev András Veres-Szentkirályi Ash Berlin Benjamin Kircher Benjamin Pollack Benjamin Wohlwend Bogdan Stoicescu Bogdan Vasilescu Bryan O'Sullivan CJ Harries Cam Cope Chad Birch Chason Chaffin Chris Jerdonek Chris Rebert Christopher Hunt Claudio Jolowicz Craig de Stigter Cristian Hotea Cyril Jouve Dan Cecile Daniel Bruce Daniele Esposti Daniele Trifirò David Black David Fischer David Sanders David Six Dennis Schwertel Devaev Maxim Eric Davis Erik Meusel Erik van Zijst Fabrice Salvaire Ferengee Florian Weimer Frazer McLean Gustavo Di Pietro Holger Frey Hugh Cole-Baker Isabella Stephens Jacob Swanson Jasper Lievisse Adriaanse Jimisola Laursen Jiri Benc Johann Miller Jonathan Robson Josh Bleecher Snyder Julia Evans Justin Clift Kevin Valk Konstantinos Smanis Kyriakos Oikonomakos Lance Eftink Legorooj Lukas Berk Martin von Zweigbergk Mathieu Bridon Mathieu Pillard Matthaus Woolard Matěj Cepl Maxwell G Michał Górny Na'aman Hirschfeld Nicolás Sanguinetti Nikita Kartashov Nikolai Zujev Nils Philippsen Noah Fontes Or Hayat Óscar San José Patrick Lühne Paul Wagland Peter Dave Hello Phil Schleihauf Philippe Ombredanne Ram Rachum Remy Suen Ridge Kennedy Rodrigo Bistolfi Ross Nicoll Rui Abreu Ferreira Rui Chen Sandro Jäckel Saul Pwanson Shane Turner Sheeo Simone Mosciatti Soasme Steven Winfield Tad Hardesty Timo Röhling Victor Florea Vladimir Rutsky Wim Jeantine-Glenn Yu Jianjian buhl chengyuhang earl odidev libgit2-pygit2-a011e26/CHANGELOG.md000066400000000000000000001766331473744024100164420ustar00rootroot00000000000000# 1.17.0 (2025-01-08) - Upgrade to libgit2 1.9 - Add `certificate_check` callback to `Remote.ls_remotes(...)` [#1326](https://github.com/libgit2/pygit2/pull/1326) - Fix build with GCC 14 [#1324](https://github.com/libgit2/pygit2/pull/1324) - Release wheels for PyPy [#1336](https://github.com/libgit2/pygit2/pull/1336) [#1339](https://github.com/libgit2/pygit2/pull/1339) - CI: update tests for macOS to use OpenSSL 3 [#1335](https://github.com/libgit2/pygit2/pull/1335) - Documentation: fix typo in `Repository.status(...)` docstring [#1327](https://github.com/libgit2/pygit2/pull/1327) # 1.16.0 (2024-10-11) - Add support for Python 3.13 - Drop support for Python 3.9 - New `Repository.hashfile(...)` [#1298](https://github.com/libgit2/pygit2/pull/1298) - New `Option.GET_MWINDOW_FILE_LIMIT` and `Option.SET_MWINDOW_FILE_LIMIT` [#1312](https://github.com/libgit2/pygit2/pull/1312) - Fix overriding `certificate_check(...)` callback via argument to `RemoteCallbacks(...)` [#1321](https://github.com/libgit2/pygit2/pull/1321) - Add py.typed [#1310](https://github.com/libgit2/pygit2/pull/1310) - Fix `discover_repository(...)` annotation [#1313](https://github.com/libgit2/pygit2/pull/1313) # 1.15.1 (2024-07-07) - New `Repository.revert(...)` [#1297](https://github.com/libgit2/pygit2/pull/1297) - New optional `depth` argument in submodules `add()` and `update()` methods [#1296](https://github.com/libgit2/pygit2/pull/1296) - Now `Submodule.url` returns `None` when the submodule does not have a url [#1294](https://github.com/libgit2/pygit2/pull/1294) - Fix use after free bug in error reporting [#1299](https://github.com/libgit2/pygit2/pull/1299) - Fix `Submodule.head_id` when the submodule is not in the current HEAD tree [#1300](https://github.com/libgit2/pygit2/pull/1300) - Fix `Submodule.open()` when subclassing `Repository` [#1295](https://github.com/libgit2/pygit2/pull/1295) - Fix error in the test suite when running with address sanitizer [#1304](https://github.com/libgit2/pygit2/pull/1304) [#1301](https://github.com/libgit2/pygit2/issues/1301) - Annotations and documentation fixes [#1293](https://github.com/libgit2/pygit2/pull/1293) # 1.15.0 (2024-05-18) - Many deprecated features have been removed, see below - Upgrade to libgit2 v1.8.1 - New `push_options` optional argument in `Repository.push(...)` [#1282](https://github.com/libgit2/pygit2/pull/1282) - New support comparison of `Oid` with text string - Fix `CheckoutNotify.IGNORED` [#1288](https://github.com/libgit2/pygit2/issues/1288) - Use default error handler when decoding/encoding paths [#537](https://github.com/libgit2/pygit2/issues/537) - Remove setuptools runtime dependency [#1281](https://github.com/libgit2/pygit2/pull/1281) - Coding style with ruff [#1280](https://github.com/libgit2/pygit2/pull/1280) - Add wheels for ppc64le [#1279](https://github.com/libgit2/pygit2/pull/1279) - Fix tests on EPEL8 builds for s390x [#1283](https://github.com/libgit2/pygit2/pull/1283) Deprecations: - Deprecate `IndexEntry.hex`, use `str(IndexEntry.id)` Breaking changes: - Remove deprecated `oid.hex`, use `str(oid)` - Remove deprecated `object.hex`, use `str(object.id)` - Remove deprecated `object.oid`, use `object.id` - Remove deprecated `Repository.add_submodule(...)`, use `Repository.submodules.add(...)` - Remove deprecated `Repository.lookup_submodule(...)`, use `Repository.submodules[...]` - Remove deprecated `Repository.init_submodules(...)`, use `Repository.submodules.init(...)` - Remove deprecated `Repository.update_submodule(...)`, use `Repository.submodules.update(...)` - Remove deprecated constants `GIT_OBJ_XXX`, use `ObjectType` - Remove deprecated constants `GIT_REVPARSE_XXX`, use `RevSpecFlag` - Remove deprecated constants `GIT_REF_XXX`, use `ReferenceType` - Remove deprecated `ReferenceType.OID`, use instead `ReferenceType.DIRECT` - Remove deprecated `ReferenceType.LISTALL`, use instead `ReferenceType.ALL` - Remove deprecated support for passing dicts to repository\'s `merge(...)`, `merge_commits(...)` and `merge_trees(...)`. Instead pass `MergeFlag` for `flags`, and `MergeFileFlag` for `file_flags`. - Remove deprecated support for passing a string for the favor argument to repository\'s `merge(...)`, `merge_commits(...)` and `merge_trees(...)`. Instead pass `MergeFavor`. # 1.14.1 (2024-02-10) - Update wheels to libgit2 v1.7.2 - Now `Object.filemode` returns `enums.FileMode` and `Reference.type` returns `enums.ReferenceType` [#1273](https://github.com/libgit2/pygit2/pull/1273) - Fix tests on Fedora 40 [#1275](https://github.com/libgit2/pygit2/pull/1275) Deprecations: - Deprecate `ReferenceType.OID`, use `ReferenceType.DIRECT` - Deprecate `ReferenceType.LISTALL`, use `ReferenceType.ALL` # 1.14.0 (2024-01-26) - Drop support for Python 3.8 - Add Linux wheels for musl on x86\_64 [#1266](https://github.com/libgit2/pygit2/pull/1266) - New `Repository.submodules` namespace [#1250](https://github.com/libgit2/pygit2/pull/1250) - New `Repository.listall_mergeheads()`, `Repository.message`, `Repository.raw_message` and `Repository.remove_message()` [#1261](https://github.com/libgit2/pygit2/pull/1261) - New `pygit2.enums` supersedes the `GIT_` constants [#1251](https://github.com/libgit2/pygit2/pull/1251) - Now `Repository.status()`, `Repository.status_file()`, `Repository.merge_analysis()`, `DiffFile.flags`, `DiffFile.mode`, `DiffDelta.flags` and `DiffDelta.status` return enums [#1263](https://github.com/libgit2/pygit2/pull/1263) - Now repository\'s `merge()`, `merge_commits()` and `merge_trees()` take enums/flags for their `favor`, `flags` and `file_flags` arguments. [#1271](https://github.com/libgit2/pygit2/pull/1271) [#1272](https://github.com/libgit2/pygit2/pull/1272) - Fix crash in filter cleanup [#1259](https://github.com/libgit2/pygit2/pull/1259) - Documentation fixes [#1255](https://github.com/libgit2/pygit2/pull/1255) [#1258](https://github.com/libgit2/pygit2/pull/1258) [#1268](https://github.com/libgit2/pygit2/pull/1268) [#1270](https://github.com/libgit2/pygit2/pull/1270) Breaking changes: - Remove deprecated `Repository.create_remote(...)` function, use instead `Repository.remotes.create(...)` Deprecations: - Deprecate `Repository.add_submodule(...)`, use `Repository.submodules.add(...)` - Deprecate `Repository.lookup_submodule(...)`, use `Repository.submodules[...]` - Deprecate `Repository.init_submodules(...)`, use `Repository.submodules.init(...)` - Deprecate `Repository.update_submodule(...)`, use `Repository.submodules.update(...)` - Deprecate `GIT_*` constants, use `pygit2.enums` - Passing dicts to repository\'s `merge(...)`, `merge_commits(...)` and `merge_trees(...)` is deprecated. Instead pass `MergeFlag` for the `flags` argument, and `MergeFileFlag` for `file_flags`. - Passing a string for the favor argument to repository\'s `merge(...)`, `merge_commits(...)` and `merge_trees(...)` is deprecated. Instead pass `MergeFavor`. # 1.13.3 (2023-11-21) - New API for filters in Python [#1237](https://github.com/libgit2/pygit2/pull/1237) [#1244](https://github.com/libgit2/pygit2/pull/1244) - Shallow repositories: New `depth` optional argument for `clone_repository(...)` and `Remote.fetch(...)` [#1245](https://github.com/libgit2/pygit2/pull/1245) [#1246](https://github.com/libgit2/pygit2/pull/1246) - New submodule `init(...)`, `update(...)` and `reload(...)` functions [#1248](https://github.com/libgit2/pygit2/pull/1248) - Release GIL in `Walker.__next__` [#1249](https://github.com/libgit2/pygit2/pull/1249) - Type hints for submodule functions in `Repository` [#1247](https://github.com/libgit2/pygit2/pull/1247) # 1.13.2 (2023-10-30) - Support Python 3.12 - Documentation updates [#1242](https://github.com/libgit2/pygit2/pull/1242) # 1.13.1 (2023-09-24) - Fix crash in reference rename [#1233](https://github.com/libgit2/pygit2/issues/1233) # 1.13.0 (2023-09-07) - Upgrade to libgit2 v1.7.1 - Don\'t distribute wheels for pypy, only universal wheels for macOS - New `Repository.remotes.create_anonymous(url)` [#1229](https://github.com/libgit2/pygit2/pull/1229) - docs: update links to pypi, pygit2.org [#1228](https://github.com/libgit2/pygit2/pull/1228) - Prep work for Python 3.12 (not yet supported) [#1223](https://github.com/libgit2/pygit2/pull/1223) # 1.12.2 (2023-06-25) - Update wheels to bundle libssh2 1.11.0 and OpenSSL 3.0.9 - Remove obsolete `Remote.save()` [#1219](https://github.com/libgit2/pygit2/issues/1219) # 1.12.1 (2023-05-07) - Fix segfault in signature when encoding is incorrect [#1210](https://github.com/libgit2/pygit2/pull/1210) - Typing improvements [#1212](https://github.com/libgit2/pygit2/pull/1212) [#1214](https://github.com/libgit2/pygit2/pull/1214) - Update wheels to libgit2 v1.6.4 # 1.12.0 (2023-04-01) - Upgrade to libgit2 v1.6.3 - Update Linux wheels to bundle OpenSSL 3.0.8 - Downgrade Linux wheels to manylinux2014 - New `ConflictCollection.__contains__` [#1181](https://github.com/libgit2/pygit2/pull/1181) - New `Repository.references.iterator(...)` [#1191](https://github.com/libgit2/pygit2/pull/1191) - New `favor`, `flags` and `file_flags` optional arguments for `Repository.merge(...)` [#1192](https://github.com/libgit2/pygit2/pull/1192) - New `keep_all` and `paths` optional arguments for `Repository.stash(...)` [#1202](https://github.com/libgit2/pygit2/pull/1202) - New `Respository.state()` [#1204](https://github.com/libgit2/pygit2/pull/1204) - Improve `Repository.write_archive(...)` performance [#1183](https://github.com/libgit2/pygit2/pull/1183) - Sync type annotations [#1203](https://github.com/libgit2/pygit2/pull/1203) # 1.11.1 (2022-11-09) - Fix Linux wheels, downgrade to manylinux 2_24 [#1176](https://github.com/libgit2/pygit2/issues/1176) - Windows wheels for Python 3.11 [#1177](https://github.com/libgit2/pygit2/pull/1177) - CI: Use 3.11 final release for testing [#1178](https://github.com/libgit2/pygit2/pull/1178) # 1.11.0 (2022-11-06) - Drop support for Python 3.7 - Update Linux wheels to manylinux 2_28 [#1136](https://github.com/libgit2/pygit2/issues/1136) - Fix crash in signature representation [#1162](https://github.com/libgit2/pygit2/pull/1162) - Fix memory leak in `Signature` [#1173](https://github.com/libgit2/pygit2/pull/1173) - New optional argument `raise_error` in `Repository.applies(...)` [#1166](https://github.com/libgit2/pygit2/pull/1166) - New notify/progress callbacks for checkout and stash [#1167](https://github.com/libgit2/pygit2/pull/1167) [#1169](https://github.com/libgit2/pygit2/pull/1169) - New `Repository.remotes.names()` [#1159](https://github.com/libgit2/pygit2/pull/1159) - Now `refname` argument in `RemoteCallbacks.push_update_reference(...)` is a string, not bytes [#1168](https://github.com/libgit2/pygit2/pull/1168) - Add missing newline at end of `pygit2/decl/pack.h` [#1163](https://github.com/libgit2/pygit2/pull/1163) # 1.10.1 (2022-08-28) - Fix segfault in `Signature` repr [#1155](https://github.com/libgit2/pygit2/pull/1155) - Linux and macOS wheels for Python 3.11 [#1154](https://github.com/libgit2/pygit2/pull/1154) # 1.10.0 (2022-07-24) - Upgrade to libgit2 1.5 - Add support for `GIT_OPT_GET_OWNER_VALIDATION` and `GIT_OPT_SET_OWNER_VALIDATION` [#1150](https://github.com/libgit2/pygit2/pull/1150) - New `untracked_files` and `ignored` optional arguments for `Repository.status(...)` [#1151](https://github.com/libgit2/pygit2/pull/1151) # 1.9.2 (2022-05-24) - New `Repository.create_commit_string(...)` and `Repository.create_commit_with_signature(...)` [#1142](https://github.com/libgit2/pygit2/pull/1142) - Linux and macOS wheels updated to libgit2 v1.4.3 - Remove redundant line [#1139](https://github.com/libgit2/pygit2/pull/1139) # 1.9.1 (2022-03-22) - Type hints: added to C code and Branches/References [#1121](https://github.com/libgit2/pygit2/pull/1121) [#1132](https://github.com/libgit2/pygit2/pull/1132) - New `Signature` supports `str()` and `repr()` [#1135](https://github.com/libgit2/pygit2/pull/1135) - Fix ODB backend\'s read in big endian architectures [#1130](https://github.com/libgit2/pygit2/pull/1130) - Fix install with poetry [#1129](https://github.com/libgit2/pygit2/pull/1129) [#1128](https://github.com/libgit2/pygit2/issues/1128) - Wheels: update to libgit2 v1.4.2 - Tests: fix testing `parse_diff` [#1131](https://github.com/libgit2/pygit2/pull/1131) - CI: various fixes after migration to libgit2 v1.4 # 1.9.0 (2022-02-22) - Upgrade to libgit2 v1.4 - Documentation, new recipes for committing and cloning [#1125](https://github.com/libgit2/pygit2/pull/1125) # 1.8.0 (2022-02-04) - Rename `RemoteCallbacks.progress(...)` callback to `.sideband_progress(...)` [#1120](https://github.com/libgit2/pygit2/pull/1120) - New `Repository.merge_base_many(...)` and `Repository.merge_base_octopus(...)` [#1112](https://github.com/libgit2/pygit2/pull/1112) - New `Repository.listall_stashes()` [#1117](https://github.com/libgit2/pygit2/pull/1117) - Code cleanup [#1118](https://github.com/libgit2/pygit2/pull/1118) Backward incompatible changes: - The `RemoteCallbacks.progress(...)` callback has been renamed to `RemoteCallbacks.sideband_progress(...)`. This matches the documentation, but may break existing code that still uses the old name. # 1.7.2 (2021-12-06) - Universal wheels for macOS [#1109](https://github.com/libgit2/pygit2/pull/1109) # 1.7.1 (2021-11-19) - New `Repository.amend_commit(...)` [#1098](https://github.com/libgit2/pygit2/pull/1098) - New `Commit.message_trailers` [#1101](https://github.com/libgit2/pygit2/pull/1101) - Windows wheels for Python 3.10 [#1103](https://github.com/libgit2/pygit2/pull/1103) - Changed: now `DiffDelta.is_binary` returns `None` if the file data has not yet been loaded, cf. [#962](https://github.com/libgit2/pygit2/issues/962) - Document `Repository.get_attr(...)` and update theme [#1017](https://github.com/libgit2/pygit2/issues/1017) [#1105](https://github.com/libgit2/pygit2/pull/1105) # 1.7.0 (2021-10-08) - Upgrade to libgit2 1.3.0 [#1089](https://github.com/libgit2/pygit2/pull/1089) - Linux wheels now bundled with libssh2 1.10.0 (instead of 1.9.0) - macOS wheels now include libssh2 - Add support for Python 3.10 [#1092](https://github.com/libgit2/pygit2/pull/1092) [#1093](https://github.com/libgit2/pygit2/pull/1093) - Drop support for Python 3.6 - New [pygit2.GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES]{.title-ref} [#1087](https://github.com/libgit2/pygit2/pull/1087) - New optional argument `location` in `Repository.applies(..)` and `Repository.apply(..)` [#1091](https://github.com/libgit2/pygit2/pull/1091) - Fix: Now the [flags]{.title-ref} argument in [Repository.blame()]{.title-ref} is passed through [#1083](https://github.com/libgit2/pygit2/pull/1083) - CI: Stop using Travis, move to GitHub actions Caveats: - Windows wheels for Python 3.10 not yet available. # 1.6.1 (2021-06-19) - Fix a number of reference leaks - Review custom object backends Breaking changes: - In custom backends the callbacks have been renamed from `read` to `read_cb`, `write` to `write_cb`, and so on. # 1.6.0 (2021-06-01) - New optional `proxy` argument in `Remote` methods [#642](https://github.com/libgit2/pygit2/issues/642) [#1063](https://github.com/libgit2/pygit2/pull/1063) [#1069](https://github.com/libgit2/pygit2/issues/1069) - New GIT_MERGE_PREFERENCE constants [#1071](https://github.com/libgit2/pygit2/pull/1071) - Don\'t require cached-property with Python 3.8 or later [#1066](https://github.com/libgit2/pygit2/pull/1066) - Add wheels for aarch64 [#1077](https://github.com/libgit2/pygit2/issues/1077) [#1078](https://github.com/libgit2/pygit2/pull/1078) - Documentation fixes [#1068](https://github.com/libgit2/pygit2/pull/1068) [#1072](https://github.com/libgit2/pygit2/pull/1072) - Refactored build and CI, new `build.sh` script Breaking changes: - Remove deprecated `GIT_CREDTYPE_XXX` contants, use `GIT_CREDENTIAL_XXX` instead. - Remove deprecated `Patch.patch` getter, use `Patch.text` instead. # 1.5.0 (2021-01-23) - New `PackBuilder` class and `Repository.pack(...)` [#1048](https://github.com/libgit2/pygit2/pull/1048) - New `Config.delete_multivar(...)` [#1056](https://github.com/libgit2/pygit2/pull/1056) - New `Repository.is_shallow` [#1058](https://github.com/libgit2/pygit2/pull/1058) - New optional `message` argument in `Repository.create_reference(...)` [#1061](https://github.com/libgit2/pygit2/issues/1061) [#1062](https://github.com/libgit2/pygit2/pull/1062) - Fix truncated diff when there are nulls [#1047](https://github.com/libgit2/pygit2/pull/1047) [#1043](https://github.com/libgit2/pygit2/issues/1043) - Unit tests & Continuous integration [#1039](https://github.com/libgit2/pygit2/issues/1039) [#1052](https://github.com/libgit2/pygit2/pull/1052) Breaking changes: - Fix `Index.add(...)` raise `TypeError` instead of `AttributeError` when arguments are of unexpected type # 1.4.0 (2020-11-06) - Upgrade to libgit2 1.1, new `GIT_BLAME_IGNORE_WHITESPACE` constant [#1040](https://github.com/libgit2/pygit2/issues/1040) - Add wheels for Python 3.9 [#1038](https://github.com/libgit2/pygit2/issues/1038) - Drop support for PyPy3 7.2 - New optional `flags` argument in `Repository.__init__(...)`, new `GIT_REPOSITORY_OPEN_*` constants [#1044](https://github.com/libgit2/pygit2/pull/1044) - Documentation [#509](https://github.com/libgit2/pygit2/issues/509) [#752](https://github.com/libgit2/pygit2/issues/752) [#1037](https://github.com/libgit2/pygit2/issues/1037) [#1045](https://github.com/libgit2/pygit2/issues/1045) # 1.3.0 (2020-09-18) - New `Repository.add_submodule(...)` [#1011](https://github.com/libgit2/pygit2/pull/1011) - New `Repository.applies(...)` [#1019](https://github.com/libgit2/pygit2/pull/1019) - New `Repository.revparse(...)` and `Repository.revparse_ext(...)` [#1022](https://github.com/libgit2/pygit2/pull/1022) - New optional `flags` and `file_flags` arguments in `Repository.merge_commits` and `Repository.merge_trees` [#1008](https://github.com/libgit2/pygit2/pull/1008) - New `Reference.raw_target`, `Repository.raw_listall_branches(...)` and `Repository.raw_listall_references()`; allow bytes in `Repository.lookup_branch(...)` and `Repository.diff(...)` [#1029](https://github.com/libgit2/pygit2/pull/1029) - New `GIT_BLAME_FIRST_PARENT` and `GIT_BLAME_USE_MAILMAP` constants [#1031](https://github.com/libgit2/pygit2/pull/1031) - New `IndexEntry` supports `repr()`, `str()`, `==` and `!=` [#1009](https://github.com/libgit2/pygit2/pull/1009) - New `Object` supports `repr()` [#1022](https://github.com/libgit2/pygit2/pull/1022) - New accept tuples of strings (not only lists) in a number of places [#1025](https://github.com/libgit2/pygit2/pull/1025) - Fix compatibility with old macOS 10.9 [#1026](https://github.com/libgit2/pygit2/issues/1026) [#1027](https://github.com/libgit2/pygit2/pull/1027) - Fix check argument type in `Repository.apply(...)` [#1033](https://github.com/libgit2/pygit2/issues/1033) - Fix raise exception if error in `Repository.listall_submodules()` commit 32133974 - Fix a couple of refcount errors in `OdbBackend.refresh()` and `Worktree_is_prunable` commit fed0c19c - Unit tests [#800](https://github.com/libgit2/pygit2/issues/800) [#1015](https://github.com/libgit2/pygit2/pull/1015) - Documentation [#705](https://github.com/libgit2/pygit2/pull/705) # 1.2.1 (2020-05-01) - Fix segfault in `Object.raw_name` when not reached through a tree [#1002](https://github.com/libgit2/pygit2/pull/1002) - Internal: Use \@ffi.def_extern instead of \@ffi.callback [#899](https://github.com/libgit2/pygit2/issues/899) - Internal: callbacks code refactored - Test suite completely switched to pytest [#824](https://github.com/libgit2/pygit2/issues/824) - New unit tests [#538](https://github.com/libgit2/pygit2/pull/538) [#996](https://github.com/libgit2/pygit2/issues/996) - Documentation changes [#999](https://github.com/libgit2/pygit2/issues/999) Deprecations: - Deprecate `Repository.create_remote(...)`, use instead `Repository.remotes.create(...)` - Deprecate `GIT_CREDTYPE_XXX` contants, use `GIT_CREDENTIAL_XXX` instead. # 1.2.0 (2020-04-05) - Drop support for Python 3.5 [#991](https://github.com/libgit2/pygit2/issues/991) - Upgrade to libgit2 1.0 [#982](https://github.com/libgit2/pygit2/pull/982) - New support for custom reference database backends [#982](https://github.com/libgit2/pygit2/pull/982) - New support for path objects [#990](https://github.com/libgit2/pygit2/pull/990) [#955](https://github.com/libgit2/pygit2/issues/955) - New `index` optional parameter in `Repository.checkout_index` [#987](https://github.com/libgit2/pygit2/pull/987) - New MacOS wheels [#988](https://github.com/libgit2/pygit2/pull/988) - Fix re-raise exception from credentials callback in clone_repository [#996](https://github.com/libgit2/pygit2/issues/996) - Fix warning with `pip install pygit2` [#986](https://github.com/libgit2/pygit2/issues/986) - Tests: disable global Git config [#989](https://github.com/libgit2/pygit2/issues/989) # 1.1.1 (2020-03-06) - Fix crash in tree iteration [#984](https://github.com/libgit2/pygit2/pull/984) [#980](https://github.com/libgit2/pygit2/issues/980) - Do not include the docs in dist files, so they\'re much smaller now # 1.1.0 (2020-03-01) - Upgrade to libgit2 0.99 [#959](https://github.com/libgit2/pygit2/pull/959) - Continued work on custom odb backends [#948](https://github.com/libgit2/pygit2/pull/948) - New `Diff.patchid` getter [#960](https://github.com/libgit2/pygit2/pull/960) [#877](https://github.com/libgit2/pygit2/issues/877) - New `settings.disable_pack_keep_file_checks(...)` [#908](https://github.com/libgit2/pygit2/pull/908) - New `GIT_DIFF_` and `GIT_DELTA_` constants [#738](https://github.com/libgit2/pygit2/issues/738) - Fix crash in iteration of config entries [#970](https://github.com/libgit2/pygit2/issues/970) - Travis: fix printing features when building Linux wheels [#977](https://github.com/libgit2/pygit2/pull/977) - Move `_pygit2` to `pygit2._pygit2` [#978](https://github.com/libgit2/pygit2/pull/978) Requirements changes: - Now libgit2 0.99 is required - New requirement: cached-property Breaking changes: - In the rare case you\'re directly importing the low level `_pygit2`, the import has changed: # Before import _pygit2 # Now from pygit2 import _pygit2 # 1.0.3 (2020-01-31) - Fix memory leak in DiffFile [#943](https://github.com/libgit2/pygit2/issues/943) # 1.0.2 (2020-01-11) - Fix enumerating tree entries with submodules [#967](https://github.com/libgit2/pygit2/issues/967) # 1.0.1 (2019-12-21) - Fix build in Mac OS [#963](https://github.com/libgit2/pygit2/issues/963) # 1.0.0 (2019-12-06) - Drop Python 2.7 and 3.4 support, six no longer required [#941](https://github.com/libgit2/pygit2/issues/941) - Add Python 3.8 support [#918](https://github.com/libgit2/pygit2/issues/918) - New support for `/` operator to traverse trees [#903](https://github.com/libgit2/pygit2/pull/903) [#924](https://github.com/libgit2/pygit2/issues/924) - New `Branch.raw_branch_name` [#954](https://github.com/libgit2/pygit2/pull/954) - New `Index.remove_all()` [#920](https://github.com/libgit2/pygit2/pull/920) - New `Remote.ls_remotes(..)` [#935](https://github.com/libgit2/pygit2/pull/935) [#936](https://github.com/libgit2/pygit2/issues/936) - New `Repository.lookup_reference_dwim(..)` and `Repository.resolve_refish(..)` [#922](https://github.com/libgit2/pygit2/issues/922) [#923](https://github.com/libgit2/pygit2/pull/923) - New `Repository.odb` returns new `Odb` type instance. And new `OdbBackend` type. [#940](https://github.com/libgit2/pygit2/pull/940) [#942](https://github.com/libgit2/pygit2/pull/942) - New `Repository.references.compress()` [#961](https://github.com/libgit2/pygit2/pull/961) - Optimization: Load notes lazily [#958](https://github.com/libgit2/pygit2/pull/958) - Fix spurious exception in config [#916](https://github.com/libgit2/pygit2/issues/916) [#917](https://github.com/libgit2/pygit2/pull/917) - Minor documentation and cosmetic changes [#919](https://github.com/libgit2/pygit2/pull/919) [#921](https://github.com/libgit2/pygit2/pull/921) [#946](https://github.com/libgit2/pygit2/pull/946) [#950](https://github.com/libgit2/pygit2/pull/950) Breaking changes: - Now the Repository has a new attribue `odb` for object database: # Before repository.read(...) repository.write(...) # Now repository.odb.read(...) repository.odb.write(...) - Now `Tree[x]` returns a `Object` instance instead of a `TreeEntry`; `Object.type` returns an integer while `TreeEntry.type` returned a string: # Before if tree[x].type == 'tree': # Now if tree[x].type == GIT_OBJ_TREE: if tree[x].type_str == 'tree': - Renamed `TreeEntry._name` to `Object.raw_name`: # Before tree[x]._name # Now tree[x].raw_name - Object comparison is done by id. In the rare case you need to do tree-entry comparison or sorting: # Before tree[x] < tree[y] sorted(list(tree)) # Now pygit2.tree_entry_cmp(x, y) < 0 sorted(list(tree), key=pygit2.tree_entry_key) # 0.28.2 (2019-05-26) - Fix crash in reflog iteration [#901](https://github.com/libgit2/pygit2/issues/901) - Support symbolic references in `branches.with_commit(..)` [#910](https://github.com/libgit2/pygit2/issues/910) - Documentation updates [#909](https://github.com/libgit2/pygit2/pull/909) - Test updates [#911](https://github.com/libgit2/pygit2/pull/911) # 0.28.1 (2019-04-19) - Now works with pycparser 2.18 and above [#846](https://github.com/libgit2/pygit2/issues/846) - Now `Repository.write_archive(..)` keeps the file mode [#616](https://github.com/libgit2/pygit2/issues/616) [#898](https://github.com/libgit2/pygit2/pull/898) - New `Patch.data` returns the raw contents of the patch as a byte string [#790](https://github.com/libgit2/pygit2/pull/790) [#893](https://github.com/libgit2/pygit2/pull/893) - New `Patch.text` returns the contents of the patch as a text string, deprecates [Patch.patch]{.title-ref} [#790](https://github.com/libgit2/pygit2/pull/790) [#893](https://github.com/libgit2/pygit2/pull/893) Deprecations: - `Patch.patch` is deprecated, use `Patch.text` instead # 0.28.0 (2019-03-19) - Upgrade to libgit2 0.28 [#878](https://github.com/libgit2/pygit2/issues/878) - Add binary wheels for Linux [#793](https://github.com/libgit2/pygit2/issues/793) [#869](https://github.com/libgit2/pygit2/pull/869) [#874](https://github.com/libgit2/pygit2/pull/874) [#875](https://github.com/libgit2/pygit2/pull/875) [#883](https://github.com/libgit2/pygit2/pull/883) - New `pygit2.Mailmap`, see documentation [#804](https://github.com/libgit2/pygit2/pull/804) - New `Repository.apply(...)` wraps `git_apply(..)` [#841](https://github.com/libgit2/pygit2/issues/841) [#843](https://github.com/libgit2/pygit2/pull/843) - Now `Repository.merge_analysis(...)` accepts an optional reference parameter [#888](https://github.com/libgit2/pygit2/pull/888) [#891](https://github.com/libgit2/pygit2/pull/891) - Now `Repository.add_worktree(...)` accepts an optional reference parameter [#814](https://github.com/libgit2/pygit2/issues/814) [#889](https://github.com/libgit2/pygit2/pull/889) - Now it\'s possible to set SSL certificate locations [#876](https://github.com/libgit2/pygit2/issues/876) [#879](https://github.com/libgit2/pygit2/pull/879) [#884](https://github.com/libgit2/pygit2/pull/884) [#886](https://github.com/libgit2/pygit2/pull/886) - Test and documentation improvements [#873](https://github.com/libgit2/pygit2/pull/873) [#887](https://github.com/libgit2/pygit2/pull/887) Breaking changes: - Now `worktree.path` returns the path to the worktree directory, not to the [.git]{.title-ref} file within [#803](https://github.com/libgit2/pygit2/issues/803) - Remove undocumented `worktree.git_path` [#803](https://github.com/libgit2/pygit2/issues/803) # 0.27.4 (2019-01-19) - New `pygit2.LIBGIT2_VER` tuple [#845](https://github.com/libgit2/pygit2/issues/845) [#848](https://github.com/libgit2/pygit2/pull/848) - New objects now support (in)equality comparison and hash [#852](https://github.com/libgit2/pygit2/issues/852) [#853](https://github.com/libgit2/pygit2/pull/853) - New references now support (in)equality comparison [#860](https://github.com/libgit2/pygit2/issues/860) [#862](https://github.com/libgit2/pygit2/pull/862) - New `paths` optional argument in `Repository.checkout()` [#858](https://github.com/libgit2/pygit2/issues/858) [#859](https://github.com/libgit2/pygit2/pull/859) - Fix speed and windows package regression [#849](https://github.com/libgit2/pygit2/issues/849) [#857](https://github.com/libgit2/pygit2/issues/857) [#851](https://github.com/libgit2/pygit2/pull/851) - Fix deprecation warning [#850](https://github.com/libgit2/pygit2/pull/850) - Documentation fixes [#855](https://github.com/libgit2/pygit2/pull/855) - Add Python classifiers to setup.py [#861](https://github.com/libgit2/pygit2/pull/861) - Speeding up tests in Travis [#854](https://github.com/libgit2/pygit2/pull/854) Breaking changes: - Remove deprecated [Reference.get_object()]{.title-ref}, use [Reference.peel()]{.title-ref} instead # 0.27.3 (2018-12-15) - Move to pytest, drop support for Python 3.3 and cffi 0.x [#824](https://github.com/libgit2/pygit2/issues/824) [#826](https://github.com/libgit2/pygit2/pull/826) [#833](https://github.com/libgit2/pygit2/pull/833) [#834](https://github.com/libgit2/pygit2/pull/834) - New support comparing signatures for (in)equality - New `Submodule.head_id` [#817](https://github.com/libgit2/pygit2/pull/817) - New `Remote.prune(...)` [#825](https://github.com/libgit2/pygit2/pull/825) - New `pygit2.reference_is_valid_name(...)` [#827](https://github.com/libgit2/pygit2/pull/827) - New `AlreadyExistsError` and `InvalidSpecError` [#828](https://github.com/libgit2/pygit2/issues/828) [#829](https://github.com/libgit2/pygit2/pull/829) - New `Reference.raw_name`, `Reference.raw_shorthand`, `Tag.raw_name`, `Tag.raw_message` and `DiffFile.raw_path` [#840](https://github.com/libgit2/pygit2/pull/840) - Fix decode error in commit messages and signatures [#839](https://github.com/libgit2/pygit2/issues/839) - Fix, raise error in `Repository.descendant_of(...)` if commit doesn\'t exist [#822](https://github.com/libgit2/pygit2/issues/822) [#842](https://github.com/libgit2/pygit2/pull/842) - Documentation fixes [#821](https://github.com/libgit2/pygit2/pull/821) Breaking changes: - Remove undocumented `Tag._message`, replaced by `Tag.raw_message` # 0.27.2 (2018-09-16) - Add support for Python 3.7 [#809](https://github.com/libgit2/pygit2/issues/809) - New `Object.short_id` [#799](https://github.com/libgit2/pygit2/issues/799) [#806](https://github.com/libgit2/pygit2/pull/806) [#807](https://github.com/libgit2/pygit2/pull/807) - New `Repository.descendant_of` and `Repository.branches.with_commit` [#815](https://github.com/libgit2/pygit2/issues/815) [#816](https://github.com/libgit2/pygit2/pull/816) - Fix repository initialization in `clone_repository(...)` [#818](https://github.com/libgit2/pygit2/issues/818) - Fix several warnings and errors, commits [cd896ddc](https://github.com/libgit2/pygit2/commit/cd896ddc) and [dfa536a3](https://github.com/libgit2/pygit2/commit/dfa536a3) - Documentation fixes and improvements [#805](https://github.com/libgit2/pygit2/pull/805) [#808](https://github.com/libgit2/pygit2/pull/808) # 0.27.1 (2018-06-02) Breaking changes: - Now `discover_repository` returns `None` if repository not found, instead of raising `KeyError` [#531](https://github.com/libgit2/pygit2/issues/531) Other changes: - New `DiffLine.raw_content` [#610](https://github.com/libgit2/pygit2/issues/610) - Fix tests failing in some cases [#795](https://github.com/libgit2/pygit2/issues/795) - Automatize wheels upload to pypi [#563](https://github.com/libgit2/pygit2/issues/563) # 0.27.0 (2018-03-30) - Update to libgit2 v0.27 [#783](https://github.com/libgit2/pygit2/pull/783) - Fix for GCC 4 [#786](https://github.com/libgit2/pygit2/pull/786) # 0.26.4 (2018-03-23) Backward incompatible changes: - Now iterating over a configuration returns `ConfigEntry` objects [#778](https://github.com/libgit2/pygit2/pull/778) # Before for name in config: value = config[name] # Now for entry in config: name = entry.name value = entry.value Other changes: - Added support for worktrees [#779](https://github.com/libgit2/pygit2/pull/779) - New `Commit.gpg_signature` [#766](https://github.com/libgit2/pygit2/pull/766) - New static `Diff.parse_diff(...)` [#774](https://github.com/libgit2/pygit2/pull/774) - New optional argument `callbacks` in `Repository.update_submodules(...)` [#763](https://github.com/libgit2/pygit2/pull/763) - New `KeypairFromMemory` credentials [#771](https://github.com/libgit2/pygit2/pull/771) - Add missing status constants [#781](https://github.com/libgit2/pygit2/issues/781) - Fix segfault [#775](https://github.com/libgit2/pygit2/issues/775) - Fix some unicode decode errors with Python 2 [#767](https://github.com/libgit2/pygit2/pull/767) [#768](https://github.com/libgit2/pygit2/pull/768) - Documentation improvements [#721](https://github.com/libgit2/pygit2/pull/721) [#769](https://github.com/libgit2/pygit2/pull/769) [#770](https://github.com/libgit2/pygit2/pull/770) # 0.26.3 (2017-12-24) - New `Diff.deltas` [#736](https://github.com/libgit2/pygit2/issues/736) - Improvements to `Patch.create_from` [#753](https://github.com/libgit2/pygit2/pull/753) [#756](https://github.com/libgit2/pygit2/pull/756) [#759](https://github.com/libgit2/pygit2/pull/759) - Fix build and tests in Windows, broken in the previous release [#749](https://github.com/libgit2/pygit2/pull/749) [#751](https://github.com/libgit2/pygit2/pull/751) - Review `Patch.patch` [#757](https://github.com/libgit2/pygit2/issues/757) - Workaround bug [#4442](https://github.com/libgit2/libgit2/issues/4442) in libgit2, and improve unit tests [#748](https://github.com/libgit2/pygit2/issues/748) [#754](https://github.com/libgit2/pygit2/issues/754) [#758](https://github.com/libgit2/pygit2/pull/758) [#761](https://github.com/libgit2/pygit2/pull/761) # 0.26.2 (2017-12-01) - New property `Patch.patch` [#739](https://github.com/libgit2/pygit2/issues/739) [#741](https://github.com/libgit2/pygit2/pull/741) - New static method `Patch.create_from` [#742](https://github.com/libgit2/pygit2/issues/742) [#744](https://github.com/libgit2/pygit2/pull/744) - New parameter `prune` in `Remote.fetch` [#743](https://github.com/libgit2/pygit2/pull/743) - Tests: skip tests that require network when there is not [#737](https://github.com/libgit2/pygit2/issues/737) - Tests: other improvements [#740](https://github.com/libgit2/pygit2/pull/740) - Documentation improvements # 0.26.1 (2017-11-19) - New `Repository.free()` [#730](https://github.com/libgit2/pygit2/pull/730) - Improve credentials handling for ssh cloning [#718](https://github.com/libgit2/pygit2/pull/718) - Documentation improvements [#714](https://github.com/libgit2/pygit2/pull/714) [#715](https://github.com/libgit2/pygit2/pull/715) [#728](https://github.com/libgit2/pygit2/pull/728) [#733](https://github.com/libgit2/pygit2/pull/733) [#734](https://github.com/libgit2/pygit2/pull/734) [#735](https://github.com/libgit2/pygit2/pull/735) # 0.26.0 (2017-07-06) - Update to libgit2 v0.26 [#713](https://github.com/libgit2/pygit2/pull/713) - Drop support for Python 3.2, add support for cffi 1.10 [#706](https://github.com/libgit2/pygit2/pull/706) [#694](https://github.com/libgit2/pygit2/issues/694) - New `Repository.revert_commit(...)` [#711](https://github.com/libgit2/pygit2/pull/711) [#710](https://github.com/libgit2/pygit2/issues/710) - New `Branch.is_checked_out()` [#696](https://github.com/libgit2/pygit2/pull/696) - Various fixes [#706](https://github.com/libgit2/pygit2/pull/706) [#707](https://github.com/libgit2/pygit2/pull/707) [#708](https://github.com/libgit2/pygit2/pull/708) # 0.25.1 (2017-04-25) - Add support for Python 3.6 - New support for stash: repository methods `stash`, `stash_apply`, `stash_drop` and `stash_pop` [#695](https://github.com/libgit2/pygit2/pull/695) - Improved support for submodules: new repository methods `init_submodules` and `update_submodules` [#692](https://github.com/libgit2/pygit2/pull/692) - New friendlier API for branches & references: `Repository.branches` and `Repository.references` [#700](https://github.com/libgit2/pygit2/pull/700) [#701](https://github.com/libgit2/pygit2/pull/701) - New support for custom backends [#690](https://github.com/libgit2/pygit2/pull/690) - Fix `init_repository` crash on None input [#688](https://github.com/libgit2/pygit2/issues/688) [#697](https://github.com/libgit2/pygit2/pull/697) - Fix checkout with an orphan master branch [#669](https://github.com/libgit2/pygit2/issues/669) [#685](https://github.com/libgit2/pygit2/pull/685) - Better error messages for opening repositories [#645](https://github.com/libgit2/pygit2/issues/645) [#698](https://github.com/libgit2/pygit2/pull/698) # 0.25.0 (2016-12-26) - Upgrade to libgit2 0.25 [#670](https://github.com/libgit2/pygit2/pull/670) - Now Commit.tree raises an error if tree is not found [#682](https://github.com/libgit2/pygit2/pull/682) - New settings.mwindow_mapped_limit, cached_memory, enable_caching, cache_max_size and cache_object_limit [#677](https://github.com/libgit2/pygit2/pull/677) # 0.24.2 (2016-11-01) - Unit tests pass on Windows, integration with AppVeyor [#641](https://github.com/libgit2/pygit2/pull/641) [#655](https://github.com/libgit2/pygit2/issues/655) [#657](https://github.com/libgit2/pygit2/pull/657) [#659](https://github.com/libgit2/pygit2/pull/659) [#660](https://github.com/libgit2/pygit2/pull/660) [#661](https://github.com/libgit2/pygit2/pull/661) [#667](https://github.com/libgit2/pygit2/pull/667) - Fix when libgit2 error messages have non-ascii chars [#651](https://github.com/libgit2/pygit2/pull/651) - Documentation improvements [#643](https://github.com/libgit2/pygit2/pull/643) [#653](https://github.com/libgit2/pygit2/pull/653) [#663](https://github.com/libgit2/pygit2/pull/663) # 0.24.1 (2016-06-21) - New `Repository.listall_reference_objects()` [#634](https://github.com/libgit2/pygit2/pull/634) - Fix `Repository.write_archive(...)` [#619](https://github.com/libgit2/pygit2/pull/619) [#621](https://github.com/libgit2/pygit2/pull/621) - Reproducible builds [#636](https://github.com/libgit2/pygit2/pull/636) - Documentation fixes [#606](https://github.com/libgit2/pygit2/pull/606) [#607](https://github.com/libgit2/pygit2/pull/607) [#609](https://github.com/libgit2/pygit2/pull/609) [#623](https://github.com/libgit2/pygit2/pull/623) - Test updates [#629](https://github.com/libgit2/pygit2/pull/629) # 0.24.0 (2016-03-05) - Update to libgit2 v0.24 [#594](https://github.com/libgit2/pygit2/pull/594) - Support Python 3.5 - New dependency, [six](https://pypi.org/project/six/) - New `Repository.path_is_ignored(path)` [#589](https://github.com/libgit2/pygit2/pull/589) - Fix error in `Repository(path)` when path is a bytes string [#588](https://github.com/libgit2/pygit2/issues/588) [#593](https://github.com/libgit2/pygit2/pull/593) - Fix memory issue in `Repository.describe(...)` [#592](https://github.com/libgit2/pygit2/issues/592) [#597](https://github.com/libgit2/pygit2/issues/597) [#599](https://github.com/libgit2/pygit2/pull/599) - Allow testing with [tox](https://pypi.org/project/tox/) [#600](https://github.com/libgit2/pygit2/pull/600) # 0.23.3 (2016-01-01) - New `Repository.create_blob_fromiobase(...)` [#490](https://github.com/libgit2/pygit2/pull/490) [#577](https://github.com/libgit2/pygit2/pull/577) - New `Repository.describe(...)` [#585](https://github.com/libgit2/pygit2/pull/585) - Fix `Signature` default encoding, UTF-8 now [#581](https://github.com/libgit2/pygit2/issues/581) - Fixing `pip install pygit2`, should install cffi first - Unit tests, fix binary diff test [#586](https://github.com/libgit2/pygit2/pull/586) - Document that `Diff.patch` can be `None` [#587](https://github.com/libgit2/pygit2/pull/587) # 0.23.2 (2015-10-11) - Unify callbacks system for remotes and clone [#568](https://github.com/libgit2/pygit2/pull/568) - New `TreeEntry._name` [#570](https://github.com/libgit2/pygit2/pull/570) - Fix segfault in `Tag._message` [#572](https://github.com/libgit2/pygit2/pull/572) - Documentation improvements [#569](https://github.com/libgit2/pygit2/pull/569) [#574](https://github.com/libgit2/pygit2/pull/574) API changes to clone: # Before clone_repository(..., credentials, certificate) # Now callbacks = RemoteCallbacks(credentials, certificate) clone_repository(..., callbacks) API changes to remote: # Before def transfer_progress(stats): ... remote.credentials = credentials remote.transfer_progress = transfer_progress remote.fetch() remote.push(specs) # Now class MyCallbacks(RemoteCallbacks): def transfer_progress(self, stats): ... callbacks = MyCallbacks(credentials) remote.fetch(callbacks=callbacks) remote.push(specs, callbacks=callbacks) # 0.23.1 (2015-09-26) - Improve support for cffi 1.0+ [#529](https://github.com/libgit2/pygit2/pull/529) [#561](https://github.com/libgit2/pygit2/pull/561) - Fix `Remote.push` [#557](https://github.com/libgit2/pygit2/pull/557) - New `TreeEntry.type` [#560](https://github.com/libgit2/pygit2/pull/560) - New `pygit2.GIT_DIFF_SHOW_BINARY` [#566](https://github.com/libgit2/pygit2/pull/566) # 0.23.0 (2015-08-14) - Update to libgit2 v0.23 [#540](https://github.com/libgit2/pygit2/pull/540) - Now `Repository.merge_base(...)` returns `None` if no merge base is found [#550](https://github.com/libgit2/pygit2/pull/550) - Documentation updates [#547](https://github.com/libgit2/pygit2/pull/547) API changes: - How to set identity (aka signature) in a reflog has changed: # Before signature = Signature('foo', 'bar') ... reference.set_target(target, signature=signature, message=message) repo.set_head(target, signature=signature) remote.fetch(signature=signature) remote.push(signature=signature) # Now repo.set_ident('foo', 'bar') ... reference.set_target(target, message=message) repo.set_head(target) remote.push() # The current identity can be get with repo.ident - Some remote setters have been replaced by methods: # Before # Now Remote.url = url Repository.remotes.set_url(name, url) Remote.push_url = url Repository.remotes.set_push_url(name, url) Remote.add_fetch(refspec) Repository.remotes.add_fetch(name, refspec) Remote.add_push(refspec) Repository.remotes.add_push(name, refspec) Remote.fetch_refspecs = [...] removed, use the config API instead Remote.push_refspecs = [...] removed, use the config API instead # 0.22.1 (2015-07-12) Diff interface refactoring [#346](https://github.com/libgit2/pygit2/pull/346) (in progress): - New `iter(pygit2.Blame)` - New `pygit2.DiffDelta`, `pygit2.DiffFile` and `pygit.DiffLine` - API changes, translation table: Hunk => DiffHunk Patch.old_file_path => Patch.delta.old_file.path Patch.new_file_path => Patch.delta.new_file.path Patch.old_id => Patch.delta.old_file.id Patch.new_id => Patch.delta.new_file.id Patch.status => Patch.delta.status Patch.similarity => Patch.delta.similarity Patch.is_binary => Patch.delta.is_binary Patch.additions => Patch.line_stats[1] Patch.deletions => Patch.line_stats[2] - `DiffHunk.lines` is now a list of `DiffLine` objects, not tuples New features: - New `Repository.expand_id(...)` and `Repository.ahead_behind(...)` [#448](https://github.com/libgit2/pygit2/pull/448) - New `prefix` parameter in `Repository.write_archive` [#481](https://github.com/libgit2/pygit2/pull/481) - New `Repository.merge_trees(...)` [#489](https://github.com/libgit2/pygit2/pull/489) - New `Repository.cherrypick(...)` [#436](https://github.com/libgit2/pygit2/issues/436) [#492](https://github.com/libgit2/pygit2/pull/492) - New support for submodules [#499](https://github.com/libgit2/pygit2/pull/499) [#514](https://github.com/libgit2/pygit2/pull/514) - New `Repository.merge_file_from_index(...)` [#503](https://github.com/libgit2/pygit2/pull/503) - Now `Repository.diff` supports diffing two blobs [#508](https://github.com/libgit2/pygit2/pull/508) - New optional `fetch` parameter in `Remote.create` [#526](https://github.com/libgit2/pygit2/pull/526) - New `pygit2.DiffStats` [#406](https://github.com/libgit2/pygit2/issues/406) [#525](https://github.com/libgit2/pygit2/pull/525) - New `Repository.get_attr(...)` [#528](https://github.com/libgit2/pygit2/pull/528) - New `level` optional parameter in `Index.remove` [#533](https://github.com/libgit2/pygit2/pull/533) - New `repr(TreeEntry)` [#543](https://github.com/libgit2/pygit2/pull/543) Build and install improvements: - Make pygit work in a frozen environment [#453](https://github.com/libgit2/pygit2/pull/453) - Make pygit2 work with pyinstaller [#510](https://github.com/libgit2/pygit2/pull/510) Bugs fixed: - Fix memory issues [#477](https://github.com/libgit2/pygit2/issues/477) [#487](https://github.com/libgit2/pygit2/pull/487) [#520](https://github.com/libgit2/pygit2/pull/520) - Fix TreeEntry equality testing [#458](https://github.com/libgit2/pygit2/issues/458) [#488](https://github.com/libgit2/pygit2/pull/488) - `Repository.write_archive` fix handling of symlinks [#480](https://github.com/libgit2/pygit2/pull/480) - Fix type check in `Diff[...]` [#495](https://github.com/libgit2/pygit2/issues/495) - Fix error when merging files with unicode content [#505](https://github.com/libgit2/pygit2/pull/505) Other: - Documentation improvements and fixes [#448](https://github.com/libgit2/pygit2/pull/448) [#491](https://github.com/libgit2/pygit2/pull/491) [#497](https://github.com/libgit2/pygit2/pull/497) [#507](https://github.com/libgit2/pygit2/pull/507) [#517](https://github.com/libgit2/pygit2/pull/517) [#518](https://github.com/libgit2/pygit2/pull/518) [#519](https://github.com/libgit2/pygit2/pull/519) [#521](https://github.com/libgit2/pygit2/pull/521) [#523](https://github.com/libgit2/pygit2/pull/523) [#527](https://github.com/libgit2/pygit2/pull/527) [#536](https://github.com/libgit2/pygit2/pull/536) - Expose the `pygit2.GIT_REPOSITORY_INIT_*` constants [#483](https://github.com/libgit2/pygit2/issues/483) # 0.22.0 (2015-01-16) New: - Update to libgit2 v0.22 [#459](https://github.com/libgit2/pygit2/pull/459) - Add support for libgit2 feature detection (new `pygit2.features` and `pygit2.GIT_FEATURE_*`) [#475](https://github.com/libgit2/pygit2/pull/475) - New `Repository.remotes` (`RemoteCollection`) [#447](https://github.com/libgit2/pygit2/pull/447) API Changes: - Prototype of `clone_repository` changed, check documentation - Removed `clone_into`, use `clone_repository` with callbacks instead - Use `Repository.remotes.rename(name, new_name)` instead of `Remote.rename(new_name)` - Use `Repository.remotes.delete(name)` instead of `Remote.delete()` - Now `Remote.push(...)` takes a list of refspecs instead of just one - Change `Patch.old_id`, `Patch.new_id`, `Note.annotated_id`, `RefLogEntry.oid_old` and `RefLogEntry.oid_new` to be `Oid` objects instead of strings [#449](https://github.com/libgit2/pygit2/pull/449) Other: - Fix `init_repository` when passing optional parameters `workdir_path`, `description`, `template_path`, `initial_head` or `origin_url` [#466](https://github.com/libgit2/pygit2/issues/466) [#471](https://github.com/libgit2/pygit2/pull/471) - Fix use-after-free when patch outlives diff [#457](https://github.com/libgit2/pygit2/issues/457) [#461](https://github.com/libgit2/pygit2/pull/461) [#474](https://github.com/libgit2/pygit2/pull/474) - Documentation improvements [#456](https://github.com/libgit2/pygit2/issues/456) [#462](https://github.com/libgit2/pygit2/pull/462) [#465](https://github.com/libgit2/pygit2/pull/465) [#472](https://github.com/libgit2/pygit2/pull/472) [#473](https://github.com/libgit2/pygit2/pull/473) - Make the GPL exception explicit in setup.py [#450](https://github.com/libgit2/pygit2/pull/450) # 0.21.4 (2014-11-04) - Fix credentials callback not set when pushing [#431](https://github.com/libgit2/pygit2/pull/431) [#435](https://github.com/libgit2/pygit2/issues/435) [#437](https://github.com/libgit2/pygit2/issues/437) [#438](https://github.com/libgit2/pygit2/pull/438) - Fix `Repository.diff(...)` when treeish is \"empty\" [#432](https://github.com/libgit2/pygit2/issues/432) - New `Reference.peel(...)` renders `Reference.get_object()` obsolete [#434](https://github.com/libgit2/pygit2/pull/434) - New, authenticate using ssh agent [#424](https://github.com/libgit2/pygit2/pull/424) - New `Repository.merge_commits(...)` [#445](https://github.com/libgit2/pygit2/pull/445) - Make it easier to run when libgit2 not in a standard location [#441](https://github.com/libgit2/pygit2/issues/441) - Documentation: review install chapter - Documentation: many corrections [#427](https://github.com/libgit2/pygit2/pull/427) [#429](https://github.com/libgit2/pygit2/pull/429) [#439](https://github.com/libgit2/pygit2/pull/439) [#440](https://github.com/libgit2/pygit2/pull/440) [#442](https://github.com/libgit2/pygit2/pull/442) [#443](https://github.com/libgit2/pygit2/pull/443) [#444](https://github.com/libgit2/pygit2/pull/444) # 0.21.3 (2014-09-15) Breaking changes: - Now `Repository.blame(...)` returns `Oid` instead of string [#413](https://github.com/libgit2/pygit2/pull/413) - New `Reference.set_target(...)` replaces the `Reference.target` setter and `Reference.log_append(...)` [#414](https://github.com/libgit2/pygit2/pull/414) - New `Repository.set_head(...)` replaces the `Repository.head` setter [#414](https://github.com/libgit2/pygit2/pull/414) - `Repository.merge(...)` now uses the `SAFE_CREATE` strategy by default [#417](https://github.com/libgit2/pygit2/pull/417) Other changes: - New `Remote.delete()` [#418](https://github.com/libgit2/pygit2/issues/418) [#420](https://github.com/libgit2/pygit2/pull/420) - New `Repository.write_archive(...)` [#421](https://github.com/libgit2/pygit2/pull/421) - Now `Repository.checkout(...)` accepts branch objects [#408](https://github.com/libgit2/pygit2/pull/408) - Fix refcount leak in remotes [#403](https://github.com/libgit2/pygit2/issues/403) [#404](https://github.com/libgit2/pygit2/pull/404) [#419](https://github.com/libgit2/pygit2/pull/419) - Various fixes to `clone_repository(...)` [#399](https://github.com/libgit2/pygit2/issues/399) [#411](https://github.com/libgit2/pygit2/pull/411) [#425](https://github.com/libgit2/pygit2/issues/425) [#426](https://github.com/libgit2/pygit2/pull/426) - Fix build error in Python 3 [#401](https://github.com/libgit2/pygit2/pull/401) - Now `pip install pygit2` installs cffi first [#380](https://github.com/libgit2/pygit2/issues/380) [#407](https://github.com/libgit2/pygit2/pull/407) - Add support for PyPy3 [#422](https://github.com/libgit2/pygit2/pull/422) - Documentation improvements [#398](https://github.com/libgit2/pygit2/pull/398) [#409](https://github.com/libgit2/pygit2/pull/409) # 0.21.2 (2014-08-09) - Fix regression with Python 2, `IndexEntry.path` returns str (bytes in Python 2 and unicode in Python 3) - Get back `IndexEntry.oid` for backwards compatibility - Config, iterate over the keys (instead of the key/value pairs) [#395](https://github.com/libgit2/pygit2/pull/395) - `Diff.find_similar` supports new threshold arguments [#396](https://github.com/libgit2/pygit2/pull/396) - Optimization, do not load the object when expanding an oid prefix [#397](https://github.com/libgit2/pygit2/pull/397) # 0.21.1 (2014-07-22) - Install fix [#382](https://github.com/libgit2/pygit2/pull/382) - Documentation improved, including [#383](https://github.com/libgit2/pygit2/pull/383) [#385](https://github.com/libgit2/pygit2/pull/385) [#388](https://github.com/libgit2/pygit2/pull/388) - Documentation, use the read-the-docs theme [#387](https://github.com/libgit2/pygit2/pull/387) - Coding style improvements [#392](https://github.com/libgit2/pygit2/pull/392) - New `Repository.state_cleanup()` [#386](https://github.com/libgit2/pygit2/pull/386) - New `Index.conflicts` [#345](https://github.com/libgit2/pygit2/issues/345) [#389](https://github.com/libgit2/pygit2/pull/389) - New checkout option to define the target directory [#390](https://github.com/libgit2/pygit2/pull/390) Backward incompatible changes: - Now the checkout strategy must be a keyword argument. Change `Repository.checkout(refname, strategy)` to `Repository.checkout(refname, strategy=strategy)` Idem for `checkout_head`, `checkout_index` and `checkout_tree` # 0.21.0 (2014-06-27) Highlights: - Drop official support for Python 2.6, and add support for Python 3.4 [#376](https://github.com/libgit2/pygit2/pull/376) - Upgrade to libgit2 v0.21.0 [#374](https://github.com/libgit2/pygit2/pull/374) - Start using cffi [#360](https://github.com/libgit2/pygit2/pull/360) [#361](https://github.com/libgit2/pygit2/pull/361) Backward incompatible changes: - Replace `oid` by `id` through the API to follow libgit2 conventions. - Merge API overhaul following changes in libgit2. - New `Remote.rename(...)` replaces `Remote.name = ...` - Now `Remote.fetch()` returns a `TransferProgress` object. - Now `Config.get_multivar(...)` returns an iterator instead of a list. New features: - New `Config.snapshot()` and `Repository.config_snapshot()` - New `Config` methods: `get_bool(...)`, `get_int(...)`, `parse_bool(...)` and `parse_int(...)` [#357](https://github.com/libgit2/pygit2/pull/357) - Blob: implement the memory buffer interface [#362](https://github.com/libgit2/pygit2/pull/362) - New `clone_into(...)` function [#368](https://github.com/libgit2/pygit2/pull/368) - Now `Index` can be used alone, without a repository [#372](https://github.com/libgit2/pygit2/pull/372) - Add more options to `init_repository` [#347](https://github.com/libgit2/pygit2/pull/347) - Support `Repository.workdir = ...` and support setting detached heads `Repository.head = ` [#377](https://github.com/libgit2/pygit2/pull/377) Other: - Fix again build with VS2008 [#364](https://github.com/libgit2/pygit2/pull/364) - Fix `Blob.diff(...)` and `Blob.diff_to_buffer(...)` arguments passing [#366](https://github.com/libgit2/pygit2/pull/366) - Fail gracefully when compiling against the wrong version of libgit2 [#365](https://github.com/libgit2/pygit2/pull/365) - Several documentation improvements and updates [#359](https://github.com/libgit2/pygit2/pull/359) [#375](https://github.com/libgit2/pygit2/pull/375) [#378](https://github.com/libgit2/pygit2/pull/378) # 0.20.3 (2014-04-02) - A number of memory issues fixed [#328](https://github.com/libgit2/pygit2/pull/328) [#348](https://github.com/libgit2/pygit2/pull/348) [#353](https://github.com/libgit2/pygit2/pull/353) [#355](https://github.com/libgit2/pygit2/pull/355) [#356](https://github.com/libgit2/pygit2/pull/356) - Compatibility fixes for PyPy ([#338](https://github.com/libgit2/pygit2/pull/338)), Visual Studio 2008 ([#343](https://github.com/libgit2/pygit2/pull/343)) and Python 3.3 ([#351](https://github.com/libgit2/pygit2/pull/351)) - Make the sort mode parameter in `Repository.walk(...)` optional [#337](https://github.com/libgit2/pygit2/pull/337) - New `Object.peel(...)` [#342](https://github.com/libgit2/pygit2/pull/342) - New `Index.add_all(...)` [#344](https://github.com/libgit2/pygit2/pull/344) - Introduce support for libgit2 options [#350](https://github.com/libgit2/pygit2/pull/350) - More informative repr for `Repository` objects [#352](https://github.com/libgit2/pygit2/pull/352) - Introduce support for credentials [#354](https://github.com/libgit2/pygit2/pull/354) - Several documentation fixes [#302](https://github.com/libgit2/pygit2/issues/302) [#336](https://github.com/libgit2/pygit2/issues/336) - Tests, remove temporary files [#341](https://github.com/libgit2/pygit2/pull/341) # 0.20.2 (2014-02-04) - Support PyPy [#209](https://github.com/libgit2/pygit2/issues/209) [#327](https://github.com/libgit2/pygit2/pull/327) [#333](https://github.com/libgit2/pygit2/pull/333) Repository: - New `Repository.default_signature` [#310](https://github.com/libgit2/pygit2/pull/310) Oid: - New `str(Oid)` deprecates `Oid.hex` [#322](https://github.com/libgit2/pygit2/pull/322) Object: - New `Object.id` deprecates `Object.oid` [#322](https://github.com/libgit2/pygit2/pull/322) - New `TreeEntry.id` deprecates `TreeEntry.oid` [#322](https://github.com/libgit2/pygit2/pull/322) - New `Blob.diff(...)` and `Blob.diff_to_buffer(...)` [#307](https://github.com/libgit2/pygit2/pull/307) - New `Commit.tree_id` and `Commit.parent_ids` [#73](https://github.com/libgit2/pygit2/issues/73) [#311](https://github.com/libgit2/pygit2/pull/311) - New rich comparison between tree entries [#305](https://github.com/libgit2/pygit2/issues/305) [#313](https://github.com/libgit2/pygit2/pull/313) - Now `Tree.__contains__(key)` supports paths [#306](https://github.com/libgit2/pygit2/issues/306) [#316](https://github.com/libgit2/pygit2/pull/316) Index: - Now possible to create `IndexEntry(...)` [#325](https://github.com/libgit2/pygit2/pull/325) - Now `IndexEntry.path`, `IndexEntry.oid` and `IndexEntry.mode` are writable [#325](https://github.com/libgit2/pygit2/pull/325) - Now `Index.add(...)` accepts an `IndexEntry` too [#325](https://github.com/libgit2/pygit2/pull/325) - Now `Index.write_tree(...)` is able to write to a different repository [#325](https://github.com/libgit2/pygit2/pull/325) - Fix memory leak in `IndexEntry.path` setter [#335](https://github.com/libgit2/pygit2/pull/335) Config: - New `Config` iterator replaces `Config.foreach` [#183](https://github.com/libgit2/pygit2/issues/183) [#312](https://github.com/libgit2/pygit2/pull/312) Remote: - New type `Refspec` [#314](https://github.com/libgit2/pygit2/pull/314) - New `Remote.push_url` [#315](https://github.com/libgit2/pygit2/pull/314) - New `Remote.add_push` and `Remote.add_fetch` [#255](https://github.com/libgit2/pygit2/issues/255) [#318](https://github.com/libgit2/pygit2/pull/318) - New `Remote.fetch_refspecs` replaces `Remote.get_fetch_refspecs()` and `Remote.set_fetch_refspecs(...)` [#319](https://github.com/libgit2/pygit2/pull/319) - New `Remote.push_refspecs` replaces `Remote.get_push_refspecs()` and `Remote.set_push_refspecs(...)` [#319](https://github.com/libgit2/pygit2/pull/319) - New `Remote.progress`, `Remote.transfer_progress` and `Remote.update_tips` [#274](https://github.com/libgit2/pygit2/issues/274) [#324](https://github.com/libgit2/pygit2/pull/324) - New type `TransferProgress` [#274](https://github.com/libgit2/pygit2/issues/274) [#324](https://github.com/libgit2/pygit2/pull/324) - Fix refcount leak in `Repository.remotes` [#321](https://github.com/libgit2/pygit2/issues/321) [#332](https://github.com/libgit2/pygit2/pull/332) Other: [#331](https://github.com/libgit2/pygit2/pull/331) # 0.20.1 (2013-12-24) - New remote ref-specs API: [#290](https://github.com/libgit2/pygit2/pull/290) - New `Repository.reset(...)`: [#292](https://github.com/libgit2/pygit2/pull/292), [#294](https://github.com/libgit2/pygit2/pull/294) - Export `GIT_DIFF_MINIMAL`: [#293](https://github.com/libgit2/pygit2/pull/293) - New `Repository.merge(...)`: [#295](https://github.com/libgit2/pygit2/pull/295) - Fix `Repository.blame` argument handling: [#297](https://github.com/libgit2/pygit2/pull/297) - Fix build error on Windows: [#298](https://github.com/libgit2/pygit2/pull/298) - Fix typo in the README file, Blog → Blob: [#301](https://github.com/libgit2/pygit2/pull/301) - Now `Diff.patch` returns `None` if no patch: [#232](https://github.com/libgit2/pygit2/pull/232), [#303](https://github.com/libgit2/pygit2/pull/303) - New `Walker.simplify_first_parent()`: [#304](https://github.com/libgit2/pygit2/pull/304) # 0.20.0 (2013-11-24) - Upgrade to libgit2 v0.20.0: [#288](https://github.com/libgit2/pygit2/pull/288) - New `Repository.head_is_unborn` replaces `Repository.head_is_orphaned` - Changed `pygit2.clone_repository(...)`. Drop `push_url`, `fetch_spec` and `push_spec` parameters. Add `ignore_cert_errors`. - New `Patch.additions` and `Patch.deletions`: [#275](https://github.com/libgit2/pygit2/pull/275) - New `Patch.is_binary`: [#276](https://github.com/libgit2/pygit2/pull/276) - New `Reference.log_append(...)`: [#277](https://github.com/libgit2/pygit2/pull/277) - New `Blob.is_binary`: [#278](https://github.com/libgit2/pygit2/pull/278) - New `len(Diff)` shows the number of patches: [#281](https://github.com/libgit2/pygit2/pull/281) - Rewrite `Repository.status()`: [#283](https://github.com/libgit2/pygit2/pull/283) - New `Reference.shorthand`: [#284](https://github.com/libgit2/pygit2/pull/284) - New `Repository.blame(...)`: [#285](https://github.com/libgit2/pygit2/pull/285) - Now `Repository.listall_references()` and `Repository.listall_branches()` return a list, not a tuple: [#289](https://github.com/libgit2/pygit2/pull/289) libgit2-pygit2-a011e26/COPYING000066400000000000000000000451551473744024100156560ustar00rootroot00000000000000 pygit2 is Copyright (C) the pygit2 contributors, unless otherwise stated. See the AUTHORS.md file for details. Note that the only valid version of the GPL as far as this project is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated. ---------------------------------------------------------------------- LINKING EXCEPTION In addition to the permissions in the GNU General Public License, the authors give you unlimited permission to link the compiled version of this library into combinations with other programs, and to distribute those combinations without any restriction coming from the use of this file. (The General Public License restrictions do apply in other respects; for example, they cover modification of the file, and distribution when not linked into a combined executable.) ---------------------------------------------------------------------- GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. libgit2-pygit2-a011e26/Makefile000066400000000000000000000002131473744024100162450ustar00rootroot00000000000000.PHONY: build html build: OPENSSL_VERSION=3.2.3 LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 sh build.sh html: build make -C docs html libgit2-pygit2-a011e26/README.md000066400000000000000000000040371473744024100160740ustar00rootroot00000000000000# pygit2 - libgit2 bindings in Python Bindings to the libgit2 shared library, implements Git plumbing. Supports Python 3.10 to 3.13 and PyPy3 7.3+ [![image](https://github.com/libgit2/pygit2/actions/workflows/tests.yml/badge.svg)](https://github.com/libgit2/pygit2/actions/workflows/tests.yml) [![image](https://ci.appveyor.com/api/projects/status/edmwc0dctk5nacx0/branch/master?svg=true)](https://ci.appveyor.com/project/jdavid/pygit2/branch/master) ## Links - Documentation - - Install - - Download - - Source code and issue tracker - - Changelog - - Authors - ## Sponsors Add your name and link here, [become a sponsor](https://github.com/sponsors/jdavid). ## License: GPLv2 with linking exception This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. In addition to the permissions in the GNU General Public License, the authors give you unlimited permission to link the compiled version of this file into combinations with other programs, and to distribute those combinations without any restriction coming from the use of this file. (The General Public License restrictions do apply in other respects; for example, they cover modification of the file, and distribution when not linked into a combined executable.) This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. libgit2-pygit2-a011e26/SPONSORS.md000066400000000000000000000005131473744024100164200ustar00rootroot00000000000000Friends of pygit2: - Add your name to the list, [become a friend of pygit2](https://github.com/sponsors/jdavid). Past sponsors: - [Microsoft](https://github.com/microsoft) - [Iterative](https://iterative.ai/) - [SourceHut](https://sourcehut.org) - [GitHub](https://github.com/github) - [omniproc](https://github.com/omniproc) libgit2-pygit2-a011e26/appveyor.yml000066400000000000000000000044261473744024100172070ustar00rootroot00000000000000version: 1.17.{build} image: Visual Studio 2019 configuration: Release environment: global: TWINE_USERNAME: __token__ TWINE_PASSWORD: secure: 7YD82RnQJ9rnJE/josiQ/V6VWh+tlhmJpWVM/u5jGdl8XqyhsLEKF5MNMYd4ZYxA/MGaYBCQ525d4m9RSDk9RB+uIFMZJLnl1eOjHQVyJ+ZZmJb65tqd/fR5hybhWtVhn+0wANiI4uqrojFFVy1HjfBYSrvyk+7LLDxfSVTqkhMEhbZbWBpGP/3VET1gPy+qdlWcL7quwhSBPSbKpyMi/cqvp5/yFLAM615RRABgQUDpRyXxtBTReRgWSxi9kUXXqR18ZvQlvMLnAsEnGFRenA== matrix: - GENERATOR: 'Visual Studio 14' PYTHON: 'C:\Python310\python.exe' - GENERATOR: 'Visual Studio 14 Win64' PYTHON: 'C:\Python310-x64\python.exe' - GENERATOR: 'Visual Studio 14' PYTHON: 'C:\Python311\python.exe' - GENERATOR: 'Visual Studio 14 Win64' PYTHON: 'C:\Python311-x64\python.exe' - GENERATOR: 'Visual Studio 14' PYTHON: 'C:\Python312\python.exe' - GENERATOR: 'Visual Studio 14 Win64' PYTHON: 'C:\Python312-x64\python.exe' - GENERATOR: 'Visual Studio 14' PYTHON: 'C:\Python313\python.exe' - GENERATOR: 'Visual Studio 14 Win64' PYTHON: 'C:\Python313-x64\python.exe' matrix: fast_finish: true init: - cmd: | "%PYTHON%" -m pip install -U pip wheel build_script: # Clone, build and install libgit2 - cmd: | set LIBGIT2=%APPVEYOR_BUILD_FOLDER%\venv git clone --depth=1 -b v1.9.0 https://github.com/libgit2/libgit2.git libgit2 cd libgit2 cmake . -DBUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="%LIBGIT2%" -G "%GENERATOR%" cmake --build . --target install cd .. # Build and install pygit2 # Rename pygit2 folder, so when testing it picks the installed one - cmd: | "%PYTHON%" -m pip install -r requirements-test.txt "%PYTHON%" -m pip wheel --wheel-dir=dist . "%PYTHON%" -m pip install --no-index --find-links=dist pygit2 mv pygit2 pygit2.bak test_script: - ps: | &$env:PYTHON -m pytest test --junitxml=testresults.xml if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } # upload results to AppVeyor $wc = New-Object 'System.Net.WebClient' $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path ".\testresults.xml")) artifacts: - path: dist\pygit2-*.whl deploy_script: - ps: if ($env:APPVEYOR_REPO_TAG -eq $TRUE) { pip install twine; twine upload dist/pygit2-*.whl } deploy: on libgit2-pygit2-a011e26/build.sh000066400000000000000000000175141473744024100162540ustar00rootroot00000000000000#!/bin/sh # # Synopsis: # # sh build.sh - Build inplace # sh build.sh test - Build inplace, and run the tests # sh build.sh wheel - Build a wheel, install, and run the tests # # Environment variables: # # AUDITWHEEL_PLAT - Linux platform for auditwheel repair # LIBSSH2_OPENSSL - Where to find openssl # LIBSSH2_VERSION= - Build the given version of libssh2 # LIBGIT2_VERSION= - Build the given version of libgit2 # OPENSSL_VERSION= - Build the given version of OpenSSL # (only needed for Mac universal on CI) # # Examples. # # Build inplace, libgit2 must be available in the path: # # sh build.sh # # Build libgit2 1.9.0 (will use libssh2 if available), then build pygit2 # inplace: # # LIBGIT2_VERSION=1.9.0 sh build.sh # # Build libssh2 1.11.1 and libgit2 1.9.0, then build pygit2 inplace: # # LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.0 sh build.sh # # Build inplace and run the tests: # # sh build.sh test # # Build a wheel: # # sh build.sh wheel # set -x # Print every command and variable set -e # Fail fast # Variables ARCH=`uname -m` KERNEL=`uname -s` BUILD_TYPE=${BUILD_TYPE:-Debug} PYTHON=${PYTHON:-python3} if [ "$CIBUILDWHEEL" != "1" ]; then PYTHON_TAG=$($PYTHON build_tag.py) fi PREFIX="${PREFIX:-$(pwd)/ci/$PYTHON_TAG}" export LDFLAGS="-Wl,-rpath,$PREFIX/lib" if [ "$CIBUILDWHEEL" = "1" ]; then if [ -f /usr/bin/apt-get ]; then apt-get update apt-get install wget -y if [ -z "$OPENSSL_VERSION" ]; then apt-get install libssl-dev -y fi elif [ -f /usr/bin/yum ]; then yum install wget zlib-devel -y if [ -z "$OPENSSL_VERSION" ]; then yum install openssl-devel -y else yum install perl-IPC-Cmd -y fi elif [ -f /sbin/apk ]; then apk add wget if [ -z "$OPENSSL_VERSION" ]; then apk add openssl-dev fi fi rm -rf ci mkdir ci || true cd ci else # Create a virtual environment $PYTHON -m venv $PREFIX cd ci fi # Install zlib # XXX Build libgit2 with USE_BUNDLED_ZLIB instead? if [ -n "$ZLIB_VERSION" ]; then FILENAME=zlib-$ZLIB_VERSION wget https://www.zlib.net/$FILENAME.tar.gz -N tar xf $FILENAME.tar.gz cd $FILENAME ./configure --prefix=$PREFIX make make install cd .. fi # Install openssl if [ -n "$OPENSSL_VERSION" ]; then FILENAME=openssl-$OPENSSL_VERSION wget https://www.openssl.org/source/$FILENAME.tar.gz -N --no-check-certificate if [ "$KERNEL" = "Darwin" ]; then tar xf $FILENAME.tar.gz mv $FILENAME openssl-x86 tar xf $FILENAME.tar.gz mv $FILENAME openssl-arm cd openssl-x86 ./Configure darwin64-x86_64-cc shared make cd ../openssl-arm ./Configure enable-rc5 zlib darwin64-arm64-cc no-asm make cd .. mkdir openssl-universal LIBSSL=$(basename openssl-x86/libssl.*.dylib) lipo -create openssl-x86/libssl.*.dylib openssl-arm/libssl.*.dylib -output openssl-universal/$LIBSSL LIBCRYPTO=$(basename openssl-x86/libcrypto.*.dylib) lipo -create openssl-x86/libcrypto.*.dylib openssl-arm/libcrypto.*.dylib -output openssl-universal/$LIBCRYPTO cd openssl-universal install_name_tool -id "@rpath/$LIBSSL" $LIBSSL install_name_tool -id "@rpath/$LIBCRYPTO" $LIBCRYPTO OPENSSL_PREFIX=$(pwd) cd .. else # Linux tar xf $FILENAME.tar.gz cd $FILENAME ./Configure shared --prefix=$PREFIX --libdir=$PREFIX/lib make make install OPENSSL_PREFIX=$(pwd) cd .. fi fi # Install libssh2 if [ -n "$LIBSSH2_VERSION" ]; then FILENAME=libssh2-$LIBSSH2_VERSION wget https://www.libssh2.org/download/$FILENAME.tar.gz -N --no-check-certificate tar xf $FILENAME.tar.gz cd $FILENAME if [ "$KERNEL" = "Darwin" ] && [ "$CIBUILDWHEEL" = "1" ]; then cmake . \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DBUILD_SHARED_LIBS=ON \ -DBUILD_EXAMPLES=OFF \ -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ -DOPENSSL_CRYPTO_LIBRARY="../openssl-universal/$LIBCRYPTO" \ -DOPENSSL_SSL_LIBRARY="../openssl-universal/$LIBSSL" \ -DOPENSSL_INCLUDE_DIR="../openssl-x86/include" \ -DBUILD_TESTING=OFF else cmake . \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DBUILD_SHARED_LIBS=ON \ -DBUILD_EXAMPLES=OFF \ -DBUILD_TESTING=OFF fi cmake --build . --target install cd .. USE_SSH=ON else USE_SSH=OFF fi # Install libgit2 if [ -n "$LIBGIT2_VERSION" ]; then FILENAME=libgit2-$LIBGIT2_VERSION wget https://github.com/libgit2/libgit2/archive/refs/tags/v$LIBGIT2_VERSION.tar.gz -N -O $FILENAME.tar.gz tar xf $FILENAME.tar.gz cd $FILENAME mkdir build -p cd build if [ "$KERNEL" = "Darwin" ] && [ "$CIBUILDWHEEL" = "1" ]; then CMAKE_PREFIX_PATH=$OPENSSL_PREFIX:$PREFIX cmake .. \ -DBUILD_SHARED_LIBS=ON \ -DBUILD_TESTS=OFF \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ -DOPENSSL_CRYPTO_LIBRARY="../openssl-universal/$LIBCRYPTO" \ -DOPENSSL_SSL_LIBRARY="../openssl-universal/$LIBSSL" \ -DOPENSSL_INCLUDE_DIR="../openssl-x86/include" \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DUSE_SSH=$USE_SSH else export CFLAGS=-I$PREFIX/include CMAKE_PREFIX_PATH=$OPENSSL_PREFIX:$PREFIX cmake .. \ -DBUILD_SHARED_LIBS=ON \ -DBUILD_TESTS=OFF \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DUSE_SSH=$USE_SSH fi cmake --build . --target install cd .. cd .. export LIBGIT2=$PREFIX fi if [ "$CIBUILDWHEEL" = "1" ]; then if [ "$KERNEL" = "Darwin" ]; then cp $OPENSSL_PREFIX/*.dylib $PREFIX/lib/ cp $OPENSSL_PREFIX/*.dylib $PREFIX/lib/ echo "PREFIX " $PREFIX echo "OPENSSL_PREFIX" $OPENSSL_PREFIX ls -l /Users/runner/work/pygit2/pygit2/ci/ ls -l $PREFIX/lib fi # we're done building dependencies, cibuildwheel action will take over exit 0 fi # Build pygit2 cd .. $PREFIX/bin/pip install -U pip wheel if [ "$1" = "wheel" ]; then shift $PREFIX/bin/pip install wheel $PREFIX/bin/python setup.py bdist_wheel WHEELDIR=dist else # Install Python requirements & build inplace $PREFIX/bin/pip install -r requirements.txt $PREFIX/bin/python setup.py build_ext --inplace fi # Bundle libraries if [ "$1" = "bundle" ]; then shift WHEELDIR=wheelhouse case "${KERNEL}" in Darwin*) $PREFIX/bin/pip install delocate $PREFIX/bin/delocate-listdeps dist/pygit2-*macosx*.whl $PREFIX/bin/delocate-wheel -v -w $WHEELDIR dist/pygit2-*macosx*.whl $PREFIX/bin/delocate-listdeps $WHEELDIR/pygit2-*macosx*.whl ;; *) # LINUX $PREFIX/bin/pip install auditwheel $PREFIX/bin/auditwheel repair dist/pygit2*-$PYTHON_TAG-*_$ARCH.whl $PREFIX/bin/auditwheel show $WHEELDIR/pygit2*-$PYTHON_TAG-*_$ARCH.whl ;; esac fi # Tests if [ "$1" = "test" ]; then shift if [ -n "$WHEELDIR" ]; then $PREFIX/bin/pip install $WHEELDIR/pygit2*-$PYTHON_TAG-*.whl fi $PREFIX/bin/pip install -r requirements-test.txt $PREFIX/bin/pytest --cov=pygit2 fi # Test .pyi stub file if [ "$1" = "stubtest" ]; then shift $PREFIX/bin/pip install mypy PYTHONPATH=. $PREFIX/bin/stubtest --mypy-config-file mypy-stubtest.ini pygit2._pygit2 [ $? == 0 ] && echo "stubtest OK" fi libgit2-pygit2-a011e26/build_tag.py000066400000000000000000000002341473744024100171140ustar00rootroot00000000000000import platform, sys py = {'CPython': 'cp', 'PyPy': 'pp'}[platform.python_implementation()] print(f'{py}{sys.version_info.major}{sys.version_info.minor}') libgit2-pygit2-a011e26/docs/000077500000000000000000000000001473744024100155415ustar00rootroot00000000000000libgit2-pygit2-a011e26/docs/Makefile000066400000000000000000000011041473744024100171750ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)libgit2-pygit2-a011e26/docs/_static/000077500000000000000000000000001473744024100171675ustar00rootroot00000000000000libgit2-pygit2-a011e26/docs/_static/.keep000066400000000000000000000000001473744024100201020ustar00rootroot00000000000000libgit2-pygit2-a011e26/docs/_templates/000077500000000000000000000000001473744024100176765ustar00rootroot00000000000000libgit2-pygit2-a011e26/docs/_templates/.keep000066400000000000000000000000001473744024100206110ustar00rootroot00000000000000libgit2-pygit2-a011e26/docs/backends.rst000066400000000000000000000017551473744024100200550ustar00rootroot00000000000000********************************************************************** Backends ********************************************************************** The use of custom backends for the git object database (odb) and reference database (refdb) are supported by pygit2. .. contents:: Contents :local: The OdbBackend class =================================== The OdbBackend class is subclassable and can be used to build a custom object database. .. autoclass:: pygit2.OdbBackend :members: Built-in OdbBackend implementations =================================== .. autoclass:: pygit2.OdbBackendLoose :members: .. autoclass:: pygit2.OdbBackendPack :members: The RefdbBackend class =================================== The RefdbBackend class is subclassable and can be used to build a custom reference database. .. autoclass:: pygit2.RefdbBackend :members: Built-in RefdbBackend implementations ===================================== .. autoclass:: pygit2.RefdbFsBackend :members: libgit2-pygit2-a011e26/docs/blame.rst000066400000000000000000000024471473744024100173620ustar00rootroot00000000000000********************************************************************** Blame ********************************************************************** .. contents:: .. automethod:: pygit2.Repository.blame The Blame type ============== .. automethod:: pygit2.Blame.for_line .. method:: Blame.__iter__() .. method:: Blame.__len__() .. method:: Blame.__getitem__(n) The BlameHunk type ================== Attributes: .. autoattribute:: pygit2.BlameHunk.lines_in_hunk .. autoattribute:: pygit2.BlameHunk.final_commit_id .. autoattribute:: pygit2.BlameHunk.final_start_line_number .. autoattribute:: pygit2.BlameHunk.orig_commit_id .. autoattribute:: pygit2.BlameHunk.orig_path .. autoattribute:: pygit2.BlameHunk.orig_start_line_number .. autoattribute:: pygit2.BlameHunk.boundary Getters: .. autoattribute:: pygit2.BlameHunk.final_committer .. autoattribute:: pygit2.BlameHunk.orig_committer Constants ========= .. py:data:: enums.BlameFlag.NORMAL .. py:data:: enums.BlameFlag.TRACK_COPIES_SAME_FILE .. py:data:: enums.BlameFlag.TRACK_COPIES_SAME_COMMIT_MOVES .. py:data:: enums.BlameFlag.TRACK_COPIES_SAME_COMMIT_COPIES .. py:data:: enums.BlameFlag.TRACK_COPIES_ANY_COMMIT_COPIES .. py:data:: enums.BlameFlag.FIRST_PARENT .. py:data:: enums.BlameFlag.USE_MAILMAP .. py:data:: enums.BlameFlag.IGNORE_WHITESPACE libgit2-pygit2-a011e26/docs/branches.rst000066400000000000000000000023761473744024100200700ustar00rootroot00000000000000********************************************************************** Branches ********************************************************************** .. autoclass:: pygit2.Repository :members: lookup_branch, raw_listall_branches :noindex: .. attribute:: branches Branches inherit from References, and additionally provide specialized accessors for some unique features. .. autoclass:: pygit2.repository.Branches :members: :undoc-members: :special-members: __getitem__, __iter__, __contains__ Example:: >>> # Listing all branches >>> branches_list = list(repo.branches) >>> # Local only >>> local_branches = list(repo.branches.local) >>> # Remote only >>> remote_branches = list(repo.branches.remote) >>> # Get a branch >>> branch = repo.branches['master'] >>> other_branch = repo.branches['does-not-exist'] # Will raise a KeyError >>> other_branch = repo.branches.get('does-not-exist') # Returns None >>> remote_branch = repo.branches.remote['upstream/feature'] >>> # Create a local branch >>> new_branch = repo.branches.local.create('new-branch') >>> And delete it >>> repo.branches.delete('new-branch') The Branch type ==================== .. autoclass:: pygit2.Branch :members: libgit2-pygit2-a011e26/docs/commit_log.rst000066400000000000000000000006041473744024100204240ustar00rootroot00000000000000********************************************************************** Commit log ********************************************************************** .. automethod:: pygit2.Repository.walk .. automethod:: pygit2.Walker.hide .. automethod:: pygit2.Walker.push .. automethod:: pygit2.Walker.reset .. automethod:: pygit2.Walker.sort .. automethod:: pygit2.Walker.simplify_first_parent libgit2-pygit2-a011e26/docs/conf.py000066400000000000000000000036541473744024100170500ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # http://www.sphinx-doc.org/en/master/config import os, sys # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- project = 'pygit2' copyright = '2010-2025 The pygit2 contributors' # author = '' # The full version, including alpha/beta/rc tags release = '1.17.0' # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' html_theme_path = ['_themes'] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] libgit2-pygit2-a011e26/docs/config.rst000066400000000000000000000010171473744024100175370ustar00rootroot00000000000000********************************************************************** Configuration files ********************************************************************** .. autoclass:: pygit2.Repository :members: config :noindex: The Config type ================ .. autoclass:: pygit2.Config :members: :undoc-members: :special-members: __contains__, __delitem__, __getitem__, __iter__, __setitem__ The ConfigEntry type ==================== .. autoclass:: pygit2.config.ConfigEntry :members: name, value, level libgit2-pygit2-a011e26/docs/development.rst000066400000000000000000000037321473744024100206220ustar00rootroot00000000000000********************************************************************** The development version ********************************************************************** .. image:: https://github.com/libgit2/pygit2/actions/workflows/tests.yml/badge.svg :target: https://github.com/libgit2/pygit2/actions/workflows/tests.yml .. image:: https://ci.appveyor.com/api/projects/status/edmwc0dctk5nacx0/branch/master?svg=true :target: https://ci.appveyor.com/project/jdavid/pygit2/branch/master .. contents:: Contents :local: Unit tests ========== .. code-block:: sh $ git clone git://github.com/libgit2/pygit2.git $ cd pygit2 $ python setup.py build_ext --inplace $ pytest test Coding style: documentation strings =================================== Example:: def f(a, b): """ The general description goes here. Returns: bla bla. Parameters: a : Bla bla. b : Bla bla. Examples:: >>> f(...) """ Building the docs =================================== To build the documentation first you need to install sphinx-rtd-theme:: $ pip install sphinx-rtd-theme Then you have to build pygit2 inplace:: $ make And finally you can build the documentation:: $ make -C docs html Running Valgrind =================================== Step 1. Build libc and libgit2 with debug symbols. See your distribution documentation. Step 2. Build Python to be used with valgrind, e.g.:: $ ./configure --prefix=~/Python-3.9.18 --without-pymalloc --with-pydebug --with-valgrind $ make $ make install $ export PYTHONBIN=~/Python-3.9.18/bin Step 3. Build pygit2 with debug symbols:: $ rm build -rf && $PYTHONBIN/python3 setup.py build_ext --inplace -g Step 4. Install requirements:: $ $PYTHONBIN/python3 setup.py install $ pip insall pytest Step 4. Run valgrind:: $ valgrind -v --leak-check=full --suppressions=misc/valgrind-python.supp $PYTHONBIN/pytest &> valgrind.txt libgit2-pygit2-a011e26/docs/diff.rst000066400000000000000000000034741473744024100172130ustar00rootroot00000000000000********************************************************************** Diff ********************************************************************** .. contents:: A diff shows the changes between trees, an index or the working dir. .. automethod:: pygit2.Repository.diff Examples .. code-block:: python # Changes between commits >>> t0 = revparse_single('HEAD') >>> t1 = revparse_single('HEAD^') >>> repo.diff(t0, t1) >>> t0.diff(t1) # equivalent >>> repo.diff('HEAD', 'HEAD^') # equivalent # Get all patches for a diff >>> diff = repo.diff('HEAD^', 'HEAD~3') >>> patches = [p for p in diff] # Get the stats for a diff >>> diff = repo.diff('HEAD^', 'HEAD~3') >>> diff.stats # Diffing the empty tree >>> tree = revparse_single('HEAD').tree >>> tree.diff_to_tree() # Diff empty tree to a tree >>> tree = revparse_single('HEAD').tree >>> tree.diff_to_tree(swap=True) The Diff type ==================== .. autoclass:: pygit2.Diff :members: deltas, find_similar, merge, parse_diff, patch, patchid, stats .. method:: Diff.__iter__() Returns an iterator over the deltas/patches in this diff. .. method:: Diff.__len__() Returns the number of deltas/patches in this diff. The Patch type ==================== Attributes: .. autoclass:: pygit2.Patch :members: create_from, data, delta, hunks, line_stats, text The DiffDelta type ==================== .. autoclass:: pygit2.DiffDelta :members: The DiffFile type ==================== .. autoclass:: pygit2.DiffFile :members: The DiffHunk type ==================== .. autoclass:: pygit2.DiffHunk :members: The DiffStats type ==================== .. autoclass:: pygit2.DiffStats :members: The DiffLine type ==================== .. autoclass:: pygit2.DiffLine :members: libgit2-pygit2-a011e26/docs/features.rst000066400000000000000000000005061473744024100201120ustar00rootroot00000000000000********************************************************************** Feature detection ********************************************************************** .. py:data:: pygit2.features This variable contains a combination of `enums.Feature` flags, indicating which features a particular build of libgit2 supports. libgit2-pygit2-a011e26/docs/filters.rst000066400000000000000000000037771473744024100177610ustar00rootroot00000000000000********************************************************************** Filters ********************************************************************** pygit2 supports defining and registering libgit2 blob filters implemented in Python. The Filter type =============== .. autoclass:: pygit2.Filter :members: .. autoclass:: pygit2.FilterSource Registering filters =================== .. autofunction:: pygit2.filter_register .. autofunction:: pygit2.filter_unregister Example ======= The following example is a simple Python implementation of a filter which enforces that blobs are stored with unix LF line-endings in the ODB, and checked out with line-endings in accordance with the .gitattributes ``eol`` setting. .. code-block:: python class CRLFFilter(pygit2.Filter): attributes = "text eol=*" def __init__(self): super().__init__() self.linesep = b'\r\n' if os.name == 'nt' else b'\n' self.buffer = io.BytesIO() def check(self, src, attr_values): if src.mode == pygit2.enums.FilterMode.SMUDGE: # attr_values contains the values of the 'text' and 'eol' # attributes in that order (as they are defined in # CRLFFilter.attributes eol = attr_values[1] if eol == 'crlf': self.linesep = b'\r\n' elif eol == 'lf': self.linesep = b'\n' else: # src.mode == pygit2.enums.FilterMode.CLEAN # always use LF line-endings when writing to the ODB self.linesep = b'\n' def write(data, src, write_next): # buffer input data in case line-ending sequences span chunk boundaries self.buffer.write(data) def close(self, write_next): # apply line-ending conversion to our buffered input and write all # of our output data self.buffer.seek(0) write_next(self.linesep.join(self.buffer.read().splitlines())) libgit2-pygit2-a011e26/docs/general.rst000066400000000000000000000034001473744024100177050ustar00rootroot00000000000000********************************************************************** General ********************************************************************** .. contents:: Contents :local: Top level constants and exceptions from the library. Version ========= The following constants provide information about the version of the libgit2 library that has been built against. The version number has a ``MAJOR.MINOR.REVISION`` format. .. py:data:: LIBGIT2_VER_MAJOR Integer value of the major version number. For example, for the version ``0.26.0``:: >>> print(pygit2.LIBGIT2_VER_MAJOR) 0 .. py:data:: LIBGIT2_VER_MINOR Integer value of the minor version number. For example, for the version ``0.26.0``:: >>> print(pygit2.LIBGIT2_VER_MINOR) 26 .. py:data:: LIBGIT2_VER_REVISION Integer value of the revision version number. For example, for the version ``0.26.0``:: >>> print(pygit2.LIBGIT2_VER_REVISION) 0 .. py:data:: LIBGIT2_VER Tuple value of the revision version numbers. For example, for the version ``0.26.0``:: >>> print(pygit2.LIBGIT2_VER) (0, 26, 0) .. py:data:: LIBGIT2_VERSION The libgit2 version number as a string:: >>> print(pygit2.LIBGIT2_VERSION) '0.26.0' Options ========= .. autofunction:: pygit2.option Exceptions ========== .. autoexception:: pygit2.GitError :members: :show-inheritance: :undoc-members: .. autoexception:: pygit2.AlreadyExistsError :members: :show-inheritance: :undoc-members: Exception when trying to create an object (reference, etc) that already exists. .. autoexception:: pygit2.InvalidSpecError :members: :show-inheritance: :undoc-members: Exception when an input specification such as a reference name is invalid. libgit2-pygit2-a011e26/docs/index.rst000066400000000000000000000046301473744024100174050ustar00rootroot00000000000000###################################################################### pygit2 - libgit2 bindings in Python ###################################################################### Bindings to the libgit2 shared library, implements Git plumbing. Supports Python 3.10 to 3.13 and PyPy3 7.3+ Links ===================================== - Documentation - https://www.pygit2.org/ - Install - https://www.pygit2.org/install.html - Download - https://pypi.org/project/pygit2/ - Source code and issue tracker - https://github.com/libgit2/pygit2 - Changelog - https://github.com/libgit2/pygit2/blob/master/CHANGELOG.md - Authors - https://github.com/libgit2/pygit2/blob/master/AUTHORS.md Sponsors ===================================== Add your name and link here, `become a sponsor `_. License: GPLv2 with linking exception ===================================== This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. In addition to the permissions in the GNU General Public License, the authors give you unlimited permission to link the compiled version of this file into combinations with other programs, and to distribute those combinations without any restriction coming from the use of this file. (The General Public License restrictions do apply in other respects; for example, they cover modification of the file, and distribution when not linked into a combined executable.) This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Table of Contents ===================================== .. toctree:: :maxdepth: 1 install recipes general backends blame branches commit_log config diff features filters index_file mailmap merge objects oid packing references remotes repository revparse settings submodule worktree development Indices and tables ===================================== * :ref:`genindex` libgit2-pygit2-a011e26/docs/index_file.rst000066400000000000000000000064051473744024100204060ustar00rootroot00000000000000********************************************************************** Index file & Working copy ********************************************************************** .. autoattribute:: pygit2.Repository.index Index read:: >>> index = repo.index >>> index.read() >>> id = index['path/to/file'].id # from path to object id >>> blob = repo[id] # from object id to object Iterate over all entries of the index:: >>> for entry in index: ... print(entry.path, entry.id) Index write:: >>> index.add('path/to/file') # git add >>> index.remove('path/to/file') # git rm >>> index.write() # don't forget to save the changes Custom entries:: >>> entry = pygit2.IndexEntry('README.md', blob_id, blob_filemode) >>> repo.index.add(entry) The index fulfills a dual role as the in-memory representation of the index file and data structure which represents a flat list of a tree. You can use it independently of the index file, e.g. >>> index = pygit2.Index() >>> entry = pygit2.IndexEntry('README.md', blob_id, blob_filemode) >>> index.add(entry) The Index type ==================== .. autoclass:: pygit2.Index :members: The IndexEntry type ==================== .. autoclass:: pygit2.IndexEntry :members: .. automethod:: __eq__ .. automethod:: __ne__ .. automethod:: __repr__ .. automethod:: __str__ The Stash type ==================== .. autoclass:: pygit2.Stash :members: commit_id, message Status ==================== .. autoclass:: pygit2.Repository :members: status_file :noindex: .. automethod:: Repository.status Example, inspect the status of the repository:: from pygit2.enums import FileStatus status = repo.status() for filepath, flags in status.items(): if flags != FileStatus.CURRENT: print(f"Filepath {filepath} isn't clean") This is the list of status flags for a single file:: enums.FileStatus.CURRENT enums.FileStatus.INDEX_NEW enums.FileStatus.INDEX_MODIFIED enums.FileStatus.INDEX_DELETED enums.FileStatus.INDEX_RENAMED enums.FileStatus.INDEX_TYPECHANGE enums.FileStatus.WT_NEW enums.FileStatus.WT_MODIFIED enums.FileStatus.WT_DELETED enums.FileStatus.WT_TYPECHANGE enums.FileStatus.WT_RENAMED enums.FileStatus.WT_UNREADABLE enums.FileStatus.IGNORED enums.FileStatus.CONFLICTED A combination of these values will be returned to indicate the status of a file. Status compares the working directory, the index, and the current HEAD of the repository. The `INDEX_...` set of flags represents the status of file in the index relative to the HEAD, and the `WT_...` set of flags represents the status of the file in the working directory relative to the index. Checkout ==================== .. automethod:: pygit2.Repository.checkout Lower level API: .. automethod:: pygit2.Repository.checkout_head .. automethod:: pygit2.Repository.checkout_tree .. automethod:: pygit2.Repository.checkout_index Stash ==================== .. automethod:: pygit2.Repository.stash .. automethod:: pygit2.Repository.stash_apply .. automethod:: pygit2.Repository.stash_drop .. automethod:: pygit2.Repository.stash_pop .. automethod:: pygit2.Repository.listall_stashes libgit2-pygit2-a011e26/docs/install.rst000066400000000000000000000232531473744024100177460ustar00rootroot00000000000000********************************************************************** Installation ********************************************************************** .. |lq| unicode:: U+00AB .. |rq| unicode:: U+00BB .. contents:: Contents :local: Quick install ============= Install pygit2: .. code-block:: sh $ pip install -U pip $ pip install pygit2 The line above will install binary wheels if available in your platform. .. note:: It is recommended to first update the version of pip, as it will increase the chances for it to install a binary wheel instead of the source distribution. At least version 19.3 of pip is required. If you get the error:: fatal error: git2.h: No such file or directory It means that pip did not find a binary wheel for your platform, so it tried to build from source, but it failed because it could not find the libgit2 headers. Then: - Verify pip is updated - Verify there is a binary wheel of pygit2 for your platform - Otherwise install from the source distribution Caveats: - Binary wheels for Windows are available, but they don't have support for ssh. Requirements ============ Supported versions of Python: - Python 3.10 to 3.13 - PyPy3 7.3+ Python requirements (these are specified in ``setup.py``): - cffi 1.17.0 or later Libgit2 **v1.9.x**; binary wheels already include libgit2, so you only need to worry about this if you install the source package. Optional libgit2 dependecies to support ssh and https: - https: WinHTTP (Windows), SecureTransport (OS X) or OpenSSL. - ssh: libssh2 1.9.0 or later, pkg-config To run the tests: - pytest Version numbers =============== The version number of pygit2 is composed of three numbers separated by dots |lq| *major.medium.minor* |rq|: - *major* will always be 1 (until we release 2.0 in a far undefined future) - *medium* will increase whenever we make breaking changes, or upgrade to new versions of libgit2. - *minor* will increase for bug fixes. The table below summarizes the latest pygit2 versions with the supported versions of Python and the required libgit2 version. +-----------+----------------+------------+ | pygit2 | Python | libgit2 | +-----------+----------------+------------+ | 1.17 | 3.10 - 3.13 | 1.9 | +-----------+----------------+------------+ | 1.16 | 3.10 - 3.13 | 1.8 | +-----------+----------------+------------+ | 1.15 | 3.9 - 3.12 | 1.8 | +-----------+----------------+------------+ | 1.14 | 3.9 - 3.12 | 1.7 | +-----------+----------------+------------+ | 1.13 | 3.8 - 3.12 | 1.7 | +-----------+----------------+------------+ | 1.12 | 3.8 - 3.11 | 1.6 | +-----------+----------------+------------+ | 1.11 | 3.8 - 3.11 | 1.5 | +-----------+----------------+------------+ | 1.10 | 3.7 - 3.10 | 1.5 | +-----------+----------------+------------+ | 1.9 | 3.7 - 3.10 | 1.4 | +-----------+----------------+------------+ | 1.7 - 1.8 | 3.7 - 3.10 | 1.3 | +-----------+----------------+------------+ | 1.4 - 1.6 | 3.6 - 3.9 | 1.1 | +-----------+----------------+------------+ | 1.2 - 1.3 | 3.6 - 3.8 | 1.0 | +-----------+----------------+------------+ | 1.1 | 3.5 - 3.8 | 0.99 - 1.0 | +-----------+----------------+------------+ | 1.0 | 3.5 - 3.8 | 0.28 | +-----------+----------------+------------+ | 0.28.2 | 2.7, 3.4 - 3.7 | 0.28 | +-----------+----------------+------------+ .. warning:: It is recommended to use the latest 1.x.y release. Because only the latest is supported. .. warning:: Backwards compatibility is not guaranteed in minor releases. Please check the release notes for incompatible changes before upgrading to a new release. History: the 0.x series ----------------------- The development of pygit2 started in October 2010, the release of 1.0.0 happened in December 2019. In the 0.x series the version numbering was lockstep with libgit2, e.g. pygit2 0.28.x worked with libgit2 0.28.x Advanced =========================== Install libgit2 from source --------------------------- To install the latest version of libgit2 system wide, in the ``/usr/local`` directory, do: .. code-block:: sh $ wget https://github.com/libgit2/libgit2/archive/refs/tags/v1.9.0.tar.gz -O libgit2-1.9.0.tar.gz $ tar xzf libgit2-1.9.0.tar.gz $ cd libgit2-1.9.0/ $ cmake . $ make $ sudo make install .. seealso:: For detailed instructions on building libgit2 check https://libgit2.github.com/docs/guides/build-and-link/ Now install pygit2, and then verify it is correctly installed: .. code-block:: sh $ pip install pygit2 ... $ python -c 'import pygit2' Troubleshooting --------------------------- The verification step may fail if the dynamic linker does not find the libgit2 library: .. code-block:: sh $ python -c 'import pygit2' Traceback (most recent call last): File "", line 1, in File "pygit2/__init__.py", line 29, in from ._pygit2 import * ImportError: libgit2.so.0: cannot open shared object file: No such file or directory This happens for instance in Ubuntu, the libgit2 library is installed within the ``/usr/local/lib`` directory, but the linker does not look for it there. To fix this call ``ldconfig``: .. code-block:: sh $ sudo ldconfig $ python -c 'import pygit2' If it still does not work, please open an issue at https://github.com/libgit2/pygit2/issues Build options --------------------------- ``LIBGIT2`` -- If you install libgit2 in an unusual place, you will need to set the ``LIBGIT2`` environment variable before installing pygit2. This variable tells pygit2 where libgit2 is installed. We will see a concrete example later, when explaining how to install libgit2 within a virtual environment. ``LIBGIT2_LIB`` -- This is a more rarely used build option, it allows to override the library directory where libgit2 is installed, useful if different from ``$LIBGIT2/lib`` and ``$LIBGIT2/lib64``. libgit2 within a virtual environment ------------------------------------ This is how to install both libgit2 and pygit2 within a virtual environment. This is useful if you don't have root acces to install libgit2 system wide. Or if you wish to have different versions of libgit2/pygit2 installed in different virtual environments, isolated from each other. Create the virtualenv, activate it, and set the ``LIBGIT2`` environment variable: .. code-block:: sh $ virtualenv venv $ source venv/bin/activate $ export LIBGIT2=$VIRTUAL_ENV Install libgit2 (see we define the installation prefix): .. code-block:: sh $ wget https://github.com/libgit2/libgit2/archive/refs/tags/v1.9.0.tar.gz -O libgit2-1.9.0.tar.gz $ tar xzf libgit2-1.9.0.tar.gz $ cd libgit2-1.9.0/ $ cmake . -DCMAKE_INSTALL_PREFIX=$LIBGIT2 $ cmake --build . --target install Install pygit2: .. code-block:: sh $ export LDFLAGS="-Wl,-rpath,'$LIBGIT2/lib',--enable-new-dtags $LDFLAGS" # on OSX: export LDFLAGS="-Wl,-rpath,'$LIBGIT2/lib' $LDFLAGS" $ pip install pygit2 $ python -c 'import pygit2' The run-path ------------------------------------------ Did you notice we set the `rpath `_ before installing pygit2? Since libgit2 is installed in a non standard location, the dynamic linker will not find it at run-time, and ``lddconfig`` will not help this time. So you need to either set ``LD_LIBRARY_PATH`` before using pygit2, like: .. code-block:: sh $ export LD_LIBRARY_PATH=$LIBGIT2/lib $ python -c 'import pygit2' Or, like we have done in the instructions above, use the `rpath `_, it hard-codes extra search paths within the pygit2 extension modules, so you don't need to set ``LD_LIBRARY_PATH`` everytime. Verify yourself if curious: .. code-block:: sh $ readelf --dynamic lib/python2.7/site-packages/pygit2-0.27.0-py2.7-linux-x86_64.egg/pygit2/_pygit2.so | grep PATH 0x000000000000001d (RUNPATH) Library runpath: [/tmp/venv/lib] Installing on Windows =================================== `pygit2` for Windows is packaged into wheels and can be easily installed with `pip`: .. code-block:: console pip install pygit2 For development it is also possible to build `pygit2` with `libgit2` from sources. `libgit2` location is specified by the ``LIBGIT2`` environment variable. The following recipe shows you how to do it from a bash shell: .. code-block:: sh $ export LIBGIT2=C:/Dev/libgit2 $ git clone --depth=1 -b v1.9.0 https://github.com/libgit2/libgit2.git $ cd libgit2 $ cmake . -DCMAKE_INSTALL_PREFIX=$LIBGIT2 -G "Visual Studio 14 Win64" $ cmake --build . --config release --target install $ ctest -v At this point, you're ready to execute the generic `pygit2` installation steps described at the start of this page. Installing on OS X =================================== There are not binary wheels available for OS X, so you will need to install the source package. .. note:: You will need the `XCode `_ Developer Tools from Apple. This free download from the Mac App Store will provide the clang compiler needed for the installation of pygit2. This section was tested on OS X 10.9 Mavericks and OS X 10.10 Yosemite with Python 3.3 in a virtual environment. The easiest way is to first install libgit2 with the `Homebrew `_ package manager and then use pip3 for pygit2. The following example assumes that XCode and Hombrew are already installed. .. code-block:: sh $ brew update $ brew install libgit2 $ pip3 install pygit2 To build from a non-Homebrew libgit2 follow the guide in `libgit2 within a virtual environment`_. libgit2-pygit2-a011e26/docs/mailmap.rst000066400000000000000000000003021473744024100177060ustar00rootroot00000000000000********************************************************************** Mailmap ********************************************************************** .. autoclass:: pygit2.Mailmap :members: libgit2-pygit2-a011e26/docs/make.bat000066400000000000000000000014231473744024100171460ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd libgit2-pygit2-a011e26/docs/merge.rst000066400000000000000000000045561473744024100174040ustar00rootroot00000000000000********************************************************************** Merge & Cherrypick ********************************************************************** .. contents:: .. automethod:: pygit2.Repository.merge_base .. automethod:: pygit2.Repository.merge .. automethod:: pygit2.Repository.merge_analysis The merge method ================= The method does a merge over the current working copy. It gets an Oid object as a parameter. As its name says, it only does the merge, does not commit nor update the branch reference in the case of a fastforward. Example:: >>> from pygit2.enums import MergeFavor, MergeFlag >>> other_branch_tip = '5ebeeebb320790caf276b9fc8b24546d63316533' >>> repo.merge(other_branch_tip) >>> repo.merge(other_branch_tip, favor=MergeFavor.OURS) >>> repo.merge(other_branch_tip, flags=MergeFlag.FIND_RENAMES | MergeFlag.NO_RECURSIVE) >>> repo.merge(other_branch_tip, flags=0) # turn off FIND_RENAMES (on by default if flags omitted) You can now inspect the index file for conflicts and get back to the user to resolve if there are. Once there are no conflicts left, you can create a commit with these two parents. >>> user = repo.default_signature >>> tree = repo.index.write_tree() >>> message = "Merging branches" >>> new_commit = repo.create_commit('HEAD', user, user, message, tree, [repo.head.target, other_branch_tip]) Cherrypick =================== .. automethod:: pygit2.Repository.cherrypick Note that after a successful cherrypick you have to run :py:meth:`.Repository.state_cleanup` in order to get the repository out of cherrypicking mode. Lower-level methods =================== These methods allow more direct control over how to perform the merging. They do not modify the working directory and return an in-memory Index representing the result of the merge. .. automethod:: pygit2.Repository.merge_commits .. automethod:: pygit2.Repository.merge_trees N-way merges ============ The following methods perform the calculation for a base to an n-way merge. .. automethod:: pygit2.Repository.merge_base_many .. automethod:: pygit2.Repository.merge_base_octopus With this base at hand one can do repeated invokations of :py:meth:`.Repository.merge_commits` and :py:meth:`.Repository.merge_trees` to perform the actual merge into one tree (and deal with conflicts along the way).libgit2-pygit2-a011e26/docs/objects.rst000066400000000000000000000170271473744024100177330ustar00rootroot00000000000000********************************************************************** Objects ********************************************************************** There are four types of Git objects: blobs, trees, commits and tags. For each one pygit2 has a type, and all four types inherit from the base ``Object`` type. .. contents:: Contents :local: Object lookup ================= In the previous chapter we learnt about Object IDs. With an Oid we can ask the repository to get the associated object. To do that the ``Repository`` class implementes a subset of the mapping interface. .. autoclass:: pygit2.Repository :noindex: .. automethod:: Repository.get Return the Git object for the given *id*, returns the *default* value if there's no object in the repository with that id. The id can be an Oid object, or an hexadecimal string. Example:: >>> from pygit2 import Repository >>> repo = Repository('path/to/pygit2') >>> obj = repo.get("101715bf37440d32291bde4f58c3142bcf7d8adb") >>> obj <_pygit2.Commit object at 0x7ff27a6b60f0> .. method:: Repository.__getitem__(id) Return the Git object for the given id, raise ``KeyError`` if there's no object in the repository with that id. The id can be an Oid object, or an hexadecimal string. .. method:: Repository.__contains__(id) Returns True if there is an object in the Repository with that id, False if there is not. The id can be an Oid object, or an hexadecimal string. The Object base type ==================== The Object type is a base type, it is not possible to make instances of it, in any way. It is the base type of the ``Blob``, ``Tree``, ``Commit`` and ``Tag`` types, so it is possible to check whether a Python value is an Object or not:: >>> from pygit2 import Object >>> commit = repository.revparse_single('HEAD') >>> print(isinstance(commit, Object)) True All Objects are immutable, they cannot be modified once they are created:: >>> commit.message = u"foobar" Traceback (most recent call last): File "", line 1, in AttributeError: attribute 'message' of '_pygit2.Commit' objects is not writable Derived types (blobs, trees, etc.) don't have a constructor, this means they cannot be created with the common idiom:: >>> from pygit2 import Blob >>> blob = Blob("data") Traceback (most recent call last): File "", line 1, in TypeError: cannot create '_pygit2.Blob' instances New objects are created using an specific API we will see later. This is the common interface for all Git objects: .. autoclass:: pygit2.Object :members: id, type, type_str, short_id, read_raw, peel, name, filemode :special-members: __eq__, __ne__, __hash__, __repr__ Blobs ================= A blob is just a raw byte string. They are the Git equivalent to files in a filesytem. This is their API: .. autoclass:: pygit2.Blob :members: Creating blobs -------------- There are a number of methods in the repository to create new blobs, and add them to the Git object database: .. autoclass:: pygit2.Repository :members: create_blob_fromworkdir, create_blob_fromdisk, create_blob_fromiobase :noindex: .. automethod:: Repository.create_blob Example: >>> id = repo.create_blob('foo bar') # Creates blob from a byte string >>> blob = repo[id] >>> blob.data 'foo bar' There are also some functions to calculate the id for a byte string without creating the blob object: .. autofunction:: pygit2.hash .. autofunction:: pygit2.hashfile Streaming blob content ---------------------- `pygit2.Blob.data` and `pygit2.Blob.read_raw()` read the full contents of the blob into memory and return Python ``bytes``. They also return the raw contents of the blob, and do not apply any filters which would be applied upon checkout to the working directory. Raw and filtered blob data can be accessed as a Python Binary I/O stream (i.e. a file-like object): .. autoclass:: pygit2.BlobIO :members: Trees ================= At the low level (libgit2) a tree is a sorted collection of tree entries. In pygit2 accessing an entry directly returns the object. A tree can be iterated, and partially implements the sequence and mapping interfaces. .. autoclass:: pygit2.Tree :members: diff_to_tree, diff_to_workdir, diff_to_index .. method:: Tree.__getitem__(name) ``Tree[name]`` Return the Object subclass instance for the given *name*. Raise ``KeyError`` if there is not a tree entry with that name. .. method:: Tree.__truediv__(name) ``Tree / name`` Return the Object subclass instance for the given *name*. Raise ``KeyError`` if there is not a tree entry with that name. This allows navigating the tree similarly to Pathlib using the slash operator via. Example:: >>> entry = tree / 'path' / 'deeper' / 'some.file' .. method:: Tree.__contains__(name) ``name in Tree`` Return True if there is a tree entry with the given name, False otherwise. .. method:: Tree.__len__() ``len(Tree)`` Return the number of objects in the tree. .. method:: Tree.__iter__() ``for object in Tree`` Return an iterator over the objects in the tree. Example:: >>> tree = commit.tree >>> len(tree) # Number of entries 6 >>> for obj in tree: # Iteration ... print(obj.id, obj.type_str, obj.name) ... 7151ca7cd3e59f3eab19c485cfbf3cb30928d7fa blob .gitignore c36f4cf1e38ec1bb9d9ad146ed572b89ecfc9f18 blob COPYING 32b30b90b062f66957d6790c3c155c289c34424e blob README.md c87dae4094b3a6d10e08bc6c5ef1f55a7e448659 blob pygit2.c 85a67270a49ef16cdd3d328f06a3e4b459f09b27 blob setup.py 3d8985bbec338eb4d47c5b01b863ee89d044bd53 tree test >>> obj = tree / 'pygit2.c' # Get an object by name >>> obj <_pygit2.Blob at 0x7f08a70acc10> Creating trees -------------------- .. autoclass:: pygit2.Repository :members: TreeBuilder :noindex: .. autoclass:: pygit2.TreeBuilder :members: Commits ================= A commit is a snapshot of the working dir with meta informations like author, committer and others. .. autoclass:: pygit2.Commit :members: Signatures ------------- The author and committer attributes of commit objects are ``Signature`` objects:: >>> commit.author pygit2.Signature('Foo Ibáñez', 'foo@example.com', 1322174594, 60, 'utf-8') Signatures can be compared for (in)equality. .. autoclass:: pygit2.Signature :members: Creating commits ---------------- .. autoclass:: pygit2.Repository :members: create_commit :noindex: Commits can be created by calling the ``create_commit`` method of the repository with the following parameters:: >>> author = Signature('Alice Author', 'alice@authors.tld') >>> committer = Signature('Cecil Committer', 'cecil@committers.tld') >>> tree = repo.TreeBuilder().write() >>> repo.create_commit( ... 'refs/heads/master', # the name of the reference to update ... author, committer, 'one line commit message\n\ndetailed commit message', ... tree, # binary string representing the tree object ID ... [] # list of binary strings representing parents of the new commit ... ) '#\xe4`_ hash of an object. It is 20 bytes long. These are the three forms of an oid in pygit2: Raw oid A raw oid is represented as a Python byte string of 20 bytes length. This form can only be used to create an Oid object. Hex oid A hex oid is represented as a Python string of 40 hexadecimal chars. This form can be used to create Oid objects, just like raw oids. Also, the pygit2 API directly accepts hex oids everywhere. Oid object An ``Oid`` object can be built from the raw or hexadecimal representations (see below). The pygit2 API always returns, and accepts, ``Oid`` objects. This is the preferred way to represent an Oid, with the hexadecimal form being used for interaction with the user. The Oid type ============ .. c:type:: pygit2.Oid(raw=None, hex=None) The constructor expects either a raw or a hex oid, but not both. An Oid object is created from the hexadecimal form this way:: >>> from pygit2 import Oid >>> hex = "cff3ceaefc955f0dbe1957017db181bc49913781" >>> oid1 = Oid(hex=hex) An Oid object is created from the raw form this way:: >>> from binascii import unhexlify >>> from pygit2 import Oid >>> raw = unhexlify("cff3ceaefc955f0dbe1957017db181bc49913781") >>> oid2 = Oid(raw=raw) And the other way around, from an Oid object we can get the hexadecimal and raw forms. You can use the built-in `str()` (or `unicode()` in python 2) to get the hexadecimal representation of the Oid. .. method:: Oid.__str__() .. autoattribute:: pygit2.Oid.raw The Oid type supports: - rich comparisons, not just for equality, also: lesser-than, lesser-or-equal, etc. - hashing, so Oid objects can be used as keys in a dictionary. Constants ========= .. py:data:: GIT_OID_RAWSZ .. py:data:: GIT_OID_HEXSZ .. py:data:: GIT_OID_HEX_ZERO .. py:data:: GIT_OID_MINPREFIXLEN libgit2-pygit2-a011e26/docs/packing.rst000066400000000000000000000005321473744024100177070ustar00rootroot00000000000000********************************************************************** Packing ********************************************************************** .. autoclass:: pygit2.Repository :members: pack :noindex: The PackBuilder ================ .. autoclass:: pygit2.PackBuilder :members: :undoc-members: :special-members: __len__ libgit2-pygit2-a011e26/docs/recipes.rst000066400000000000000000000030301473744024100177210ustar00rootroot00000000000000********************************************************************** Recipes ********************************************************************** A list of some standard git commands and their pygit2 equivalents. This document is a work in progress, and is organized according to the `git man page`_. ---------------------------------------------------------------------- High Level Commands ---------------------------------------------------------------------- ====================================================================== Main porcelain commands ====================================================================== .. toctree:: :maxdepth: 1 git-cherry-pick (Apply the changes introduced by some existing commits.) git-init (Create an empty git repository or reinitialize an existing one.) git-log (Show commit logs.) git-show (Show various types of objects.) git-tag (Create, list, delete or verify a tag object signed with GPG.) git clone (Clone with progress monitor) git clone --mirror (Clone with a mirroring configuration) git clone username@hostname (Clone over ssh) git-add / git-reset HEAD (Add file contents to the index / Unstage) git commit (Make an initial commit, and a subsequent commit) .. _git man page: https://www.kernel.org/pub/software/scm/git/docs/git.html libgit2-pygit2-a011e26/docs/recipes/000077500000000000000000000000001473744024100171735ustar00rootroot00000000000000libgit2-pygit2-a011e26/docs/recipes/git-add-reset.rst000066400000000000000000000035371473744024100223660ustar00rootroot00000000000000********************************************************************** git-add / git-reset ********************************************************************** ---------------------------------------------------------------------- Add file contents to the index / Stage ---------------------------------------------------------------------- We can add a new (untracked) file or a modified file to the index. .. code-block:: bash $ git add foo.txt .. code-block:: python >>> index = repo.index >>> index.add(path) >>> index.write() ---------------------------------------------------------------------- Restore the entry in the index / Unstage ---------------------------------------------------------------------- .. code-block:: bash $ git reset HEAD src/tree.c .. code-block:: python >>> index = repo.index # Remove path from the index >>> path = 'src/tree.c' >>> index.remove(path) # Restore object from db >>> obj = repo.revparse_single('HEAD').tree[path] # Get object from db >>> index.add(pygit2.IndexEntry(path, obj.id, obj.filemode)) # Add to index # Write index >>> index.write() ---------------------------------------------------------------------- Query the index state / Is file staged ? ---------------------------------------------------------------------- .. code-block:: bash $ git status foo.txt .. code-block:: python # Return True is the file is modified in the working tree >>> repo.status_file(path) & pygit2.enums.FileStatus.WT_MODIFIED ---------------------------------------------------------------------- References ---------------------------------------------------------------------- - git-add_. .. _git-add: https://www.kernel.org/pub/software/scm/git/docs/git-add.html - git-reset_. .. _git-reset: https://www.kernel.org/pub/software/scm/git/docs/git-reset.html libgit2-pygit2-a011e26/docs/recipes/git-cherry-pick.rst000066400000000000000000000045551473744024100227370ustar00rootroot00000000000000********************************************************************** git-cherry-pick ********************************************************************** The convenient way to cherry-pick a commit is to use :py:meth:`.Repository.cherrypick()`. It is limited to cherry-picking with a working copy and on-disk index. .. code-block:: bash $ cd /path/to/repo $ git checkout basket $ git cherry-pick 9e044d03c .. code-block:: python repo = pygit2.Repository('/path/to/repo') repo.checkout('basket') cherry_id = pygit2.Oid('9e044d03c') repo.cherrypick(cherry_id) if repo.index.conflicts is None: tree_id = repo.index.write_tree() cherry = repo.get(cherry_id) committer = pygit2.Signature('Archimedes', 'archy@jpl-classics.org') repo.create_commit(basket.name, cherry.author, committer, cherry.message, tree_id, [basket.target]) del basket # outdated, prevent from accidentally using it repo.state_cleanup() ---------------------------------------------------------------------- Cherry-picking a commit without a working copy ---------------------------------------------------------------------- This way of cherry-picking gives you more control over the process and works on bare repositories as well as repositories with a working copy. :py:meth:`~.Repository.merge_trees()` can also be used for other tasks, for example `three-argument rebases`_. .. _`three-argument rebases`: https://www.kernel.org/pub/software/scm/git/docs/git-rebase.html .. code-block:: python repo = pygit2.Repository('/path/to/repo') cherry = repo.revparse_single('9e044d03c') basket = repo.branches.get('basket') base_tree = cherry.parents[0].tree index = repo.merge_trees(base_tree, basket, cherry) tree_id = index.write_tree(repo) author = cherry.author committer = pygit2.Signature('Archimedes', 'archy@jpl-classics.org') repo.create_commit(basket.name, author, committer, cherry.message, tree_id, [basket.target]) del None # outdated, prevent from accidentally using it ---------------------------------------------------------------------- References ---------------------------------------------------------------------- - git-cherry-pick_. .. _git-cherry-pick: https://www.kernel.org/pub/software/scm/git/docs/git-cherry-pick.html libgit2-pygit2-a011e26/docs/recipes/git-clone-mirror.rst000066400000000000000000000020041473744024100231120ustar00rootroot00000000000000********************************************************************** git-clone --mirror ********************************************************************** git provides an argument to set up the repository as a mirror, which involves setting the refspec to one which copies all refs and a mirror option for push in the remote. .. code-block:: bash $ git clone --mirror https://github.com/libgit2/pygit2 .. code-block:: python def init_remote(repo, name, url): # Create the remote with a mirroring url remote = repo.remotes.create(name, url, "+refs/*:refs/*") # And set the configuration option to true for the push command mirror_var = f"remote.{name.decode()}.mirror" repo.config[mirror_var] = True # Return the remote, which pygit2 will use to perform the clone return remote print("Cloning pygit2 as mirror") pygit2.clone_repository("https://github.com/libgit2/pygit2", "pygit2.git", bare=True, remote=init_remote) libgit2-pygit2-a011e26/docs/recipes/git-clone-progress.rst000066400000000000000000000012341473744024100234500ustar00rootroot00000000000000********************************************************************** git-clone with progress monitor ********************************************************************** Example for cloning a git repository with progress monitoring: .. code-block:: bash $ git clone https://github.com/libgit2/pygit2 .. code-block:: python class MyRemoteCallbacks(pygit2.RemoteCallbacks): def transfer_progress(self, stats): print(f'{stats.indexed_objects}/{stats.total_objects}') print("Cloning pygit2") pygit2.clone_repository("https://github.com/libgit2/pygit2", "pygit2.git", callbacks=MyRemoteCallbacks()) libgit2-pygit2-a011e26/docs/recipes/git-clone-ssh.rst000066400000000000000000000023101473744024100223750ustar00rootroot00000000000000********************************************************************** git-clone ssh://git@example.com ********************************************************************** Example for cloning a git repository over ssh. .. code-block:: bash $ git clone git@example.com .. code-block:: python class MyRemoteCallbacks(pygit2.RemoteCallbacks): def credentials(self, url, username_from_url, allowed_types): if allowed_types & pygit2.enums.CredentialType.USERNAME: return pygit2.Username("git") elif allowed_types & pygit2.enums.CredentialType.SSH_KEY: return pygit2.Keypair("git", "id_rsa.pub", "id_rsa", "") else: return None print("Cloning pygit2 over ssh") pygit2.clone_repository("ssh://github.com/libgit2/pygit2", "pygit2.git", callbacks=MyRemoteCallbacks()) print("Cloning pygit2 over ssh with the username in the URL") keypair = pygit2.Keypair("git", "id_rsa.pub", "id_rsa", "") callbacks = pygit2.RemoteCallbacks(credentials=keypair) pygit2.clone_repository("ssh://git@github.com/libgit2/pygit2", "pygit2.git", callbacks=callbacks) libgit2-pygit2-a011e26/docs/recipes/git-commit.rst000066400000000000000000000064171473744024100220060ustar00rootroot00000000000000********************************************************************** git-commit ********************************************************************** ---------------------------------------------------------------------- Initial commit ---------------------------------------------------------------------- Add everything, and make an initial commit: .. code-block:: bash $ git add . $ git commit -m "Initial commit" .. code-block:: python >>> index = repo.index >>> index.add_all() >>> index.write() >>> ref = "HEAD" >>> author = Signature('Alice Author', 'alice@authors.tld') >>> committer = Signature('Cecil Committer', 'cecil@committers.tld') >>> message = "Initial commit" >>> tree = index.write_tree() >>> parents = [] >>> repo.create_commit(ref, author, committer, message, tree, parents) ---------------------------------------------------------------------- Subsequent commit ---------------------------------------------------------------------- Once ``HEAD`` has a commit to point to, you can use ``repo.head.name`` as the reference to be updated by the commit, and you should name parents: .. code-block:: python >>> ref = repo.head.name >>> parents = [repo.head.target] The rest is the same: .. code-block:: python >>> index = repo.index >>> index.add_all() >>> index.write() >>> author = Signature('Alice Author', 'alice@authors.tld') >>> committer = Signature('Cecil Committer', 'cecil@committers.tld') >>> message = "Initial commit" >>> tree = index.write_tree() >>> repo.create_commit(ref, author, committer, message, tree, parents) ---------------------------------------------------------------------- Signing a commit ---------------------------------------------------------------------- Add everything, and commit with a GPG signature: .. code-block:: bash $ git add . $ git commit -S -m "Signed commit" .. code-block:: python >>> index = repo.index >>> index.add_all() >>> index.write() >>> author = Signature('Alice Author', 'alice@authors.tld') >>> committer = Signature('Cecil Committer', 'cecil@committers.tld') >>> message = "Signed commit" >>> tree = index.write_tree() >>> parents = [] >>> commit_string = repo.create_commit_string( >>> author, committer, message, tree, parents >>> ) The ``commit_string`` can then be signed by a third party library: .. code-block:: python >>> gpg = YourGPGToolHere() >>> signed_commit = gpg.sign( >>> commit_string, >>> passphrase='secret', >>> detach=True, >>> ) .. note:: The commit signature should resemble: .. code-block:: none >>> -----BEGIN PGP SIGNATURE----- >>> >>> < base64 encoded hash here > >>> -----END PGP SIGNATURE----- The signed commit can then be added to the branch: .. code-block:: python >>> commit = repo.create_commit_with_signature( >>> commit_string, signed_commit.data.decode('utf-8') >>> ) >>> repo.head.set_target(commit) ---------------------------------------------------------------------- References ---------------------------------------------------------------------- - git-commit_. .. _git-commit: https://www.kernel.org/pub/software/scm/git/docs/git-commit.html libgit2-pygit2-a011e26/docs/recipes/git-init.rst000066400000000000000000000023751473744024100214600ustar00rootroot00000000000000********************************************************************** git-init ********************************************************************** ---------------------------------------------------------------------- Creating a new repository ---------------------------------------------------------------------- ====================================================================== Create bare repository ====================================================================== .. code-block:: bash $ git init --bare path/to/git .. code-block:: python >>> pygit2.init_repository('path/to/git', True) ====================================================================== Create standard repository ====================================================================== .. code-block:: bash $ git init path/to/git .. code-block:: python >>> pygit2.init_repository('path/to/git', False) ---------------------------------------------------------------------- References ---------------------------------------------------------------------- - git-init_ .. _git-init: https://www.kernel.org/pub/software/scm/git/docs/git-init.html libgit2-pygit2-a011e26/docs/recipes/git-log.rst000066400000000000000000000035361473744024100212760ustar00rootroot00000000000000********************************************************************** git-log ********************************************************************** ---------------------------------------------------------------------- Showing HEAD commit logs ---------------------------------------------------------------------- ====================================================================== Show HEAD commit ====================================================================== .. code-block:: bash $ git log -1 .. code-block:: python >>> commit = repo[repo.head.target] >>> commit.message 'commit message' ====================================================================== Traverse commit history ====================================================================== .. code-block:: bash $ git log .. code-block:: python >>> last = repo[repo.head.target] >>> for commit in repo.walk(last.id, pygit2.enums.SortMode.TIME): >>> print(commit.message) # or some other operation ====================================================================== Show trailers from the last commit ====================================================================== .. code-block:: bash $ git log --format='%(trailers:key=Bug)' .. code-block:: python >>> last = repo[repo.head.target] >>> for commit in repo.walk(last.id, pygit2.enums.SortMode.TIME): >>> print(commit.message_trailers.get('Bug')) ---------------------------------------------------------------------- References ---------------------------------------------------------------------- - git-log_. - `libgit2 discussion about walker behavior `_. Note that the libgit2's walker functions differently than ``git-log`` in some ways. .. _git-log: https://www.kernel.org/pub/software/scm/git/docs/git-log.html libgit2-pygit2-a011e26/docs/recipes/git-show.rst000066400000000000000000000043661473744024100214770ustar00rootroot00000000000000********************************************************************** git-show ********************************************************************** ---------------------------------------------------------------------- Showing a commit ---------------------------------------------------------------------- .. code-block:: bash $ git show d370f56 .. code-block:: python >>> repo = pygit2.Repository('/path/to/repository') >>> commit = repo.revparse_single('d370f56') ====================================================================== Show log message ====================================================================== >>> message = commit.message ====================================================================== Show SHA hash ====================================================================== >>> hash = str(commit.id) ====================================================================== Show diff ====================================================================== >>> diff = repo.diff(commit.parents[0], commit) ====================================================================== Show all files in commit ====================================================================== >>> for e in commit.tree: >>> print(e.name) ====================================================================== Produce something like a ``git show`` message ====================================================================== Then you can make your message: >>> from datetime import datetime, timezone, timedelta >>> tzinfo = timezone( timedelta(minutes=commit.author.offset) ) >>> >>> dt = datetime.fromtimestamp(float(commit.author.time), tzinfo) >>> timestr = dt.strftime('%c %z') >>> msg = '\n'.join([f'commit {commit.tree_id}', ... f'Author: {commit.author.name} <{commit.author.email}>', ... f'Date: {timestr}', ... '', ... commit.message]) ---------------------------------------------------------------------- References ---------------------------------------------------------------------- - git-show_. .. _git-show: https://www.kernel.org/pub/software/scm/git/docs/git-show.html libgit2-pygit2-a011e26/docs/recipes/git-tag.rst000066400000000000000000000013271473744024100212640ustar00rootroot00000000000000********************************************************************** git-tag ********************************************************************** ---------------------------------------------------------------------- Showing all tags ---------------------------------------------------------------------- .. code-block:: bash $ git tag .. code-block:: python >>> import re >>> regex = re.compile('^refs/tags/') >>> [r for r in repo.references if regex.match(r)] ---------------------------------------------------------------------- References ---------------------------------------------------------------------- - git-tag_. .. _git-tag: https://www.kernel.org/pub/software/scm/git/docs/git-tag.html libgit2-pygit2-a011e26/docs/references.rst000066400000000000000000000047501473744024100204220ustar00rootroot00000000000000********************************************************************** References ********************************************************************** .. autoclass:: pygit2.Repository :members: lookup_reference, lookup_reference_dwim, raw_listall_references, resolve_refish :noindex: .. attribute:: references Returns an instance of the References class (see below). .. autoclass:: pygit2.repository.References :members: :undoc-members: :special-members: __getitem__, __iter__, __contains__ Example:: >>> all_refs = list(repo.references) >>> master_ref = repo.references["refs/heads/master"] >>> commit = master_ref.peel() # or repo[master_ref.target] # Create a reference >>> ref = repo.references.create('refs/tags/version1', LAST_COMMIT) # Delete a reference >>> repo.references.delete('refs/tags/version1') # Pack loose references >>> repo.references.compress() Functions =================================== .. autofunction:: pygit2.reference_is_valid_name Check if the passed string is a valid reference name. Example:: >>> from pygit2 import reference_is_valid_name >>> reference_is_valid_name("refs/heads/master") True >>> reference_is_valid_name("HEAD") True >>> reference_is_valid_name("refs/heads/..") False The Reference type ==================== .. autoclass:: pygit2.Reference :members: :special-members: __eq__, __ne__ :exclude-members: log .. automethod:: log The HEAD ==================== Example. These two lines are equivalent:: >>> head = repo.references['HEAD'].resolve() >>> head = repo.head .. autoattribute:: pygit2.Repository.head .. autoattribute:: pygit2.Repository.head_is_detached .. autoattribute:: pygit2.Repository.head_is_unborn The reference log ==================== Example:: >>> head = repo.references.get('refs/heads/master') # Returns None if not found >>> # Almost equivalent to >>> head = repo.references['refs/heads/master'] # Raises KeyError if not found >>> for entry in head.log(): ... print(entry.message) .. autoclass:: pygit2.RefLogEntry :members: Notes ==================== .. automethod:: pygit2.Repository.notes .. automethod:: pygit2.Repository.create_note .. automethod:: pygit2.Repository.lookup_note The Note type -------------------- .. autoattribute:: pygit2.Note.annotated_id .. autoattribute:: pygit2.Note.id .. autoattribute:: pygit2.Note.message .. automethod:: pygit2.Note.remove libgit2-pygit2-a011e26/docs/remotes.rst000066400000000000000000000032021473744024100177460ustar00rootroot00000000000000********************************************************************** Remotes ********************************************************************** .. py:attribute:: Repository.remotes The collection of configured remotes, an instance of :py:class:`pygit2.remotes.RemoteCollection` The remote collection ========================== .. autoclass:: pygit2.remotes.RemoteCollection :members: The Remote type ==================== .. autoclass:: pygit2.Remote :members: The RemoteCallbacks type ======================== .. autoclass:: pygit2.RemoteCallbacks :members: The TransferProgress type =========================== This class contains the data which is available to us during a fetch. .. autoclass:: pygit2.remotes.TransferProgress :members: The Refspec type =================== Refspecs objects are not constructed directly, but returned by :meth:`pygit2.Remote.get_refspec`. To create a new a refspec on a Remote, use :meth:`pygit2.Remote.add_fetch` or :meth:`pygit2.Remote.add_push`. .. autoclass:: pygit2.refspec.Refspec :members: Credentials ================ There are several types of credentials. All of them are callable objects, with the appropriate signature for the credentials callback. They will ignore all the arguments and return themselves. This is useful for scripts where the credentials are known ahead of time. More complete interfaces would want to look up in their keychain or ask the user for the data to use in the credentials. .. autoclass:: pygit2.Username .. autoclass:: pygit2.UserPass .. autoclass:: pygit2.Keypair .. autoclass:: pygit2.KeypairFromAgent .. autoclass:: pygit2.KeypairFromMemory libgit2-pygit2-a011e26/docs/repository.rst000066400000000000000000000061631473744024100205200ustar00rootroot00000000000000********************************************************************** Repository ********************************************************************** Everything starts either by creating a new repository, or by opening an existing one. .. contents:: Contents :local: Functions =================================== .. autofunction:: pygit2.init_repository Example:: >>> from pygit2 import init_repository >>> repo = init_repository('test') # Creates a non-bare repository >>> repo = init_repository('test', bare=True) # Creates a bare repository .. autofunction:: pygit2.clone_repository Example:: >>> from pygit2 import clone_repository >>> repo_url = 'git://github.com/libgit2/pygit2.git' >>> repo_path = '/path/to/create/repository' >>> repo = clone_repository(repo_url, repo_path) # Clones a non-bare repository >>> repo = clone_repository(repo_url, repo_path, bare=True) # Clones a bare repository .. autofunction:: pygit2.discover_repository Example:: >>> current_working_directory = os.getcwd() >>> repository_path = discover_repository(current_working_directory) >>> repo = Repository(repository_path) .. autofunction:: pygit2.tree_entry_cmp(object, other) The Repository class =================================== The API of the Repository class is quite large. Since this documentation is organized by features, the related bits are explained in the related chapters, for instance the :py:meth:`pygit2.Repository.checkout` method is explained in the Checkout section. Below there are some general attributes and methods: .. autoclass:: pygit2.Repository :members: ahead_behind, amend_commit, applies, apply, create_reference, default_signature, descendant_of, describe, free, get_attr, is_bare, is_empty, is_shallow, odb, path, path_is_ignored, reset, revert_commit, state_cleanup, workdir, write, write_archive, set_odb, set_refdb The Repository constructor will most commonly be called with one argument, the path of the repository to open. Alternatively, constructing a repository with no arguments will create a repository with no backends. You can use this path to create repositories with custom backends. Note that most operations on the repository are considered invalid and may lead to undefined behavior if attempted before providing an odb and refdb via :py:meth:`set_odb` and :py:meth:`set_refdb`. Parameters: path The path to open — if not provided, the repository will have no backend. flags Flags controlling how to open the repository can optionally be provided — any combination of: * enums.RepositoryOpenFlag.NO_SEARCH * enums.RepositoryOpenFlag.CROSS_FS * enums.RepositoryOpenFlag.BARE * enums.RepositoryOpenFlag.NO_DOTGIT * enums.RepositoryOpenFlag.FROM_ENV Example:: >>> from pygit2 import Repository >>> repo = Repository('pygit2/.git') The Odb class =================================== .. autoclass:: pygit2.Odb :members: The Refdb class =================================== .. autoclass:: pygit2.Refdb :members: libgit2-pygit2-a011e26/docs/revparse.rst000066400000000000000000000010721473744024100201220ustar00rootroot00000000000000********************************************************************** Revision parsing ********************************************************************** .. autoclass:: pygit2.Repository :members: revparse, revparse_ext, revparse_single :noindex: You can use any of the fancy `` forms supported by libgit2:: >>> commit = repo.revparse_single('HEAD^') .. autoclass:: pygit2.RevSpec :members: Constants: .. py:data:: pygit2.enums.RevSpecFlag.SINGLE .. py:data:: pygit2.enums.RevSpecFlag.RANGE .. py:data:: pygit2.enums.RevSpecFlag.MERGE_BASE libgit2-pygit2-a011e26/docs/settings.rst000066400000000000000000000003231473744024100201310ustar00rootroot00000000000000********************************************************************** Settings ********************************************************************** .. contents:: .. autoclass:: pygit2.Settings :members: libgit2-pygit2-a011e26/docs/submodule.rst000066400000000000000000000011761473744024100202770ustar00rootroot00000000000000********************************************************************** Submodules ********************************************************************** A submodule is a foreign repository that is embedded within a dedicated subdirectory of the repositories tree. .. autoclass:: pygit2.Repository :members: listall_submodules .. py:attribute:: Repository.submodules The collection of submodules, an instance of :py:class:`pygit2.submodules.SubmoduleCollection` .. autoclass:: pygit2.submodules.SubmoduleCollection :members: The Submodule type ==================== .. autoclass:: pygit2.Submodule :members: libgit2-pygit2-a011e26/docs/worktree.rst000066400000000000000000000005301473744024100201330ustar00rootroot00000000000000********************************************************************** Worktrees ********************************************************************** .. automethod:: pygit2.Repository.add_worktree .. automethod:: pygit2.Repository.list_worktrees .. automethod:: pygit2.Repository.lookup_worktree .. autoclass:: pygit2.Worktree :members: libgit2-pygit2-a011e26/misc/000077500000000000000000000000001473744024100155445ustar00rootroot00000000000000libgit2-pygit2-a011e26/misc/valgrind-python.supp000066400000000000000000000202511473744024100216020ustar00rootroot00000000000000# # This is a valgrind suppression file that should be used when using valgrind. # # Here's an example of running valgrind: # # cd python/dist/src # valgrind --tool=memcheck --suppressions=Misc/valgrind-python.supp \ # ./python -E ./Lib/test/regrtest.py -u gui,network # # You must edit Objects/obmalloc.c and uncomment Py_USING_MEMORY_DEBUGGER # to use the preferred suppressions with address_in_range. # # If you do not want to recompile Python, you can uncomment # suppressions for _PyObject_Free and _PyObject_Realloc. # # See Misc/README.valgrind for more information. # all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif { ADDRESS_IN_RANGE/Invalid read of size 4 Memcheck:Addr4 fun:address_in_range } { ADDRESS_IN_RANGE/Invalid read of size 4 Memcheck:Value4 fun:address_in_range } { ADDRESS_IN_RANGE/Invalid read of size 8 (x86_64 aka amd64) Memcheck:Value8 fun:address_in_range } { ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value Memcheck:Cond fun:address_in_range } # # Leaks (including possible leaks) # Hmmm, I wonder if this masks some real leaks. I think it does. # Will need to fix that. # { Suppress leaking the GIL. Happens once per process, see comment in ceval.c. Memcheck:Leak fun:malloc fun:PyThread_allocate_lock fun:PyEval_InitThreads } { Suppress leaking the GIL after a fork. Memcheck:Leak fun:malloc fun:PyThread_allocate_lock fun:PyEval_ReInitThreads } { Suppress leaking the autoTLSkey. This looks like it shouldn't leak though. Memcheck:Leak fun:malloc fun:PyThread_create_key fun:_PyGILState_Init fun:Py_InitializeEx fun:Py_Main } { Hmmm, is this a real leak or like the GIL? Memcheck:Leak fun:malloc fun:PyThread_ReInitTLS } { Handle PyMalloc confusing valgrind (possibly leaked) Memcheck:Leak fun:realloc fun:_PyObject_GC_Resize fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING } { Handle PyMalloc confusing valgrind (possibly leaked) Memcheck:Leak fun:malloc fun:_PyObject_GC_New fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING } { Handle PyMalloc confusing valgrind (possibly leaked) Memcheck:Leak fun:malloc fun:_PyObject_GC_NewVar fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING } # # Non-python specific leaks # { Handle pthread issue (possibly leaked) Memcheck:Leak fun:calloc fun:allocate_dtv fun:_dl_allocate_tls_storage fun:_dl_allocate_tls } { Handle pthread issue (possibly leaked) Memcheck:Leak fun:memalign fun:_dl_allocate_tls_storage fun:_dl_allocate_tls } ###{ ### ADDRESS_IN_RANGE/Invalid read of size 4 ### Memcheck:Addr4 ### fun:_PyObject_Free ###} ### ###{ ### ADDRESS_IN_RANGE/Invalid read of size 4 ### Memcheck:Value4 ### fun:_PyObject_Free ###} ### ###{ ### ADDRESS_IN_RANGE/Use of uninitialised value of size 8 ### Memcheck:Addr8 ### fun:_PyObject_Free ###} ### ###{ ### ADDRESS_IN_RANGE/Use of uninitialised value of size 8 ### Memcheck:Value8 ### fun:_PyObject_Free ###} ### ###{ ### ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value ### Memcheck:Cond ### fun:_PyObject_Free ###} ###{ ### ADDRESS_IN_RANGE/Invalid read of size 4 ### Memcheck:Addr4 ### fun:_PyObject_Realloc ###} ### ###{ ### ADDRESS_IN_RANGE/Invalid read of size 4 ### Memcheck:Value4 ### fun:_PyObject_Realloc ###} ### ###{ ### ADDRESS_IN_RANGE/Use of uninitialised value of size 8 ### Memcheck:Addr8 ### fun:_PyObject_Realloc ###} ### ###{ ### ADDRESS_IN_RANGE/Use of uninitialised value of size 8 ### Memcheck:Value8 ### fun:_PyObject_Realloc ###} ### ###{ ### ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value ### Memcheck:Cond ### fun:_PyObject_Realloc ###} ### ### All the suppressions below are for errors that occur within libraries ### that Python uses. The problems to not appear to be related to Python's ### use of the libraries. ### { Generic ubuntu ld problems Memcheck:Addr8 obj:/lib/ld-2.4.so obj:/lib/ld-2.4.so obj:/lib/ld-2.4.so obj:/lib/ld-2.4.so } { Generic gentoo ld problems Memcheck:Cond obj:/lib/ld-2.3.4.so obj:/lib/ld-2.3.4.so obj:/lib/ld-2.3.4.so obj:/lib/ld-2.3.4.so } { DBM problems, see test_dbm Memcheck:Param write(buf) fun:write obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 fun:dbm_close } { DBM problems, see test_dbm Memcheck:Value8 fun:memmove obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 fun:dbm_store fun:dbm_ass_sub } { DBM problems, see test_dbm Memcheck:Cond obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 fun:dbm_store fun:dbm_ass_sub } { DBM problems, see test_dbm Memcheck:Cond fun:memmove obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 obj:/usr/lib/libdb1.so.2 fun:dbm_store fun:dbm_ass_sub } { GDBM problems, see test_gdbm Memcheck:Param write(buf) fun:write fun:gdbm_open } { ZLIB problems, see test_gzip Memcheck:Cond obj:/lib/libz.so.1.2.3 obj:/lib/libz.so.1.2.3 fun:deflate } { Avoid problems w/readline doing a putenv and leaking on exit Memcheck:Leak fun:malloc fun:xmalloc fun:sh_set_lines_and_columns fun:_rl_get_screen_size fun:_rl_init_terminal_io obj:/lib/libreadline.so.4.3 fun:rl_initialize } ### ### These occur from somewhere within the SSL, when running ### test_socket_sll. They are too general to leave on by default. ### ###{ ### somewhere in SSL stuff ### Memcheck:Cond ### fun:memset ###} ###{ ### somewhere in SSL stuff ### Memcheck:Value4 ### fun:memset ###} ### ###{ ### somewhere in SSL stuff ### Memcheck:Cond ### fun:MD5_Update ###} ### ###{ ### somewhere in SSL stuff ### Memcheck:Value4 ### fun:MD5_Update ###} # Fedora's package "openssl-1.0.1-0.1.beta2.fc17.x86_64" on x86_64 # See http://bugs.python.org/issue14171 { openssl 1.0.1 prng 1 Memcheck:Cond fun:bcmp fun:fips_get_entropy fun:FIPS_drbg_instantiate fun:RAND_init_fips fun:OPENSSL_init_library fun:SSL_library_init fun:init_hashlib } { openssl 1.0.1 prng 2 Memcheck:Cond fun:fips_get_entropy fun:FIPS_drbg_instantiate fun:RAND_init_fips fun:OPENSSL_init_library fun:SSL_library_init fun:init_hashlib } { openssl 1.0.1 prng 3 Memcheck:Value8 fun:_x86_64_AES_encrypt_compact fun:AES_encrypt } # # All of these problems come from using test_socket_ssl # { from test_socket_ssl Memcheck:Cond fun:BN_bin2bn } { from test_socket_ssl Memcheck:Cond fun:BN_num_bits_word } { from test_socket_ssl Memcheck:Value4 fun:BN_num_bits_word } { from test_socket_ssl Memcheck:Cond fun:BN_mod_exp_mont_word } { from test_socket_ssl Memcheck:Cond fun:BN_mod_exp_mont } { from test_socket_ssl Memcheck:Param write(buf) fun:write obj:/usr/lib/libcrypto.so.0.9.7 } { from test_socket_ssl Memcheck:Cond fun:RSA_verify } { from test_socket_ssl Memcheck:Value4 fun:RSA_verify } { from test_socket_ssl Memcheck:Value4 fun:DES_set_key_unchecked } { from test_socket_ssl Memcheck:Value4 fun:DES_encrypt2 } { from test_socket_ssl Memcheck:Cond obj:/usr/lib/libssl.so.0.9.7 } { from test_socket_ssl Memcheck:Value4 obj:/usr/lib/libssl.so.0.9.7 } { from test_socket_ssl Memcheck:Cond fun:BUF_MEM_grow_clean } { from test_socket_ssl Memcheck:Cond fun:memcpy fun:ssl3_read_bytes } { from test_socket_ssl Memcheck:Cond fun:SHA1_Update } { from test_socket_ssl Memcheck:Value4 fun:SHA1_Update } { test_buffer_non_debug Memcheck:Addr4 fun:PyUnicodeUCS2_FSConverter } { test_buffer_non_debug Memcheck:Addr4 fun:PyUnicode_FSConverter } { wcscmp_false_positive Memcheck:Addr8 fun:wcscmp fun:_PyOS_GetOpt fun:Py_Main fun:main } # Additional suppressions for the unified decimal tests: { test_decimal Memcheck:Addr4 fun:PyUnicodeUCS2_FSConverter } { test_decimal2 Memcheck:Addr4 fun:PyUnicode_FSConverter } libgit2-pygit2-a011e26/mypy-stubtest.ini000066400000000000000000000007151473744024100201660ustar00rootroot00000000000000# Config file for testing the stub file (_pygit2.pyi) with "stubtest" # (tool shipped with mypy). # # Run "build.sh stubtest", or: # stubtest --mypy-config-file test/mypy-stubtest.ini pygit2._pygit2 # # Format info: # https://mypy.readthedocs.io/en/stable/config_file.html [mypy] warn_unused_configs = True disallow_any_explicit = True # don't follow import pygit2 from _pygit2.pyi, we only want to check the pyi file. [mypy-pygit2] follow_imports = skip libgit2-pygit2-a011e26/pygit2/000077500000000000000000000000001473744024100160275ustar00rootroot00000000000000libgit2-pygit2-a011e26/pygit2/__init__.py000066400000000000000000000161421473744024100201440ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Standard Library import functools from os import PathLike import typing # Low level API from ._pygit2 import * from ._pygit2 import _cache_enums # High level API from . import enums from ._build import __version__ from .blame import Blame, BlameHunk from .blob import BlobIO from .callbacks import Payload, RemoteCallbacks, CheckoutCallbacks, StashApplyCallbacks from .callbacks import git_clone_options, git_fetch_options, get_credentials from .config import Config from .credentials import * from .errors import check_error, Passthrough from .ffi import ffi, C from .filter import Filter from .index import Index, IndexEntry from .legacyenums import * from .packbuilder import PackBuilder from .remotes import Remote from .repository import Repository from .settings import Settings from .submodules import Submodule from .utils import to_bytes, to_str # Features features = enums.Feature(C.git_libgit2_features()) # libgit version tuple LIBGIT2_VER = (LIBGIT2_VER_MAJOR, LIBGIT2_VER_MINOR, LIBGIT2_VER_REVISION) # Let _pygit2 cache references to Python enum types. # This is separate from PyInit__pygit2() to avoid a circular import. _cache_enums() def init_repository( path: typing.Union[str, bytes, PathLike, None], bare: bool = False, flags: enums.RepositoryInitFlag = enums.RepositoryInitFlag.MKPATH, mode: typing.Union[ int, enums.RepositoryInitMode ] = enums.RepositoryInitMode.SHARED_UMASK, workdir_path: typing.Optional[str] = None, description: typing.Optional[str] = None, template_path: typing.Optional[str] = None, initial_head: typing.Optional[str] = None, origin_url: typing.Optional[str] = None, ) -> Repository: """ Creates a new Git repository in the given *path*. If *bare* is True the repository will be bare, i.e. it will not have a working copy. The *flags* may be a combination of enums.RepositoryInitFlag constants: - BARE (overriden by the *bare* parameter) - NO_REINIT - NO_DOTGIT_DIR - MKDIR - MKPATH (set by default) - EXTERNAL_TEMPLATE The *mode* parameter may be any of the predefined modes in enums.RepositoryInitMode (SHARED_UMASK being the default), or a custom int. The *workdir_path*, *description*, *template_path*, *initial_head* and *origin_url* are all strings. See libgit2's documentation on git_repository_init_ext for further details. """ # Pre-process input parameters if path is None: raise TypeError('Expected string type for path, found None.') if bare: flags |= enums.RepositoryInitFlag.BARE # Options options = ffi.new('git_repository_init_options *') C.git_repository_init_options_init(options, C.GIT_REPOSITORY_INIT_OPTIONS_VERSION) options.flags = int(flags) options.mode = mode if workdir_path: workdir_path_ref = ffi.new('char []', to_bytes(workdir_path)) options.workdir_path = workdir_path_ref if description: description_ref = ffi.new('char []', to_bytes(description)) options.description = description_ref if template_path: template_path_ref = ffi.new('char []', to_bytes(template_path)) options.template_path = template_path_ref if initial_head: initial_head_ref = ffi.new('char []', to_bytes(initial_head)) options.initial_head = initial_head_ref if origin_url: origin_url_ref = ffi.new('char []', to_bytes(origin_url)) options.origin_url = origin_url_ref # Call crepository = ffi.new('git_repository **') err = C.git_repository_init_ext(crepository, to_bytes(path), options) check_error(err) # Ok return Repository(to_str(path)) def clone_repository( url, path, bare=False, repository=None, remote=None, checkout_branch=None, callbacks=None, depth=0, ): """ Clones a new Git repository from *url* in the given *path*. Returns: a Repository class pointing to the newly cloned repository. Parameters: url : str URL of the repository to clone. path : str Local path to clone into. bare : bool Whether the local repository should be bare. remote : callable Callback for the remote to use. The remote callback has `(Repository, name, url) -> Remote` as a signature. The Remote it returns will be used instead of the default one. repository : callable Callback for the repository to use. The repository callback has `(path, bare) -> Repository` as a signature. The Repository it returns will be used instead of creating a new one. checkout_branch : str Branch to checkout after the clone. The default is to use the remote's default branch. callbacks : RemoteCallbacks Object which implements the callbacks as methods. The callbacks should be an object which inherits from `pyclass:RemoteCallbacks`. depth : int Number of commits to clone. If greater than 0, creates a shallow clone with a history truncated to the specified number of commits. The default is 0 (full commit history). """ if callbacks is None: callbacks = RemoteCallbacks() # Add repository and remote to the payload payload = callbacks payload.repository = repository payload.remote = remote with git_clone_options(payload): opts = payload.clone_options opts.bare = bare opts.fetch_opts.depth = depth if checkout_branch: checkout_branch_ref = ffi.new('char []', to_bytes(checkout_branch)) opts.checkout_branch = checkout_branch_ref with git_fetch_options(payload, opts=opts.fetch_opts): crepo = ffi.new('git_repository **') err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts) payload.check_error(err) # Ok return Repository._from_c(crepo[0], owned=True) tree_entry_key = functools.cmp_to_key(tree_entry_cmp) settings = Settings() libgit2-pygit2-a011e26/pygit2/_build.py000066400000000000000000000044171473744024100176450ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """ This is an special module, it provides stuff used by setup.py at build time. But also used by pygit2 at run time. """ import os from pathlib import Path # # The version number of pygit2 # __version__ = '1.17.0' # # Utility functions to get the paths required for bulding extensions # def _get_libgit2_path(): # LIBGIT2 environment variable takes precedence libgit2_path = os.getenv('LIBGIT2') if libgit2_path is not None: return Path(libgit2_path) # Default if os.name == 'nt': return Path(r'%s\libgit2' % os.getenv('ProgramFiles')) return Path('/usr/local') def get_libgit2_paths(): # Base path path = _get_libgit2_path() # Library dirs libgit2_lib = os.getenv('LIBGIT2_LIB') if libgit2_lib is None: library_dirs = [path / 'lib', path / 'lib64'] else: library_dirs = [libgit2_lib] include_dirs = [path / 'include'] return ( path / 'bin', { 'libraries': ['git2'], 'include_dirs': [str(x) for x in include_dirs], 'library_dirs': [str(x) for x in library_dirs], }, ) libgit2-pygit2-a011e26/pygit2/_pygit2.pyi000066400000000000000000000426131473744024100201350ustar00rootroot00000000000000from typing import Iterator, Literal, Optional, overload from io import IOBase from . import Index from .enums import ( ApplyLocation, BranchType, DeltaStatus, DiffFind, DiffFlag, DiffOption, DiffStatsFormat, FileMode, MergeAnalysis, MergePreference, ObjectType, Option, ReferenceFilter, ReferenceType, ResetMode, SortMode, ) GIT_OBJ_BLOB: Literal[3] GIT_OBJ_COMMIT: Literal[1] GIT_OBJ_TAG: Literal[4] GIT_OBJ_TREE: Literal[2] GIT_OID_HEXSZ: int GIT_OID_HEX_ZERO: str GIT_OID_MINPREFIXLEN: int GIT_OID_RAWSZ: int LIBGIT2_VERSION: str LIBGIT2_VER_MAJOR: int LIBGIT2_VER_MINOR: int LIBGIT2_VER_REVISION: int class Object: _pointer: bytes filemode: FileMode hex: str id: Oid name: str | None oid: Oid raw_name: bytes | None short_id: str type: 'Literal[GIT_OBJ_COMMIT] | Literal[GIT_OBJ_TREE] | Literal[GIT_OBJ_TAG] | Literal[GIT_OBJ_BLOB]' type_str: "Literal['commit'] | Literal['tree'] | Literal['tag'] | Literal['blob']" @overload def peel(self, target_type: 'Literal[GIT_OBJ_COMMIT]') -> 'Commit': ... @overload def peel(self, target_type: 'Literal[GIT_OBJ_TREE]') -> 'Tree': ... @overload def peel(self, target_type: 'Literal[GIT_OBJ_TAG]') -> 'Tag': ... @overload def peel(self, target_type: 'Literal[GIT_OBJ_BLOB]') -> 'Blob': ... @overload def peel(self, target_type: 'None') -> 'Commit|Tree|Blob': ... def read_raw(self) -> bytes: ... def __eq__(self, other) -> bool: ... def __ge__(self, other) -> bool: ... def __gt__(self, other) -> bool: ... def __hash__(self) -> int: ... def __le__(self, other) -> bool: ... def __lt__(self, other) -> bool: ... def __ne__(self, other) -> bool: ... class Reference: name: str raw_name: bytes raw_shorthand: bytes raw_target: Oid | bytes shorthand: str target: Oid | str type: ReferenceType def __init__(self, *args) -> None: ... def delete(self) -> None: ... def log(self) -> Iterator[RefLogEntry]: ... @overload def peel(self, type: 'Literal[GIT_OBJ_COMMIT]') -> 'Commit': ... @overload def peel(self, type: 'Literal[GIT_OBJ_TREE]') -> 'Tree': ... @overload def peel(self, type: 'Literal[GIT_OBJ_TAG]') -> 'Tag': ... @overload def peel(self, type: 'Literal[GIT_OBJ_BLOB]') -> 'Blob': ... @overload def peel(self, type: 'None') -> 'Commit|Tree|Blob': ... def rename(self, new_name: str) -> None: ... def resolve(self) -> Reference: ... def set_target(self, target: _OidArg, message: str = ...) -> None: ... def __eq__(self, other) -> bool: ... def __ge__(self, other) -> bool: ... def __gt__(self, other) -> bool: ... def __le__(self, other) -> bool: ... def __lt__(self, other) -> bool: ... def __ne__(self, other) -> bool: ... class AlreadyExistsError(ValueError): ... class Blob(Object): data: bytes is_binary: bool size: int def diff( self, blob: Blob = ..., flag: int = ..., old_as_path: str = ..., new_as_path: str = ..., ) -> Patch: ... def diff_to_buffer( self, buffer: Optional[bytes] = None, flag: DiffOption = DiffOption.NORMAL, old_as_path: str = ..., buffer_as_path: str = ..., ) -> Patch: ... class Branch(Reference): branch_name: str raw_branch_name: bytes remote_name: str upstream: Branch upstream_name: str def delete(self) -> None: ... def is_checked_out(self) -> bool: ... def is_head(self) -> bool: ... def rename(self, name: str, force: bool = False) -> None: ... class Commit(Object): author: Signature commit_time: int commit_time_offset: int committer: Signature gpg_signature: tuple[bytes, bytes] message: str message_encoding: str message_trailers: dict[str, str] parent_ids: list[Oid] parents: list[Commit] raw_message: bytes tree: Tree tree_id: Oid class Diff: deltas: Iterator[DiffDelta] patch: str | None patchid: Oid stats: DiffStats def find_similar( self, flags: DiffFind = DiffFind.FIND_BY_CONFIG, rename_threshold: int = 50, copy_threshold: int = 50, rename_from_rewrite_threshold: int = 50, break_rewrite_threshold: int = 60, rename_limit: int = 1000, ) -> None: ... def merge(self, diff: Diff) -> None: ... @staticmethod def from_c(diff, repo) -> Diff: ... @staticmethod def parse_diff(git_diff: str | bytes) -> Diff: ... def __getitem__(self, index: int) -> Patch: ... # Diff_getitem def __iter__(self) -> Iterator[Patch]: ... # -> DiffIter def __len__(self) -> int: ... class DiffDelta: flags: DiffFlag is_binary: bool nfiles: int new_file: DiffFile old_file: DiffFile similarity: int status: DeltaStatus def status_char(self) -> str: ... class DiffFile: flags: DiffFlag id: Oid mode: FileMode path: str raw_path: bytes size: int @staticmethod def from_c(bytes) -> DiffFile: ... class DiffHunk: header: str lines: list[DiffLine] new_lines: int new_start: int old_lines: int old_start: int class DiffLine: content: str content_offset: int new_lineno: int num_lines: int old_lineno: int origin: str raw_content: bytes class DiffStats: deletions: int files_changed: int insertions: int def format(self, format: DiffStatsFormat, width: int) -> str: ... class GitError(Exception): ... class InvalidSpecError(ValueError): ... class Mailmap: def __init__(self, *args) -> None: ... def add_entry( self, real_name: str = ..., real_email: str = ..., replace_name: str = ..., replace_email: str = ..., ) -> None: ... @staticmethod def from_buffer(buffer: str | bytes) -> Mailmap: ... @staticmethod def from_repository(repository: Repository) -> Mailmap: ... def resolve(self, name: str, email: str) -> tuple[str, str]: ... def resolve_signature(self, sig: Signature) -> Signature: ... class Note: annotated_id: Oid id: Oid message: str def remove( self, author: Signature, committer: Signature, ref: str = 'refs/notes/commits' ) -> None: ... class Odb: backends: Iterator[OdbBackend] def __init__(self, *args, **kwargs) -> None: ... def add_backend(self, backend: OdbBackend, priority: int) -> None: ... def add_disk_alternate(self, path: str) -> None: ... def exists(self, oid: _OidArg) -> bool: ... def read(self, oid: _OidArg) -> tuple[int, int, bytes]: ... def write(self, type: int, data: bytes) -> Oid: ... def __contains__(self, other: _OidArg) -> bool: ... def __iter__(self) -> Iterator[Oid]: ... # Odb_as_iter class OdbBackend: def __init__(self, *args, **kwargs) -> None: ... def exists(self, oid: _OidArg) -> bool: ... def exists_prefix(self, partial_id: _OidArg) -> Oid: ... def read(self, oid: _OidArg) -> tuple[int, bytes]: ... def read_header(self, oid: _OidArg) -> tuple[int, int]: ... def read_prefix(self, oid: _OidArg) -> tuple[int, bytes, Oid]: ... def refresh(self) -> None: ... def __iter__(self) -> Iterator[Oid]: ... # OdbBackend_as_iter class OdbBackendLoose(OdbBackend): def __init__(self, *args, **kwargs) -> None: ... class OdbBackendPack(OdbBackend): def __init__(self, *args, **kwargs) -> None: ... class Oid: hex: str raw: bytes def __init__(self, raw: bytes = ..., hex: str = ...) -> None: ... def __eq__(self, other) -> bool: ... def __ge__(self, other) -> bool: ... def __gt__(self, other) -> bool: ... def __hash__(self) -> int: ... def __le__(self, other) -> bool: ... def __lt__(self, other) -> bool: ... def __ne__(self, other) -> bool: ... class Patch: data: bytes delta: DiffDelta hunks: list[DiffHunk] line_stats: tuple[int, int, int] # context, additions, deletions text: str | None @staticmethod def create_from( old: Blob | bytes | None, new: Blob | bytes | None, old_as_path: str = ..., new_as_path: str = ..., flag: DiffOption = DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0, ) -> Patch: ... class RefLogEntry: committer: Signature message: str oid_new: Oid oid_old: Oid def __init__(self, *args, **kwargs) -> None: ... class Refdb: def __init__(self, *args, **kwargs) -> None: ... def compress(self) -> None: ... @staticmethod def new(repo: Repository) -> Refdb: ... @staticmethod def open(repo: Repository) -> Refdb: ... def set_backend(self, backend: RefdbBackend) -> None: ... class RefdbBackend: def __init__(self, *args, **kwargs) -> None: ... def compress(self) -> None: ... def delete(self, ref_name: str, old_id: _OidArg, old_target: str) -> None: ... def ensure_log(self, ref_name: str) -> bool: ... def exists(self, refname: str) -> bool: ... def has_log(self, ref_name: str) -> bool: ... def lookup(self, refname: str) -> Reference: ... def rename( self, old_name: str, new_name: str, force: bool, who: Signature, message: str ) -> Reference: ... def write( self, ref: Reference, force: bool, who: Signature, message: str, old: _OidArg, old_target: str, ) -> None: ... class RefdbFsBackend(RefdbBackend): def __init__(self, *args, **kwargs) -> None: ... class Repository: _pointer: bytes default_signature: Signature head: Reference head_is_detached: bool head_is_unborn: bool is_bare: bool is_empty: bool is_shallow: bool odb: Odb path: str refdb: Refdb workdir: str def __init__(self, *args, **kwargs) -> None: ... def TreeBuilder(self, src: Tree | _OidArg = ...) -> TreeBuilder: ... def _disown(self, *args, **kwargs) -> None: ... def _from_c(self, *args, **kwargs) -> None: ... def add_worktree(self, name: str, path: str, ref: Reference = ...) -> Worktree: ... def applies( self, diff: Diff, location: ApplyLocation = ApplyLocation.INDEX, raise_error: bool = False, ) -> bool: ... def apply( self, diff: Diff, location: ApplyLocation = ApplyLocation.WORKDIR ) -> None: ... def cherrypick(self, id: _OidArg) -> None: ... def compress_references(self) -> None: ... def create_blob(self, data: bytes) -> Oid: ... def create_blob_fromdisk(self, path: str) -> Oid: ... def create_blob_fromiobase(self, iobase: IOBase) -> Oid: ... def create_blob_fromworkdir(self, path: str) -> Oid: ... def create_branch(self, name: str, commit: Commit, force=False) -> Branch: ... def create_commit( self, reference_name: Optional[str], author: Signature, committer: Signature, message: str | bytes, tree: _OidArg, parents: list[_OidArg], encoding: str = ..., ) -> Oid: ... def create_commit_string( self, author: Signature, committer: Signature, message: str | bytes, tree: _OidArg, parents: list[_OidArg], encoding: str = ..., ) -> Oid: ... def create_commit_with_signature( self, content: str, signature: str, signature_field: Optional[str] = None ) -> Oid: ... def create_note( self, message: str, author: Signature, committer: Signature, annotated_id: str, ref: str = 'refs/notes/commits', force: bool = False, ) -> Oid: ... def create_reference_direct( self, name: str, target: _OidArg, force: bool, message: Optional[str] = None ) -> Reference: ... def create_reference_symbolic( self, name: str, target: str, force: bool, message: Optional[str] = None ) -> Reference: ... def create_tag( self, name: str, oid: _OidArg, type: ObjectType, tagger: Signature, message: str ) -> Oid: ... def descendant_of(self, oid1: _OidArg, oid2: _OidArg) -> bool: ... def expand_id(self, hex: str) -> Oid: ... def free(self) -> None: ... def git_object_lookup_prefix(self, oid: _OidArg) -> Object: ... def list_worktrees(self) -> list[str]: ... def listall_branches(self, flag: BranchType = BranchType.LOCAL) -> list[str]: ... def listall_mergeheads(self) -> list[Oid]: ... def listall_stashes(self) -> list[Stash]: ... def listall_submodules(self) -> list[str]: ... def lookup_branch( self, branch_name: str, branch_type: BranchType = BranchType.LOCAL ) -> Branch: ... def lookup_note( self, annotated_id: str, ref: str = 'refs/notes/commits' ) -> Note: ... def lookup_reference(self, name: str) -> Reference: ... def lookup_reference_dwim(self, name: str) -> Reference: ... def lookup_worktree(self, name: str) -> Worktree: ... def merge_analysis( self, their_head: _OidArg, our_ref: str = 'HEAD' ) -> tuple[MergeAnalysis, MergePreference]: ... def merge_base(self, oid1: _OidArg, oid2: _OidArg) -> Oid: ... def merge_base_many(self, oids: list[_OidArg]) -> Oid: ... def merge_base_octopus(self, oids: list[_OidArg]) -> Oid: ... def notes(self) -> Iterator[Note]: ... def path_is_ignored(self, path: str) -> bool: ... def raw_listall_branches( self, flag: BranchType = BranchType.LOCAL ) -> list[bytes]: ... def raw_listall_references(self) -> list[bytes]: ... def references_iterator_init(self) -> Iterator[Reference]: ... def references_iterator_next( self, iter: Iterator, references_return_type: ReferenceFilter = ReferenceFilter.ALL, ) -> Reference: ... def reset(self, oid: _OidArg, reset_type: ResetMode) -> None: ... def revparse(self, revspec: str) -> RevSpec: ... def revparse_ext(self, revision: str) -> tuple[Object, Reference]: ... def revparse_single(self, revision: str) -> Object: ... def set_odb(self, odb: Odb) -> None: ... def set_refdb(self, refdb: Refdb) -> None: ... def status( self, untracked_files: str = 'all', ignored: bool = False ) -> dict[str, int]: ... def status_file(self, path: str) -> int: ... def walk( self, oid: _OidArg | None, sort_mode: SortMode = SortMode.NONE ) -> Walker: ... class RevSpec: flags: int from_object: Object to_object: Object class Signature: _encoding: str | None _pointer: bytes email: str name: str offset: int raw_email: bytes raw_name: bytes time: int def __init__( self, name: str, email: str, time: int = -1, offset: int = 0, encoding: Optional[str] = None, ) -> None: ... def __eq__(self, other) -> bool: ... def __ge__(self, other) -> bool: ... def __gt__(self, other) -> bool: ... def __le__(self, other) -> bool: ... def __lt__(self, other) -> bool: ... def __ne__(self, other) -> bool: ... class Stash: commit_id: Oid message: str raw_message: bytes def __eq__(self, other) -> bool: ... def __ge__(self, other) -> bool: ... def __gt__(self, other) -> bool: ... def __le__(self, other) -> bool: ... def __lt__(self, other) -> bool: ... def __ne__(self, other) -> bool: ... class Tag(Object): message: str name: str raw_message: bytes raw_name: bytes tagger: Signature target: Oid def get_object(self) -> Object: ... class Tree(Object): def diff_to_index( self, index: Index, flags: DiffOption = DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0, ) -> Diff: ... def diff_to_tree( self, tree: Tree = ..., flags: DiffOption = DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 3, swap: bool = False, ) -> Diff: ... def diff_to_workdir( self, flags: DiffOption = DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0, ) -> Diff: ... def __contains__(self, other: str) -> bool: ... # Tree_contains def __getitem__(self, index: str | int) -> Object: ... # Tree_subscript def __iter__(self) -> Iterator[Object]: ... def __len__(self) -> int: ... # Tree_len def __rtruediv__(self, other: str) -> Object: ... def __truediv__(self, other: str) -> Object: ... # Tree_divide class TreeBuilder: def clear(self) -> None: ... def get(self, name: str) -> Object: ... def insert(self, name: str, oid: _OidArg, attr: int) -> None: ... def remove(self, name: str) -> None: ... def write(self) -> Oid: ... def __len__(self) -> int: ... class Walker: def hide(self, oid: _OidArg) -> None: ... def push(self, oid: _OidArg) -> None: ... def reset(self) -> None: ... def simplify_first_parent(self) -> None: ... def sort(self, mode: SortMode) -> None: ... def __iter__(self) -> Iterator[Commit]: ... # Walker: ... def __next__(self) -> Commit: ... class Worktree: is_prunable: bool name: str path: str def prune(self, force=False) -> None: ... def discover_repository( path: str, across_fs: bool = False, ceiling_dirs: str = ... ) -> str | None: ... def hash(data: bytes) -> Oid: ... def hashfile(path: str) -> Oid: ... def init_file_backend(path: str, flags: int = 0) -> object: ... def option(opt: Option, *args) -> None: ... def reference_is_valid_name(refname: str) -> bool: ... def tree_entry_cmp(a: Object, b: Object) -> int: ... _OidArg = str | Oid libgit2-pygit2-a011e26/pygit2/_run.py000066400000000000000000000052731473744024100173530ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """ This is an special module, it provides stuff used by by pygit2 at run-time. """ # Import from the Standard Library import codecs from pathlib import Path import sys # Import from cffi from cffi import FFI # Import from pygit2 try: from _build import get_libgit2_paths except ImportError: from ._build import get_libgit2_paths # C_HEADER_SRC if getattr(sys, 'frozen', False): if hasattr(sys, '_MEIPASS'): dir_path = Path(sys._MEIPASS) else: dir_path = Path(sys.executable).parent else: dir_path = Path(__file__).parent.absolute() # Order matters h_files = [ 'types.h', 'oid.h', 'attr.h', 'blame.h', 'buffer.h', 'strarray.h', 'diff.h', 'checkout.h', 'transport.h', 'proxy.h', 'indexer.h', 'pack.h', 'remote.h', 'clone.h', 'common.h', 'config.h', 'describe.h', 'errors.h', 'graph.h', 'index.h', 'merge.h', 'net.h', 'refspec.h', 'repository.h', 'commit.h', 'revert.h', 'stash.h', 'submodule.h', 'callbacks.h', # Bridge from libgit2 to Python ] h_source = [] for h_file in h_files: h_file = dir_path / 'decl' / h_file with codecs.open(h_file, 'r', 'utf-8') as f: h_source.append(f.read()) C_HEADER_SRC = '\n'.join(h_source) C_PREAMBLE = """\ #include #include """ # ffi _, libgit2_kw = get_libgit2_paths() ffi = FFI() ffi.set_source('pygit2._libgit2', C_PREAMBLE, **libgit2_kw) ffi.cdef(C_HEADER_SRC) if __name__ == '__main__': ffi.compile() libgit2-pygit2-a011e26/pygit2/blame.py000066400000000000000000000100731473744024100174620ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Import from pygit2 from .ffi import ffi, C from .utils import GenericIterator from ._pygit2 import Signature, Oid def wrap_signature(csig): if not csig: return None return Signature( ffi.string(csig.name).decode('utf-8'), ffi.string(csig.email).decode('utf-8'), csig.when.time, csig.when.offset, 'utf-8', ) class BlameHunk: @classmethod def _from_c(cls, blame, ptr): hunk = cls.__new__(cls) hunk._blame = blame hunk._hunk = ptr return hunk @property def lines_in_hunk(self): """Number of lines""" return self._hunk.lines_in_hunk @property def boundary(self): """Tracked to a boundary commit""" # Casting directly to bool via cffi does not seem to work return int(ffi.cast('int', self._hunk.boundary)) != 0 @property def final_start_line_number(self): """Final start line number""" return self._hunk.final_start_line_number @property def final_committer(self): """Final committer""" return wrap_signature(self._hunk.final_signature) @property def final_commit_id(self): return Oid( raw=bytes(ffi.buffer(ffi.addressof(self._hunk, 'final_commit_id'))[:]) ) @property def orig_start_line_number(self): """Origin start line number""" return self._hunk.orig_start_line_number @property def orig_committer(self): """Original committer""" return wrap_signature(self._hunk.orig_signature) @property def orig_commit_id(self): return Oid( raw=bytes(ffi.buffer(ffi.addressof(self._hunk, 'orig_commit_id'))[:]) ) @property def orig_path(self): """Original path""" path = self._hunk.orig_path if not path: return None return ffi.string(path).decode('utf-8') class Blame: @classmethod def _from_c(cls, repo, ptr): blame = cls.__new__(cls) blame._repo = repo blame._blame = ptr return blame def __del__(self): C.git_blame_free(self._blame) def __len__(self): return C.git_blame_get_hunk_count(self._blame) def __getitem__(self, index): chunk = C.git_blame_get_hunk_byindex(self._blame, index) if not chunk: raise IndexError return BlameHunk._from_c(self, chunk) def for_line(self, line_no): """ Returns the object for a given line given its number in the current Blame. Parameters: line_no Line number, starts at 1. """ if line_no < 0: raise IndexError chunk = C.git_blame_get_hunk_byline(self._blame, line_no) if not chunk: raise IndexError return BlameHunk._from_c(self, chunk) def __iter__(self): return GenericIterator(self) libgit2-pygit2-a011e26/pygit2/blob.py000066400000000000000000000112021473744024100173130ustar00rootroot00000000000000import io import threading import time from contextlib import AbstractContextManager from typing import Optional from queue import Queue from ._pygit2 import Blob, Oid from .enums import BlobFilter class _BlobIO(io.RawIOBase): """Low-level wrapper for streaming blob content. The underlying libgit2 git_writestream filter chain will be run in a separate thread. The GIL will be released while running libgit2 filtering. """ def __init__( self, blob: Blob, as_path: Optional[str] = None, flags: BlobFilter = BlobFilter.CHECK_FOR_BINARY, commit_id: Optional[Oid] = None, ): super().__init__() self._blob = blob self._queue = Queue(maxsize=1) self._ready = threading.Event() self._writer_closed = threading.Event() self._chunk: Optional[bytes] = None self._thread = threading.Thread( target=self._blob._write_to_queue, args=(self._queue, self._ready, self._writer_closed), kwargs={ 'as_path': as_path, 'flags': int(flags), 'commit_id': commit_id, }, daemon=True, ) self._thread.start() def __exit__(self, exc_type, exc_value, traceback): self.close() def isatty(): return False def readable(self): return True def writable(self): return False def seekable(self): return False def readinto(self, b, /): try: while self._chunk is None: self._ready.wait() if self._queue.empty(): if self._writer_closed.is_set(): # EOF return 0 self._ready.clear() time.sleep(0) continue chunk = self._queue.get() if chunk: self._chunk = chunk if len(self._chunk) <= len(b): bytes_written = len(self._chunk) b[:bytes_written] = self._chunk self._chunk = None return bytes_written bytes_written = len(b) b[:] = self._chunk[:bytes_written] self._chunk = self._chunk[bytes_written:] return bytes_written except KeyboardInterrupt: return 0 def close(self): try: self._ready.wait() self._writer_closed.wait() while self._queue is not None and not self._queue.empty(): self._queue.get() self._thread.join() except KeyboardInterrupt: pass self._queue = None class BlobIO(io.BufferedReader, AbstractContextManager): """Read-only wrapper for streaming blob content. Supports reading both raw and filtered blob content. Implements io.BufferedReader. Example: >>> with BlobIO(blob) as f: ... while True: ... # Read blob data in 1KB chunks until EOF is reached ... chunk = f.read(1024) ... if not chunk: ... break By default, `BlobIO` will stream the raw contents of the blob, but it can also be used to stream filtered content (i.e. to read the content after applying filters which would be used when checking out the blob to the working directory). Example: >>> with BlobIO(blob, as_path='my_file.ext') as f: ... # Read the filtered content which would be returned upon ... # running 'git checkout -- my_file.txt' ... filtered_data = f.read() """ def __init__( self, blob: Blob, as_path: Optional[str] = None, flags: BlobFilter = BlobFilter.CHECK_FOR_BINARY, commit_id: Optional[Oid] = None, ): """Wrap the specified blob. Parameters: blob: The blob to wrap. as_path: Filter the contents of the blob as if it had the specified path. If `as_path` is None, the raw contents of the blob will be read. flags: A combination of enums.BlobFilter constants (only applicable when `as_path` is set). commit_id: Commit to load attributes from when ATTRIBUTES_FROM_COMMIT is specified in `flags` (only applicable when `as_path` is set). """ raw = _BlobIO(blob, as_path=as_path, flags=flags, commit_id=commit_id) super().__init__(raw) def __exit__(self, exc_type, exc_value, traceback): self.close() io.RawIOBase.register(_BlobIO) io.BufferedIOBase.register(BlobIO) libgit2-pygit2-a011e26/pygit2/branches.py000066400000000000000000000070241473744024100201710ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from __future__ import annotations from typing import TYPE_CHECKING from .enums import BranchType, ReferenceType from ._pygit2 import Commit, Oid # Need BaseRepository for type hints, but don't let it cause a circular dependency if TYPE_CHECKING: from .repository import BaseRepository class Branches: def __init__( self, repository: BaseRepository, flag: BranchType = BranchType.ALL, commit=None ): self._repository = repository self._flag = flag if commit is not None: if isinstance(commit, Commit): commit = commit.id elif not isinstance(commit, Oid): commit = self._repository.expand_id(commit) self._commit = commit if flag == BranchType.ALL: self.local = Branches(repository, flag=BranchType.LOCAL, commit=commit) self.remote = Branches(repository, flag=BranchType.REMOTE, commit=commit) def __getitem__(self, name: str): branch = None if self._flag & BranchType.LOCAL: branch = self._repository.lookup_branch(name, BranchType.LOCAL) if branch is None and self._flag & BranchType.REMOTE: branch = self._repository.lookup_branch(name, BranchType.REMOTE) if branch is None or not self._valid(branch): raise KeyError(f'Branch not found: {name}') return branch def get(self, key: str): try: return self[key] except KeyError: return None def __iter__(self): for branch_name in self._repository.listall_branches(self._flag): if self._commit is None or self.get(branch_name) is not None: yield branch_name def create(self, name: str, commit, force=False): return self._repository.create_branch(name, commit, force) def delete(self, name: str): self[name].delete() def _valid(self, branch): if branch.type == ReferenceType.SYMBOLIC: branch = branch.resolve() return ( self._commit is None or branch.target == self._commit or self._repository.descendant_of(branch.target, self._commit) ) def with_commit(self, commit): assert self._commit is None return Branches(self._repository, self._flag, commit) def __contains__(self, name): return self.get(name) is not None libgit2-pygit2-a011e26/pygit2/callbacks.py000066400000000000000000000603321473744024100203240ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """ In this module we keep everything concerning callback. This is how it works, with an example: 1. The pygit2 API calls libgit2, it passes a payload object e.g. Remote.fetch calls git_remote_fetch 2. libgit2 calls Python callbacks e.g. git_remote_fetch calls _transfer_progress_cb 3. Optionally, the Python callback may proxy to a user defined function e.g. _transfer_progress_cb calls RemoteCallbacks.transfer_progress 4. The user defined function may return something on success, or raise an exception on error, or raise the special Passthrough exception. 5. The callback may return in 3 different ways to libgit2: - Returns GIT_OK on success. - Returns GIT_PASSTHROUGH if the user defined function raised Passthrough, this tells libgit2 to act as if this callback didn't exist in the first place. - Returns GIT_EUSER if another exception was raised, and keeps the exception in the payload to be re-raised later. 6. libgit2 returns to the pygit2 API, with an error code e.g. git_remote_fetch returns to Remote.fetch 7. The pygit2 API will: - Return something on success. - Raise the original exception if libgit2 returns GIT_EUSER - Raise another exception if libgit2 returns another error code The payload object is passed all the way, so pygit2 API can send information to the inner user defined function, and this can send back results to the pygit2 API. """ # Standard Library from contextlib import contextmanager from functools import wraps from typing import Optional, Union # pygit2 from ._pygit2 import Oid, DiffFile from .enums import CheckoutNotify, CheckoutStrategy, CredentialType, StashApplyProgress from .errors import check_error, Passthrough from .ffi import ffi, C from .utils import maybe_string, to_bytes, ptr_to_bytes, StrArray # # The payload is the way to pass information from the pygit2 API, through # libgit2, to the Python callbacks. And back. # class Payload: def __init__(self, **kw): for key, value in kw.items(): setattr(self, key, value) self._stored_exception = None def check_error(self, error_code): if error_code == C.GIT_EUSER: assert self._stored_exception is not None raise self._stored_exception elif self._stored_exception is not None: # A callback mapped to a C function returning void # might still have raised an exception. raise self._stored_exception check_error(error_code) class RemoteCallbacks(Payload): """Base class for pygit2 remote callbacks. Inherit from this class and override the callbacks which you want to use in your class, which you can then pass to the network operations. For the credentials, you can either subclass and override the 'credentials' method, or if it's a constant value, pass the value to the constructor, e.g. RemoteCallbacks(credentials=credentials). You can as well pass the certificate the same way, for example: RemoteCallbacks(certificate=certificate). """ def __init__(self, credentials=None, certificate_check=None): super().__init__() if credentials is not None: self.credentials = credentials if certificate_check is not None: self.certificate_check = certificate_check def sideband_progress(self, string): """ Progress output callback. Override this function with your own progress reporting function Parameters: string : str Progress output from the remote. """ def credentials( self, url: str, username_from_url: Union[str, None], allowed_types: CredentialType, ): """ Credentials callback. If the remote server requires authentication, this function will be called and its return value used for authentication. Override it if you want to be able to perform authentication. Returns: credential Parameters: url : str The url of the remote. username_from_url : str or None Username extracted from the url, if any. allowed_types : CredentialType A combination of CredentialType bitflags representing the credential types supported by the remote. """ raise Passthrough def certificate_check(self, certificate, valid, host): """ Certificate callback. Override with your own function to determine whether to accept the server's certificate. Returns: True to connect, False to abort. Parameters: certificate : None The certificate. It is currently always None while we figure out how to represent it cross-platform. valid : bool Whether the TLS/SSH library thinks the certificate is valid. host : str The hostname we want to connect to. """ raise Passthrough def transfer_progress(self, stats): """ Transfer progress callback. Override with your own function to report transfer progress. Parameters: stats : TransferProgress The progress up to now. """ def update_tips(self, refname, old, new): """ Update tips callback. Override with your own function to report reference updates. Parameters: refname : str The name of the reference that's being updated. old : Oid The reference's old value. new : Oid The reference's new value. """ def push_update_reference(self, refname, message): """ Push update reference callback. Override with your own function to report the remote's acceptance or rejection of reference updates. refname : str The name of the reference (on the remote). message : str Rejection message from the remote. If None, the update was accepted. """ class CheckoutCallbacks(Payload): """Base class for pygit2 checkout callbacks. Inherit from this class and override the callbacks that you want to use in your class, which you can then pass to checkout operations. """ def __init__(self): super().__init__() def checkout_notify_flags(self) -> CheckoutNotify: """ Returns a bit mask of the notifications to receive from a checkout (a combination of enums.CheckoutNotify constants). By default, if you override `checkout_notify`, all notifications will be enabled. You can fine tune the notification types to enable by overriding `checkout_notify_flags`. Please note that the flags are only sampled once when checkout begins. You cannot change the flags while a checkout is in progress. """ if type(self).checkout_notify == CheckoutCallbacks.checkout_notify: # If the user hasn't overridden the notify function, # filter out all notifications. return CheckoutNotify.NONE else: # If the user provides their own notify function, # enable all notifications by default. return CheckoutNotify.ALL def checkout_notify( self, why: CheckoutNotify, path: str, baseline: Optional[DiffFile], target: Optional[DiffFile], workdir: Optional[DiffFile], ): """ Checkout will invoke an optional notification callback for certain cases - you pick which ones via `checkout_notify_flags`. Raising an exception from this callback will cancel the checkout. The exception will be propagated back and raised by the Repository.checkout_... call. Notification callbacks are made prior to modifying any files on disk, so canceling on any notification will still happen prior to any files being modified. """ pass def checkout_progress(self, path: str, completed_steps: int, total_steps: int): """ Optional callback to notify the consumer of checkout progress. """ pass class StashApplyCallbacks(CheckoutCallbacks): """Base class for pygit2 stash apply callbacks. Inherit from this class and override the callbacks that you want to use in your class, which you can then pass to stash apply or pop operations. """ def stash_apply_progress(self, progress: StashApplyProgress): """ Stash application progress notification function. `progress` is a StashApplyProgress constant. Raising an exception from this callback will abort the stash application. """ pass # # The context managers below wrap the calls to libgit2 functions, which them in # turn call to callbacks defined later in this module. These context managers # are used in the pygit2 API, see for instance remote.py # @contextmanager def git_clone_options(payload, opts=None): if opts is None: opts = ffi.new('git_clone_options *') C.git_clone_options_init(opts, C.GIT_CLONE_OPTIONS_VERSION) handle = ffi.new_handle(payload) # Plug callbacks if payload.repository: opts.repository_cb = C._repository_create_cb opts.repository_cb_payload = handle if payload.remote: opts.remote_cb = C._remote_create_cb opts.remote_cb_payload = handle # Give back control payload._stored_exception = None payload.clone_options = opts yield payload @contextmanager def git_fetch_options(payload, opts=None): if payload is None: payload = RemoteCallbacks() if opts is None: opts = ffi.new('git_fetch_options *') C.git_fetch_options_init(opts, C.GIT_FETCH_OPTIONS_VERSION) # Plug callbacks opts.callbacks.sideband_progress = C._sideband_progress_cb opts.callbacks.transfer_progress = C._transfer_progress_cb opts.callbacks.update_tips = C._update_tips_cb opts.callbacks.credentials = C._credentials_cb opts.callbacks.certificate_check = C._certificate_check_cb # Payload handle = ffi.new_handle(payload) opts.callbacks.payload = handle # Give back control payload.fetch_options = opts payload._stored_exception = None yield payload @contextmanager def git_push_options(payload, opts=None): if payload is None: payload = RemoteCallbacks() opts = ffi.new('git_push_options *') C.git_push_options_init(opts, C.GIT_PUSH_OPTIONS_VERSION) # Plug callbacks opts.callbacks.sideband_progress = C._sideband_progress_cb opts.callbacks.transfer_progress = C._transfer_progress_cb opts.callbacks.update_tips = C._update_tips_cb opts.callbacks.credentials = C._credentials_cb opts.callbacks.certificate_check = C._certificate_check_cb opts.callbacks.push_update_reference = C._push_update_reference_cb # Payload handle = ffi.new_handle(payload) opts.callbacks.payload = handle # Give back control payload.push_options = opts payload._stored_exception = None yield payload @contextmanager def git_remote_callbacks(payload): if payload is None: payload = RemoteCallbacks() cdata = ffi.new('git_remote_callbacks *') C.git_remote_init_callbacks(cdata, C.GIT_REMOTE_CALLBACKS_VERSION) # Plug callbacks cdata.credentials = C._credentials_cb cdata.update_tips = C._update_tips_cb cdata.certificate_check = C._certificate_check_cb # Payload handle = ffi.new_handle(payload) cdata.payload = handle # Give back control payload._stored_exception = None payload.remote_callbacks = cdata yield payload # # C callbacks # # These functions are called by libgit2. They cannot raise execptions, since # they return to libgit2, they can only send back error codes. # # They cannot be overriden, but sometimes the only thing these functions do is # to proxy the call to a user defined function. If user defined functions # raises an exception, the callback must store it somewhere and return # GIT_EUSER to libgit2, then the outer Python code will be able to reraise the # exception. # def libgit2_callback(f): @wraps(f) def wrapper(*args): data = ffi.from_handle(args[-1]) args = args[:-1] + (data,) try: return f(*args) except Passthrough: # A user defined callback can raise Passthrough to decline to act; # then libgit2 will behave as if there was no callback set in the # first place. return C.GIT_PASSTHROUGH except BaseException as e: # Keep the exception to be re-raised later, and inform libgit2 that # the user defined callback has failed. data._stored_exception = e return C.GIT_EUSER return ffi.def_extern()(wrapper) def libgit2_callback_void(f): @wraps(f) def wrapper(*args): data = ffi.from_handle(args[-1]) args = args[:-1] + (data,) try: f(*args) except Passthrough: # A user defined callback can raise Passthrough to decline to act; # then libgit2 will behave as if there was no callback set in the # first place. pass # Function returns void except BaseException as e: # Keep the exception to be re-raised later data._stored_exception = e pass # Function returns void, so we can't do much here. return ffi.def_extern()(wrapper) @libgit2_callback def _certificate_check_cb(cert_i, valid, host, data): # We want to simulate what should happen if libgit2 supported pass-through # for this callback. For SSH, 'valid' is always False, because it doesn't # look at known_hosts, but we do want to let it through in order to do what # libgit2 would if the callback were not set. try: is_ssh = cert_i.cert_type == C.GIT_CERT_HOSTKEY_LIBSSH2 # python's parsing is deep in the libraries and assumes an OpenSSL-owned cert val = data.certificate_check(None, bool(valid), ffi.string(host)) if not val: return C.GIT_ECERTIFICATE except Passthrough: if is_ssh: return 0 elif valid: return 0 else: return C.GIT_ECERTIFICATE return 0 @libgit2_callback def _credentials_cb(cred_out, url, username, allowed, data): credentials = getattr(data, 'credentials', None) if not credentials: return 0 # convert int flags to enum before forwarding to user code allowed = CredentialType(allowed) ccred = get_credentials(credentials, url, username, allowed) cred_out[0] = ccred[0] return 0 @libgit2_callback def _push_update_reference_cb(ref, msg, data): push_update_reference = getattr(data, 'push_update_reference', None) if not push_update_reference: return 0 refname = maybe_string(ref) message = maybe_string(msg) push_update_reference(refname, message) return 0 @libgit2_callback def _remote_create_cb(remote_out, repo, name, url, data): from .repository import Repository remote = data.remote( Repository._from_c(repo, False), ffi.string(name), ffi.string(url) ) remote_out[0] = remote._remote # we no longer own the C object remote._remote = ffi.NULL return 0 @libgit2_callback def _repository_create_cb(repo_out, path, bare, data): repository = data.repository(ffi.string(path), bare != 0) # we no longer own the C object repository._disown() repo_out[0] = repository._repo return 0 @libgit2_callback def _sideband_progress_cb(string, length, data): sideband_progress = getattr(data, 'sideband_progress', None) if not sideband_progress: return 0 s = ffi.string(string, length).decode('utf-8') sideband_progress(s) return 0 @libgit2_callback def _transfer_progress_cb(stats_ptr, data): from .remotes import TransferProgress transfer_progress = getattr(data, 'transfer_progress', None) if not transfer_progress: return 0 transfer_progress(TransferProgress(stats_ptr)) return 0 @libgit2_callback def _update_tips_cb(refname, a, b, data): update_tips = getattr(data, 'update_tips', None) if not update_tips: return 0 s = maybe_string(refname) a = Oid(raw=bytes(ffi.buffer(a)[:])) b = Oid(raw=bytes(ffi.buffer(b)[:])) update_tips(s, a, b) return 0 # # Other functions, used above. # def get_credentials(fn, url, username, allowed): """Call fn and return the credentials object.""" url_str = maybe_string(url) username_str = maybe_string(username) creds = fn(url_str, username_str, allowed) credential_type = getattr(creds, 'credential_type', None) credential_tuple = getattr(creds, 'credential_tuple', None) if not credential_type or not credential_tuple: raise TypeError('credential does not implement interface') cred_type = credential_type if not (allowed & cred_type): raise TypeError('invalid credential type') ccred = ffi.new('git_credential **') if cred_type == CredentialType.USERPASS_PLAINTEXT: name, passwd = credential_tuple err = C.git_credential_userpass_plaintext_new( ccred, to_bytes(name), to_bytes(passwd) ) elif cred_type == CredentialType.SSH_KEY: name, pubkey, privkey, passphrase = credential_tuple name = to_bytes(name) if pubkey is None and privkey is None: err = C.git_credential_ssh_key_from_agent(ccred, name) else: err = C.git_credential_ssh_key_new( ccred, name, to_bytes(pubkey), to_bytes(privkey), to_bytes(passphrase) ) elif cred_type == CredentialType.USERNAME: (name,) = credential_tuple err = C.git_credential_username_new(ccred, to_bytes(name)) elif cred_type == CredentialType.SSH_MEMORY: name, pubkey, privkey, passphrase = credential_tuple if pubkey is None and privkey is None: raise TypeError('SSH keys from memory are empty') err = C.git_credential_ssh_key_memory_new( ccred, to_bytes(name), to_bytes(pubkey), to_bytes(privkey), to_bytes(passphrase), ) else: raise TypeError('unsupported credential type') check_error(err) return ccred # # Checkout callbacks # @libgit2_callback def _checkout_notify_cb( why, path_cstr, baseline, target, workdir, data: CheckoutCallbacks ): pypath = maybe_string(path_cstr) pybaseline = DiffFile.from_c(ptr_to_bytes(baseline)) pytarget = DiffFile.from_c(ptr_to_bytes(target)) pyworkdir = DiffFile.from_c(ptr_to_bytes(workdir)) try: data.checkout_notify(why, pypath, pybaseline, pytarget, pyworkdir) except Passthrough: # Unlike most other operations with optional callbacks, checkout # doesn't support the GIT_PASSTHROUGH return code, so we must bypass # libgit2_callback's error handling and return 0 explicitly here. pass # If the user's callback has raised any other exception type, # it's caught by the libgit2_callback decorator by now. # So, return success code to libgit2. return 0 @libgit2_callback_void def _checkout_progress_cb(path, completed_steps, total_steps, data: CheckoutCallbacks): data.checkout_progress(maybe_string(path), completed_steps, total_steps) def _git_checkout_options( callbacks=None, strategy=None, directory=None, paths=None, c_checkout_options_ptr=None, ): if callbacks is None: payload = CheckoutCallbacks() else: payload = callbacks # Get handle to payload handle = ffi.new_handle(payload) # Create the options struct to pass if not c_checkout_options_ptr: opts = ffi.new('git_checkout_options *') else: opts = c_checkout_options_ptr check_error(C.git_checkout_options_init(opts, 1)) # References we need to keep to strings and so forth refs = [handle] # pygit2's default is SAFE | RECREATE_MISSING if strategy is None: strategy = CheckoutStrategy.SAFE | CheckoutStrategy.RECREATE_MISSING opts.checkout_strategy = int(strategy) if directory: target_dir = ffi.new('char[]', to_bytes(directory)) refs.append(target_dir) opts.target_directory = target_dir if paths: strarray = StrArray(paths) refs.append(strarray) opts.paths = strarray.ptr[0] # If we want to receive any notifications, set up notify_cb in the options notify_flags = payload.checkout_notify_flags() if notify_flags != CheckoutNotify.NONE: opts.notify_cb = C._checkout_notify_cb opts.notify_flags = int(notify_flags) opts.notify_payload = handle # Set up progress callback if the user has provided their own if type(payload).checkout_progress != CheckoutCallbacks.checkout_progress: opts.progress_cb = C._checkout_progress_cb opts.progress_payload = handle # Give back control payload.checkout_options = opts payload._ffi_handle = handle payload._refs = refs payload._stored_exception = None return payload @contextmanager def git_checkout_options(callbacks=None, strategy=None, directory=None, paths=None): yield _git_checkout_options( callbacks=callbacks, strategy=strategy, directory=directory, paths=paths ) # # Stash callbacks # @libgit2_callback def _stash_apply_progress_cb(progress: StashApplyProgress, data: StashApplyCallbacks): try: data.stash_apply_progress(progress) except Passthrough: # Unlike most other operations with optional callbacks, stash apply # doesn't support the GIT_PASSTHROUGH return code, so we must bypass # libgit2_callback's error handling and return 0 explicitly here. pass # If the user's callback has raised any other exception type, # it's caught by the libgit2_callback decorator by now. # So, return success code to libgit2. return 0 @contextmanager def git_stash_apply_options( callbacks=None, reinstate_index=False, strategy=None, directory=None, paths=None ): if callbacks is None: callbacks = StashApplyCallbacks() # Set up stash options stash_apply_options = ffi.new('git_stash_apply_options *') check_error(C.git_stash_apply_options_init(stash_apply_options, 1)) flags = reinstate_index * C.GIT_STASH_APPLY_REINSTATE_INDEX stash_apply_options.flags = flags # Now set up checkout options c_checkout_options_ptr = ffi.addressof(stash_apply_options.checkout_options) payload = _git_checkout_options( callbacks=callbacks, strategy=strategy, directory=directory, paths=paths, c_checkout_options_ptr=c_checkout_options_ptr, ) assert payload == callbacks assert payload.checkout_options == c_checkout_options_ptr # Set up stash progress callback if the user has provided their own if type(callbacks).stash_apply_progress != StashApplyCallbacks.stash_apply_progress: stash_apply_options.progress_cb = C._stash_apply_progress_cb stash_apply_options.progress_payload = payload._ffi_handle # Give back control payload.stash_apply_options = stash_apply_options yield payload libgit2-pygit2-a011e26/pygit2/config.py000066400000000000000000000253451473744024100176570ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. try: from functools import cached_property except ImportError: from cached_property import cached_property # Import from pygit2 from .errors import check_error from .ffi import ffi, C from .utils import to_bytes def str_to_bytes(value, name): if not isinstance(value, str): raise TypeError(f'{name} must be a string') return to_bytes(value) class ConfigIterator: def __init__(self, config, ptr): self._iter = ptr self._config = config def __del__(self): C.git_config_iterator_free(self._iter) def __iter__(self): return self def __next__(self): return self._next_entry() def _next_entry(self): centry = ffi.new('git_config_entry **') err = C.git_config_next(centry, self._iter) check_error(err) return ConfigEntry._from_c(centry[0], self) class ConfigMultivarIterator(ConfigIterator): def __next__(self): entry = self._next_entry() return entry.value class Config: """Git configuration management.""" def __init__(self, path=None): cconfig = ffi.new('git_config **') if not path: err = C.git_config_new(cconfig) else: path = str_to_bytes(path, 'path') err = C.git_config_open_ondisk(cconfig, path) check_error(err, io=True) self._config = cconfig[0] @classmethod def from_c(cls, repo, ptr): config = cls.__new__(cls) config._repo = repo config._config = ptr return config def __del__(self): try: C.git_config_free(self._config) except AttributeError: pass def _get(self, key): key = str_to_bytes(key, 'key') entry = ffi.new('git_config_entry **') err = C.git_config_get_entry(entry, self._config, key) return err, ConfigEntry._from_c(entry[0]) def _get_entry(self, key): err, entry = self._get(key) if err == C.GIT_ENOTFOUND: raise KeyError(key) check_error(err) return entry def __contains__(self, key): err, cstr = self._get(key) if err == C.GIT_ENOTFOUND: return False check_error(err) return True def __getitem__(self, key): """ When using the mapping interface, the value is returned as a string. In order to apply the git-config parsing rules, you can use :meth:`Config.get_bool` or :meth:`Config.get_int`. """ entry = self._get_entry(key) return entry.value def __setitem__(self, key, value): key = str_to_bytes(key, 'key') err = 0 if isinstance(value, bool): err = C.git_config_set_bool(self._config, key, value) elif isinstance(value, int): err = C.git_config_set_int64(self._config, key, value) else: err = C.git_config_set_string(self._config, key, to_bytes(value)) check_error(err) def __delitem__(self, key): key = str_to_bytes(key, 'key') err = C.git_config_delete_entry(self._config, key) check_error(err) def __iter__(self): """ Iterate over configuration entries, returning a ``ConfigEntry`` objects. These contain the name, level, and value of each configuration variable. Be aware that this may return multiple versions of each entry if they are set multiple times in the configuration files. """ citer = ffi.new('git_config_iterator **') err = C.git_config_iterator_new(citer, self._config) check_error(err) return ConfigIterator(self, citer[0]) def get_multivar(self, name, regex=None): """Get each value of a multivar ''name'' as a list of strings. The optional ''regex'' parameter is expected to be a regular expression to filter the variables we're interested in. """ name = str_to_bytes(name, 'name') regex = to_bytes(regex or None) citer = ffi.new('git_config_iterator **') err = C.git_config_multivar_iterator_new(citer, self._config, name, regex) check_error(err) return ConfigMultivarIterator(self, citer[0]) def set_multivar(self, name, regex, value): """Set a multivar ''name'' to ''value''. ''regexp'' is a regular expression to indicate which values to replace. """ name = str_to_bytes(name, 'name') regex = str_to_bytes(regex, 'regex') value = str_to_bytes(value, 'value') err = C.git_config_set_multivar(self._config, name, regex, value) check_error(err) def delete_multivar(self, name, regex): """Delete a multivar ''name''. ''regexp'' is a regular expression to indicate which values to delete. """ name = str_to_bytes(name, 'name') regex = str_to_bytes(regex, 'regex') err = C.git_config_delete_multivar(self._config, name, regex) check_error(err) def get_bool(self, key): """Look up *key* and parse its value as a boolean as per the git-config rules. Return a boolean value (True or False). Truthy values are: 'true', 1, 'on' or 'yes'. Falsy values are: 'false', 0, 'off' and 'no' """ entry = self._get_entry(key) res = ffi.new('int *') err = C.git_config_parse_bool(res, entry.c_value) check_error(err) return res[0] != 0 def get_int(self, key): """Look up *key* and parse its value as an integer as per the git-config rules. Return an integer. A value can have a suffix 'k', 'm' or 'g' which stand for 'kilo', 'mega' and 'giga' respectively. """ entry = self._get_entry(key) res = ffi.new('int64_t *') err = C.git_config_parse_int64(res, entry.c_value) check_error(err) return res[0] def add_file(self, path, level=0, force=0): """Add a config file instance to an existing config.""" err = C.git_config_add_file_ondisk( self._config, to_bytes(path), level, ffi.NULL, force ) check_error(err) def snapshot(self): """Create a snapshot from this Config object. This means that looking up multiple values will use the same version of the configuration files. """ ccfg = ffi.new('git_config **') err = C.git_config_snapshot(ccfg, self._config) check_error(err) return Config.from_c(self._repo, ccfg[0]) # # Methods to parse a string according to the git-config rules # @staticmethod def parse_bool(text): res = ffi.new('int *') err = C.git_config_parse_bool(res, to_bytes(text)) check_error(err) return res[0] != 0 @staticmethod def parse_int(text): res = ffi.new('int64_t *') err = C.git_config_parse_int64(res, to_bytes(text)) check_error(err) return res[0] # # Static methods to get specialized version of the config # @staticmethod def _from_found_config(fn): buf = ffi.new('git_buf *', (ffi.NULL, 0)) err = fn(buf) check_error(err, io=True) cpath = ffi.string(buf.ptr).decode('utf-8') C.git_buf_dispose(buf) return Config(cpath) @staticmethod def get_system_config(): """Return a object representing the system configuration file.""" return Config._from_found_config(C.git_config_find_system) @staticmethod def get_global_config(): """Return a object representing the global configuration file.""" return Config._from_found_config(C.git_config_find_global) @staticmethod def get_xdg_config(): """Return a object representing the global configuration file.""" return Config._from_found_config(C.git_config_find_xdg) class ConfigEntry: """An entry in a configuation object.""" @classmethod def _from_c(cls, ptr, iterator=None): """Builds the entry from a ``git_config_entry`` pointer. ``iterator`` must be a ``ConfigIterator`` instance if the entry was created during ``git_config_iterator`` actions. """ entry = cls.__new__(cls) entry._entry = ptr entry.iterator = iterator # It should be enough to keep a reference to iterator, so we only call # git_config_iterator_free when we've deleted all ConfigEntry objects. # But it's not, to reproduce the error comment the lines below and run # the script in https://github.com/libgit2/pygit2/issues/970 # So instead we load the Python object immmediately. Ideally we should # investigate libgit2 source code. if iterator is not None: entry.raw_name = entry.raw_name entry.raw_value = entry.raw_value entry.level = entry.level return entry def __del__(self): if self.iterator is None: C.git_config_entry_free(self._entry) @property def c_value(self): """The raw ``cData`` entry value.""" return self._entry.value @cached_property def raw_name(self): return ffi.string(self._entry.name) @cached_property def raw_value(self): return ffi.string(self.c_value) @cached_property def level(self): """The entry's ``git_config_level_t`` value.""" return self._entry.level @property def name(self): """The entry's name.""" return self.raw_name.decode('utf-8') @property def value(self): """The entry's value as a string.""" return self.raw_value.decode('utf-8') libgit2-pygit2-a011e26/pygit2/credentials.py000066400000000000000000000070711473744024100207030ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from .ffi import C from .enums import CredentialType class Username: """Username credentials This is an object suitable for passing to a remote's credentials callback and for returning from said callback. """ def __init__(self, username): self._username = username @property def credential_type(self) -> CredentialType: return CredentialType.USERNAME @property def credential_tuple(self): return (self._username,) def __call__(self, _url, _username, _allowed): return self class UserPass: """Username/Password credentials This is an object suitable for passing to a remote's credentials callback and for returning from said callback. """ def __init__(self, username, password): self._username = username self._password = password @property def credential_type(self) -> CredentialType: return CredentialType.USERPASS_PLAINTEXT @property def credential_tuple(self): return (self._username, self._password) def __call__(self, _url, _username, _allowed): return self class Keypair: """ SSH key pair credentials. This is an object suitable for passing to a remote's credentials callback and for returning from said callback. Parameters: username : str The username being used to authenticate with the remote server. pubkey : str The path to the user's public key file. privkey : str The path to the user's private key file. passphrase : str The password used to decrypt the private key file, or empty string if no passphrase is required. """ def __init__(self, username, pubkey, privkey, passphrase): self._username = username self._pubkey = pubkey self._privkey = privkey self._passphrase = passphrase @property def credential_type(self) -> CredentialType: return CredentialType.SSH_KEY @property def credential_tuple(self): return (self._username, self._pubkey, self._privkey, self._passphrase) def __call__(self, _url, _username, _allowed): return self class KeypairFromAgent(Keypair): def __init__(self, username): super().__init__(username, None, None, None) class KeypairFromMemory(Keypair): @property def credential_type(self) -> CredentialType: return CredentialType.SSH_MEMORY libgit2-pygit2-a011e26/pygit2/decl/000077500000000000000000000000001473744024100167365ustar00rootroot00000000000000libgit2-pygit2-a011e26/pygit2/decl/attr.h000066400000000000000000000015771473744024100200730ustar00rootroot00000000000000#define GIT_ATTR_CHECK_FILE_THEN_INDEX 0 #define GIT_ATTR_CHECK_INDEX_THEN_FILE 1 #define GIT_ATTR_CHECK_INDEX_ONLY 2 #define GIT_ATTR_CHECK_NO_SYSTEM 4 #define GIT_ATTR_CHECK_INCLUDE_HEAD 8 #define GIT_ATTR_CHECK_INCLUDE_COMMIT 16 #define GIT_ATTR_OPTIONS_VERSION ... typedef enum { GIT_ATTR_VALUE_UNSPECIFIED = 0, /**< The attribute has been left unspecified */ GIT_ATTR_VALUE_TRUE, /**< The attribute has been set */ GIT_ATTR_VALUE_FALSE, /**< The attribute has been unset */ GIT_ATTR_VALUE_STRING /**< This attribute has a value */ } git_attr_value_t; typedef struct { unsigned int version; unsigned int flags; git_oid *commit_id; git_oid attr_commit_id; } git_attr_options; int git_attr_get_ext( const char **value_out, git_repository *repo, git_attr_options *opts, const char *path, const char *name); git_attr_value_t git_attr_value(const char *attr); libgit2-pygit2-a011e26/pygit2/decl/blame.h000066400000000000000000000021621473744024100201700ustar00rootroot00000000000000#define GIT_BLAME_OPTIONS_VERSION ... typedef struct git_blame git_blame; typedef struct git_blame_options { unsigned int version; uint32_t flags; uint16_t min_match_characters; git_oid newest_commit; git_oid oldest_commit; size_t min_line; size_t max_line; } git_blame_options; typedef struct git_blame_hunk { size_t lines_in_hunk; git_oid final_commit_id; size_t final_start_line_number; git_signature *final_signature; git_signature *final_committer; git_oid orig_commit_id; const char *orig_path; size_t orig_start_line_number; git_signature *orig_signature; git_signature *orig_committer; const char *summary; char boundary; } git_blame_hunk; int git_blame_options_init( git_blame_options *opts, unsigned int version); uint32_t git_blame_get_hunk_count(git_blame *blame); const git_blame_hunk* git_blame_get_hunk_byindex( git_blame *blame, uint32_t index); const git_blame_hunk* git_blame_get_hunk_byline( git_blame *blame, size_t lineno); int git_blame_file( git_blame **out, git_repository *repo, const char *path, git_blame_options *options); void git_blame_free(git_blame *blame); libgit2-pygit2-a011e26/pygit2/decl/buffer.h000066400000000000000000000001601473744024100203550ustar00rootroot00000000000000typedef struct { char *ptr; size_t reserved; size_t size; } git_buf; void git_buf_dispose(git_buf *buffer); libgit2-pygit2-a011e26/pygit2/decl/callbacks.h000066400000000000000000000026661473744024100210400ustar00rootroot00000000000000extern "Python" int _certificate_check_cb( git_cert *cert, int valid, const char *host, void *payload); extern "Python" int _credentials_cb( git_credential **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload); extern "Python" int _push_update_reference_cb( const char *refname, const char *status, void *data); extern "Python" int _remote_create_cb( git_remote **out, git_repository *repo, const char *name, const char *url, void *payload); extern "Python" int _repository_create_cb( git_repository **out, const char *path, int bare, void *payload); extern "Python" int _sideband_progress_cb( const char *str, int len, void *payload); extern "Python" int _transfer_progress_cb( const git_indexer_progress *stats, void *payload); extern "Python" int _update_tips_cb( const char *refname, const git_oid *a, const git_oid *b, void *data); /* Checkout */ extern "Python" int _checkout_notify_cb( git_checkout_notify_t why, const char *path, const git_diff_file *baseline, const git_diff_file *target, const git_diff_file *workdir, void *payload); extern "Python" void _checkout_progress_cb( const char *path, size_t completed_steps, size_t total_steps, void *payload); /* Stash */ extern "Python" int _stash_apply_progress_cb( git_stash_apply_progress_t progress, void *payload); libgit2-pygit2-a011e26/pygit2/decl/checkout.h000066400000000000000000000034421473744024100207170ustar00rootroot00000000000000typedef enum { GIT_CHECKOUT_NOTIFY_NONE = 0, GIT_CHECKOUT_NOTIFY_CONFLICT = 1, GIT_CHECKOUT_NOTIFY_DIRTY = 2, GIT_CHECKOUT_NOTIFY_UPDATED = 4, GIT_CHECKOUT_NOTIFY_UNTRACKED = 8, GIT_CHECKOUT_NOTIFY_IGNORED = 16, GIT_CHECKOUT_NOTIFY_ALL = 0x0FFFF } git_checkout_notify_t; typedef int (*git_checkout_notify_cb)( git_checkout_notify_t why, const char *path, const git_diff_file *baseline, const git_diff_file *target, const git_diff_file *workdir, void *payload); typedef void (*git_checkout_progress_cb)( const char *path, size_t completed_steps, size_t total_steps, void *payload); typedef struct { size_t mkdir_calls; size_t stat_calls; size_t chmod_calls; } git_checkout_perfdata; typedef void (*git_checkout_perfdata_cb)( const git_checkout_perfdata *perfdata, void *payload); typedef struct git_checkout_options { unsigned int version; unsigned int checkout_strategy; int disable_filters; unsigned int dir_mode; unsigned int file_mode; int file_open_flags; unsigned int notify_flags; git_checkout_notify_cb notify_cb; void *notify_payload; git_checkout_progress_cb progress_cb; void *progress_payload; git_strarray paths; git_tree *baseline; git_index *baseline_index; const char *target_directory; const char *ancestor_label; const char *our_label; const char *their_label; git_checkout_perfdata_cb perfdata_cb; void *perfdata_payload; } git_checkout_options; int git_checkout_options_init( git_checkout_options *opts, unsigned int version); int git_checkout_tree( git_repository *repo, const git_object *treeish, const git_checkout_options *opts); int git_checkout_head( git_repository *repo, const git_checkout_options *opts); int git_checkout_index( git_repository *repo, git_index *index, const git_checkout_options *opts); libgit2-pygit2-a011e26/pygit2/decl/clone.h000066400000000000000000000016701473744024100202130ustar00rootroot00000000000000#define GIT_CLONE_OPTIONS_VERSION ... typedef int (*git_remote_create_cb)( git_remote **out, git_repository *repo, const char *name, const char *url, void *payload); typedef int (*git_repository_create_cb)( git_repository **out, const char *path, int bare, void *payload); typedef enum { GIT_CLONE_LOCAL_AUTO, GIT_CLONE_LOCAL, GIT_CLONE_NO_LOCAL, GIT_CLONE_LOCAL_NO_LINKS, } git_clone_local_t; typedef struct git_clone_options { unsigned int version; git_checkout_options checkout_opts; git_fetch_options fetch_opts; int bare; git_clone_local_t local; const char* checkout_branch; git_repository_create_cb repository_cb; void *repository_cb_payload; git_remote_create_cb remote_cb; void *remote_cb_payload; } git_clone_options; int git_clone_options_init( git_clone_options *opts, unsigned int version); int git_clone( git_repository **out, const char *url, const char *local_path, const git_clone_options *options); libgit2-pygit2-a011e26/pygit2/decl/commit.h000066400000000000000000000006571473744024100204070ustar00rootroot00000000000000int git_commit_amend( git_oid *id, const git_commit *commit_to_amend, const char *update_ref, const git_signature *author, const git_signature *committer, const char *message_encoding, const char *message, const git_tree *tree); int git_annotated_commit_lookup( git_annotated_commit **out, git_repository *repo, const git_oid *id); void git_annotated_commit_free(git_annotated_commit *commit); libgit2-pygit2-a011e26/pygit2/decl/common.h000066400000000000000000000003261473744024100204000ustar00rootroot00000000000000#define GIT_PATH_MAX ... typedef enum { GIT_FEATURE_THREADS = (1 << 0), GIT_FEATURE_HTTPS = (1 << 1), GIT_FEATURE_SSH = (1 << 2), GIT_FEATURE_NSEC = (1 << 3) } git_feature_t; int git_libgit2_features(void); libgit2-pygit2-a011e26/pygit2/decl/config.h000066400000000000000000000041721473744024100203600ustar00rootroot00000000000000typedef struct git_config_iterator git_config_iterator; typedef enum { GIT_CONFIG_LEVEL_PROGRAMDATA = 1, GIT_CONFIG_LEVEL_SYSTEM = 2, GIT_CONFIG_LEVEL_XDG = 3, GIT_CONFIG_LEVEL_GLOBAL = 4, GIT_CONFIG_LEVEL_LOCAL = 5, GIT_CONFIG_LEVEL_WORKTREE = 6, GIT_CONFIG_LEVEL_APP = 7, GIT_CONFIG_HIGHEST_LEVEL = -1 } git_config_level_t; typedef struct git_config_entry { const char *name; const char *value; const char *backend_type; const char *origin_path; unsigned int include_depth; git_config_level_t level; } git_config_entry; void git_config_entry_free(git_config_entry *); void git_config_free(git_config *cfg); int git_config_get_entry( git_config_entry **out, const git_config *cfg, const char *name); int git_config_get_string(const char **out, const git_config *cfg, const char *name); int git_config_set_string(git_config *cfg, const char *name, const char *value); int git_config_set_bool(git_config *cfg, const char *name, int value); int git_config_set_int64(git_config *cfg, const char *name, int64_t value); int git_config_parse_bool(int *out, const char *value); int git_config_parse_int64(int64_t *out, const char *value); int git_config_delete_entry(git_config *cfg, const char *name); int git_config_add_file_ondisk( git_config *cfg, const char *path, git_config_level_t level, const git_repository *repo, int force); int git_config_iterator_new(git_config_iterator **out, const git_config *cfg); int git_config_next(git_config_entry **entry, git_config_iterator *iter); void git_config_iterator_free(git_config_iterator *iter); int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp); int git_config_new(git_config **out); int git_config_snapshot(git_config **out, git_config *config); int git_config_open_ondisk(git_config **out, const char *path); int git_config_find_system(git_buf *out); int git_config_find_global(git_buf *out); int git_config_find_xdg(git_buf *out); libgit2-pygit2-a011e26/pygit2/decl/describe.h000066400000000000000000000023141473744024100206670ustar00rootroot00000000000000typedef enum { GIT_DESCRIBE_DEFAULT, GIT_DESCRIBE_TAGS, GIT_DESCRIBE_ALL, } git_describe_strategy_t; typedef struct git_describe_options { unsigned int version; unsigned int max_candidates_tags; unsigned int describe_strategy; const char *pattern; int only_follow_first_parent; int show_commit_oid_as_fallback; } git_describe_options; #define GIT_DESCRIBE_OPTIONS_VERSION 1 int git_describe_options_init(git_describe_options *opts, unsigned int version); typedef struct { unsigned int version; unsigned int abbreviated_size; int always_use_long_format; const char *dirty_suffix; } git_describe_format_options; #define GIT_DESCRIBE_FORMAT_OPTIONS_VERSION 1 int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version); typedef struct git_describe_result git_describe_result; int git_describe_commit( git_describe_result **result, git_object *committish, git_describe_options *opts); int git_describe_workdir( git_describe_result **out, git_repository *repo, git_describe_options *opts); int git_describe_format( git_buf *out, const git_describe_result *result, const git_describe_format_options *opts); void git_describe_result_free(git_describe_result *result); libgit2-pygit2-a011e26/pygit2/decl/diff.h000066400000000000000000000041311473744024100200160ustar00rootroot00000000000000typedef struct git_diff git_diff; typedef enum { GIT_DELTA_UNMODIFIED = 0, GIT_DELTA_ADDED = 1, GIT_DELTA_DELETED = 2, GIT_DELTA_MODIFIED = 3, GIT_DELTA_RENAMED = 4, GIT_DELTA_COPIED = 5, GIT_DELTA_IGNORED = 6, GIT_DELTA_UNTRACKED = 7, GIT_DELTA_TYPECHANGE = 8, GIT_DELTA_UNREADABLE = 9, GIT_DELTA_CONFLICTED = 10, } git_delta_t; typedef struct { git_oid id; const char *path; git_off_t size; uint32_t flags; uint16_t mode; uint16_t id_abbrev; } git_diff_file; typedef struct { git_delta_t status; uint32_t flags; uint16_t similarity; uint16_t nfiles; git_diff_file old_file; git_diff_file new_file; } git_diff_delta; typedef int (*git_diff_notify_cb)( const git_diff *diff_so_far, const git_diff_delta *delta_to_add, const char *matched_pathspec, void *payload); typedef int (*git_diff_progress_cb)( const git_diff *diff_so_far, const char *old_path, const char *new_path, void *payload); typedef struct { unsigned int version; uint32_t flags; git_submodule_ignore_t ignore_submodules; git_strarray pathspec; git_diff_notify_cb notify_cb; git_diff_progress_cb progress_cb; void *payload; uint32_t context_lines; uint32_t interhunk_lines; git_oid_t oid_type; uint16_t id_abbrev; git_off_t max_size; const char *old_prefix; const char *new_prefix; } git_diff_options; int git_diff_options_init( git_diff_options *opts, unsigned int version); typedef struct { int (*file_signature)( void **out, const git_diff_file *file, const char *fullpath, void *payload); int (*buffer_signature)( void **out, const git_diff_file *file, const char *buf, size_t buflen, void *payload); void (*free_signature)(void *sig, void *payload); int (*similarity)(int *score, void *siga, void *sigb, void *payload); void *payload; } git_diff_similarity_metric; int git_diff_tree_to_index( git_diff **diff, git_repository *repo, git_tree *old_tree, git_index *index, const git_diff_options *opts); int git_diff_index_to_workdir( git_diff **diff, git_repository *repo, git_index *index, const git_diff_options *opts); libgit2-pygit2-a011e26/pygit2/decl/errors.h000066400000000000000000000047061473744024100204320ustar00rootroot00000000000000typedef enum { GIT_OK = 0, /**< No error */ GIT_ERROR = -1, /**< Generic error */ GIT_ENOTFOUND = -3, /**< Requested object could not be found */ GIT_EEXISTS = -4, /**< Object exists preventing operation */ GIT_EAMBIGUOUS = -5, /**< More than one object matches */ GIT_EBUFS = -6, /**< Output buffer too short to hold data */ /** * GIT_EUSER is a special error that is never generated by libgit2 * code. You can return it from a callback (e.g to stop an iteration) * to know that it was generated by the callback and not by libgit2. */ GIT_EUSER = -7, GIT_EBAREREPO = -8, /**< Operation not allowed on bare repository */ GIT_EUNBORNBRANCH = -9, /**< HEAD refers to branch with no commits */ GIT_EUNMERGED = -10, /**< Merge in progress prevented operation */ GIT_ENONFASTFORWARD = -11, /**< Reference was not fast-forwardable */ GIT_EINVALIDSPEC = -12, /**< Name/ref spec was not in a valid format */ GIT_ECONFLICT = -13, /**< Checkout conflicts prevented operation */ GIT_ELOCKED = -14, /**< Lock file prevented operation */ GIT_EMODIFIED = -15, /**< Reference value does not match expected */ GIT_EAUTH = -16, /**< Authentication error */ GIT_ECERTIFICATE = -17, /**< Server certificate is invalid */ GIT_EAPPLIED = -18, /**< Patch/merge has already been applied */ GIT_EPEEL = -19, /**< The requested peel operation is not possible */ GIT_EEOF = -20, /**< Unexpected EOF */ GIT_EINVALID = -21, /**< Invalid operation or input */ GIT_EUNCOMMITTED = -22, /**< Uncommitted changes in index prevented operation */ GIT_EDIRECTORY = -23, /**< The operation is not valid for a directory */ GIT_EMERGECONFLICT = -24, /**< A merge conflict exists and cannot continue */ GIT_PASSTHROUGH = -30, /**< A user-configured callback refused to act */ GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */ GIT_RETRY = -32, /**< Internal only */ GIT_EMISMATCH = -33, /**< Hashsum mismatch in object */ GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */ GIT_EAPPLYFAIL = -35, /**< Patch application failed */ GIT_EOWNER = -36, /**< The object is not owned by the current user */ GIT_TIMEOUT = -37 /**< The operation timed out */ } git_error_code; typedef struct { char *message; int klass; } git_error; const git_error * git_error_last(void); libgit2-pygit2-a011e26/pygit2/decl/graph.h000066400000000000000000000002001473744024100202000ustar00rootroot00000000000000int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, const git_oid *local, const git_oid *upstream); libgit2-pygit2-a011e26/pygit2/decl/index.h000066400000000000000000000044331473744024100202220ustar00rootroot00000000000000typedef struct { int32_t seconds; uint32_t nanoseconds; } git_index_time; typedef struct git_index_entry { git_index_time ctime; git_index_time mtime; uint32_t dev; uint32_t ino; uint32_t mode; uint32_t uid; uint32_t gid; uint32_t file_size; git_oid id; uint16_t flags; uint16_t flags_extended; const char *path; } git_index_entry; typedef int (*git_index_matched_path_cb)( const char *path, const char *matched_pathspec, void *payload); void git_index_free(git_index *index); int git_index_open(git_index **out, const char *index_path); int git_index_read(git_index *index, int force); int git_index_write(git_index *index); size_t git_index_entrycount(const git_index *index); int git_index_find(size_t *at_pos, git_index *index, const char *path); int git_index_add_bypath(git_index *index, const char *path); int git_index_add(git_index *index, const git_index_entry *source_entry); int git_index_remove(git_index *index, const char *path, int stage); int git_index_read_tree(git_index *index, const git_tree *tree); int git_index_clear(git_index *index); int git_index_write_tree(git_oid *out, git_index *index); int git_index_write_tree_to(git_oid *out, git_index *index, git_repository *repo); const git_index_entry * git_index_get_bypath( git_index *index, const char *path, int stage); const git_index_entry * git_index_get_byindex( git_index *index, size_t n); int git_index_add_all( git_index *index, const git_strarray *pathspec, unsigned int flags, git_index_matched_path_cb callback, void *payload); int git_index_remove_all( git_index *index, const git_strarray *pathspec, git_index_matched_path_cb callback, void *payload); int git_index_has_conflicts(const git_index *index); void git_index_conflict_iterator_free( git_index_conflict_iterator *iterator); int git_index_conflict_iterator_new( git_index_conflict_iterator **iterator_out, git_index *index); int git_index_conflict_get( const git_index_entry **ancestor_out, const git_index_entry **our_out, const git_index_entry **their_out, git_index *index, const char *path); int git_index_conflict_next( const git_index_entry **ancestor_out, const git_index_entry **our_out, const git_index_entry **their_out, git_index_conflict_iterator *iterator); int git_index_conflict_remove(git_index *index, const char *path); libgit2-pygit2-a011e26/pygit2/decl/indexer.h000066400000000000000000000005441473744024100205500ustar00rootroot00000000000000typedef struct git_indexer_progress { unsigned int total_objects; unsigned int indexed_objects; unsigned int received_objects; unsigned int local_objects; unsigned int total_deltas; unsigned int indexed_deltas; size_t received_bytes; } git_indexer_progress; typedef int (*git_indexer_progress_cb)(const git_indexer_progress *stats, void *payload); libgit2-pygit2-a011e26/pygit2/decl/merge.h000066400000000000000000000046231473744024100202130ustar00rootroot00000000000000#define GIT_MERGE_OPTIONS_VERSION 1 typedef enum { GIT_MERGE_FIND_RENAMES = 1, GIT_MERGE_FAIL_ON_CONFLICT = 2, GIT_MERGE_SKIP_REUC = 4, GIT_MERGE_NO_RECURSIVE = 8, GIT_MERGE_VIRTUAL_BASE = (1 << 4), } git_merge_flag_t; typedef enum { GIT_MERGE_FILE_FAVOR_NORMAL = 0, GIT_MERGE_FILE_FAVOR_OURS = 1, GIT_MERGE_FILE_FAVOR_THEIRS = 2, GIT_MERGE_FILE_FAVOR_UNION = 3, } git_merge_file_favor_t; typedef enum { GIT_MERGE_FILE_DEFAULT = 0, GIT_MERGE_FILE_STYLE_MERGE = 1, GIT_MERGE_FILE_STYLE_DIFF3 = 2, GIT_MERGE_FILE_SIMPLIFY_ALNUM = 4, GIT_MERGE_FILE_IGNORE_WHITESPACE = 8, GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE = 16, GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL = 32, GIT_MERGE_FILE_DIFF_PATIENCE = 64, GIT_MERGE_FILE_DIFF_MINIMAL = 128, GIT_MERGE_FILE_STYLE_ZDIFF3 = (1 << 8), GIT_MERGE_FILE_ACCEPT_CONFLICTS = (1 << 9), } git_merge_file_flag_t; typedef struct { unsigned int version; git_merge_flag_t flags; unsigned int rename_threshold; unsigned int target_limit; git_diff_similarity_metric *metric; unsigned int recursion_limit; const char *default_driver; git_merge_file_favor_t file_favor; git_merge_file_flag_t file_flags; } git_merge_options; typedef struct { unsigned int automergeable; const char *path; unsigned int mode; const char *ptr; size_t len; } git_merge_file_result; typedef struct { unsigned int version; const char *ancestor_label; const char *our_label; const char *their_label; git_merge_file_favor_t favor; git_merge_file_flag_t flags; unsigned short marker_size; } git_merge_file_options; int git_merge_options_init( git_merge_options *opts, unsigned int version); int git_merge_commits( git_index **out, git_repository *repo, const git_commit *our_commit, const git_commit *their_commit, const git_merge_options *opts); int git_merge_trees( git_index **out, git_repository *repo, const git_tree *ancestor_tree, const git_tree *our_tree, const git_tree *their_tree, const git_merge_options *opts); int git_merge_file_from_index( git_merge_file_result *out, git_repository *repo, const git_index_entry *ancestor, const git_index_entry *ours, const git_index_entry *theirs, const git_merge_file_options *opts); int git_merge( git_repository *repo, const git_annotated_commit **their_heads, size_t their_heads_len, const git_merge_options *merge_opts, const git_checkout_options *checkout_opts); void git_merge_file_result_free(git_merge_file_result *result); libgit2-pygit2-a011e26/pygit2/decl/net.h000066400000000000000000000001241473744024100176720ustar00rootroot00000000000000typedef enum { GIT_DIRECTION_FETCH = 0, GIT_DIRECTION_PUSH = 1 } git_direction; libgit2-pygit2-a011e26/pygit2/decl/oid.h000066400000000000000000000005631473744024100176660ustar00rootroot00000000000000typedef enum { GIT_OID_SHA1 = 1, /**< SHA1 */ } git_oid_t; typedef struct git_oid { unsigned char id[20]; } git_oid; // This should go to net.h but due to h_order in _run.py, ffi won't compile properly. typedef struct git_remote_head { int local; git_oid oid; git_oid loid; char *name; char *symref_target; } git_remote_head; libgit2-pygit2-a011e26/pygit2/decl/pack.h000066400000000000000000000013541473744024100200300ustar00rootroot00000000000000typedef int (*git_packbuilder_progress)( int stage, uint32_t current, uint32_t total, void *payload); int git_packbuilder_new(git_packbuilder **out, git_repository *repo); void git_packbuilder_free(git_packbuilder *pb); int git_packbuilder_insert(git_packbuilder *pb, const git_oid *id, const char *name); int git_packbuilder_insert_recur(git_packbuilder *pb, const git_oid *id, const char *name); size_t git_packbuilder_object_count(git_packbuilder *pb); int git_packbuilder_write(git_packbuilder *pb, const char *path, unsigned int mode, git_indexer_progress_cb progress_cb, void *progress_cb_payload); uint32_t git_packbuilder_written(git_packbuilder *pb); unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n); libgit2-pygit2-a011e26/pygit2/decl/proxy.h000066400000000000000000000006331473744024100202720ustar00rootroot00000000000000#define GIT_PROXY_OPTIONS_VERSION ... typedef enum { GIT_PROXY_NONE, GIT_PROXY_AUTO, GIT_PROXY_SPECIFIED, } git_proxy_t; typedef struct { unsigned int version; git_proxy_t type; const char *url; git_credential_acquire_cb credentials; git_transport_certificate_check_cb certificate_check; void *payload; } git_proxy_options; int git_proxy_options_init(git_proxy_options *opts, unsigned int version); libgit2-pygit2-a011e26/pygit2/decl/refspec.h000066400000000000000000000011471473744024100205410ustar00rootroot00000000000000const char * git_refspec_src(const git_refspec *refspec); const char * git_refspec_dst(const git_refspec *refspec); int git_refspec_force(const git_refspec *refspec); const char * git_refspec_string(const git_refspec *refspec); git_direction git_refspec_direction(const git_refspec *spec); int git_refspec_src_matches(const git_refspec *refspec, const char *refname); int git_refspec_dst_matches(const git_refspec *refspec, const char *refname); int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name); int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name); libgit2-pygit2-a011e26/pygit2/decl/remote.h000066400000000000000000000120171473744024100204030ustar00rootroot00000000000000#define GIT_FETCH_OPTIONS_VERSION ... #define GIT_PUSH_OPTIONS_VERSION ... #define GIT_REMOTE_CALLBACKS_VERSION ... typedef enum { GIT_REMOTE_REDIRECT_NONE, GIT_REMOTE_REDIRECT_INITIAL, GIT_REMOTE_REDIRECT_ALL } git_remote_redirect_t; typedef enum git_remote_completion_t { GIT_REMOTE_COMPLETION_DOWNLOAD, GIT_REMOTE_COMPLETION_INDEXING, GIT_REMOTE_COMPLETION_ERROR, } git_remote_completion_t; typedef int (*git_push_transfer_progress_cb)( unsigned int current, unsigned int total, size_t bytes, void* payload); typedef struct { char *src_refname; char *dst_refname; git_oid src; git_oid dst; } git_push_update; typedef int (*git_push_negotiation)(const git_push_update **updates, size_t len, void *payload); typedef int (*git_push_update_reference_cb)(const char *refname, const char *status, void *data); typedef int (*git_remote_ready_cb)(git_remote *remote, int direction, void *payload); typedef int (*git_url_resolve_cb)(git_buf *url_resolved, const char *url, int direction, void *payload); struct git_remote_callbacks { unsigned int version; git_transport_message_cb sideband_progress; int (*completion)(git_remote_completion_t type, void *data); git_credential_acquire_cb credentials; git_transport_certificate_check_cb certificate_check; git_indexer_progress_cb transfer_progress; int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data); git_packbuilder_progress pack_progress; git_push_transfer_progress_cb push_transfer_progress; git_push_update_reference_cb push_update_reference; git_push_negotiation push_negotiation; git_transport_cb transport; git_remote_ready_cb remote_ready; void *payload; git_url_resolve_cb resolve_url; int (*update_refs)(const char *refname, const git_oid *a, const git_oid *b, git_refspec *spec, void *data); }; typedef struct { unsigned int version; unsigned int pb_parallelism; git_remote_callbacks callbacks; git_proxy_options proxy_opts; git_remote_redirect_t follow_redirects; git_strarray custom_headers; git_strarray remote_push_options; } git_push_options; int git_push_options_init( git_push_options *opts, unsigned int version); typedef enum { GIT_FETCH_PRUNE_UNSPECIFIED, GIT_FETCH_PRUNE, GIT_FETCH_NO_PRUNE, } git_fetch_prune_t; typedef enum { GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED = 0, GIT_REMOTE_DOWNLOAD_TAGS_AUTO, GIT_REMOTE_DOWNLOAD_TAGS_NONE, GIT_REMOTE_DOWNLOAD_TAGS_ALL, } git_remote_autotag_option_t; typedef struct { int version; git_remote_callbacks callbacks; git_fetch_prune_t prune; unsigned int update_fetchhead; git_remote_autotag_option_t download_tags; git_proxy_options proxy_opts; int depth; git_remote_redirect_t follow_redirects; git_strarray custom_headers; } git_fetch_options; int git_fetch_options_init( git_fetch_options *opts, unsigned int version); int git_remote_list(git_strarray *out, git_repository *repo); int git_remote_lookup(git_remote **out, git_repository *repo, const char *name); int git_remote_create( git_remote **out, git_repository *repo, const char *name, const char *url); int git_remote_create_with_fetchspec( git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch); int git_remote_create_anonymous( git_remote **out, git_repository *repo, const char *url); int git_remote_delete(git_repository *repo, const char *name); const char * git_remote_name(const git_remote *remote); int git_remote_rename( git_strarray *problems, git_repository *repo, const char *name, const char *new_name); const char * git_remote_url(const git_remote *remote); int git_remote_set_url(git_repository *repo, const char *remote, const char* url); const char * git_remote_pushurl(const git_remote *remote); int git_remote_set_pushurl(git_repository *repo, const char *remote, const char* url); int git_remote_fetch( git_remote *remote, const git_strarray *refspecs, const git_fetch_options *opts, const char *reflog_message); int git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks); int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_push_options *opts); const git_indexer_progress * git_remote_stats(git_remote *remote); int git_remote_add_push(git_repository *repo, const char *remote, const char *refspec); int git_remote_add_fetch(git_repository *repo, const char *remote, const char *refspec); int git_remote_init_callbacks( git_remote_callbacks *opts, unsigned int version); size_t git_remote_refspec_count(const git_remote *remote); const git_refspec * git_remote_get_refspec(const git_remote *remote, size_t n); int git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote); int git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote); void git_remote_free(git_remote *remote); int git_remote_connect( git_remote *remote, int direction, const git_remote_callbacks *callbacks, const git_proxy_options *proxy_opts, const git_strarray *custom_headers); int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote); libgit2-pygit2-a011e26/pygit2/decl/repository.h000066400000000000000000000057361473744024100213410ustar00rootroot00000000000000#define GIT_REPOSITORY_INIT_OPTIONS_VERSION ... void git_repository_free(git_repository *repo); int git_repository_state_cleanup(git_repository *repo); int git_repository_config(git_config **out, git_repository *repo); int git_repository_config_snapshot(git_config **out, git_repository *repo); typedef enum { GIT_REPOSITORY_INIT_BARE = 1, GIT_REPOSITORY_INIT_NO_REINIT = 2, GIT_REPOSITORY_INIT_NO_DOTGIT_DIR = 4, GIT_REPOSITORY_INIT_MKDIR = 8, GIT_REPOSITORY_INIT_MKPATH = 16, GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = 32, GIT_REPOSITORY_INIT_RELATIVE_GITLINK = 64, } git_repository_init_flag_t; typedef enum { GIT_REPOSITORY_INIT_SHARED_UMASK = 0, GIT_REPOSITORY_INIT_SHARED_GROUP = 0002775, GIT_REPOSITORY_INIT_SHARED_ALL = 0002777, } git_repository_init_mode_t; typedef enum { GIT_REPOSITORY_STATE_NONE, GIT_REPOSITORY_STATE_MERGE, GIT_REPOSITORY_STATE_REVERT, GIT_REPOSITORY_STATE_REVERT_SEQUENCE, GIT_REPOSITORY_STATE_CHERRYPICK, GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE, GIT_REPOSITORY_STATE_BISECT, GIT_REPOSITORY_STATE_REBASE, GIT_REPOSITORY_STATE_REBASE_INTERACTIVE, GIT_REPOSITORY_STATE_REBASE_MERGE, GIT_REPOSITORY_STATE_APPLY_MAILBOX, GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE } git_repository_state_t; typedef struct { unsigned int version; uint32_t flags; uint32_t mode; const char *workdir_path; const char *description; const char *template_path; const char *initial_head; const char *origin_url; } git_repository_init_options; int git_repository_init_options_init( git_repository_init_options *opts, unsigned int version); int git_repository_init( git_repository **out, const char *path, unsigned is_bare); int git_repository_init_ext( git_repository **out, const char *repo_path, git_repository_init_options *opts); typedef enum { GIT_REPOSITORY_OPEN_NO_SEARCH = 1, GIT_REPOSITORY_OPEN_CROSS_FS = 2, GIT_REPOSITORY_OPEN_BARE = 4, GIT_REPOSITORY_OPEN_NO_DOTGIT = 8, GIT_REPOSITORY_OPEN_FROM_ENV = 16, } git_repository_open_flag_t; int git_repository_open_ext( git_repository **out, const char *path, unsigned int flags, const char *ceiling_dirs); int git_repository_set_head( git_repository* repo, const char* refname); int git_repository_set_head_detached( git_repository* repo, const git_oid* commitish); int git_repository_hashfile(git_oid *out, git_repository *repo, const char *path, git_object_t type, const char *as_path); int git_repository_ident(const char **name, const char **email, const git_repository *repo); int git_repository_set_ident(git_repository *repo, const char *name, const char *email); int git_repository_index(git_index **out, git_repository *repo); git_repository_state_t git_repository_state(git_repository *repo); int git_repository_message(git_buf *out, git_repository *repo); int git_repository_message_remove(git_repository *repo); int git_repository_submodule_cache_all(git_repository *repo); int git_repository_submodule_cache_clear(git_repository *repo); libgit2-pygit2-a011e26/pygit2/decl/revert.h000066400000000000000000000007371473744024100204250ustar00rootroot00000000000000#define GIT_REVERT_OPTIONS_VERSION ... typedef struct { unsigned int version; unsigned int mainline; git_merge_options merge_opts; git_checkout_options checkout_opts; } git_revert_options; int git_revert( git_repository *repo, git_commit *commit, const git_revert_options *given_opts); int git_revert_commit( git_index **out, git_repository *repo, git_commit *revert_commit, git_commit *our_commit, unsigned int mainline, const git_merge_options *merge_options); libgit2-pygit2-a011e26/pygit2/decl/stash.h000066400000000000000000000041251473744024100202330ustar00rootroot00000000000000#define GIT_STASH_APPLY_OPTIONS_VERSION 1 #define GIT_STASH_SAVE_OPTIONS_VERSION ... typedef int (*git_stash_cb)( size_t index, const char* message, const git_oid *stash_id, void *payload); typedef enum { GIT_STASH_APPLY_PROGRESS_NONE = 0, GIT_STASH_APPLY_PROGRESS_LOADING_STASH, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED, GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED, GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED, GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED, GIT_STASH_APPLY_PROGRESS_DONE, } git_stash_apply_progress_t; typedef int (*git_stash_apply_progress_cb)( git_stash_apply_progress_t progress, void *payload); typedef enum { GIT_STASH_DEFAULT = 0, GIT_STASH_KEEP_INDEX = 1, GIT_STASH_INCLUDE_UNTRACKED = 2, GIT_STASH_INCLUDE_IGNORED = 4, GIT_STASH_KEEP_ALL = 8, } git_stash_flags; typedef enum { GIT_STASH_APPLY_DEFAULT = 0, GIT_STASH_APPLY_REINSTATE_INDEX = 1, } git_stash_apply_flags; typedef struct git_stash_apply_options { unsigned int version; git_stash_apply_flags flags; git_checkout_options checkout_options; git_stash_apply_progress_cb progress_cb; void *progress_payload; } git_stash_apply_options; int git_stash_save( git_oid *out, git_repository *repo, const git_signature *stasher, const char *message, uint32_t flags); int git_stash_apply_options_init( git_stash_apply_options *opts, unsigned int version); int git_stash_apply( git_repository *repo, size_t index, const git_stash_apply_options *options); typedef struct git_stash_save_options { unsigned int version; uint32_t flags; const git_signature *stasher; const char *message; git_strarray paths; } git_stash_save_options; int git_stash_save_options_init( git_stash_save_options *opts, unsigned int version); int git_stash_save_with_opts( git_oid *out, git_repository *repo, const git_stash_save_options *opts); int git_stash_foreach( git_repository *repo, git_stash_cb callback, void *payload); int git_stash_drop( git_repository *repo, size_t index); int git_stash_pop( git_repository *repo, size_t index, const git_stash_apply_options *options); libgit2-pygit2-a011e26/pygit2/decl/strarray.h000066400000000000000000000001771473744024100207630ustar00rootroot00000000000000typedef struct git_strarray { char **strings; size_t count; } git_strarray; void git_strarray_dispose(git_strarray *array); libgit2-pygit2-a011e26/pygit2/decl/submodule.h000066400000000000000000000027531473744024100211150ustar00rootroot00000000000000#define GIT_SUBMODULE_UPDATE_OPTIONS_VERSION ... typedef struct git_submodule_update_options { unsigned int version; git_checkout_options checkout_opts; git_fetch_options fetch_opts; int allow_fetch; } git_submodule_update_options; int git_submodule_update_options_init( git_submodule_update_options *opts, unsigned int version); int git_submodule_add_setup( git_submodule **out, git_repository *repo, const char *url, const char *path, int use_gitlink); int git_submodule_clone( git_repository **out, git_submodule *submodule, const git_submodule_update_options *opts); int git_submodule_add_finalize(git_submodule *submodule); int git_submodule_update(git_submodule *submodule, int init, git_submodule_update_options *options); int git_submodule_lookup( git_submodule **out, git_repository *repo, const char *name); void git_submodule_free(git_submodule *submodule); int git_submodule_open(git_repository **repo, git_submodule *submodule); int git_submodule_init(git_submodule *submodule, int overwrite); int git_submodule_reload(git_submodule *submodule, int force); const char * git_submodule_name(git_submodule *submodule); const char * git_submodule_path(git_submodule *submodule); const char * git_submodule_url(git_submodule *submodule); const char * git_submodule_branch(git_submodule *submodule); const git_oid * git_submodule_head_id(git_submodule *submodule); int git_submodule_status(unsigned int *status, git_repository *repo, const char *name, git_submodule_ignore_t ignore); libgit2-pygit2-a011e26/pygit2/decl/transport.h000066400000000000000000000027601473744024100211500ustar00rootroot00000000000000typedef struct git_credential git_credential; typedef enum { GIT_CREDENTIAL_USERPASS_PLAINTEXT = (1u << 0), GIT_CREDENTIAL_SSH_KEY = (1u << 1), GIT_CREDENTIAL_SSH_CUSTOM = (1u << 2), GIT_CREDENTIAL_DEFAULT = (1u << 3), GIT_CREDENTIAL_SSH_INTERACTIVE = (1u << 4), GIT_CREDENTIAL_USERNAME = (1u << 5), GIT_CREDENTIAL_SSH_MEMORY = (1u << 6), } git_credential_t; typedef enum { GIT_CERT_SSH_MD5 = 1, GIT_CERT_SSH_SHA1 = 2, } git_cert_ssh_t; typedef struct { git_cert parent; git_cert_ssh_t type; unsigned char hash_md5[16]; unsigned char hash_sha1[20]; } git_cert_hostkey; typedef struct { git_cert parent; void *data; size_t len; } git_cert_x509; typedef int (*git_credential_acquire_cb)( git_credential **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload); typedef int (*git_transport_cb)(git_transport **out, git_remote *owner, void *param); int git_credential_username_new(git_credential **out, const char *username); int git_credential_userpass_plaintext_new( git_credential **out, const char *username, const char *password); int git_credential_ssh_key_new( git_credential **out, const char *username, const char *publickey, const char *privatekey, const char *passphrase); int git_credential_ssh_key_from_agent( git_credential **out, const char *username); int git_credential_ssh_key_memory_new( git_credential **out, const char *username, const char *publickey, const char *privatekey, const char *passphrase); libgit2-pygit2-a011e26/pygit2/decl/types.h000066400000000000000000000035241473744024100202570ustar00rootroot00000000000000typedef struct git_commit git_commit; typedef struct git_annotated_commit git_annotated_commit; typedef struct git_config git_config; typedef struct git_index git_index; typedef struct git_index_conflict_iterator git_index_conflict_iterator; typedef struct git_object git_object; typedef struct git_refspec git_refspec; typedef struct git_remote git_remote; typedef struct git_remote_callbacks git_remote_callbacks; typedef struct git_repository git_repository; typedef struct git_submodule git_submodule; typedef struct git_transport git_transport; typedef struct git_tree git_tree; typedef struct git_packbuilder git_packbuilder; typedef int64_t git_off_t; typedef int64_t git_time_t; typedef enum { GIT_REFERENCE_INVALID = 0, GIT_REFERENCE_DIRECT = 1, GIT_REFERENCE_SYMBOLIC = 2, GIT_REFERENCE_ALL = 3, } git_reference_t; typedef struct git_time { git_time_t time; int offset; char sign; } git_time; typedef struct git_signature { char *name; char *email; git_time when; } git_signature; typedef enum git_cert_t { GIT_CERT_NONE, GIT_CERT_X509, GIT_CERT_HOSTKEY_LIBSSH2, GIT_CERT_STRARRAY, } git_cert_t; typedef struct { git_cert_t cert_type; } git_cert; typedef int (*git_transport_message_cb)(const char *str, int len, void *payload); typedef int (*git_transport_certificate_check_cb)(git_cert *cert, int valid, const char *host, void *payload); typedef enum { GIT_SUBMODULE_IGNORE_UNSPECIFIED = -1, GIT_SUBMODULE_IGNORE_NONE = 1, GIT_SUBMODULE_IGNORE_UNTRACKED = 2, GIT_SUBMODULE_IGNORE_DIRTY = 3, GIT_SUBMODULE_IGNORE_ALL = 4, } git_submodule_ignore_t; typedef enum { GIT_OBJECT_ANY = ..., GIT_OBJECT_INVALID = ..., GIT_OBJECT_COMMIT = ..., GIT_OBJECT_TREE = ..., GIT_OBJECT_BLOB = ..., GIT_OBJECT_TAG = ..., GIT_OBJECT_OFS_DELTA = ..., GIT_OBJECT_REF_DELTA = ..., } git_object_t; libgit2-pygit2-a011e26/pygit2/enums.py000066400000000000000000001317571473744024100175460ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from enum import IntEnum, IntFlag from . import _pygit2 from .ffi import C class ApplyLocation(IntEnum): """Possible application locations for patches""" WORKDIR = _pygit2.GIT_APPLY_LOCATION_WORKDIR """ Apply the patch to the workdir, leaving the index untouched. This is the equivalent of `git apply` with no location argument. """ INDEX = _pygit2.GIT_APPLY_LOCATION_INDEX """ Apply the patch to the index, leaving the working directory untouched. This is the equivalent of `git apply --cached`. """ BOTH = _pygit2.GIT_APPLY_LOCATION_BOTH """ Apply the patch to both the working directory and the index. This is the equivalent of `git apply --index`. """ class AttrCheck(IntFlag): FILE_THEN_INDEX = C.GIT_ATTR_CHECK_FILE_THEN_INDEX INDEX_THEN_FILE = C.GIT_ATTR_CHECK_INDEX_THEN_FILE INDEX_ONLY = C.GIT_ATTR_CHECK_INDEX_ONLY NO_SYSTEM = C.GIT_ATTR_CHECK_NO_SYSTEM INCLUDE_HEAD = C.GIT_ATTR_CHECK_INCLUDE_HEAD INCLUDE_COMMIT = C.GIT_ATTR_CHECK_INCLUDE_COMMIT class BlameFlag(IntFlag): NORMAL = _pygit2.GIT_BLAME_NORMAL 'Normal blame, the default' TRACK_COPIES_SAME_FILE = _pygit2.GIT_BLAME_TRACK_COPIES_SAME_FILE 'Not yet implemented and reserved for future use (as of libgit2 1.9.0).' TRACK_COPIES_SAME_COMMIT_MOVES = _pygit2.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES 'Not yet implemented and reserved for future use (as of libgit2 1.9.0).' TRACK_COPIES_SAME_COMMIT_COPIES = _pygit2.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES 'Not yet implemented and reserved for future use (as of libgit2 1.9.0).' TRACK_COPIES_ANY_COMMIT_COPIES = _pygit2.GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES 'Not yet implemented and reserved for future use (as of libgit2 1.9.0).' FIRST_PARENT = _pygit2.GIT_BLAME_FIRST_PARENT 'Restrict the search of commits to those reachable following only the first parents.' USE_MAILMAP = _pygit2.GIT_BLAME_USE_MAILMAP """ Use mailmap file to map author and committer names and email addresses to canonical real names and email addresses. The mailmap will be read from the working directory, or HEAD in a bare repository. """ IGNORE_WHITESPACE = _pygit2.GIT_BLAME_IGNORE_WHITESPACE 'Ignore whitespace differences' class BlobFilter(IntFlag): CHECK_FOR_BINARY = _pygit2.GIT_BLOB_FILTER_CHECK_FOR_BINARY 'Do not apply filters to binary files.' NO_SYSTEM_ATTRIBUTES = _pygit2.GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES 'Filters will not load configuration from the system-wide `gitattributes` in `/etc` (or system equivalent).' ATTRIBUTES_FROM_HEAD = _pygit2.GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD 'Load filters from a `.gitattributes` file in the HEAD commit.' ATTRIBUTES_FROM_COMMIT = _pygit2.GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT 'Load filters from a `.gitattributes` file in the specified commit.' class BranchType(IntFlag): LOCAL = _pygit2.GIT_BRANCH_LOCAL REMOTE = _pygit2.GIT_BRANCH_REMOTE ALL = _pygit2.GIT_BRANCH_ALL class CheckoutNotify(IntFlag): """ Checkout notification flags Checkout will invoke an options notification callback (`CheckoutCallbacks.checkout_notify`) for certain cases - you pick which ones via `CheckoutCallbacks.checkout_notify_flags`. """ NONE = C.GIT_CHECKOUT_NOTIFY_NONE CONFLICT = C.GIT_CHECKOUT_NOTIFY_CONFLICT 'Invokes checkout on conflicting paths.' DIRTY = C.GIT_CHECKOUT_NOTIFY_DIRTY """ Notifies about "dirty" files, i.e. those that do not need an update but no longer match the baseline. Core git displays these files when checkout runs, but won't stop the checkout. """ UPDATED = C.GIT_CHECKOUT_NOTIFY_UPDATED 'Sends notification for any file changed.' UNTRACKED = C.GIT_CHECKOUT_NOTIFY_UNTRACKED 'Notifies about untracked files.' IGNORED = C.GIT_CHECKOUT_NOTIFY_IGNORED 'Notifies about ignored files.' ALL = C.GIT_CHECKOUT_NOTIFY_ALL class CheckoutStrategy(IntFlag): NONE = _pygit2.GIT_CHECKOUT_NONE 'Dry run, no actual updates' SAFE = _pygit2.GIT_CHECKOUT_SAFE """ Allow safe updates that cannot overwrite uncommitted data. If the uncommitted changes don't conflict with the checked out files, the checkout will still proceed, leaving the changes intact. Mutually exclusive with FORCE. FORCE takes precedence over SAFE. """ FORCE = _pygit2.GIT_CHECKOUT_FORCE """ Allow all updates to force working directory to look like index. Mutually exclusive with SAFE. FORCE takes precedence over SAFE. """ RECREATE_MISSING = _pygit2.GIT_CHECKOUT_RECREATE_MISSING """ Allow checkout to recreate missing files """ ALLOW_CONFLICTS = _pygit2.GIT_CHECKOUT_ALLOW_CONFLICTS """ Allow checkout to make safe updates even if conflicts are found """ REMOVE_UNTRACKED = _pygit2.GIT_CHECKOUT_REMOVE_UNTRACKED """ Remove untracked files not in index (that are not ignored) """ REMOVE_IGNORED = _pygit2.GIT_CHECKOUT_REMOVE_IGNORED """ Remove ignored files not in index """ UPDATE_ONLY = _pygit2.GIT_CHECKOUT_UPDATE_ONLY """ Only update existing files, don't create new ones """ DONT_UPDATE_INDEX = _pygit2.GIT_CHECKOUT_DONT_UPDATE_INDEX """ Normally checkout updates index entries as it goes; this stops that. Implies `DONT_WRITE_INDEX`. """ NO_REFRESH = _pygit2.GIT_CHECKOUT_NO_REFRESH """ Don't refresh index/config/etc before doing checkout """ SKIP_UNMERGED = _pygit2.GIT_CHECKOUT_SKIP_UNMERGED """ Allow checkout to skip unmerged files """ USE_OURS = _pygit2.GIT_CHECKOUT_USE_OURS """ For unmerged files, checkout stage 2 from index """ USE_THEIRS = _pygit2.GIT_CHECKOUT_USE_THEIRS """ For unmerged files, checkout stage 3 from index """ DISABLE_PATHSPEC_MATCH = _pygit2.GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH """ Treat pathspec as simple list of exact match file paths """ SKIP_LOCKED_DIRECTORIES = _pygit2.GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES """ Ignore directories in use, they will be left empty """ DONT_OVERWRITE_IGNORED = _pygit2.GIT_CHECKOUT_DONT_OVERWRITE_IGNORED """ Don't overwrite ignored files that exist in the checkout target """ CONFLICT_STYLE_MERGE = _pygit2.GIT_CHECKOUT_CONFLICT_STYLE_MERGE """ Write normal merge files for conflicts """ CONFLICT_STYLE_DIFF3 = _pygit2.GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 """ Include common ancestor data in diff3 format files for conflicts """ DONT_REMOVE_EXISTING = _pygit2.GIT_CHECKOUT_DONT_REMOVE_EXISTING """ Don't overwrite existing files or folders """ DONT_WRITE_INDEX = _pygit2.GIT_CHECKOUT_DONT_WRITE_INDEX """ Normally checkout writes the index upon completion; this prevents that. """ DRY_RUN = _pygit2.GIT_CHECKOUT_DRY_RUN """ Show what would be done by a checkout. Stop after sending notifications; don't update the working directory or index. """ CONFLICT_STYLE_ZDIFF3 = _pygit2.GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 """ Include common ancestor data in zdiff3 format for conflicts """ class ConfigLevel(IntEnum): """ Priority level of a config file. These priority levels correspond to the natural escalation logic (from higher to lower) when searching for config entries in git.git. """ PROGRAMDATA = _pygit2.GIT_CONFIG_LEVEL_PROGRAMDATA 'System-wide on Windows, for compatibility with portable git' SYSTEM = _pygit2.GIT_CONFIG_LEVEL_SYSTEM 'System-wide configuration file; /etc/gitconfig on Linux systems' XDG = _pygit2.GIT_CONFIG_LEVEL_XDG 'XDG compatible configuration file; typically ~/.config/git/config' GLOBAL = _pygit2.GIT_CONFIG_LEVEL_GLOBAL 'User-specific configuration file (also called Global configuration file); typically ~/.gitconfig' LOCAL = _pygit2.GIT_CONFIG_LEVEL_LOCAL 'Repository specific configuration file; $WORK_DIR/.git/config on non-bare repos' WORKTREE = _pygit2.GIT_CONFIG_LEVEL_WORKTREE 'Worktree specific configuration file; $GIT_DIR/config.worktree' APP = _pygit2.GIT_CONFIG_LEVEL_APP 'Application specific configuration file; freely defined by applications' HIGHEST_LEVEL = _pygit2.GIT_CONFIG_HIGHEST_LEVEL """Represents the highest level available config file (i.e. the most specific config file available that actually is loaded)""" class CredentialType(IntFlag): """ Supported credential types. This represents the various types of authentication methods supported by the library. """ USERPASS_PLAINTEXT = C.GIT_CREDENTIAL_USERPASS_PLAINTEXT 'A vanilla user/password request' SSH_KEY = C.GIT_CREDENTIAL_SSH_KEY 'An SSH key-based authentication request' SSH_CUSTOM = C.GIT_CREDENTIAL_SSH_CUSTOM 'An SSH key-based authentication request, with a custom signature' DEFAULT = C.GIT_CREDENTIAL_DEFAULT 'An NTLM/Negotiate-based authentication request.' SSH_INTERACTIVE = C.GIT_CREDENTIAL_SSH_INTERACTIVE 'An SSH interactive authentication request.' USERNAME = C.GIT_CREDENTIAL_USERNAME """ Username-only authentication request. Used as a pre-authentication step if the underlying transport (eg. SSH, with no username in its URL) does not know which username to use. """ SSH_MEMORY = C.GIT_CREDENTIAL_SSH_MEMORY """ An SSH key-based authentication request. Allows credentials to be read from memory instead of files. Note that because of differences in crypto backend support, it might not be functional. """ class DeltaStatus(IntEnum): """ What type of change is described by a DiffDelta? `RENAMED` and `COPIED` will only show up if you run `find_similar()` on the Diff object. `TYPECHANGE` only shows up given `INCLUDE_TYPECHANGE` in the DiffOption option flags (otherwise type changes will be split into ADDED / DELETED pairs). """ UNMODIFIED = _pygit2.GIT_DELTA_UNMODIFIED 'no changes' ADDED = _pygit2.GIT_DELTA_ADDED 'entry does not exist in old version' DELETED = _pygit2.GIT_DELTA_DELETED 'entry does not exist in new version' MODIFIED = _pygit2.GIT_DELTA_MODIFIED 'entry content changed between old and new' RENAMED = _pygit2.GIT_DELTA_RENAMED 'entry was renamed between old and new' COPIED = _pygit2.GIT_DELTA_COPIED 'entry was copied from another old entry' IGNORED = _pygit2.GIT_DELTA_IGNORED 'entry is ignored item in workdir' UNTRACKED = _pygit2.GIT_DELTA_UNTRACKED 'entry is untracked item in workdir' TYPECHANGE = _pygit2.GIT_DELTA_TYPECHANGE 'type of entry changed between old and new' UNREADABLE = _pygit2.GIT_DELTA_UNREADABLE 'entry is unreadable' CONFLICTED = _pygit2.GIT_DELTA_CONFLICTED 'entry in the index is conflicted' class DescribeStrategy(IntEnum): """ Reference lookup strategy. These behave like the --tags and --all options to git-describe, namely they say to look for any reference in either refs/tags/ or refs/ respectively. """ DEFAULT = _pygit2.GIT_DESCRIBE_DEFAULT TAGS = _pygit2.GIT_DESCRIBE_TAGS ALL = _pygit2.GIT_DESCRIBE_ALL class DiffFind(IntFlag): """Flags to control the behavior of diff rename/copy detection.""" FIND_BY_CONFIG = _pygit2.GIT_DIFF_FIND_BY_CONFIG """ Obey `diff.renames`. Overridden by any other FIND_... flag. """ FIND_RENAMES = _pygit2.GIT_DIFF_FIND_RENAMES """ Look for renames? (`--find-renames`) """ FIND_RENAMES_FROM_REWRITES = _pygit2.GIT_DIFF_FIND_RENAMES_FROM_REWRITES """ Consider old side of MODIFIED for renames? (`--break-rewrites=N`) """ FIND_COPIES = _pygit2.GIT_DIFF_FIND_COPIES """ Look for copies? (a la `--find-copies`). """ FIND_COPIES_FROM_UNMODIFIED = _pygit2.GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED """ Consider UNMODIFIED as copy sources? (`--find-copies-harder`). For this to work correctly, use INCLUDE_UNMODIFIED when the initial `Diff` is being generated. """ FIND_REWRITES = _pygit2.GIT_DIFF_FIND_REWRITES """ Mark significant rewrites for split (`--break-rewrites=/M`) """ BREAK_REWRITES = _pygit2.GIT_DIFF_BREAK_REWRITES """ Actually split large rewrites into delete/add pairs """ FIND_AND_BREAK_REWRITES = _pygit2.GIT_DIFF_FIND_AND_BREAK_REWRITES """ Mark rewrites for split and break into delete/add pairs """ FIND_FOR_UNTRACKED = _pygit2.GIT_DIFF_FIND_FOR_UNTRACKED """ Find renames/copies for UNTRACKED items in working directory. For this to work correctly, use INCLUDE_UNTRACKED when the initial `Diff` is being generated (and obviously the diff must be against the working directory for this to make sense). """ FIND_ALL = _pygit2.GIT_DIFF_FIND_ALL """ Turn on all finding features. """ FIND_IGNORE_LEADING_WHITESPACE = _pygit2.GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE """ Measure similarity ignoring leading whitespace (default) """ FIND_IGNORE_WHITESPACE = _pygit2.GIT_DIFF_FIND_IGNORE_WHITESPACE """ Measure similarity ignoring all whitespace """ FIND_DONT_IGNORE_WHITESPACE = _pygit2.GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE """ Measure similarity including all data """ FIND_EXACT_MATCH_ONLY = _pygit2.GIT_DIFF_FIND_EXACT_MATCH_ONLY """ Measure similarity only by comparing SHAs (fast and cheap) """ BREAK_REWRITES_FOR_RENAMES_ONLY = _pygit2.GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY """ Do not break rewrites unless they contribute to a rename. Normally, FIND_AND_BREAK_REWRITES will measure the self- similarity of modified files and split the ones that have changed a lot into a DELETE / ADD pair. Then the sides of that pair will be considered candidates for rename and copy detection. If you add this flag in and the split pair is *not* used for an actual rename or copy, then the modified record will be restored to a regular MODIFIED record instead of being split. """ FIND_REMOVE_UNMODIFIED = _pygit2.GIT_DIFF_FIND_REMOVE_UNMODIFIED """ Remove any UNMODIFIED deltas after find_similar is done. Using FIND_COPIES_FROM_UNMODIFIED to emulate the --find-copies-harder behavior requires building a diff with the INCLUDE_UNMODIFIED flag. If you do not want UNMODIFIED records in the final result, pass this flag to have them removed. """ class DiffFlag(IntFlag): """ Flags for the delta object and the file objects on each side. These flags are used for both the `flags` value of the `DiffDelta` and the flags for the `DiffFile` objects representing the old and new sides of the delta. Values outside of this public range should be considered reserved for internal or future use. """ BINARY = _pygit2.GIT_DIFF_FLAG_BINARY 'file(s) treated as binary data' NOT_BINARY = _pygit2.GIT_DIFF_FLAG_NOT_BINARY 'file(s) treated as text data' VALID_ID = _pygit2.GIT_DIFF_FLAG_VALID_ID '`id` value is known correct' EXISTS = _pygit2.GIT_DIFF_FLAG_EXISTS 'file exists at this side of the delta' VALID_SIZE = _pygit2.GIT_DIFF_FLAG_VALID_SIZE 'file size value is known correct' class DiffOption(IntFlag): """ Flags for diff options. A combination of these flags can be passed in via the `flags` value in `diff_*` functions. """ NORMAL = _pygit2.GIT_DIFF_NORMAL 'Normal diff, the default' REVERSE = _pygit2.GIT_DIFF_REVERSE 'Reverse the sides of the diff' INCLUDE_IGNORED = _pygit2.GIT_DIFF_INCLUDE_IGNORED 'Include ignored files in the diff' RECURSE_IGNORED_DIRS = _pygit2.GIT_DIFF_RECURSE_IGNORED_DIRS """ Even with INCLUDE_IGNORED, an entire ignored directory will be marked with only a single entry in the diff; this flag adds all files under the directory as IGNORED entries, too. """ INCLUDE_UNTRACKED = _pygit2.GIT_DIFF_INCLUDE_UNTRACKED 'Include untracked files in the diff' RECURSE_UNTRACKED_DIRS = _pygit2.GIT_DIFF_RECURSE_UNTRACKED_DIRS """ Even with INCLUDE_UNTRACKED, an entire untracked directory will be marked with only a single entry in the diff (a la what core Git does in `git status`); this flag adds *all* files under untracked directories as UNTRACKED entries, too. """ INCLUDE_UNMODIFIED = _pygit2.GIT_DIFF_INCLUDE_UNMODIFIED 'Include unmodified files in the diff' INCLUDE_TYPECHANGE = _pygit2.GIT_DIFF_INCLUDE_TYPECHANGE """ Normally, a type change between files will be converted into a DELETED record for the old and an ADDED record for the new; this options enabled the generation of TYPECHANGE delta records. """ INCLUDE_TYPECHANGE_TREES = _pygit2.GIT_DIFF_INCLUDE_TYPECHANGE_TREES """ Even with INCLUDE_TYPECHANGE, blob->tree changes still generally show as a DELETED blob. This flag tries to correctly label blob->tree transitions as TYPECHANGE records with new_file's mode set to tree. Note: the tree SHA will not be available. """ IGNORE_FILEMODE = _pygit2.GIT_DIFF_IGNORE_FILEMODE 'Ignore file mode changes' IGNORE_SUBMODULES = _pygit2.GIT_DIFF_IGNORE_SUBMODULES 'Treat all submodules as unmodified' IGNORE_CASE = _pygit2.GIT_DIFF_IGNORE_CASE 'Use case insensitive filename comparisons' INCLUDE_CASECHANGE = _pygit2.GIT_DIFF_INCLUDE_CASECHANGE """ May be combined with IGNORE_CASE to specify that a file that has changed case will be returned as an add/delete pair. """ DISABLE_PATHSPEC_MATCH = _pygit2.GIT_DIFF_DISABLE_PATHSPEC_MATCH """ If the pathspec is set in the diff options, this flags indicates that the paths will be treated as literal paths instead of fnmatch patterns. Each path in the list must either be a full path to a file or a directory. (A trailing slash indicates that the path will _only_ match a directory). If a directory is specified, all children will be included. """ SKIP_BINARY_CHECK = _pygit2.GIT_DIFF_SKIP_BINARY_CHECK """ Disable updating of the `binary` flag in delta records. This is useful when iterating over a diff if you don't need hunk and data callbacks and want to avoid having to load file completely. """ ENABLE_FAST_UNTRACKED_DIRS = _pygit2.GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS """ When diff finds an untracked directory, to match the behavior of core Git, it scans the contents for IGNORED and UNTRACKED files. If *all* contents are IGNORED, then the directory is IGNORED; if any contents are not IGNORED, then the directory is UNTRACKED. This is extra work that may not matter in many cases. This flag turns off that scan and immediately labels an untracked directory as UNTRACKED (changing the behavior to not match core Git). """ UPDATE_INDEX = _pygit2.GIT_DIFF_UPDATE_INDEX """ When diff finds a file in the working directory with stat information different from the index, but the OID ends up being the same, write the correct stat information into the index. Note: without this flag, diff will always leave the index untouched. """ INCLUDE_UNREADABLE = _pygit2.GIT_DIFF_INCLUDE_UNREADABLE 'Include unreadable files in the diff' INCLUDE_UNREADABLE_AS_UNTRACKED = _pygit2.GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED 'Include unreadable files in the diff' INDENT_HEURISTIC = _pygit2.GIT_DIFF_INDENT_HEURISTIC """ Use a heuristic that takes indentation and whitespace into account which generally can produce better diffs when dealing with ambiguous diff hunks. """ IGNORE_BLANK_LINES = _pygit2.GIT_DIFF_IGNORE_BLANK_LINES 'Ignore blank lines' FORCE_TEXT = _pygit2.GIT_DIFF_FORCE_TEXT 'Treat all files as text, disabling binary attributes & detection' FORCE_BINARY = _pygit2.GIT_DIFF_FORCE_BINARY 'Treat all files as binary, disabling text diffs' IGNORE_WHITESPACE = _pygit2.GIT_DIFF_IGNORE_WHITESPACE 'Ignore all whitespace' IGNORE_WHITESPACE_CHANGE = _pygit2.GIT_DIFF_IGNORE_WHITESPACE_CHANGE 'Ignore changes in amount of whitespace' IGNORE_WHITESPACE_EOL = _pygit2.GIT_DIFF_IGNORE_WHITESPACE_EOL 'Ignore whitespace at end of line' SHOW_UNTRACKED_CONTENT = _pygit2.GIT_DIFF_SHOW_UNTRACKED_CONTENT """ When generating patch text, include the content of untracked files. This automatically turns on INCLUDE_UNTRACKED but it does not turn on RECURSE_UNTRACKED_DIRS. Add that flag if you want the content of every single UNTRACKED file. """ SHOW_UNMODIFIED = _pygit2.GIT_DIFF_SHOW_UNMODIFIED """ When generating output, include the names of unmodified files if they are included in the git_diff. Normally these are skipped in the formats that list files (e.g. name-only, name-status, raw). Even with this, these will not be included in patch format. """ PATIENCE = _pygit2.GIT_DIFF_PATIENCE "Use the 'patience diff' algorithm" MINIMAL = _pygit2.GIT_DIFF_MINIMAL 'Take extra time to find minimal diff' SHOW_BINARY = _pygit2.GIT_DIFF_SHOW_BINARY """ Include the necessary deflate / delta information so that `git-apply` can apply given diff information to binary files. """ class DiffStatsFormat(IntFlag): """Formatting options for diff stats""" NONE = _pygit2.GIT_DIFF_STATS_NONE 'No stats' FULL = _pygit2.GIT_DIFF_STATS_FULL 'Full statistics, equivalent of `--stat`' SHORT = _pygit2.GIT_DIFF_STATS_SHORT 'Short statistics, equivalent of `--shortstat`' NUMBER = _pygit2.GIT_DIFF_STATS_NUMBER 'Number statistics, equivalent of `--numstat`' INCLUDE_SUMMARY = _pygit2.GIT_DIFF_STATS_INCLUDE_SUMMARY 'Extended header information such as creations, renames and mode changes, equivalent of `--summary`' class Feature(IntFlag): """ Combinations of these values describe the features with which libgit2 was compiled. """ THREADS = C.GIT_FEATURE_THREADS HTTPS = C.GIT_FEATURE_HTTPS SSH = C.GIT_FEATURE_SSH NSEC = C.GIT_FEATURE_NSEC class FetchPrune(IntEnum): """Acceptable prune settings when fetching.""" UNSPECIFIED = C.GIT_FETCH_PRUNE_UNSPECIFIED 'Use the setting from the configuration' PRUNE = C.GIT_FETCH_PRUNE """Force pruning on: remove any remote branch in the local repository that does not exist in the remote.""" NO_PRUNE = C.GIT_FETCH_NO_PRUNE """Force pruning off: always keep the remote branches.""" class FileMode(IntFlag): UNREADABLE = _pygit2.GIT_FILEMODE_UNREADABLE TREE = _pygit2.GIT_FILEMODE_TREE BLOB = _pygit2.GIT_FILEMODE_BLOB BLOB_EXECUTABLE = _pygit2.GIT_FILEMODE_BLOB_EXECUTABLE LINK = _pygit2.GIT_FILEMODE_LINK COMMIT = _pygit2.GIT_FILEMODE_COMMIT class FileStatus(IntFlag): """ Status flags for a single file. A combination of these values will be returned to indicate the status of a file. Status compares the working directory, the index, and the current HEAD of the repository. The `INDEX_...` set of flags represents the status of the file in the index relative to the HEAD, and the `WT_...` set of flags represents the status of the file in the working directory relative to the index. """ CURRENT = _pygit2.GIT_STATUS_CURRENT INDEX_NEW = _pygit2.GIT_STATUS_INDEX_NEW INDEX_MODIFIED = _pygit2.GIT_STATUS_INDEX_MODIFIED INDEX_DELETED = _pygit2.GIT_STATUS_INDEX_DELETED INDEX_RENAMED = _pygit2.GIT_STATUS_INDEX_RENAMED INDEX_TYPECHANGE = _pygit2.GIT_STATUS_INDEX_TYPECHANGE WT_NEW = _pygit2.GIT_STATUS_WT_NEW WT_MODIFIED = _pygit2.GIT_STATUS_WT_MODIFIED WT_DELETED = _pygit2.GIT_STATUS_WT_DELETED WT_TYPECHANGE = _pygit2.GIT_STATUS_WT_TYPECHANGE WT_RENAMED = _pygit2.GIT_STATUS_WT_RENAMED WT_UNREADABLE = _pygit2.GIT_STATUS_WT_UNREADABLE IGNORED = _pygit2.GIT_STATUS_IGNORED CONFLICTED = _pygit2.GIT_STATUS_CONFLICTED class FilterFlag(IntFlag): """Filter option flags.""" DEFAULT = _pygit2.GIT_FILTER_DEFAULT ALLOW_UNSAFE = _pygit2.GIT_FILTER_ALLOW_UNSAFE "Don't error for `safecrlf` violations, allow them to continue." NO_SYSTEM_ATTRIBUTES = _pygit2.GIT_FILTER_NO_SYSTEM_ATTRIBUTES "Don't load `/etc/gitattributes` (or the system equivalent)" ATTRIBUTES_FROM_HEAD = _pygit2.GIT_FILTER_ATTRIBUTES_FROM_HEAD 'Load attributes from `.gitattributes` in the root of HEAD' ATTRIBUTES_FROM_COMMIT = _pygit2.GIT_FILTER_ATTRIBUTES_FROM_COMMIT 'Load attributes from `.gitattributes` in a given commit. This can only be specified in a `git_filter_options`.' class FilterMode(IntEnum): """ Filters are applied in one of two directions: smudging - which is exporting a file from the Git object database to the working directory, and cleaning - which is importing a file from the working directory to the Git object database. These values control which direction of change is being applied. """ TO_WORKTREE = _pygit2.GIT_FILTER_TO_WORKTREE SMUDGE = _pygit2.GIT_FILTER_SMUDGE TO_ODB = _pygit2.GIT_FILTER_TO_ODB CLEAN = _pygit2.GIT_FILTER_CLEAN class MergeAnalysis(IntFlag): """The results of `Repository.merge_analysis` indicate the merge opportunities.""" NONE = _pygit2.GIT_MERGE_ANALYSIS_NONE 'No merge is possible. (Unused.)' NORMAL = _pygit2.GIT_MERGE_ANALYSIS_NORMAL """ A "normal" merge; both HEAD and the given merge input have diverged from their common ancestor. The divergent commits must be merged. """ UP_TO_DATE = _pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE """ All given merge inputs are reachable from HEAD, meaning the repository is up-to-date and no merge needs to be performed. """ FASTFORWARD = _pygit2.GIT_MERGE_ANALYSIS_FASTFORWARD """ The given merge input is a fast-forward from HEAD and no merge needs to be performed. Instead, the client can check out the given merge input. """ UNBORN = _pygit2.GIT_MERGE_ANALYSIS_UNBORN """ The HEAD of the current repository is "unborn" and does not point to a valid commit. No merge can be performed, but the caller may wish to simply set HEAD to the target commit(s). """ class MergeFavor(IntEnum): """ Merge file favor options for `Repository.merge` instruct the file-level merging functionality how to deal with conflicting regions of the files. """ NORMAL = C.GIT_MERGE_FILE_FAVOR_NORMAL """ When a region of a file is changed in both branches, a conflict will be recorded in the index so that `checkout` can produce a merge file with conflict markers in the working directory. This is the default. """ OURS = C.GIT_MERGE_FILE_FAVOR_OURS """ When a region of a file is changed in both branches, the file created in the index will contain the "ours" side of any conflicting region. The index will not record a conflict. """ THEIRS = C.GIT_MERGE_FILE_FAVOR_THEIRS """ When a region of a file is changed in both branches, the file created in the index will contain the "theirs" side of any conflicting region. The index will not record a conflict. """ UNION = C.GIT_MERGE_FILE_FAVOR_UNION """ When a region of a file is changed in both branches, the file created in the index will contain each unique line from each side, which has the result of combining both files. The index will not record a conflict. """ class MergeFileFlag(IntFlag): """File merging flags""" DEFAULT = C.GIT_MERGE_FILE_DEFAULT """ Defaults """ STYLE_MERGE = C.GIT_MERGE_FILE_STYLE_MERGE """ Create standard conflicted merge files """ STYLE_DIFF3 = C.GIT_MERGE_FILE_STYLE_DIFF3 """ Create diff3-style files """ SIMPLIFY_ALNUM = C.GIT_MERGE_FILE_SIMPLIFY_ALNUM """ Condense non-alphanumeric regions for simplified diff file """ IGNORE_WHITESPACE = C.GIT_MERGE_FILE_IGNORE_WHITESPACE """ Ignore all whitespace """ IGNORE_WHITESPACE_CHANGE = C.GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE """ Ignore changes in amount of whitespace """ IGNORE_WHITESPACE_EOL = C.GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL """ Ignore whitespace at end of line """ DIFF_PATIENCE = C.GIT_MERGE_FILE_DIFF_PATIENCE """ Use the "patience diff" algorithm """ DIFF_MINIMAL = C.GIT_MERGE_FILE_DIFF_MINIMAL """ Take extra time to find minimal diff """ STYLE_ZDIFF3 = C.GIT_MERGE_FILE_STYLE_ZDIFF3 """ Create zdiff3 ("zealous diff3")-style files """ ACCEPT_CONFLICTS = C.GIT_MERGE_FILE_ACCEPT_CONFLICTS """ Do not produce file conflicts when common regions have changed; keep the conflict markers in the file and accept that as the merge result. """ class MergeFlag(IntFlag): """ Flags for `Repository.merge` options. A combination of these flags can be passed in via the `flags` value. """ FIND_RENAMES = C.GIT_MERGE_FIND_RENAMES """ Detect renames that occur between the common ancestor and the "ours" side or the common ancestor and the "theirs" side. This will enable the ability to merge between a modified and renamed file. """ FAIL_ON_CONFLICT = C.GIT_MERGE_FAIL_ON_CONFLICT """ If a conflict occurs, exit immediately instead of attempting to continue resolving conflicts. The merge operation will raise GitError (GIT_EMERGECONFLICT) and no index will be returned. """ SKIP_REUC = C.GIT_MERGE_SKIP_REUC """ Do not write the REUC extension on the generated index. """ NO_RECURSIVE = C.GIT_MERGE_NO_RECURSIVE """ If the commits being merged have multiple merge bases, do not build a recursive merge base (by merging the multiple merge bases), instead simply use the first base. This flag provides a similar merge base to `git-merge-resolve`. """ VIRTUAL_BASE = C.GIT_MERGE_VIRTUAL_BASE """ Treat this merge as if it is to produce the virtual base of a recursive merge. This will ensure that there are no conflicts, any conflicting regions will keep conflict markers in the merge result. """ class MergePreference(IntFlag): """The user's stated preference for merges.""" NONE = _pygit2.GIT_MERGE_PREFERENCE_NONE 'No configuration was found that suggests a preferred behavior for merge.' NO_FASTFORWARD = _pygit2.GIT_MERGE_PREFERENCE_NO_FASTFORWARD """ There is a `merge.ff=false` configuration setting, suggesting that the user does not want to allow a fast-forward merge. """ FASTFORWARD_ONLY = _pygit2.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY """ There is a `merge.ff=only` configuration setting, suggesting that the user only wants fast-forward merges. """ class ObjectType(IntEnum): ANY = _pygit2.GIT_OBJECT_ANY 'Object can be any of the following' INVALID = _pygit2.GIT_OBJECT_INVALID 'Object is invalid.' COMMIT = _pygit2.GIT_OBJECT_COMMIT 'A commit object.' TREE = _pygit2.GIT_OBJECT_TREE 'A tree (directory listing) object.' BLOB = _pygit2.GIT_OBJECT_BLOB 'A file revision object.' TAG = _pygit2.GIT_OBJECT_TAG 'An annotated tag object.' OFS_DELTA = _pygit2.GIT_OBJECT_OFS_DELTA 'A delta, base is given by an offset.' REF_DELTA = _pygit2.GIT_OBJECT_REF_DELTA 'A delta, base is given by object id.' class Option(IntEnum): """Global libgit2 library options""" # Commented out values --> exists in libgit2 but not supported in pygit2's options.c yet GET_MWINDOW_SIZE = _pygit2.GIT_OPT_GET_MWINDOW_SIZE SET_MWINDOW_SIZE = _pygit2.GIT_OPT_SET_MWINDOW_SIZE GET_MWINDOW_MAPPED_LIMIT = _pygit2.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT SET_MWINDOW_MAPPED_LIMIT = _pygit2.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT GET_SEARCH_PATH = _pygit2.GIT_OPT_GET_SEARCH_PATH SET_SEARCH_PATH = _pygit2.GIT_OPT_SET_SEARCH_PATH SET_CACHE_OBJECT_LIMIT = _pygit2.GIT_OPT_SET_CACHE_OBJECT_LIMIT SET_CACHE_MAX_SIZE = _pygit2.GIT_OPT_SET_CACHE_MAX_SIZE ENABLE_CACHING = _pygit2.GIT_OPT_ENABLE_CACHING GET_CACHED_MEMORY = _pygit2.GIT_OPT_GET_CACHED_MEMORY GET_TEMPLATE_PATH = _pygit2.GIT_OPT_GET_TEMPLATE_PATH SET_TEMPLATE_PATH = _pygit2.GIT_OPT_SET_TEMPLATE_PATH SET_SSL_CERT_LOCATIONS = _pygit2.GIT_OPT_SET_SSL_CERT_LOCATIONS SET_USER_AGENT = _pygit2.GIT_OPT_SET_USER_AGENT ENABLE_STRICT_OBJECT_CREATION = _pygit2.GIT_OPT_ENABLE_STRICT_OBJECT_CREATION ENABLE_STRICT_SYMBOLIC_REF_CREATION = ( _pygit2.GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION ) SET_SSL_CIPHERS = _pygit2.GIT_OPT_SET_SSL_CIPHERS GET_USER_AGENT = _pygit2.GIT_OPT_GET_USER_AGENT ENABLE_OFS_DELTA = _pygit2.GIT_OPT_ENABLE_OFS_DELTA ENABLE_FSYNC_GITDIR = _pygit2.GIT_OPT_ENABLE_FSYNC_GITDIR GET_WINDOWS_SHAREMODE = _pygit2.GIT_OPT_GET_WINDOWS_SHAREMODE SET_WINDOWS_SHAREMODE = _pygit2.GIT_OPT_SET_WINDOWS_SHAREMODE ENABLE_STRICT_HASH_VERIFICATION = _pygit2.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION SET_ALLOCATOR = _pygit2.GIT_OPT_SET_ALLOCATOR ENABLE_UNSAVED_INDEX_SAFETY = _pygit2.GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY GET_PACK_MAX_OBJECTS = _pygit2.GIT_OPT_GET_PACK_MAX_OBJECTS SET_PACK_MAX_OBJECTS = _pygit2.GIT_OPT_SET_PACK_MAX_OBJECTS DISABLE_PACK_KEEP_FILE_CHECKS = _pygit2.GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS # ENABLE_HTTP_EXPECT_CONTINUE = _pygit2.GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE GET_MWINDOW_FILE_LIMIT = _pygit2.GIT_OPT_GET_MWINDOW_FILE_LIMIT SET_MWINDOW_FILE_LIMIT = _pygit2.GIT_OPT_SET_MWINDOW_FILE_LIMIT # SET_ODB_PACKED_PRIORITY = _pygit2.GIT_OPT_SET_ODB_PACKED_PRIORITY # SET_ODB_LOOSE_PRIORITY = _pygit2.GIT_OPT_SET_ODB_LOOSE_PRIORITY # GET_EXTENSIONS = _pygit2.GIT_OPT_GET_EXTENSIONS # SET_EXTENSIONS = _pygit2.GIT_OPT_SET_EXTENSIONS GET_OWNER_VALIDATION = _pygit2.GIT_OPT_GET_OWNER_VALIDATION SET_OWNER_VALIDATION = _pygit2.GIT_OPT_SET_OWNER_VALIDATION # GET_HOMEDIR = _pygit2.GIT_OPT_GET_HOMEDIR # SET_HOMEDIR = _pygit2.GIT_OPT_SET_HOMEDIR # SET_SERVER_CONNECT_TIMEOUT = _pygit2.GIT_OPT_SET_SERVER_CONNECT_TIMEOUT # GET_SERVER_CONNECT_TIMEOUT = _pygit2.GIT_OPT_GET_SERVER_CONNECT_TIMEOUT # SET_SERVER_TIMEOUT = _pygit2.GIT_OPT_SET_SERVER_TIMEOUT # GET_SERVER_TIMEOUT = _pygit2.GIT_OPT_GET_SERVER_TIMEOUT class ReferenceFilter(IntEnum): """Filters for References.iterator().""" ALL = _pygit2.GIT_REFERENCES_ALL BRANCHES = _pygit2.GIT_REFERENCES_BRANCHES TAGS = _pygit2.GIT_REFERENCES_TAGS class ReferenceType(IntFlag): """Basic type of any Git reference.""" INVALID = C.GIT_REFERENCE_INVALID 'Invalid reference' DIRECT = C.GIT_REFERENCE_DIRECT 'A reference that points at an object id' SYMBOLIC = C.GIT_REFERENCE_SYMBOLIC 'A reference that points at another reference' ALL = C.GIT_REFERENCE_ALL 'Bitwise OR of (DIRECT | SYMBOLIC)' class RepositoryInitFlag(IntFlag): """ Option flags for pygit2.init_repository(). """ BARE = C.GIT_REPOSITORY_INIT_BARE 'Create a bare repository with no working directory.' NO_REINIT = C.GIT_REPOSITORY_INIT_NO_REINIT 'Raise GitError if the path appears to already be a git repository.' NO_DOTGIT_DIR = C.GIT_REPOSITORY_INIT_NO_DOTGIT_DIR """Normally a "/.git/" will be appended to the repo path for non-bare repos (if it is not already there), but passing this flag prevents that behavior.""" MKDIR = C.GIT_REPOSITORY_INIT_MKDIR """Make the repo_path (and workdir_path) as needed. Init is always willing to create the ".git" directory even without this flag. This flag tells init to create the trailing component of the repo and workdir paths as needed.""" MKPATH = C.GIT_REPOSITORY_INIT_MKPATH 'Recursively make all components of the repo and workdir paths as necessary.' EXTERNAL_TEMPLATE = C.GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE """libgit2 normally uses internal templates to initialize a new repo. This flags enables external templates, looking at the "template_path" from the options if set, or the `init.templatedir` global config if not, or falling back on "/usr/share/git-core/templates" if it exists.""" RELATIVE_GITLINK = C.GIT_REPOSITORY_INIT_RELATIVE_GITLINK """If an alternate workdir is specified, use relative paths for the gitdir and core.worktree.""" class RepositoryInitMode(IntEnum): """ Mode options for pygit2.init_repository(). """ SHARED_UMASK = C.GIT_REPOSITORY_INIT_SHARED_UMASK 'Use permissions configured by umask - the default.' SHARED_GROUP = C.GIT_REPOSITORY_INIT_SHARED_GROUP """ Use '--shared=group' behavior, chmod'ing the new repo to be group writable and "g+sx" for sticky group assignment. """ SHARED_ALL = C.GIT_REPOSITORY_INIT_SHARED_ALL "Use '--shared=all' behavior, adding world readability." class RepositoryOpenFlag(IntFlag): """ Option flags for Repository.__init__(). """ DEFAULT = 0 'Default flags.' NO_SEARCH = C.GIT_REPOSITORY_OPEN_NO_SEARCH """ Only open the repository if it can be immediately found in the start_path. Do not walk up from the start_path looking at parent directories. """ CROSS_FS = C.GIT_REPOSITORY_OPEN_CROSS_FS """ Unless this flag is set, open will not continue searching across filesystem boundaries (i.e. when `st_dev` changes from the `stat` system call). For example, searching in a user's home directory at "/home/user/source/" will not return "/.git/" as the found repo if "/" is a different filesystem than "/home". """ BARE = C.GIT_REPOSITORY_OPEN_BARE """ Open repository as a bare repo regardless of core.bare config, and defer loading config file for faster setup. Unlike `git_repository_open_bare`, this can follow gitlinks. """ NO_DOTGIT = C.GIT_REPOSITORY_OPEN_NO_DOTGIT """ Do not check for a repository by appending /.git to the start_path; only open the repository if start_path itself points to the git directory. """ FROM_ENV = C.GIT_REPOSITORY_OPEN_FROM_ENV """ Find and open a git repository, respecting the environment variables used by the git command-line tools. If set, `git_repository_open_ext` will ignore the other flags and the `ceiling_dirs` argument, and will allow a NULL `path` to use `GIT_DIR` or search from the current directory. The search for a repository will respect $GIT_CEILING_DIRECTORIES and $GIT_DISCOVERY_ACROSS_FILESYSTEM. The opened repository will respect $GIT_INDEX_FILE, $GIT_NAMESPACE, $GIT_OBJECT_DIRECTORY, and $GIT_ALTERNATE_OBJECT_DIRECTORIES. In the future, this flag will also cause `git_repository_open_ext` to respect $GIT_WORK_TREE and $GIT_COMMON_DIR; currently, `git_repository_open_ext` with this flag will error out if either $GIT_WORK_TREE or $GIT_COMMON_DIR is set. """ class RepositoryState(IntEnum): """ Repository state: These values represent possible states for the repository to be in, based on the current operation which is ongoing. """ NONE = C.GIT_REPOSITORY_STATE_NONE MERGE = C.GIT_REPOSITORY_STATE_MERGE REVERT = C.GIT_REPOSITORY_STATE_REVERT REVERT_SEQUENCE = C.GIT_REPOSITORY_STATE_REVERT_SEQUENCE CHERRYPICK = C.GIT_REPOSITORY_STATE_CHERRYPICK CHERRYPICK_SEQUENCE = C.GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE BISECT = C.GIT_REPOSITORY_STATE_BISECT REBASE = C.GIT_REPOSITORY_STATE_REBASE REBASE_INTERACTIVE = C.GIT_REPOSITORY_STATE_REBASE_INTERACTIVE REBASE_MERGE = C.GIT_REPOSITORY_STATE_REBASE_MERGE APPLY_MAILBOX = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX APPLY_MAILBOX_OR_REBASE = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE class ResetMode(IntEnum): """Kinds of reset operation.""" SOFT = _pygit2.GIT_RESET_SOFT 'Move the head to the given commit' MIXED = _pygit2.GIT_RESET_MIXED 'SOFT plus reset index to the commit' HARD = _pygit2.GIT_RESET_HARD 'MIXED plus changes in working tree discarded' class RevSpecFlag(IntFlag): """ Revparse flags. These indicate the intended behavior of the spec passed to Repository.revparse() """ SINGLE = _pygit2.GIT_REVSPEC_SINGLE 'The spec targeted a single object.' RANGE = _pygit2.GIT_REVSPEC_RANGE 'The spec targeted a range of commits.' MERGE_BASE = _pygit2.GIT_REVSPEC_MERGE_BASE "The spec used the '...' operator, which invokes special semantics." class SortMode(IntFlag): """ Flags to specify the sorting which a revwalk should perform. """ NONE = _pygit2.GIT_SORT_NONE """ Sort the output with the same default method from `git`: reverse chronological order. This is the default sorting for new walkers. """ TOPOLOGICAL = _pygit2.GIT_SORT_TOPOLOGICAL """ Sort the repository contents in topological order (no parents before all of its children are shown); this sorting mode can be combined with TIME sorting to produce `git`'s `--date-order``. """ TIME = _pygit2.GIT_SORT_TIME """ Sort the repository contents by commit time; this sorting mode can be combined with TOPOLOGICAL. """ REVERSE = _pygit2.GIT_SORT_REVERSE """ Iterate through the repository contents in reverse order; this sorting mode can be combined with any of the above. """ class StashApplyProgress(IntEnum): """ Stash apply progression states """ NONE = C.GIT_STASH_APPLY_PROGRESS_NONE LOADING_STASH = C.GIT_STASH_APPLY_PROGRESS_LOADING_STASH 'Loading the stashed data from the object database.' ANALYZE_INDEX = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX 'The stored index is being analyzed.' ANALYZE_MODIFIED = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED 'The modified files are being analyzed.' ANALYZE_UNTRACKED = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED 'The untracked and ignored files are being analyzed.' CHECKOUT_UNTRACKED = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED 'The untracked files are being written to disk.' CHECKOUT_MODIFIED = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED 'The modified files are being written to disk.' DONE = C.GIT_STASH_APPLY_PROGRESS_DONE 'The stash was applied successfully.' class SubmoduleIgnore(IntEnum): UNSPECIFIED = _pygit2.GIT_SUBMODULE_IGNORE_UNSPECIFIED "use the submodule's configuration" NONE = _pygit2.GIT_SUBMODULE_IGNORE_NONE 'any change or untracked == dirty' UNTRACKED = _pygit2.GIT_SUBMODULE_IGNORE_UNTRACKED 'dirty if tracked files change' DIRTY = _pygit2.GIT_SUBMODULE_IGNORE_DIRTY 'only dirty if HEAD moved' ALL = _pygit2.GIT_SUBMODULE_IGNORE_ALL 'never dirty' class SubmoduleStatus(IntFlag): IN_HEAD = _pygit2.GIT_SUBMODULE_STATUS_IN_HEAD 'superproject head contains submodule' IN_INDEX = _pygit2.GIT_SUBMODULE_STATUS_IN_INDEX 'superproject index contains submodule' IN_CONFIG = _pygit2.GIT_SUBMODULE_STATUS_IN_CONFIG 'superproject gitmodules has submodule' IN_WD = _pygit2.GIT_SUBMODULE_STATUS_IN_WD 'superproject workdir has submodule' INDEX_ADDED = _pygit2.GIT_SUBMODULE_STATUS_INDEX_ADDED 'in index, not in head (flag available if ignore is not ALL)' INDEX_DELETED = _pygit2.GIT_SUBMODULE_STATUS_INDEX_DELETED 'in head, not in index (flag available if ignore is not ALL)' INDEX_MODIFIED = _pygit2.GIT_SUBMODULE_STATUS_INDEX_MODIFIED "index and head don't match (flag available if ignore is not ALL)" WD_UNINITIALIZED = _pygit2.GIT_SUBMODULE_STATUS_WD_UNINITIALIZED 'workdir contains empty repository (flag available if ignore is not ALL)' WD_ADDED = _pygit2.GIT_SUBMODULE_STATUS_WD_ADDED 'in workdir, not index (flag available if ignore is not ALL)' WD_DELETED = _pygit2.GIT_SUBMODULE_STATUS_WD_DELETED 'in index, not workdir (flag available if ignore is not ALL)' WD_MODIFIED = _pygit2.GIT_SUBMODULE_STATUS_WD_MODIFIED "index and workdir head don't match (flag available if ignore is not ALL)" WD_INDEX_MODIFIED = _pygit2.GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED 'submodule workdir index is dirty (flag available if ignore is NONE or UNTRACKED)' WD_WD_MODIFIED = _pygit2.GIT_SUBMODULE_STATUS_WD_WD_MODIFIED 'submodule workdir has modified files (flag available if ignore is NONE or UNTRACKED)' WD_UNTRACKED = _pygit2.GIT_SUBMODULE_STATUS_WD_UNTRACKED 'submodule workdir contains untracked files (flag available if ignore is NONE)' libgit2-pygit2-a011e26/pygit2/errors.py000066400000000000000000000044731473744024100177250ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Import from pygit2 from .ffi import ffi, C from ._pygit2 import GitError value_errors = set([C.GIT_EEXISTS, C.GIT_EINVALIDSPEC, C.GIT_EAMBIGUOUS]) def check_error(err, io=False): if err >= 0: return # These are special error codes, they should never reach here test = err != C.GIT_EUSER and err != C.GIT_PASSTHROUGH assert test, f'Unexpected error code {err}' # Error message giterr = C.git_error_last() if giterr != ffi.NULL: message = ffi.string(giterr.message).decode('utf8') else: message = f'err {err} (no message provided)' # Translate to Python errors if err in value_errors: raise ValueError(message) if err == C.GIT_ENOTFOUND: if io: raise IOError(message) raise KeyError(message) if err == C.GIT_EINVALIDSPEC: raise ValueError(message) if err == C.GIT_ITEROVER: raise StopIteration() # Generic Git error raise GitError(message) # Indicate that we want libgit2 to pretend a function was not set class Passthrough(Exception): def __init__(self): super().__init__('The function asked for pass-through') libgit2-pygit2-a011e26/pygit2/ffi.py000066400000000000000000000023361473744024100171510ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Import from pygit2 from ._libgit2 import ffi, lib as C libgit2-pygit2-a011e26/pygit2/filter.py000066400000000000000000000074521473744024100176760ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from typing import Callable, List, Optional from ._pygit2 import FilterSource class Filter: """ Base filter class to be used with libgit2 filters. Inherit from this class and override the `check()`, `write()` and `close()` methods to define a filter which can then be registered via `pygit2.filter_register()`. A new Filter instance will be instantiated for each stream which needs to be filtered. For each stream, filter methods will be called in this order: - `check()` - `write()` (may be called multiple times) - `close()` Filtered output data should be written to the next filter in the chain during `write()` and `close()` via the `write_next` method. All output data must be written to the next filter before returning from `close()`. If a filter is dependent on reading the complete input data stream, the filter should only write output data in `close()`. """ #: Space-separated string list of attributes to be used in `check()` attributes: str = '' @classmethod def nattrs(cls) -> int: return len(cls.attributes.split()) def check(self, src: FilterSource, attr_values: List[Optional[str]]): """ Check whether this filter should be applied to the given source. `check` will be called once per stream. If `Passthrough` is raised, the filter will not be applied. Parameters: src: The source of the filtered blob. attr_values: The values of each attribute for the blob being filtered. `attr_values` will be a sorted list containing attributes in the order they were defined in ``cls.attributes``. """ def write( self, data: bytes, src: FilterSource, write_next: Callable[[bytes], None] ): """ Write input `data` to this filter. `write()` may be called multiple times per stream. Parameters: data: Input data. src: The source of the filtered blob. write_next: The ``write()`` method of the next filter in the chain. Filtered output data should be written to `write_next` whenever it is available. """ write_next(data) def close(self, write_next: Callable[[bytes], None]): """ Close this filter. `close()` will be called once per stream whenever all writes() to this stream have been completed. Parameters: write_next: The ``write()`` method of the next filter in the chain. Any remaining filtered output data must be written to `write_next` before returning. """ libgit2-pygit2-a011e26/pygit2/index.py000066400000000000000000000362371473744024100175230ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import warnings import weakref # Import from pygit2 from ._pygit2 import Oid, Tree, Diff from .enums import DiffOption, FileMode from .errors import check_error from .ffi import ffi, C from .utils import to_bytes, to_str from .utils import GenericIterator, StrArray class Index: # XXX Implement the basic features in C (_pygit2.Index) and make # pygit2.Index to inherit from _pygit2.Index? This would allow for # a proper implementation in some places: e.g. checking the index type # from C code (see Tree_diff_to_index) def __init__(self, path=None): """Create a new Index If path is supplied, the read and write methods will use that path to read from and write to. """ cindex = ffi.new('git_index **') err = C.git_index_open(cindex, to_bytes(path)) check_error(err) self._repo = None self._index = cindex[0] self._cindex = cindex @classmethod def from_c(cls, repo, ptr): index = cls.__new__(cls) index._repo = repo index._index = ptr[0] index._cindex = ptr return index @property def _pointer(self): return bytes(ffi.buffer(self._cindex)[:]) def __del__(self): C.git_index_free(self._index) def __len__(self): return C.git_index_entrycount(self._index) def __contains__(self, path): err = C.git_index_find(ffi.NULL, self._index, to_bytes(path)) if err == C.GIT_ENOTFOUND: return False check_error(err) return True def __getitem__(self, key): centry = ffi.NULL if isinstance(key, str) or hasattr(key, '__fspath__'): centry = C.git_index_get_bypath(self._index, to_bytes(key), 0) elif isinstance(key, int): if key >= 0: centry = C.git_index_get_byindex(self._index, key) else: raise ValueError(key) else: raise TypeError(f'Expected str or int, got {type(key)}') if centry == ffi.NULL: raise KeyError(key) return IndexEntry._from_c(centry) def __iter__(self): return GenericIterator(self) def read(self, force=True): """ Update the contents of the Index by reading from a file. Parameters: force If True (the default) always reload. If False, only if the file has changed. """ err = C.git_index_read(self._index, force) check_error(err, io=True) def write(self): """Write the contents of the Index to disk.""" err = C.git_index_write(self._index) check_error(err, io=True) def clear(self): err = C.git_index_clear(self._index) check_error(err) def read_tree(self, tree): """Replace the contents of the Index with those of the given tree, expressed either as a object or as an oid (string or ). The tree will be read recursively and all its children will also be inserted into the Index. """ repo = self._repo if isinstance(tree, str): tree = repo[tree] if isinstance(tree, Oid): if repo is None: raise TypeError('id given but no associated repository') tree = repo[tree] elif not isinstance(tree, Tree): raise TypeError('argument must be Oid or Tree') tree_cptr = ffi.new('git_tree **') ffi.buffer(tree_cptr)[:] = tree._pointer[:] err = C.git_index_read_tree(self._index, tree_cptr[0]) check_error(err) def write_tree(self, repo=None): """Create a tree out of the Index. Return the object of the written tree. The contents of the index will be written out to the object database. If there is no associated repository, 'repo' must be passed. If there is an associated repository and 'repo' is passed, then that repository will be used instead. It returns the id of the resulting tree. """ coid = ffi.new('git_oid *') repo = repo or self._repo if repo: err = C.git_index_write_tree_to(coid, self._index, repo._repo) else: err = C.git_index_write_tree(coid, self._index) check_error(err) return Oid(raw=bytes(ffi.buffer(coid)[:])) def remove(self, path, level=0): """Remove an entry from the Index.""" err = C.git_index_remove(self._index, to_bytes(path), level) check_error(err, io=True) def remove_all(self, pathspecs): """Remove all index entries matching pathspecs.""" with StrArray(pathspecs) as arr: err = C.git_index_remove_all(self._index, arr.ptr, ffi.NULL, ffi.NULL) check_error(err, io=True) def add_all(self, pathspecs=None): """Add or update index entries matching files in the working directory. If pathspecs are specified, only files matching those pathspecs will be added. """ pathspecs = pathspecs or [] with StrArray(pathspecs) as arr: err = C.git_index_add_all(self._index, arr.ptr, 0, ffi.NULL, ffi.NULL) check_error(err, io=True) def add(self, path_or_entry): """Add or update an entry in the Index. If a path is given, that file will be added. The path must be relative to the root of the worktree and the Index must be associated with a repository. If an IndexEntry is given, that entry will be added or update in the Index without checking for the existence of the path or id. """ if isinstance(path_or_entry, IndexEntry): entry = path_or_entry centry, str_ref = entry._to_c() err = C.git_index_add(self._index, centry) elif isinstance(path_or_entry, str) or hasattr(path_or_entry, '__fspath__'): path = path_or_entry err = C.git_index_add_bypath(self._index, to_bytes(path)) else: raise TypeError('argument must be string or IndexEntry') check_error(err, io=True) def diff_to_workdir( self, flags: DiffOption = DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0, ) -> Diff: """ Diff the index against the working directory. Return a object with the differences between the index and the working copy. Parameters: flags A combination of enums.DiffOption constants. context_lines The number of unchanged lines that define the boundary of a hunk (and to display before and after). interhunk_lines The maximum number of unchanged lines between hunk boundaries before the hunks will be merged into a one. """ repo = self._repo if repo is None: raise ValueError('diff needs an associated repository') copts = ffi.new('git_diff_options *') err = C.git_diff_options_init(copts, 1) check_error(err) copts.flags = int(flags) copts.context_lines = context_lines copts.interhunk_lines = interhunk_lines cdiff = ffi.new('git_diff **') err = C.git_diff_index_to_workdir(cdiff, repo._repo, self._index, copts) check_error(err) return Diff.from_c(bytes(ffi.buffer(cdiff)[:]), repo) def diff_to_tree( self, tree: Tree, flags: DiffOption = DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0, ) -> Diff: """ Diff the index against a tree. Return a object with the differences between the index and the given tree. Parameters: tree The tree to diff. flags A combination of enums.DiffOption constants. context_lines The number of unchanged lines that define the boundary of a hunk (and to display before and after). interhunk_lines The maximum number of unchanged lines between hunk boundaries before the hunks will be merged into a one. """ repo = self._repo if repo is None: raise ValueError('diff needs an associated repository') if not isinstance(tree, Tree): raise TypeError('tree must be a Tree') copts = ffi.new('git_diff_options *') err = C.git_diff_options_init(copts, 1) check_error(err) copts.flags = int(flags) copts.context_lines = context_lines copts.interhunk_lines = interhunk_lines ctree = ffi.new('git_tree **') ffi.buffer(ctree)[:] = tree._pointer[:] cdiff = ffi.new('git_diff **') err = C.git_diff_tree_to_index(cdiff, repo._repo, ctree[0], self._index, copts) check_error(err) return Diff.from_c(bytes(ffi.buffer(cdiff)[:]), repo) # # Conflicts # _conflicts = None @property def conflicts(self): """A collection of conflict information If there are no conflicts None is returned. Otherwise return an object that represents the conflicts in the index. This object presents a mapping interface with the paths as keys. You can use the ``del`` operator to remove a conflict from the Index. Each conflict is made up of three elements. Access or iteration of the conflicts returns a three-tuple of :py:class:`~pygit2.IndexEntry`. The first is the common ancestor, the second is the "ours" side of the conflict, and the third is the "theirs" side. These elements may be None depending on which sides exist for the particular conflict. """ if not C.git_index_has_conflicts(self._index): self._conflicts = None return None if self._conflicts is None or self._conflicts() is None: conflicts = ConflictCollection(self) self._conflicts = weakref.ref(conflicts) return conflicts return self._conflicts() class IndexEntry: path: str 'The path of this entry' id: Oid 'The id of the referenced object' mode: FileMode 'The mode of this entry, a FileMode value' def __init__(self, path, object_id: Oid, mode: FileMode): self.path = path self.id = object_id self.mode = mode @property def oid(self): # For backwards compatibility return self.id @property def hex(self): """The id of the referenced object as a hex string""" warnings.warn('Use str(entry.id)', DeprecationWarning) return str(self.id) def __str__(self): return f'' def __repr__(self): t = type(self) return f'<{t.__module__}.{t.__qualname__} path={self.path} id={self.id} mode={self.mode}>' def __eq__(self, other): if self is other: return True if not isinstance(other, IndexEntry): return NotImplemented return ( self.path == other.path and self.id == other.id and self.mode == other.mode ) def _to_c(self): """Convert this entry into the C structure The first returned arg is the pointer, the second is the reference to the string we allocated, which we need to exist past this function """ centry = ffi.new('git_index_entry *') # basically memcpy() ffi.buffer(ffi.addressof(centry, 'id'))[:] = self.id.raw[:] centry.mode = int(self.mode) path = ffi.new('char[]', to_bytes(self.path)) centry.path = path return centry, path @classmethod def _from_c(cls, centry): if centry == ffi.NULL: return None entry = cls.__new__(cls) entry.path = to_str(ffi.string(centry.path)) entry.mode = FileMode(centry.mode) entry.id = Oid(raw=bytes(ffi.buffer(ffi.addressof(centry, 'id'))[:])) return entry class ConflictCollection: def __init__(self, index): self._index = index def __getitem__(self, path): cancestor = ffi.new('git_index_entry **') cours = ffi.new('git_index_entry **') ctheirs = ffi.new('git_index_entry **') err = C.git_index_conflict_get( cancestor, cours, ctheirs, self._index._index, to_bytes(path) ) check_error(err) ancestor = IndexEntry._from_c(cancestor[0]) ours = IndexEntry._from_c(cours[0]) theirs = IndexEntry._from_c(ctheirs[0]) return ancestor, ours, theirs def __delitem__(self, path): err = C.git_index_conflict_remove(self._index._index, to_bytes(path)) check_error(err) def __iter__(self): return ConflictIterator(self._index) def __contains__(self, path): cancestor = ffi.new('git_index_entry **') cours = ffi.new('git_index_entry **') ctheirs = ffi.new('git_index_entry **') err = C.git_index_conflict_get( cancestor, cours, ctheirs, self._index._index, to_bytes(path) ) if err == C.GIT_ENOTFOUND: return False check_error(err) return True class ConflictIterator: def __init__(self, index): citer = ffi.new('git_index_conflict_iterator **') err = C.git_index_conflict_iterator_new(citer, index._index) check_error(err) self._index = index self._iter = citer[0] def __del__(self): C.git_index_conflict_iterator_free(self._iter) def __iter__(self): return self def __next__(self): cancestor = ffi.new('git_index_entry **') cours = ffi.new('git_index_entry **') ctheirs = ffi.new('git_index_entry **') err = C.git_index_conflict_next(cancestor, cours, ctheirs, self._iter) if err == C.GIT_ITEROVER: raise StopIteration check_error(err) ancestor = IndexEntry._from_c(cancestor[0]) ours = IndexEntry._from_c(cours[0]) theirs = IndexEntry._from_c(ctheirs[0]) return ancestor, ours, theirs libgit2-pygit2-a011e26/pygit2/legacyenums.py000066400000000000000000000127401473744024100207210ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """ GIT_* enum values for compatibility with legacy code. These values are deprecated starting with pygit2 1.14. User programs should migrate to the enum classes defined in `pygit2.enums`. Note that our C module _pygit2 already exports many libgit2 enums (which are all imported by __init__.py). This file only exposes the enums that are not available through _pygit2. """ from . import enums GIT_FEATURE_THREADS = enums.Feature.THREADS GIT_FEATURE_HTTPS = enums.Feature.HTTPS GIT_FEATURE_SSH = enums.Feature.SSH GIT_FEATURE_NSEC = enums.Feature.NSEC GIT_REPOSITORY_INIT_BARE = enums.RepositoryInitFlag.BARE GIT_REPOSITORY_INIT_NO_REINIT = enums.RepositoryInitFlag.NO_REINIT GIT_REPOSITORY_INIT_NO_DOTGIT_DIR = enums.RepositoryInitFlag.NO_DOTGIT_DIR GIT_REPOSITORY_INIT_MKDIR = enums.RepositoryInitFlag.MKDIR GIT_REPOSITORY_INIT_MKPATH = enums.RepositoryInitFlag.MKPATH GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = enums.RepositoryInitFlag.EXTERNAL_TEMPLATE GIT_REPOSITORY_INIT_RELATIVE_GITLINK = enums.RepositoryInitFlag.RELATIVE_GITLINK GIT_REPOSITORY_INIT_SHARED_UMASK = enums.RepositoryInitMode.SHARED_UMASK GIT_REPOSITORY_INIT_SHARED_GROUP = enums.RepositoryInitMode.SHARED_GROUP GIT_REPOSITORY_INIT_SHARED_ALL = enums.RepositoryInitMode.SHARED_ALL GIT_REPOSITORY_OPEN_NO_SEARCH = enums.RepositoryOpenFlag.NO_SEARCH GIT_REPOSITORY_OPEN_CROSS_FS = enums.RepositoryOpenFlag.CROSS_FS GIT_REPOSITORY_OPEN_BARE = enums.RepositoryOpenFlag.BARE GIT_REPOSITORY_OPEN_NO_DOTGIT = enums.RepositoryOpenFlag.NO_DOTGIT GIT_REPOSITORY_OPEN_FROM_ENV = enums.RepositoryOpenFlag.FROM_ENV GIT_REPOSITORY_STATE_NONE = enums.RepositoryState.NONE GIT_REPOSITORY_STATE_MERGE = enums.RepositoryState.MERGE GIT_REPOSITORY_STATE_REVERT = enums.RepositoryState.REVERT GIT_REPOSITORY_STATE_REVERT_SEQUENCE = enums.RepositoryState.REVERT_SEQUENCE GIT_REPOSITORY_STATE_CHERRYPICK = enums.RepositoryState.CHERRYPICK GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE = enums.RepositoryState.CHERRYPICK_SEQUENCE GIT_REPOSITORY_STATE_BISECT = enums.RepositoryState.BISECT GIT_REPOSITORY_STATE_REBASE = enums.RepositoryState.REBASE GIT_REPOSITORY_STATE_REBASE_INTERACTIVE = enums.RepositoryState.REBASE_INTERACTIVE GIT_REPOSITORY_STATE_REBASE_MERGE = enums.RepositoryState.REBASE_MERGE GIT_REPOSITORY_STATE_APPLY_MAILBOX = enums.RepositoryState.APPLY_MAILBOX GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE = ( enums.RepositoryState.APPLY_MAILBOX_OR_REBASE ) GIT_ATTR_CHECK_FILE_THEN_INDEX = enums.AttrCheck.FILE_THEN_INDEX GIT_ATTR_CHECK_INDEX_THEN_FILE = enums.AttrCheck.INDEX_THEN_FILE GIT_ATTR_CHECK_INDEX_ONLY = enums.AttrCheck.INDEX_ONLY GIT_ATTR_CHECK_NO_SYSTEM = enums.AttrCheck.NO_SYSTEM GIT_ATTR_CHECK_INCLUDE_HEAD = enums.AttrCheck.INCLUDE_HEAD GIT_ATTR_CHECK_INCLUDE_COMMIT = enums.AttrCheck.INCLUDE_COMMIT GIT_FETCH_PRUNE_UNSPECIFIED = enums.FetchPrune.UNSPECIFIED GIT_FETCH_PRUNE = enums.FetchPrune.PRUNE GIT_FETCH_NO_PRUNE = enums.FetchPrune.NO_PRUNE GIT_CHECKOUT_NOTIFY_NONE = enums.CheckoutNotify.NONE GIT_CHECKOUT_NOTIFY_CONFLICT = enums.CheckoutNotify.CONFLICT GIT_CHECKOUT_NOTIFY_DIRTY = enums.CheckoutNotify.DIRTY GIT_CHECKOUT_NOTIFY_UPDATED = enums.CheckoutNotify.UPDATED GIT_CHECKOUT_NOTIFY_UNTRACKED = enums.CheckoutNotify.UNTRACKED GIT_CHECKOUT_NOTIFY_IGNORED = enums.CheckoutNotify.IGNORED GIT_CHECKOUT_NOTIFY_ALL = enums.CheckoutNotify.ALL GIT_STASH_APPLY_PROGRESS_NONE = enums.StashApplyProgress.NONE GIT_STASH_APPLY_PROGRESS_LOADING_STASH = enums.StashApplyProgress.LOADING_STASH GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX = enums.StashApplyProgress.ANALYZE_INDEX GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED = enums.StashApplyProgress.ANALYZE_MODIFIED GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED = enums.StashApplyProgress.ANALYZE_UNTRACKED GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED = ( enums.StashApplyProgress.CHECKOUT_UNTRACKED ) GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED = enums.StashApplyProgress.CHECKOUT_MODIFIED GIT_STASH_APPLY_PROGRESS_DONE = enums.StashApplyProgress.DONE GIT_CREDENTIAL_USERPASS_PLAINTEXT = enums.CredentialType.USERPASS_PLAINTEXT GIT_CREDENTIAL_SSH_KEY = enums.CredentialType.SSH_KEY GIT_CREDENTIAL_SSH_CUSTOM = enums.CredentialType.SSH_CUSTOM GIT_CREDENTIAL_DEFAULT = enums.CredentialType.DEFAULT GIT_CREDENTIAL_SSH_INTERACTIVE = enums.CredentialType.SSH_INTERACTIVE GIT_CREDENTIAL_USERNAME = enums.CredentialType.USERNAME GIT_CREDENTIAL_SSH_MEMORY = enums.CredentialType.SSH_MEMORY libgit2-pygit2-a011e26/pygit2/packbuilder.py000066400000000000000000000054031473744024100206700ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Import from pygit2 from .errors import check_error from .ffi import ffi, C from .utils import to_bytes class PackBuilder: def __init__(self, repo): cpackbuilder = ffi.new('git_packbuilder **') err = C.git_packbuilder_new(cpackbuilder, repo._repo) check_error(err) self._repo = repo self._packbuilder = cpackbuilder[0] self._cpackbuilder = cpackbuilder @property def _pointer(self): return bytes(ffi.buffer(self._packbuilder)[:]) def __del__(self): C.git_packbuilder_free(self._packbuilder) def __len__(self): return C.git_packbuilder_object_count(self._packbuilder) @staticmethod def __convert_object_to_oid(oid): git_oid = ffi.new('git_oid *') ffi.buffer(git_oid)[:] = oid.raw[:] return git_oid def add(self, oid): git_oid = self.__convert_object_to_oid(oid) err = C.git_packbuilder_insert(self._packbuilder, git_oid, ffi.NULL) check_error(err) def add_recur(self, oid): git_oid = self.__convert_object_to_oid(oid) err = C.git_packbuilder_insert_recur(self._packbuilder, git_oid, ffi.NULL) check_error(err) def set_threads(self, n_threads): return C.git_packbuilder_set_threads(self._packbuilder, n_threads) def write(self, path=None): path = ffi.NULL if path is None else to_bytes(path) err = C.git_packbuilder_write(self._packbuilder, path, 0, ffi.NULL, ffi.NULL) check_error(err) @property def written_objects_count(self): return C.git_packbuilder_written(self._packbuilder) libgit2-pygit2-a011e26/pygit2/py.typed000066400000000000000000000000751473744024100175300ustar00rootroot00000000000000# python type marker, see: https://peps.python.org/pep-0561/ libgit2-pygit2-a011e26/pygit2/references.py000066400000000000000000000071211473744024100205230ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from __future__ import annotations from typing import TYPE_CHECKING from .enums import ReferenceFilter # Need BaseRepository for type hints, but don't let it cause a circular dependency if TYPE_CHECKING: from .repository import BaseRepository class References: def __init__(self, repository: BaseRepository): self._repository = repository def __getitem__(self, name: str): return self._repository.lookup_reference(name) def get(self, key: str): try: return self[key] except KeyError: return None def __iter__(self): iter = self._repository.references_iterator_init() while True: ref = self._repository.references_iterator_next(iter) if ref: yield ref.name else: return def iterator(self, references_return_type: ReferenceFilter = ReferenceFilter.ALL): """Creates a new iterator and fetches references for a given repository. Can also filter and pass all refs or only branches or only tags. Parameters: references_return_type: ReferenceFilter Optional specifier to filter references. By default, all references are returned. The following values are accepted: - ReferenceFilter.ALL, fetches all refs, this is the default - ReferenceFilter.BRANCHES, fetches only branches - ReferenceFilter.TAGS, fetches only tags TODO: Add support for filtering by reference types notes and remotes. """ # Enforce ReferenceFilter type - raises ValueError if we're given an invalid value references_return_type = ReferenceFilter(references_return_type) iter = self._repository.references_iterator_init() while True: ref = self._repository.references_iterator_next( iter, references_return_type ) if ref: yield ref else: return def create(self, name, target, force=False): return self._repository.create_reference(name, target, force) def delete(self, name: str): self[name].delete() def __contains__(self, name: str): return self.get(name) is not None @property def objects(self): return self._repository.listall_reference_objects() def compress(self): return self._repository.compress_references() libgit2-pygit2-a011e26/pygit2/refspec.py000066400000000000000000000065361473744024100200420ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Import from pygit2 from .errors import check_error from .ffi import ffi, C from .utils import to_bytes class Refspec: """The constructor is for internal use only.""" def __init__(self, owner, ptr): self._owner = owner self._refspec = ptr @property def src(self): """Source or lhs of the refspec""" return ffi.string(C.git_refspec_src(self._refspec)).decode('utf-8') @property def dst(self): """Destinaton or rhs of the refspec""" return ffi.string(C.git_refspec_dst(self._refspec)).decode('utf-8') @property def force(self): """Whether this refspeca llows non-fast-forward updates""" return bool(C.git_refspec_force(self._refspec)) @property def string(self): """String which was used to create this refspec""" return ffi.string(C.git_refspec_string(self._refspec)).decode('utf-8') @property def direction(self): """Direction of this refspec (fetch or push)""" return C.git_refspec_direction(self._refspec) def src_matches(self, ref): """Return True if the given string matches the source of this refspec, False otherwise. """ return bool(C.git_refspec_src_matches(self._refspec, to_bytes(ref))) def dst_matches(self, ref): """Return True if the given string matches the destination of this refspec, False otherwise.""" return bool(C.git_refspec_dst_matches(self._refspec, to_bytes(ref))) def _transform(self, ref, fn): buf = ffi.new('git_buf *', (ffi.NULL, 0)) err = fn(buf, self._refspec, to_bytes(ref)) check_error(err) try: return ffi.string(buf.ptr).decode('utf-8') finally: C.git_buf_dispose(buf) def transform(self, ref): """Transform a reference name according to this refspec from the lhs to the rhs. Return an string. """ return self._transform(ref, C.git_refspec_transform) def rtransform(self, ref): """Transform a reference name according to this refspec from the lhs to the rhs. Return an string. """ return self._transform(ref, C.git_refspec_rtransform) libgit2-pygit2-a011e26/pygit2/remotes.py000066400000000000000000000340711473744024100200640ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from __future__ import annotations from typing import TYPE_CHECKING # Import from pygit2 from ._pygit2 import Oid from .callbacks import git_fetch_options, git_push_options, git_remote_callbacks from .enums import FetchPrune from .errors import check_error from .ffi import ffi, C from .refspec import Refspec from . import utils from .utils import maybe_string, to_bytes, strarray_to_strings, StrArray # Need BaseRepository for type hints, but don't let it cause a circular dependency if TYPE_CHECKING: from .repository import BaseRepository class TransferProgress: """Progress downloading and indexing data during a fetch.""" def __init__(self, tp): self.total_objects = tp.total_objects """Total number of objects to download""" self.indexed_objects = tp.indexed_objects """Objects which have been indexed""" self.received_objects = tp.received_objects """Objects which have been received up to now""" self.local_objects = tp.local_objects """Local objects which were used to fix the thin pack""" self.total_deltas = tp.total_deltas """Total number of deltas in the pack""" self.indexed_deltas = tp.indexed_deltas """Deltas which have been indexed""" self.received_bytes = tp.received_bytes """"Number of bytes received up to now""" class Remote: def __init__(self, repo: BaseRepository, ptr): """The constructor is for internal use only.""" self._repo = repo self._remote = ptr self._stored_exception = None def __del__(self): C.git_remote_free(self._remote) @property def name(self): """Name of the remote""" return maybe_string(C.git_remote_name(self._remote)) @property def url(self): """Url of the remote""" return maybe_string(C.git_remote_url(self._remote)) @property def push_url(self): """Push url of the remote""" return maybe_string(C.git_remote_pushurl(self._remote)) def connect(self, callbacks=None, direction=C.GIT_DIRECTION_FETCH, proxy=None): """Connect to the remote. Parameters: proxy : None or True or str Proxy configuration. Can be one of: * `None` (the default) to disable proxy usage * `True` to enable automatic proxy detection * an url to a proxy (`http://proxy.example.org:3128/`) """ proxy_opts = ffi.new('git_proxy_options *') C.git_proxy_options_init(proxy_opts, C.GIT_PROXY_OPTIONS_VERSION) self.__set_proxy(proxy_opts, proxy) with git_remote_callbacks(callbacks) as payload: err = C.git_remote_connect( self._remote, direction, payload.remote_callbacks, proxy_opts, ffi.NULL ) payload.check_error(err) def fetch( self, refspecs=None, message=None, callbacks=None, prune: FetchPrune = FetchPrune.UNSPECIFIED, proxy=None, depth=0, ): """Perform a fetch against this remote. Returns a object. Parameters: prune : enums.FetchPrune * `UNSPECIFIED`: use the configuration from the repository. * `PRUNE`: remove any remote branch in the local repository that does not exist in the remote. * `NO_PRUNE`: always keep the remote branches proxy : None or True or str Proxy configuration. Can be one of: * `None` (the default) to disable proxy usage * `True` to enable automatic proxy detection * an url to a proxy (`http://proxy.example.org:3128/`) depth : int Number of commits from the tip of each remote branch history to fetch. If non-zero, the number of commits from the tip of each remote branch history to fetch. If zero, all history is fetched. The default is 0 (all history is fetched). """ with git_fetch_options(callbacks) as payload: opts = payload.fetch_options opts.prune = prune opts.depth = depth self.__set_proxy(opts.proxy_opts, proxy) with StrArray(refspecs) as arr: err = C.git_remote_fetch(self._remote, arr.ptr, opts, to_bytes(message)) payload.check_error(err) return TransferProgress(C.git_remote_stats(self._remote)) def ls_remotes(self, callbacks=None, proxy=None): """ Return a list of dicts that maps to `git_remote_head` from a `ls_remotes` call. Parameters: callbacks : Passed to connect() proxy : Passed to connect() """ self.connect(callbacks=callbacks, proxy=proxy) refs = ffi.new('git_remote_head ***') refs_len = ffi.new('size_t *') err = C.git_remote_ls(refs, refs_len, self._remote) check_error(err) results = [] for i in range(int(refs_len[0])): ref = refs[0][i] local = bool(ref.local) if local: loid = Oid(raw=bytes(ffi.buffer(ref.loid.id)[:])) else: loid = None remote = { 'local': local, 'loid': loid, 'name': maybe_string(ref.name), 'symref_target': maybe_string(ref.symref_target), 'oid': Oid(raw=bytes(ffi.buffer(ref.oid.id)[:])), } results.append(remote) return results def prune(self, callbacks=None): """Perform a prune against this remote.""" with git_remote_callbacks(callbacks) as payload: err = C.git_remote_prune(self._remote, payload.remote_callbacks) payload.check_error(err) @property def refspec_count(self): """Total number of refspecs in this remote""" return C.git_remote_refspec_count(self._remote) def get_refspec(self, n): """Return the object at the given position.""" spec = C.git_remote_get_refspec(self._remote, n) return Refspec(self, spec) @property def fetch_refspecs(self): """Refspecs that will be used for fetching""" specs = ffi.new('git_strarray *') err = C.git_remote_get_fetch_refspecs(specs, self._remote) check_error(err) return strarray_to_strings(specs) @property def push_refspecs(self): """Refspecs that will be used for pushing""" specs = ffi.new('git_strarray *') err = C.git_remote_get_push_refspecs(specs, self._remote) check_error(err) return strarray_to_strings(specs) def push(self, specs, callbacks=None, proxy=None, push_options=None): """ Push the given refspec to the remote. Raises ``GitError`` on protocol error or unpack failure. When the remote has a githook installed, that denies the reference this function will return successfully. Thus it is strongly recommended to install a callback, that implements :py:meth:`RemoteCallbacks.push_update_reference` and check the passed parameters for successfull operations. Parameters: specs : [str] Push refspecs to use. proxy : None or True or str Proxy configuration. Can be one of: * `None` (the default) to disable proxy usage * `True` to enable automatic proxy detection * an url to a proxy (`http://proxy.example.org:3128/`) push_options : [str] Push options to send to the server, which passes them to the pre-receive as well as the post-receive hook. """ with git_push_options(callbacks) as payload: opts = payload.push_options self.__set_proxy(opts.proxy_opts, proxy) with StrArray(specs) as refspecs, StrArray(push_options) as pushopts: pushopts.assign_to(opts.remote_push_options) err = C.git_remote_push(self._remote, refspecs.ptr, opts) payload.check_error(err) def __set_proxy(self, proxy_opts, proxy): if proxy is None: proxy_opts.type = C.GIT_PROXY_NONE elif proxy is True: proxy_opts.type = C.GIT_PROXY_AUTO elif type(proxy) is str: proxy_opts.type = C.GIT_PROXY_SPECIFIED # Keep url in memory, otherwise memory is freed and bad things happen self.__url = ffi.new('char[]', to_bytes(proxy)) proxy_opts.url = self.__url else: raise TypeError('Proxy must be None, True, or a string') class RemoteCollection: """Collection of configured remotes You can use this class to look up and manage the remotes configured in a repository. You can access repositories using index access. E.g. to look up the "origin" remote, you can use >>> repo.remotes["origin"] """ def __init__(self, repo: BaseRepository): self._repo = repo def __len__(self): with utils.new_git_strarray() as names: err = C.git_remote_list(names, self._repo._repo) check_error(err) return names.count def __iter__(self): cremote = ffi.new('git_remote **') for name in self._ffi_names(): err = C.git_remote_lookup(cremote, self._repo._repo, name) check_error(err) yield Remote(self._repo, cremote[0]) def __getitem__(self, name): if isinstance(name, int): return list(self)[name] cremote = ffi.new('git_remote **') err = C.git_remote_lookup(cremote, self._repo._repo, to_bytes(name)) check_error(err) return Remote(self._repo, cremote[0]) def _ffi_names(self): with utils.new_git_strarray() as names: err = C.git_remote_list(names, self._repo._repo) check_error(err) for i in range(names.count): yield names.strings[i] def names(self): """An iterator over the names of the available remotes.""" for name in self._ffi_names(): yield maybe_string(name) def create(self, name, url, fetch=None): """Create a new remote with the given name and url. Returns a object. If 'fetch' is provided, this fetch refspec will be used instead of the default. """ cremote = ffi.new('git_remote **') name = to_bytes(name) url = to_bytes(url) if fetch: fetch = to_bytes(fetch) err = C.git_remote_create_with_fetchspec( cremote, self._repo._repo, name, url, fetch ) else: err = C.git_remote_create(cremote, self._repo._repo, name, url) check_error(err) return Remote(self._repo, cremote[0]) def create_anonymous(self, url): """Create a new anonymous (in-memory only) remote with the given URL. Returns a object. """ cremote = ffi.new('git_remote **') url = to_bytes(url) err = C.git_remote_create_anonymous(cremote, self._repo._repo, url) check_error(err) return Remote(self._repo, cremote[0]) def rename(self, name, new_name): """Rename a remote in the configuration. The refspecs in standard format will be renamed. Returns a list of fetch refspecs (list of strings) which were not in the standard format and thus could not be remapped. """ if not new_name: raise ValueError('Current remote name must be a non-empty string') if not new_name: raise ValueError('New remote name must be a non-empty string') problems = ffi.new('git_strarray *') err = C.git_remote_rename( problems, self._repo._repo, to_bytes(name), to_bytes(new_name) ) check_error(err) return strarray_to_strings(problems) def delete(self, name): """Remove a remote from the configuration All remote-tracking branches and configuration settings for the remote will be removed. """ err = C.git_remote_delete(self._repo._repo, to_bytes(name)) check_error(err) def set_url(self, name, url): """Set the URL for a remote""" err = C.git_remote_set_url(self._repo._repo, to_bytes(name), to_bytes(url)) check_error(err) def set_push_url(self, name, url): """Set the push-URL for a remote""" err = C.git_remote_set_pushurl(self._repo._repo, to_bytes(name), to_bytes(url)) check_error(err) def add_fetch(self, name, refspec): """Add a fetch refspec (str) to the remote""" err = C.git_remote_add_fetch( self._repo._repo, to_bytes(name), to_bytes(refspec) ) check_error(err) def add_push(self, name, refspec): """Add a push refspec (str) to the remote""" err = C.git_remote_add_push(self._repo._repo, to_bytes(name), to_bytes(refspec)) check_error(err) libgit2-pygit2-a011e26/pygit2/repository.py000066400000000000000000001502351473744024100206260ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from io import BytesIO from os import PathLike from string import hexdigits from time import time import tarfile import typing # Import from pygit2 from ._pygit2 import Repository as _Repository, init_file_backend from ._pygit2 import Oid, GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN from ._pygit2 import Reference, Tree, Commit, Blob, Signature from ._pygit2 import InvalidSpecError from .blame import Blame from .branches import Branches from .callbacks import git_checkout_options, git_stash_apply_options from .config import Config from .enums import ( AttrCheck, BlameFlag, BranchType, CheckoutStrategy, DescribeStrategy, DiffOption, FileMode, MergeFavor, MergeFileFlag, MergeFlag, ObjectType, RepositoryOpenFlag, RepositoryState, ) from .errors import check_error from .ffi import ffi, C from .index import Index, IndexEntry from .packbuilder import PackBuilder from .references import References from .remotes import RemoteCollection from .submodules import SubmoduleCollection from .utils import to_bytes, StrArray class BaseRepository(_Repository): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._common_init() def _common_init(self): self.branches = Branches(self) self.references = References(self) self.remotes = RemoteCollection(self) self.submodules = SubmoduleCollection(self) # Get the pointer as the contents of a buffer and store it for # later access repo_cptr = ffi.new('git_repository **') ffi.buffer(repo_cptr)[:] = self._pointer[:] self._repo = repo_cptr[0] # Backwards compatible ODB access def read(self, *args, **kwargs): """read(oid) -> type, data, size Read raw object data from the repository. """ return self.odb.read(*args, **kwargs) def write(self, *args, **kwargs): """write(type, data) -> Oid Write raw object data into the repository. First arg is the object type, the second one a buffer with data. Return the Oid of the created object.""" return self.odb.write(*args, **kwargs) def pack(self, path=None, pack_delegate=None, n_threads=None): """Pack the objects in the odb chosen by the pack_delegate function and write `.pack` and `.idx` files for them. Returns: the number of objects written to the pack Parameters: path The path to which the `.pack` and `.idx` files should be written. `None` will write to the default location. pack_delegate The method which will provide add the objects to the pack builder. Defaults to all objects. n_threads The number of threads the `PackBuilder` will spawn. If set to 0, libgit2 will autodetect the number of CPUs. """ def pack_all_objects(pack_builder): for obj in self.odb: pack_builder.add(obj) pack_delegate = pack_delegate or pack_all_objects builder = PackBuilder(self) if n_threads is not None: builder.set_threads(n_threads) pack_delegate(builder) builder.write(path=path) return builder.written_objects_count def hashfile( self, path: str, object_type: ObjectType = ObjectType.BLOB, as_path: typing.Optional[str] = None, ): """Calculate the hash of a file using repository filtering rules. If you simply want to calculate the hash of a file on disk with no filters, you can just use `pygit2.hashfile()`. However, if you want to hash a file in the repository and you want to apply filtering rules (e.g. crlf filters) before generating the SHA, then use this function. Note: if the repository has `core.safecrlf` set to fail and the filtering triggers that failure, then this function will raise an error and not calculate the hash of the file. Returns: Output value of calculated SHA (Oid) Parameters: path Path to file on disk whose contents should be hashed. This may be an absolute path or a relative path, in which case it will be treated as a path within the working directory. object_type The object type to hash (e.g. enums.ObjectType.BLOB) as_path The path to use to look up filtering rules. If this is an empty string then no filters will be applied when calculating the hash. If this is `None` and the `path` parameter is a file within the repository's working directory, then the `path` will be used. """ c_path = to_bytes(path) if as_path is None: c_as_path = ffi.NULL else: c_as_path = to_bytes(as_path) c_oid = ffi.new('git_oid *') err = C.git_repository_hashfile( c_oid, self._repo, c_path, int(object_type), c_as_path ) check_error(err) oid = Oid(raw=bytes(ffi.buffer(c_oid.id)[:])) return oid def __iter__(self): return iter(self.odb) # # Mapping interface # def get(self, key, default=None): value = self.git_object_lookup_prefix(key) return value if (value is not None) else default def __getitem__(self, key): value = self.git_object_lookup_prefix(key) if value is None: raise KeyError(key) return value def __contains__(self, key): return self.git_object_lookup_prefix(key) is not None def __repr__(self): return f'pygit2.Repository({repr(self.path)})' # # Configuration # @property def config(self): """The configuration file for this repository. If a the configuration hasn't been set yet, the default config for repository will be returned, including global and system configurations (if they are available). """ cconfig = ffi.new('git_config **') err = C.git_repository_config(cconfig, self._repo) check_error(err) return Config.from_c(self, cconfig[0]) @property def config_snapshot(self): """A snapshot for this repositiory's configuration This allows reads over multiple values to use the same version of the configuration files. """ cconfig = ffi.new('git_config **') err = C.git_repository_config_snapshot(cconfig, self._repo) check_error(err) return Config.from_c(self, cconfig[0]) # # References # def create_reference(self, name, target, force=False, message=None): """Create a new reference "name" which points to an object or to another reference. Based on the type and value of the target parameter, this method tries to guess whether it is a direct or a symbolic reference. Keyword arguments: force: bool If True references will be overridden, otherwise (the default) an exception is raised. message: str Optional message to use for the reflog. Examples:: repo.create_reference('refs/heads/foo', repo.head.target) repo.create_reference('refs/tags/foo', 'refs/heads/master') repo.create_reference('refs/tags/foo', 'bbb78a9cec580') """ direct = type(target) is Oid or ( all(c in hexdigits for c in target) and GIT_OID_MINPREFIXLEN <= len(target) <= GIT_OID_HEXSZ ) if direct: return self.create_reference_direct(name, target, force, message=message) return self.create_reference_symbolic(name, target, force, message=message) def listall_references(self) -> typing.List[str]: """Return a list with all the references in the repository.""" return list(x.name for x in self.references.iterator()) def listall_reference_objects(self) -> typing.List[Reference]: """Return a list with all the reference objects in the repository.""" return list(x for x in self.references.iterator()) def resolve_refish(self, refish): """Convert a reference-like short name "ref-ish" to a valid (commit, reference) pair. If ref-ish points to a commit, the reference element of the result will be None. Examples:: repo.resolve_refish('mybranch') repo.resolve_refish('sometag') repo.resolve_refish('origin/master') repo.resolve_refish('bbb78a9') """ try: reference = self.lookup_reference_dwim(refish) except (KeyError, InvalidSpecError): reference = None commit = self.revparse_single(refish) else: commit = reference.peel(Commit) return (commit, reference) # # Checkout # def checkout_head(self, **kwargs): """Checkout HEAD For arguments, see Repository.checkout(). """ with git_checkout_options(**kwargs) as payload: err = C.git_checkout_head(self._repo, payload.checkout_options) payload.check_error(err) def checkout_index(self, index=None, **kwargs): """Checkout the given index or the repository's index For arguments, see Repository.checkout(). """ with git_checkout_options(**kwargs) as payload: err = C.git_checkout_index( self._repo, index._index if index else ffi.NULL, payload.checkout_options, ) payload.check_error(err) def checkout_tree(self, treeish, **kwargs): """Checkout the given treeish For arguments, see Repository.checkout(). """ with git_checkout_options(**kwargs) as payload: cptr = ffi.new('git_object **') ffi.buffer(cptr)[:] = treeish._pointer[:] err = C.git_checkout_tree(self._repo, cptr[0], payload.checkout_options) payload.check_error(err) def checkout(self, refname=None, **kwargs): """ Checkout the given reference using the given strategy, and update the HEAD. The reference may be a reference name or a Reference object. The default strategy is SAFE | RECREATE_MISSING. If no reference is given, checkout from the index. Parameters: refname : str or Reference The reference to checkout. After checkout, the current branch will be switched to this one. strategy : CheckoutStrategy A ``CheckoutStrategy`` value. The default is ``SAFE | RECREATE_MISSING``. directory : str Alternative checkout path to workdir. paths : list[str] A list of files to checkout from the given reference. If paths is provided, HEAD will not be set to the reference. callbacks : CheckoutCallbacks Optional. Supply a `callbacks` object to get information about conflicted files, updated files, etc. as the checkout is being performed. The callbacks can also abort the checkout prematurely. The callbacks should be an object which inherits from `pyclass:CheckoutCallbacks`. It should implement the callbacks as overridden methods. Examples: * To checkout from the HEAD, just pass 'HEAD':: >>> checkout('HEAD') This is identical to calling checkout_head(). """ # Case 1: Checkout index if refname is None: return self.checkout_index(**kwargs) # Case 2: Checkout head if refname == 'HEAD': return self.checkout_head(**kwargs) # Case 3: Reference if isinstance(refname, Reference): reference = refname refname = refname.name else: reference = self.lookup_reference(refname) oid = reference.resolve().target treeish = self[oid] self.checkout_tree(treeish, **kwargs) if 'paths' not in kwargs: self.set_head(refname) # # Setting HEAD # def set_head(self, target): """ Set HEAD to point to the given target. Parameters: target The new target for HEAD. Can be a string or Oid (to detach). """ if isinstance(target, Oid): oid = ffi.new('git_oid *') ffi.buffer(oid)[:] = target.raw[:] err = C.git_repository_set_head_detached(self._repo, oid) check_error(err) return # if it's a string, then it's a reference name err = C.git_repository_set_head(self._repo, to_bytes(target)) check_error(err) # # Diff # def __whatever_to_tree_or_blob(self, obj): if obj is None: return None # If it's a string, then it has to be valid revspec if isinstance(obj, str) or isinstance(obj, bytes): obj = self.revparse_single(obj) elif isinstance(obj, Oid): obj = self[obj] # First we try to get to a blob try: obj = obj.peel(Blob) except Exception: # And if that failed, try to get a tree, raising a type # error if that still doesn't work try: obj = obj.peel(Tree) except Exception: raise TypeError(f'unexpected "{type(obj)}"') return obj def diff( self, a=None, b=None, cached=False, flags: DiffOption = DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0, ): """ Show changes between the working tree and the index or a tree, changes between the index and a tree, changes between two trees, or changes between two blobs. Keyword arguments: a None, a str (that refers to an Object, see revparse_single()) or a Reference object. If None, b must be None, too. In this case the working directory is compared with the index. Otherwise the referred object is compared to 'b'. b None, a str (that refers to an Object, see revparse_single()) or a Reference object. If None, the working directory is compared to 'a'. (except 'cached' is True, in which case the index is compared to 'a'). Otherwise the referred object is compared to 'a' cached If 'b' is None, by default the working directory is compared to 'a'. If 'cached' is set to True, the index/staging area is used for comparing. flag A combination of enums.DiffOption constants. context_lines The number of unchanged lines that define the boundary of a hunk (and to display before and after) interhunk_lines The maximum number of unchanged lines between hunk boundaries before the hunks will be merged into a one Examples:: # Changes in the working tree not yet staged for the next commit >>> diff() # Changes between the index and your last commit >>> diff(cached=True) # Changes in the working tree since your last commit >>> diff('HEAD') # Changes between commits >>> t0 = revparse_single('HEAD') >>> t1 = revparse_single('HEAD^') >>> diff(t0, t1) >>> diff('HEAD', 'HEAD^') # equivalent If you want to diff a tree against an empty tree, use the low level API (Tree.diff_to_tree()) directly. """ a = self.__whatever_to_tree_or_blob(a) b = self.__whatever_to_tree_or_blob(b) opt_keys = ['flags', 'context_lines', 'interhunk_lines'] opt_values = [int(flags), context_lines, interhunk_lines] # Case 1: Diff tree to tree if isinstance(a, Tree) and isinstance(b, Tree): return a.diff_to_tree(b, **dict(zip(opt_keys, opt_values))) # Case 2: Index to workdir elif a is None and b is None: return self.index.diff_to_workdir(*opt_values) # Case 3: Diff tree to index or workdir elif isinstance(a, Tree) and b is None: if cached: return a.diff_to_index(self.index, *opt_values) else: return a.diff_to_workdir(*opt_values) # Case 4: Diff blob to blob if isinstance(a, Blob) and isinstance(b, Blob): return a.diff(b) raise ValueError('Only blobs and treeish can be diffed') def state(self) -> RepositoryState: """Determines the state of a git repository - ie, whether an operation (merge, cherry-pick, etc) is in progress. Returns a RepositoryState constant. """ cstate: int = C.git_repository_state(self._repo) try: return RepositoryState(cstate) except ValueError: # Some value not in the IntEnum - newer libgit2 version? return cstate def state_cleanup(self): """Remove all the metadata associated with an ongoing command like merge, revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG, etc. """ C.git_repository_state_cleanup(self._repo) # # blame # def blame( self, path, flags: BlameFlag = BlameFlag.NORMAL, min_match_characters=None, newest_commit=None, oldest_commit=None, min_line=None, max_line=None, ): """ Return a Blame object for a single file. Parameters: path Path to the file to blame. flags An enums.BlameFlag constant. min_match_characters The number of alphanum chars that must be detected as moving/copying within a file for it to associate those lines with the parent commit. newest_commit The id of the newest commit to consider. oldest_commit The id of the oldest commit to consider. min_line The first line in the file to blame. max_line The last line in the file to blame. Examples:: repo.blame('foo.c', flags=enums.BlameFlag.IGNORE_WHITESPACE) """ options = ffi.new('git_blame_options *') C.git_blame_options_init(options, C.GIT_BLAME_OPTIONS_VERSION) if flags: options.flags = int(flags) if min_match_characters: options.min_match_characters = min_match_characters if newest_commit: if not isinstance(newest_commit, Oid): newest_commit = Oid(hex=newest_commit) ffi.buffer(ffi.addressof(options, 'newest_commit'))[:] = newest_commit.raw if oldest_commit: if not isinstance(oldest_commit, Oid): oldest_commit = Oid(hex=oldest_commit) ffi.buffer(ffi.addressof(options, 'oldest_commit'))[:] = oldest_commit.raw if min_line: options.min_line = min_line if max_line: options.max_line = max_line cblame = ffi.new('git_blame **') err = C.git_blame_file(cblame, self._repo, to_bytes(path), options) check_error(err) return Blame._from_c(self, cblame[0]) # # Index # @property def index(self): """Index representing the repository's index file.""" cindex = ffi.new('git_index **') err = C.git_repository_index(cindex, self._repo) check_error(err, io=True) return Index.from_c(self, cindex) # # Merging # @staticmethod def _merge_options(favor: MergeFavor, flags: MergeFlag, file_flags: MergeFileFlag): """Return a 'git_merge_opts *'""" # Check arguments type if not isinstance(favor, (int, MergeFavor)): raise TypeError('favor argument must be MergeFavor') if not isinstance(flags, (int, MergeFlag)): raise TypeError('flags argument must be MergeFlag') if not isinstance(file_flags, (int, MergeFileFlag)): raise TypeError('file_flags argument must be MergeFileFlag') opts = ffi.new('git_merge_options *') err = C.git_merge_options_init(opts, C.GIT_MERGE_OPTIONS_VERSION) check_error(err) opts.file_favor = int(favor) opts.flags = int(flags) opts.file_flags = int(file_flags) return opts def merge_file_from_index( self, ancestor: typing.Union[None, IndexEntry], ours: typing.Union[None, IndexEntry], theirs: typing.Union[None, IndexEntry], ) -> str: """Merge files from index. Return a string with the merge result containing possible conflicts. ancestor The index entry which will be used as a common ancestor. ours The index entry to take as "ours" or base. theirs The index entry which will be merged into "ours" """ cmergeresult = ffi.new('git_merge_file_result *') cancestor, ancestor_str_ref = ( ancestor._to_c() if ancestor is not None else (ffi.NULL, ffi.NULL) ) cours, ours_str_ref = ours._to_c() if ours is not None else (ffi.NULL, ffi.NULL) ctheirs, theirs_str_ref = ( theirs._to_c() if theirs is not None else (ffi.NULL, ffi.NULL) ) err = C.git_merge_file_from_index( cmergeresult, self._repo, cancestor, cours, ctheirs, ffi.NULL ) check_error(err) ret = ffi.string(cmergeresult.ptr, cmergeresult.len).decode('utf-8') C.git_merge_file_result_free(cmergeresult) return ret def merge_commits( self, ours: typing.Union[str, Oid, Commit], theirs: typing.Union[str, Oid, Commit], favor=MergeFavor.NORMAL, flags=MergeFlag.FIND_RENAMES, file_flags=MergeFileFlag.DEFAULT, ) -> Index: """ Merge two arbitrary commits. Returns: an index with the result of the merge. Parameters: ours The commit to take as "ours" or base. theirs The commit which will be merged into "ours" favor An enums.MergeFavor constant specifying how to deal with file-level conflicts. For all but NORMAL, the index will not record a conflict. flags A combination of enums.MergeFlag constants. file_flags A combination of enums.MergeFileFlag constants. Both "ours" and "theirs" can be any object which peels to a commit or the id (string or Oid) of an object which peels to a commit. """ ours_ptr = ffi.new('git_commit **') theirs_ptr = ffi.new('git_commit **') cindex = ffi.new('git_index **') if isinstance(ours, (str, Oid)): ours = self[ours] if isinstance(theirs, (str, Oid)): theirs = self[theirs] ours = ours.peel(Commit) theirs = theirs.peel(Commit) opts = self._merge_options(favor, flags, file_flags) ffi.buffer(ours_ptr)[:] = ours._pointer[:] ffi.buffer(theirs_ptr)[:] = theirs._pointer[:] err = C.git_merge_commits(cindex, self._repo, ours_ptr[0], theirs_ptr[0], opts) check_error(err) return Index.from_c(self, cindex) def merge_trees( self, ancestor: typing.Union[str, Oid, Tree], ours: typing.Union[str, Oid, Tree], theirs: typing.Union[str, Oid, Tree], favor=MergeFavor.NORMAL, flags=MergeFlag.FIND_RENAMES, file_flags=MergeFileFlag.DEFAULT, ): """ Merge two trees. Returns: an Index that reflects the result of the merge. Parameters: ancestor The tree which is the common ancestor between 'ours' and 'theirs'. ours The commit to take as "ours" or base. theirs The commit which will be merged into "ours". favor An enums.MergeFavor constant specifying how to deal with file-level conflicts. For all but NORMAL, the index will not record a conflict. flags A combination of enums.MergeFlag constants. file_flags A combination of enums.MergeFileFlag constants. """ ancestor_ptr = ffi.new('git_tree **') ours_ptr = ffi.new('git_tree **') theirs_ptr = ffi.new('git_tree **') cindex = ffi.new('git_index **') if isinstance(ancestor, (str, Oid)): ancestor = self[ancestor] if isinstance(ours, (str, Oid)): ours = self[ours] if isinstance(theirs, (str, Oid)): theirs = self[theirs] ancestor = ancestor.peel(Tree) ours = ours.peel(Tree) theirs = theirs.peel(Tree) opts = self._merge_options(favor, flags, file_flags) ffi.buffer(ancestor_ptr)[:] = ancestor._pointer[:] ffi.buffer(ours_ptr)[:] = ours._pointer[:] ffi.buffer(theirs_ptr)[:] = theirs._pointer[:] err = C.git_merge_trees( cindex, self._repo, ancestor_ptr[0], ours_ptr[0], theirs_ptr[0], opts ) check_error(err) return Index.from_c(self, cindex) def merge( self, id: typing.Union[Oid, str], favor=MergeFavor.NORMAL, flags=MergeFlag.FIND_RENAMES, file_flags=MergeFileFlag.DEFAULT, ): """ Merges the given id into HEAD. Merges the given commit(s) into HEAD, writing the results into the working directory. Any changes are staged for commit and any conflicts are written to the index. Callers should inspect the repository's index after this completes, resolve any conflicts and prepare a commit. Parameters: id The id to merge into HEAD favor An enums.MergeFavor constant specifying how to deal with file-level conflicts. For all but NORMAL, the index will not record a conflict. flags A combination of enums.MergeFlag constants. file_flags A combination of enums.MergeFileFlag constants. """ if not isinstance(id, (str, Oid)): raise TypeError(f'expected oid (string or ) got {type(id)}') id = self[id].id c_id = ffi.new('git_oid *') ffi.buffer(c_id)[:] = id.raw[:] merge_opts = self._merge_options(favor, flags, file_flags) checkout_opts = ffi.new('git_checkout_options *') C.git_checkout_options_init(checkout_opts, 1) checkout_opts.checkout_strategy = int( CheckoutStrategy.SAFE | CheckoutStrategy.RECREATE_MISSING ) commit_ptr = ffi.new('git_annotated_commit **') err = C.git_annotated_commit_lookup(commit_ptr, self._repo, c_id) check_error(err) err = C.git_merge(self._repo, commit_ptr, 1, merge_opts, checkout_opts) C.git_annotated_commit_free(commit_ptr[0]) check_error(err) # # Prepared message (MERGE_MSG) # @property def raw_message(self) -> bytes: """ Retrieve git's prepared message (bytes). See `Repository.message` for more information. """ buf = ffi.new('git_buf *', (ffi.NULL, 0)) try: err = C.git_repository_message(buf, self._repo) if err == C.GIT_ENOTFOUND: return b'' check_error(err) return ffi.string(buf.ptr) finally: C.git_buf_dispose(buf) @property def message(self) -> str: """ Retrieve git's prepared message. Operations such as git revert/cherry-pick/merge with the -n option stop just short of creating a commit with the changes and save their prepared message in .git/MERGE_MSG so the next git-commit execution can present it to the user for them to amend if they wish. Use this function to get the contents of this file. Don't forget to call `Repository.remove_message()` after you create the commit. Note that the message is also removed by `Repository.state_cleanup()`. If there is no such message, an empty string is returned. """ return self.raw_message.decode('utf-8') def remove_message(self): """ Remove git's prepared message. """ err = C.git_repository_message_remove(self._repo) check_error(err) # # Describe # def describe( self, committish=None, max_candidates_tags=None, describe_strategy: DescribeStrategy = DescribeStrategy.DEFAULT, pattern=None, only_follow_first_parent=None, show_commit_oid_as_fallback=None, abbreviated_size=None, always_use_long_format=None, dirty_suffix=None, ): """ Describe a commit-ish or the current working tree. Returns: The description (str). Parameters: committish : `str`, :class:`~.Reference`, or :class:`~.Commit` Commit-ish object or object name to describe, or `None` to describe the current working tree. max_candidates_tags : int The number of candidate tags to consider. Increasing above 10 will take slightly longer but may produce a more accurate result. A value of 0 will cause only exact matches to be output. describe_strategy : DescribeStrategy Can be one of: * `DescribeStrategy.DEFAULT` - Only match annotated tags. * `DescribeStrategy.TAGS` - Match everything under refs/tags/ (includes lightweight tags). * `DescribeStrategy.ALL` - Match everything under refs/ (includes branches). pattern : str Only consider tags matching the given `glob(7)` pattern, excluding the "refs/tags/" prefix. only_follow_first_parent : bool Follow only the first parent commit upon seeing a merge commit. show_commit_oid_as_fallback : bool Show uniquely abbreviated commit object as fallback. abbreviated_size : int The minimum number of hexadecimal digits to show for abbreviated object names. A value of 0 will suppress long format, only showing the closest tag. always_use_long_format : bool Always output the long format (the nearest tag, the number of commits, and the abbrevated commit name) even when the committish matches a tag. dirty_suffix : str A string to append if the working tree is dirty. Example:: repo.describe(pattern='public/*', dirty_suffix='-dirty') """ options = ffi.new('git_describe_options *') C.git_describe_options_init(options, C.GIT_DESCRIBE_OPTIONS_VERSION) if max_candidates_tags is not None: options.max_candidates_tags = max_candidates_tags if describe_strategy is not None: options.describe_strategy = int(describe_strategy) if pattern: # The returned pointer object has ownership on the allocated # memory. Make sure it is kept alive until git_describe_commit() or # git_describe_workdir() are called below. pattern_char = ffi.new('char[]', to_bytes(pattern)) options.pattern = pattern_char if only_follow_first_parent is not None: options.only_follow_first_parent = only_follow_first_parent if show_commit_oid_as_fallback is not None: options.show_commit_oid_as_fallback = show_commit_oid_as_fallback result = ffi.new('git_describe_result **') if committish: if isinstance(committish, str): committish = self.revparse_single(committish) commit = committish.peel(Commit) cptr = ffi.new('git_object **') ffi.buffer(cptr)[:] = commit._pointer[:] err = C.git_describe_commit(result, cptr[0], options) else: err = C.git_describe_workdir(result, self._repo, options) check_error(err) try: format_options = ffi.new('git_describe_format_options *') C.git_describe_init_format_options( format_options, C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION ) if abbreviated_size is not None: format_options.abbreviated_size = abbreviated_size if always_use_long_format is not None: format_options.always_use_long_format = always_use_long_format dirty_ptr = None if dirty_suffix: dirty_ptr = ffi.new('char[]', to_bytes(dirty_suffix)) format_options.dirty_suffix = dirty_ptr buf = ffi.new('git_buf *', (ffi.NULL, 0)) err = C.git_describe_format(buf, result[0], format_options) check_error(err) try: return ffi.string(buf.ptr).decode('utf-8') finally: C.git_buf_dispose(buf) finally: C.git_describe_result_free(result[0]) # # Stash # def stash( self, stasher: Signature, message: typing.Optional[str] = None, keep_index: bool = False, include_untracked: bool = False, include_ignored: bool = False, keep_all: bool = False, paths: typing.Optional[typing.List[str]] = None, ): """ Save changes to the working directory to the stash. Returns: The Oid of the stash merge commit (Oid). Parameters: stasher : Signature The identity of the person doing the stashing. message : str An optional description of stashed state. keep_index : bool Leave changes already added to the index in the working directory. include_untracked : bool Also stash untracked files. include_ignored : bool Also stash ignored files. keep_all : bool All changes in the index and working directory are left intact. paths : list[str] An optional list of paths that control which files are stashed. Example:: >>> repo = pygit2.Repository('.') >>> repo.stash(repo.default_signature(), 'WIP: stashing') """ opts = ffi.new('git_stash_save_options *') C.git_stash_save_options_init(opts, C.GIT_STASH_SAVE_OPTIONS_VERSION) flags = 0 flags |= keep_index * C.GIT_STASH_KEEP_INDEX flags |= keep_all * C.GIT_STASH_KEEP_ALL flags |= include_untracked * C.GIT_STASH_INCLUDE_UNTRACKED flags |= include_ignored * C.GIT_STASH_INCLUDE_IGNORED opts.flags = flags stasher_cptr = ffi.new('git_signature **') ffi.buffer(stasher_cptr)[:] = stasher._pointer[:] opts.stasher = stasher_cptr[0] if message: message_ref = ffi.new('char[]', to_bytes(message)) opts.message = message_ref if paths: arr = StrArray(paths) opts.paths = arr.ptr[0] coid = ffi.new('git_oid *') err = C.git_stash_save_with_opts(coid, self._repo, opts) check_error(err) return Oid(raw=bytes(ffi.buffer(coid)[:])) def stash_apply(self, index=0, **kwargs): """ Apply a stashed state in the stash list to the working directory. Parameters: index : int The position within the stash list of the stash to apply. 0 is the most recent stash. reinstate_index : bool Try to reinstate stashed changes to the index. callbacks : StashApplyCallbacks Optional. Supply a `callbacks` object to get information about the progress of the stash application as it is being performed. The callbacks should be an object which inherits from `pyclass:StashApplyCallbacks`. It should implement the callbacks as overridden methods. Note that this class inherits from CheckoutCallbacks, so you can also get information from the checkout part of the unstashing process via the callbacks. The checkout options may be customized using the same arguments taken by Repository.checkout(). Example:: >>> repo = pygit2.Repository('.') >>> repo.stash(repo.default_signature(), 'WIP: stashing') >>> repo.stash_apply(strategy=CheckoutStrategy.ALLOW_CONFLICTS) """ with git_stash_apply_options(**kwargs) as payload: err = C.git_stash_apply(self._repo, index, payload.stash_apply_options) payload.check_error(err) def stash_drop(self, index=0): """ Remove a stashed state from the stash list. Parameters: index : int The position within the stash list of the stash to remove. 0 is the most recent stash. """ check_error(C.git_stash_drop(self._repo, index)) def stash_pop(self, index=0, **kwargs): """Apply a stashed state and remove it from the stash list. For arguments, see Repository.stash_apply(). """ with git_stash_apply_options(**kwargs) as payload: err = C.git_stash_pop(self._repo, index, payload.stash_apply_options) payload.check_error(err) # # Utility for writing a tree into an archive # def write_archive(self, treeish, archive, timestamp=None, prefix=''): """ Write treeish into an archive. If no timestamp is provided and 'treeish' is a commit, its committer timestamp will be used. Otherwise the current time will be used. All path names in the archive are added to 'prefix', which defaults to an empty string. Parameters: treeish The treeish to write. archive An archive from the 'tarfile' module. timestamp Timestamp to use for the files in the archive. prefix Extra prefix to add to the path names in the archive. Example:: >>> import tarfile, pygit2 >>> with tarfile.open('foo.tar', 'w') as archive: >>> repo = pygit2.Repository('.') >>> repo.write_archive(repo.head.target, archive) """ # Try to get a tree form whatever we got if isinstance(treeish, (str, Oid)): treeish = self[treeish] tree = treeish.peel(Tree) # if we don't have a timestamp, try to get it from a commit if not timestamp: try: commit = treeish.peel(Commit) timestamp = commit.committer.time except Exception: pass # as a last resort, use the current timestamp if not timestamp: timestamp = int(time()) index = Index() index.read_tree(tree) for entry in index: content = self[entry.id].read_raw() info = tarfile.TarInfo(prefix + entry.path) info.size = len(content) info.mtime = timestamp info.uname = info.gname = 'root' # just because git does this if entry.mode == FileMode.LINK: info.type = tarfile.SYMTYPE info.linkname = content.decode('utf-8') info.mode = 0o777 # symlinks get placeholder info.size = 0 archive.addfile(info) else: info.mode = entry.mode archive.addfile(info, BytesIO(content)) # # Ahead-behind, which mostly lives on its own namespace # def ahead_behind(self, local, upstream): """ Calculate how many different commits are in the non-common parts of the history between the two given ids. Ahead is how many commits are in the ancestry of the `local` commit which are not in the `upstream` commit. Behind is the opposite. Returns: a tuple of two integers with the number of commits ahead and behind respectively. Parameters: local The commit which is considered the local or current state. upstream The commit which is considered the upstream. """ if not isinstance(local, Oid): local = self.expand_id(local) if not isinstance(upstream, Oid): upstream = self.expand_id(upstream) ahead, behind = ffi.new('size_t*'), ffi.new('size_t*') oid1, oid2 = ffi.new('git_oid *'), ffi.new('git_oid *') ffi.buffer(oid1)[:] = local.raw[:] ffi.buffer(oid2)[:] = upstream.raw[:] err = C.git_graph_ahead_behind(ahead, behind, self._repo, oid1, oid2) check_error(err) return int(ahead[0]), int(behind[0]) # # Git attributes # def get_attr( self, path: typing.Union[str, bytes, PathLike], name: typing.Union[str, bytes], flags: AttrCheck = AttrCheck.FILE_THEN_INDEX, commit: typing.Union[Oid, str, None] = None, ) -> typing.Union[bool, None, str]: """ Retrieve an attribute for a file by path. Returns: a boolean, `None` if the value is unspecified, or string with the value of the attribute. Parameters: path The path of the file to look up attributes for, relative to the workdir root. name The name of the attribute to look up. flags A combination of enums.AttrCheck flags which determine the lookup order. commit Optional id of commit to load attributes from when the `INCLUDE_COMMIT` flag is specified. Examples:: >>> print(repo.get_attr('splash.bmp', 'binary')) True >>> print(repo.get_attr('splash.bmp', 'unknown-attr')) None >>> repo.get_attr('test.h', 'whitespace') 'tab-in-indent,trailing-space' """ copts = ffi.new('git_attr_options *') copts.version = C.GIT_ATTR_OPTIONS_VERSION copts.flags = int(flags) if commit is not None: if not isinstance(commit, Oid): commit = Oid(hex=commit) ffi.buffer(ffi.addressof(copts, 'attr_commit_id'))[:] = commit.raw cvalue = ffi.new('char **') err = C.git_attr_get_ext( cvalue, self._repo, copts, to_bytes(path), to_bytes(name) ) check_error(err) # Now let's see if we can figure out what the value is attr_kind = C.git_attr_value(cvalue[0]) if attr_kind == C.GIT_ATTR_VALUE_UNSPECIFIED: return None elif attr_kind == C.GIT_ATTR_VALUE_TRUE: return True elif attr_kind == C.GIT_ATTR_VALUE_FALSE: return False elif attr_kind == C.GIT_ATTR_VALUE_STRING: return ffi.string(cvalue[0]).decode('utf-8') assert False, 'the attribute value from libgit2 is invalid' # # Identity for reference operations # @property def ident(self): cname = ffi.new('char **') cemail = ffi.new('char **') err = C.git_repository_ident(cname, cemail, self._repo) check_error(err) return (ffi.string(cname).decode('utf-8'), ffi.string(cemail).decode('utf-8')) def set_ident(self, name, email): """Set the identity to be used for reference operations. Updates to some references also append data to their reflog. You can use this method to set what identity will be used. If none is set, it will be read from the configuration. """ err = C.git_repository_set_ident(self._repo, to_bytes(name), to_bytes(email)) check_error(err) def revert(self, commit: Commit): """ Revert the given commit, producing changes in the index and working directory. This operation updates the repository's state and prepared message (MERGE_MSG). """ commit_ptr = ffi.new('git_commit **') ffi.buffer(commit_ptr)[:] = commit._pointer[:] err = C.git_revert(self._repo, commit_ptr[0], ffi.NULL) check_error(err) def revert_commit(self, revert_commit, our_commit, mainline=0): """ Revert the given Commit against the given "our" Commit, producing an Index that reflects the result of the revert. Returns: an Index with the result of the revert. Parameters: revert_commit The Commit to revert. our_commit The Commit to revert against (eg, HEAD). mainline The parent of the revert Commit, if it is a merge (i.e. 1, 2). """ cindex = ffi.new('git_index **') revert_commit_ptr = ffi.new('git_commit **') our_commit_ptr = ffi.new('git_commit **') ffi.buffer(revert_commit_ptr)[:] = revert_commit._pointer[:] ffi.buffer(our_commit_ptr)[:] = our_commit._pointer[:] opts = ffi.new('git_merge_options *') err = C.git_merge_options_init(opts, C.GIT_MERGE_OPTIONS_VERSION) check_error(err) err = C.git_revert_commit( cindex, self._repo, revert_commit_ptr[0], our_commit_ptr[0], mainline, opts ) check_error(err) return Index.from_c(self, cindex) # # Amend commit # def amend_commit( self, commit, refname, author=None, committer=None, message=None, tree=None, encoding='UTF-8', ): """ Amend an existing commit by replacing only explicitly passed values, return the rewritten commit's oid. This creates a new commit that is exactly the same as the old commit, except that any explicitly passed values will be updated. The new commit has the same parents as the old commit. You may omit the `author`, `committer`, `message`, `tree`, and `encoding` parameters, in which case this will use the values from the original `commit`. Parameters: commit : Commit, Oid, or str The commit to amend. refname : Reference or str If not `None`, name of the reference that will be updated to point to the newly rewritten commit. Use "HEAD" to update the HEAD of the current branch and make it point to the rewritten commit. If you want to amend a commit that is not currently the tip of the branch and then rewrite the following commits to reach a ref, pass this as `None` and update the rest of the commit chain and ref separately. author : Signature If not None, replace the old commit's author signature with this one. committer : Signature If not None, replace the old commit's committer signature with this one. message : str If not None, replace the old commit's message with this one. tree : Tree, Oid, or str If not None, replace the old commit's tree with this one. encoding : str Optional encoding for `message`. """ # Initialize parameters to pass on to C function git_commit_amend. # Note: the pointers are all initialized to NULL by default. coid = ffi.new('git_oid *') commit_cptr = ffi.new('git_commit **') refname_cstr = ffi.NULL author_cptr = ffi.new('git_signature **') committer_cptr = ffi.new('git_signature **') message_cstr = ffi.NULL encoding_cstr = ffi.NULL tree_cptr = ffi.new('git_tree **') # Get commit as pointer to git_commit. if isinstance(commit, (str, Oid)): commit = self[commit] elif isinstance(commit, Commit): pass elif commit is None: raise ValueError('the commit to amend cannot be None') else: raise TypeError('the commit to amend must be a Commit, str, or Oid') commit = commit.peel(Commit) ffi.buffer(commit_cptr)[:] = commit._pointer[:] # Get refname as C string. if isinstance(refname, Reference): refname_cstr = ffi.new('char[]', to_bytes(refname.name)) elif type(refname) is str: refname_cstr = ffi.new('char[]', to_bytes(refname)) elif refname is not None: raise TypeError('refname must be a str or Reference') # Get author as pointer to git_signature. if isinstance(author, Signature): ffi.buffer(author_cptr)[:] = author._pointer[:] elif author is not None: raise TypeError('author must be a Signature') # Get committer as pointer to git_signature. if isinstance(committer, Signature): ffi.buffer(committer_cptr)[:] = committer._pointer[:] elif committer is not None: raise TypeError('committer must be a Signature') # Get message and encoding as C strings. if message is not None: message_cstr = ffi.new('char[]', to_bytes(message, encoding)) encoding_cstr = ffi.new('char[]', to_bytes(encoding)) # Get tree as pointer to git_tree. if tree is not None: if isinstance(tree, (str, Oid)): tree = self[tree] tree = tree.peel(Tree) ffi.buffer(tree_cptr)[:] = tree._pointer[:] # Amend the commit. err = C.git_commit_amend( coid, commit_cptr[0], refname_cstr, author_cptr[0], committer_cptr[0], encoding_cstr, message_cstr, tree_cptr[0], ) check_error(err) return Oid(raw=bytes(ffi.buffer(coid)[:])) class Repository(BaseRepository): def __init__( self, path: typing.Optional[str] = None, flags: RepositoryOpenFlag = RepositoryOpenFlag.DEFAULT, ): """ The Repository constructor will commonly be called with one argument, the path of the repository to open. Alternatively, constructing a repository with no arguments will create a repository with no backends. You can use this path to create repositories with custom backends. Note that most operations on the repository are considered invalid and may lead to undefined behavior if attempted before providing an odb and refdb via set_odb and set_refdb. Parameters: path : str The path to open - if not provided, the repository will have no backend. flags : enums.RepositoryOpenFlag An optional combination of enums.RepositoryOpenFlag constants controlling how to open the repository. """ if path is not None: if hasattr(path, '__fspath__'): path = path.__fspath__() if not isinstance(path, str): path = path.decode('utf-8') path_backend = init_file_backend(path, int(flags)) super().__init__(path_backend) else: super().__init__() @classmethod def _from_c(cls, ptr, owned): cptr = ffi.new('git_repository **') cptr[0] = ptr repo = cls.__new__(cls) BaseRepository._from_c(repo, bytes(ffi.buffer(cptr)[:]), owned) repo._common_init() return repo libgit2-pygit2-a011e26/pygit2/settings.py000066400000000000000000000152001473744024100202370ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """ Settings mapping. """ from ssl import get_default_verify_paths import pygit2.enums from ._pygit2 import option from .enums import Option from .errors import GitError class SearchPathList: def __getitem__(self, key): return option(Option.GET_SEARCH_PATH, key) def __setitem__(self, key, value): option(Option.SET_SEARCH_PATH, key, value) class Settings: """Library-wide settings interface.""" __slots__ = '_default_tls_verify_paths', '_ssl_cert_dir', '_ssl_cert_file' _search_path = SearchPathList() def __init__(self): """Initialize global pygit2 and libgit2 settings.""" self._initialize_tls_certificate_locations() def _initialize_tls_certificate_locations(self): """Set up initial TLS file and directory lookup locations.""" self._default_tls_verify_paths = get_default_verify_paths() try: self.set_ssl_cert_locations( self._default_tls_verify_paths.cafile, self._default_tls_verify_paths.capath, ) except GitError as git_err: valid_msg = "TLS backend doesn't support certificate locations" if str(git_err) != valid_msg: raise self._default_tls_verify_paths = None self._ssl_cert_file = None self._ssl_cert_dir = None @property def search_path(self): """Configuration file search path. This behaves like an array whose indices correspond to ConfigLevel values. The local search path cannot be changed. """ return self._search_path @property def mwindow_size(self): """Get or set the maximum mmap window size""" return option(Option.GET_MWINDOW_SIZE) @mwindow_size.setter def mwindow_size(self, value): option(Option.SET_MWINDOW_SIZE, value) @property def mwindow_mapped_limit(self): """ Get or set the maximum memory that will be mapped in total by the library """ return option(Option.GET_MWINDOW_MAPPED_LIMIT) @mwindow_mapped_limit.setter def mwindow_mapped_limit(self, value): option(Option.SET_MWINDOW_MAPPED_LIMIT, value) @property def cached_memory(self): """ Get the current bytes in cache and the maximum that would be allowed in the cache. """ return option(Option.GET_CACHED_MEMORY) def enable_caching(self, value=True): """ Enable or disable caching completely. Because caches are repository-specific, disabling the cache cannot immediately clear all cached objects, but each cache will be cleared on the next attempt to update anything in it. """ return option(Option.ENABLE_CACHING, value) def disable_pack_keep_file_checks(self, value=True): """ This will cause .keep file existence checks to be skipped when accessing packfiles, which can help performance with remote filesystems. """ return option(Option.DISABLE_PACK_KEEP_FILE_CHECKS, value) def cache_max_size(self, value): """ Set the maximum total data size that will be cached in memory across all repositories before libgit2 starts evicting objects from the cache. This is a soft limit, in that the library might briefly exceed it, but will start aggressively evicting objects from cache when that happens. The default cache size is 256MB. """ return option(Option.SET_CACHE_MAX_SIZE, value) def cache_object_limit(self, object_type: pygit2.enums.ObjectType, value): """ Set the maximum data size for the given type of object to be considered eligible for caching in memory. Setting to value to zero means that that type of object will not be cached. Defaults to 0 for enums.ObjectType.BLOB (i.e. won't cache blobs) and 4k for COMMIT, TREE, and TAG. """ return option(Option.SET_CACHE_OBJECT_LIMIT, object_type, value) @property def ssl_cert_file(self): """TLS certificate file path.""" return self._ssl_cert_file @ssl_cert_file.setter def ssl_cert_file(self, value): """Set the TLS cert file path.""" self.set_ssl_cert_locations(value, self._ssl_cert_dir) @ssl_cert_file.deleter def ssl_cert_file(self): """Reset the TLS cert file path.""" self.ssl_cert_file = self._default_tls_verify_paths.cafile @property def ssl_cert_dir(self): """TLS certificates lookup directory path.""" return self._ssl_cert_dir @ssl_cert_dir.setter def ssl_cert_dir(self, value): """Set the TLS certificate lookup folder.""" self.set_ssl_cert_locations(self._ssl_cert_file, value) @ssl_cert_dir.deleter def ssl_cert_dir(self): """Reset the TLS certificate lookup folder.""" self.ssl_cert_dir = self._default_tls_verify_paths.capath def set_ssl_cert_locations(self, cert_file, cert_dir): """ Set the SSL certificate-authority locations. - `cert_file` is the location of a file containing several certificates concatenated together. - `cert_dir` is the location of a directory holding several certificates, one per file. Either parameter may be `NULL`, but not both. """ option(Option.SET_SSL_CERT_LOCATIONS, cert_file, cert_dir) self._ssl_cert_file = cert_file self._ssl_cert_dir = cert_dir libgit2-pygit2-a011e26/pygit2/submodules.py000066400000000000000000000304441473744024100205700ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from __future__ import annotations from typing import TYPE_CHECKING, Iterable, Iterator, Optional, Union from ._pygit2 import Oid from .callbacks import git_fetch_options, RemoteCallbacks from .enums import SubmoduleIgnore, SubmoduleStatus from .errors import check_error from .ffi import ffi, C from .utils import to_bytes, maybe_string # Need BaseRepository for type hints, but don't let it cause a circular dependency if TYPE_CHECKING: from .repository import BaseRepository class Submodule: @classmethod def _from_c(cls, repo: BaseRepository, cptr): subm = cls.__new__(cls) subm._repo = repo subm._subm = cptr return subm def __del__(self): C.git_submodule_free(self._subm) def open(self): """Open the repository for a submodule.""" crepo = ffi.new('git_repository **') err = C.git_submodule_open(crepo, self._subm) check_error(err) return self._repo._from_c(crepo[0], True) def init(self, overwrite: bool = False): """ Just like "git submodule init", this copies information about the submodule into ".git/config". Parameters: overwrite By default, existing submodule entries will not be overwritten, but setting this to True forces them to be updated. """ err = C.git_submodule_init(self._subm, int(overwrite)) check_error(err) def update( self, init: bool = False, callbacks: RemoteCallbacks = None, depth: int = 0 ): """ Update a submodule. This will clone a missing submodule and checkout the subrepository to the commit specified in the index of the containing repository. If the submodule repository doesn't contain the target commit (e.g. because fetchRecurseSubmodules isn't set), then the submodule is fetched using the fetch options supplied in options. Parameters: init If the submodule is not initialized, setting this flag to True will initialize the submodule before updating. Otherwise, this will raise an error if attempting to update an uninitialized repository. callbacks Optional RemoteCallbacks to clone or fetch the submodule. depth Number of commits to fetch. The default is 0 (full commit history). """ opts = ffi.new('git_submodule_update_options *') C.git_submodule_update_options_init( opts, C.GIT_SUBMODULE_UPDATE_OPTIONS_VERSION ) opts.fetch_opts.depth = depth with git_fetch_options(callbacks, opts=opts.fetch_opts) as payload: err = C.git_submodule_update(self._subm, int(init), opts) payload.check_error(err) def reload(self, force: bool = False): """ Reread submodule info from config, index, and HEAD. Call this to reread cached submodule information for this submodule if you have reason to believe that it has changed. Parameters: force Force reload even if the data doesn't seem out of date """ err = C.git_submodule_reload(self._subm, int(force)) check_error(err) @property def name(self): """Name of the submodule.""" name = C.git_submodule_name(self._subm) return ffi.string(name).decode('utf-8') @property def path(self): """Path of the submodule.""" path = C.git_submodule_path(self._subm) return ffi.string(path).decode('utf-8') @property def url(self) -> Union[str, None]: """URL of the submodule.""" url = C.git_submodule_url(self._subm) return maybe_string(url) @property def branch(self): """Branch that is to be tracked by the submodule.""" branch = C.git_submodule_branch(self._subm) return ffi.string(branch).decode('utf-8') @property def head_id(self) -> Union[Oid, None]: """ The submodule's HEAD commit id (as recorded in the superproject's current HEAD tree). Returns None if the superproject's HEAD doesn't contain the submodule. """ head = C.git_submodule_head_id(self._subm) if head == ffi.NULL: return None return Oid(raw=bytes(ffi.buffer(head.id)[:])) class SubmoduleCollection: """Collection of submodules in a repository.""" def __init__(self, repository: BaseRepository): self._repository = repository def __getitem__(self, name: str) -> Submodule: """ Look up submodule information by name or path. Raises KeyError if there is no such submodule. """ csub = ffi.new('git_submodule **') cpath = ffi.new('char[]', to_bytes(name)) err = C.git_submodule_lookup(csub, self._repository._repo, cpath) check_error(err) return Submodule._from_c(self._repository, csub[0]) def __contains__(self, name: str) -> bool: return self.get(name) is not None def __iter__(self) -> Iterator[Submodule]: for s in self._repository.listall_submodules(): yield self[s] def get(self, name: str) -> Union[Submodule, None]: """ Look up submodule information by name or path. Unlike __getitem__, this returns None if the submodule is not found. """ try: return self[name] except KeyError: return None def add( self, url: str, path: str, link: bool = True, callbacks: Optional[RemoteCallbacks] = None, depth: int = 0, ) -> Submodule: """ Add a submodule to the index. The submodule is automatically cloned. Returns: the submodule that was added. Parameters: url The URL of the submodule. path The path within the parent repository to add the submodule link Should workdir contain a gitlink to the repo in `.git/modules` vs. repo directly in workdir. callbacks Optional RemoteCallbacks to clone the submodule. depth Number of commits to fetch. The default is 0 (full commit history). """ csub = ffi.new('git_submodule **') curl = ffi.new('char[]', to_bytes(url)) cpath = ffi.new('char[]', to_bytes(path)) gitlink = 1 if link else 0 err = C.git_submodule_add_setup( csub, self._repository._repo, curl, cpath, gitlink ) check_error(err) submodule_instance = Submodule._from_c(self._repository, csub[0]) # Prepare options opts = ffi.new('git_submodule_update_options *') C.git_submodule_update_options_init( opts, C.GIT_SUBMODULE_UPDATE_OPTIONS_VERSION ) opts.fetch_opts.depth = depth with git_fetch_options(callbacks, opts=opts.fetch_opts) as payload: crepo = ffi.new('git_repository **') err = C.git_submodule_clone(crepo, submodule_instance._subm, opts) payload.check_error(err) # Clean up submodule repository from .repository import Repository Repository._from_c(crepo[0], True) err = C.git_submodule_add_finalize(submodule_instance._subm) check_error(err) return submodule_instance def init(self, submodules: Optional[Iterable[str]] = None, overwrite: bool = False): """ Initialize submodules in the repository. Just like "git submodule init", this copies information about the submodules into ".git/config". Parameters: submodules Optional list of submodule paths or names to initialize. Default argument initializes all submodules. overwrite Flag indicating if initialization should overwrite submodule entries. """ if submodules is None: submodules = self._repository.listall_submodules() instances = [self[s] for s in submodules] for submodule in instances: submodule.init(overwrite) def update( self, submodules: Optional[Iterable[str]] = None, init: bool = False, callbacks: Optional[RemoteCallbacks] = None, depth: int = 0, ): """ Update submodules. This will clone a missing submodule and checkout the subrepository to the commit specified in the index of the containing repository. If the submodule repository doesn't contain the target commit (e.g. because fetchRecurseSubmodules isn't set), then the submodule is fetched using the fetch options supplied in options. Parameters: submodules Optional list of submodule paths or names. If you omit this parameter or pass None, all submodules will be updated. init If the submodule is not initialized, setting this flag to True will initialize the submodule before updating. Otherwise, this will raise an error if attempting to update an uninitialized repository. callbacks Optional RemoteCallbacks to clone or fetch the submodule. depth Number of commits to fetch. The default is 0 (full commit history). """ if submodules is None: submodules = self._repository.listall_submodules() instances = [self[s] for s in submodules] for submodule in instances: submodule.update(init, callbacks, depth) def status( self, name: str, ignore: SubmoduleIgnore = SubmoduleIgnore.UNSPECIFIED ) -> SubmoduleStatus: """ Get the status of a submodule. Returns: A combination of SubmoduleStatus flags. Parameters: name Submodule name or path. ignore A SubmoduleIgnore value indicating how deeply to examine the working directory. """ cstatus = ffi.new('unsigned int *') err = C.git_submodule_status( cstatus, self._repository._repo, to_bytes(name), ignore ) check_error(err) return SubmoduleStatus(cstatus[0]) def cache_all(self): """ Load and cache all submodules in the repository. Because the `.gitmodules` file is unstructured, loading submodules is an O(N) operation. Any operation that requires accessing all submodules is O(N^2) in the number of submodules, if it has to look each one up individually. This function loads all submodules and caches them so that subsequent submodule lookups by name are O(1). """ err = C.git_repository_submodule_cache_all(self._repository._repo) check_error(err) def cache_clear(self): """ Clear the submodule cache populated by `submodule_cache_all`. If there is no cache, do nothing. The cache incorporates data from the repository's configuration, as well as the state of the working tree, the index, and HEAD. So any time any of these has changed, the cache might become invalid. """ err = C.git_repository_submodule_cache_clear(self._repository._repo) check_error(err) libgit2-pygit2-a011e26/pygit2/utils.py000066400000000000000000000116601473744024100175450ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import contextlib import os # Import from pygit2 from .ffi import ffi, C def maybe_string(ptr): if not ptr: return None return ffi.string(ptr).decode('utf8') def to_bytes(s, encoding='utf-8', errors='strict'): if s == ffi.NULL or s is None: return ffi.NULL if hasattr(s, '__fspath__'): s = os.fspath(s) if isinstance(s, bytes): return s return s.encode(encoding, errors) def to_str(s): if hasattr(s, '__fspath__'): s = os.fspath(s) if type(s) is str: return s if type(s) is bytes: return s.decode() raise TypeError(f'unexpected type "{repr(s)}"') def ptr_to_bytes(ptr_cdata): """ Convert a pointer coming from C code () to a byte buffer containing the address that the pointer refers to. """ pp = ffi.new('void **', ptr_cdata) return bytes(ffi.buffer(pp)[:]) @contextlib.contextmanager def new_git_strarray(): strarray = ffi.new('git_strarray *') yield strarray C.git_strarray_dispose(strarray) def strarray_to_strings(arr): """ Return a list of strings from a git_strarray pointer. Free the strings contained in the git_strarry, this means it won't be usable after calling this function. """ try: return [ffi.string(arr.strings[i]).decode('utf-8') for i in range(arr.count)] finally: C.git_strarray_dispose(arr) class StrArray: """A git_strarray wrapper Use this in order to get a git_strarray* to pass to libgit2 out of a list of strings. This has a context manager, which you should use, e.g. with StrArray(list_of_strings) as arr: C.git_function_that_takes_strarray(arr.ptr) To make a pre-existing git_strarray point to the provided list of strings, use the context manager's assign_to() method: struct = ffi.new('git_strarray *', [ffi.NULL, 0]) with StrArray(list_of_strings) as arr: arr.assign_to(struct) The above construct is still subject to FFI scoping rules, i.e. the contents of 'struct' only remain valid within the StrArray context. """ def __init__(self, l): # Allow passing in None as lg2 typically considers them the same as empty if l is None: self.__array = ffi.NULL return if not isinstance(l, (list, tuple)): raise TypeError('Value must be a list') strings = [None] * len(l) for i in range(len(l)): li = l[i] if not isinstance(li, str) and not hasattr(li, '__fspath__'): raise TypeError('Value must be a string or PathLike object') strings[i] = ffi.new('char []', to_bytes(li)) self.__arr = ffi.new('char *[]', strings) self.__strings = strings self.__array = ffi.new('git_strarray *', [self.__arr, len(strings)]) def __enter__(self): return self def __exit__(self, type, value, traceback): pass @property def ptr(self): return self.__array def assign_to(self, git_strarray): if self.__array == ffi.NULL: git_strarray.strings = ffi.NULL git_strarray.count = 0 else: git_strarray.strings = self.__arr git_strarray.count = len(self.__strings) class GenericIterator: """Helper to easily implement an iterator. The constructor gets a container which must implement __len__ and __getitem__ """ def __init__(self, container): self.container = container self.length = len(container) self.idx = 0 def __iter__(self): return self def __next__(self): idx = self.idx if idx >= self.length: raise StopIteration self.idx += 1 return self.container[idx] libgit2-pygit2-a011e26/pyproject.toml000066400000000000000000000023051473744024100175250ustar00rootroot00000000000000[build-system] requires = ["setuptools", "wheel"] [tool.cibuildwheel] enable = ["pypy"] skip = "*musllinux_aarch64 *musllinux_ppc64le" archs = ["auto"] build-frontend = "default" dependency-versions = "pinned" environment = {LIBGIT2_VERSION="1.9.0", LIBSSH2_VERSION="1.11.1", OPENSSL_VERSION="3.2.3", LIBGIT2="/project/ci"} before-all = "sh build.sh" [tool.cibuildwheel.linux] repair-wheel-command = "LD_LIBRARY_PATH=/project/ci/lib64 auditwheel repair -w {dest_dir} {wheel}" archs = ["x86_64", "aarch64", "ppc64le"] [[tool.cibuildwheel.overrides]] select = "*-musllinux*" repair-wheel-command = "LD_LIBRARY_PATH=/project/ci/lib auditwheel repair -w {dest_dir} {wheel}" [tool.cibuildwheel.macos] archs = ["universal2"] environment = {LIBGIT2_VERSION="1.9.0", LIBSSH2_VERSION="1.11.1", OPENSSL_VERSION="3.2.3", LIBGIT2="/Users/runner/work/pygit2/pygit2/ci"} repair-wheel-command = "DYLD_LIBRARY_PATH=/Users/runner/work/pygit2/pygit2/ci/lib delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" [tool.ruff] target-version = "py310" # oldest supported Python version fix = true extend-exclude = [ ".cache", ".coverage", "build", "venv*", ] [tool.ruff.format] quote-style = "single" libgit2-pygit2-a011e26/pytest.ini000066400000000000000000000001001473744024100166310ustar00rootroot00000000000000[pytest] addopts = --capture=no -ra --verbose testpaths = test/ libgit2-pygit2-a011e26/requirements-test.txt000066400000000000000000000000221473744024100210440ustar00rootroot00000000000000pytest pytest-cov libgit2-pygit2-a011e26/requirements.txt000066400000000000000000000000631473744024100200740ustar00rootroot00000000000000cffi>=1.16.0 setuptools ; python_version >= "3.12" libgit2-pygit2-a011e26/setup.cfg000066400000000000000000000001311473744024100164250ustar00rootroot00000000000000[pycodestyle] exclude = .eggs,.git,.tox,build,dist,docs,venv* select = E4,E9,W1,W2,W3,W6 libgit2-pygit2-a011e26/setup.py000066400000000000000000000126701473744024100163310ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Import setuptools before distutils to avoid user warning from setuptools import setup, Extension from distutils.command.build import build from distutils.command.sdist import sdist from distutils import log import os from pathlib import Path from subprocess import Popen, PIPE import sys # Import stuff from pygit2/_utils.py without loading the whole pygit2 package sys.path.insert(0, 'pygit2') from _build import __version__, get_libgit2_paths del sys.path[0] libgit2_bin, libgit2_kw = get_libgit2_paths() class sdist_files_from_git(sdist): def get_file_list(self): popen = Popen( ['git', 'ls-files'], stdout=PIPE, stderr=PIPE, universal_newlines=True ) stdoutdata, stderrdata = popen.communicate() if popen.returncode != 0: print(stderrdata) sys.exit() def exclude(line): for prefix in ['.', 'appveyor.yml', 'docs/', 'misc/']: if line.startswith(prefix): return True return False for line in stdoutdata.splitlines(): if not exclude(line): self.filelist.append(line) # Ok self.filelist.sort() self.filelist.remove_duplicates() self.write_manifest() classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software Development :: Version Control', 'Typing :: Typed', ] __dir__ = Path(__file__).parent long_description = (__dir__ / 'README.md').read_text() cmdclass = { 'sdist': sdist_files_from_git, } # On Windows, we install the git2.dll too. class BuildWithDLLs(build): def _get_dlls(self): # return a list of (FQ-in-name, relative-out-name) tuples. ret = [] bld_ext = self.distribution.get_command_obj('build_ext') compiler_type = bld_ext.compiler.compiler_type libgit2_dlls = [] if compiler_type == 'msvc': libgit2_dlls.append('git2.dll') elif compiler_type == 'mingw32': libgit2_dlls.append('libgit2.dll') look_dirs = [libgit2_bin] + os.getenv('PATH', '').split(os.pathsep) target = Path(self.build_lib).absolute() / 'pygit2' for dll in libgit2_dlls: for look in look_dirs: f = Path(look) / dll if f.is_file(): ret.append((f, target)) break else: log.warn(f'Could not find required DLL {dll} to include') log.debug(f'(looked in {look_dirs})') return ret def run(self): build.run(self) for s, d in self._get_dlls(): self.copy_file(s, d) # On Windows we package up the dlls with the plugin. if os.name == 'nt': cmdclass['build'] = BuildWithDLLs src = __dir__ / 'src' pygit2_exts = [str(path) for path in sorted(src.iterdir()) if path.suffix == '.c'] ext_modules = [Extension('pygit2._pygit2', pygit2_exts, **libgit2_kw)] setup( name='pygit2', description='Python bindings for libgit2.', keywords='git', version=__version__, classifiers=classifiers, license='GPLv2 with linking exception', maintainer='J. David Ibáñez', maintainer_email='jdavid.ibp@gmail.com', long_description=long_description, long_description_content_type='text/markdown', packages=['pygit2'], package_data={'pygit2': ['decl/*.h', '*.pyi', 'py.typed']}, zip_safe=False, cmdclass=cmdclass, cffi_modules=['pygit2/_run.py:ffi'], ext_modules=ext_modules, # Requirements python_requires='>=3.10', setup_requires=['cffi>=1.17.0'], install_requires=['cffi>=1.17.0'], # URLs url='https://github.com/libgit2/pygit2', project_urls={ 'Documentation': 'https://www.pygit2.org/', 'Changelog': 'https://github.com/libgit2/pygit2/blob/master/CHANGELOG.md', 'Funding': 'https://github.com/sponsors/jdavid', }, ) libgit2-pygit2-a011e26/src/000077500000000000000000000000001473744024100154005ustar00rootroot00000000000000libgit2-pygit2-a011e26/src/blob.c000066400000000000000000000406201473744024100164640ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "diff.h" #include "error.h" #include "object.h" #include "oid.h" #include "patch.h" #include "utils.h" extern PyObject *GitError; extern PyTypeObject BlobType; PyDoc_STRVAR(Blob_diff__doc__, "diff([blob: Blob, flag: int = GIT_DIFF_NORMAL, old_as_path: str, new_as_path: str]) -> Patch\n" "\n" "Directly generate a :py:class:`pygit2.Patch` from the difference\n" "between two blobs.\n" "\n" "Returns: Patch.\n" "\n" "Parameters:\n" "\n" "blob : Blob\n" " The :py:class:`~pygit2.Blob` to diff.\n" "\n" "flag\n" " A GIT_DIFF_* constant.\n" "\n" "old_as_path : str\n" " Treat old blob as if it had this filename.\n" "\n" "new_as_path : str\n" " Treat new blob as if it had this filename.\n"); PyObject * Blob_diff(Blob *self, PyObject *args, PyObject *kwds) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_patch *patch; char *old_as_path = NULL, *new_as_path = NULL; Blob *other = NULL; int err; char *keywords[] = {"blob", "flag", "old_as_path", "new_as_path", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!Iss", keywords, &BlobType, &other, &opts.flags, &old_as_path, &new_as_path)) return NULL; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load if (other && Object__load((Object*)other) == NULL) { return NULL; } // Lazy load err = git_patch_from_blobs(&patch, self->blob, old_as_path, other ? other->blob : NULL, new_as_path, &opts); if (err < 0) return Error_set(err); return wrap_patch(patch, self, other); } PyDoc_STRVAR(Blob_diff_to_buffer__doc__, "diff_to_buffer(buffer: bytes = None, flag: int = GIT_DIFF_NORMAL[, old_as_path: str, buffer_as_path: str]) -> Patch\n" "\n" "Directly generate a :py:class:`~pygit2.Patch` from the difference\n" "between a blob and a buffer.\n" "\n" "Returns: Patch.\n" "\n" "Parameters:\n" "\n" "buffer : bytes\n" " Raw data for new side of diff.\n" "\n" "flag\n" " A GIT_DIFF_* constant.\n" "\n" "old_as_path : str\n" " Treat old blob as if it had this filename.\n" "\n" "buffer_as_path : str\n" " Treat buffer as if it had this filename.\n"); PyObject * Blob_diff_to_buffer(Blob *self, PyObject *args, PyObject *kwds) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_patch *patch; char *old_as_path = NULL, *buffer_as_path = NULL; const char *buffer = NULL; Py_ssize_t buffer_len; int err; char *keywords[] = {"buffer", "flag", "old_as_path", "buffer_as_path", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|z#Iss", keywords, &buffer, &buffer_len, &opts.flags, &old_as_path, &buffer_as_path)) return NULL; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load err = git_patch_from_blob_and_buffer(&patch, self->blob, old_as_path, buffer, buffer_len, buffer_as_path, &opts); if (err < 0) return Error_set(err); return wrap_patch(patch, self, NULL); } struct blob_filter_stream { git_writestream stream; PyObject *py_queue; PyObject *py_ready; PyObject *py_closed; Py_ssize_t chunk_size; }; static int blob_filter_stream_write( git_writestream *s, const char *buffer, size_t len) { struct blob_filter_stream *stream = (struct blob_filter_stream *)s; const char *pos = buffer; const char *endpos = buffer + len; Py_ssize_t chunk_size; PyObject *result; PyGILState_STATE gil = PyGILState_Ensure(); int err = 0; while (pos < endpos) { chunk_size = endpos - pos; if (stream->chunk_size < chunk_size) chunk_size = stream->chunk_size; result = PyObject_CallMethod(stream->py_queue, "put", "y#", pos, chunk_size); if (result == NULL) { PyErr_Clear(); git_error_set(GIT_ERROR_OS, "failed to put chunk to queue"); err = GIT_ERROR; goto done; } Py_DECREF(result); result = PyObject_CallMethod(stream->py_ready, "set", NULL); if (result == NULL) { PyErr_Clear(); git_error_set(GIT_ERROR_OS, "failed to signal queue ready"); err = GIT_ERROR; goto done; } pos += chunk_size; } done: PyGILState_Release(gil); return err; } static int blob_filter_stream_close(git_writestream *s) { struct blob_filter_stream *stream = (struct blob_filter_stream *)s; PyGILState_STATE gil = PyGILState_Ensure(); PyObject *result; int err = 0; /* Signal closed and then ready in that order so consumers can block on * ready.wait() and then check for indicated EOF (via closed.is_set()) */ result = PyObject_CallMethod(stream->py_closed, "set", NULL); if (result == NULL) { PyErr_Clear(); git_error_set(GIT_ERROR_OS, "failed to signal writer closed"); err = GIT_ERROR; } result = PyObject_CallMethod(stream->py_ready, "set", NULL); if (result == NULL) { PyErr_Clear(); git_error_set(GIT_ERROR_OS, "failed to signal queue ready"); err = GIT_ERROR; } PyGILState_Release(gil); return err; } static void blob_filter_stream_free(git_writestream *s) { } #define STREAM_CHUNK_SIZE (8 * 1024) PyDoc_STRVAR(Blob__write_to_queue__doc__, "_write_to_queue(queue: queue.Queue, closed: threading.Event, chunk_size: int = io.DEFAULT_BUFFER_SIZE, [as_path: str = None, flags: enums.BlobFilter = enums.BlobFilter.CHECK_FOR_BINARY, commit_id: oid = None]) -> None\n" "\n" "Write the contents of the blob in chunks to `queue`.\n" "If `as_path` is None, the raw contents of blob will be written to the queue,\n" "otherwise the contents of the blob will be filtered.\n" "\n" "In most cases, the higher level `BlobIO` wrapper should be used when\n" "streaming blob content instead of calling this method directly.\n" "\n" "Note that this method will block the current thread until all chunks have\n" "been written to the queue. The GIL will be released while running\n" "libgit2 filtering.\n" "\n" "Returns: The filtered content.\n" "\n" "Parameters:\n" "\n" "queue: queue.Queue\n" " Destination queue.\n" "\n" "ready: threading.Event\n" " Event to signal consumers that the data is available for reading.\n" " This event is also set upon closing the writer in order to indicate \n" " EOF.\n" "\n" "closed: threading.Event\n" " Event to signal consumers that the writer is closed.\n" "\n" "chunk_size : int\n" " Maximum size of chunks to be written to `queue`.\n" "\n" "as_path : str\n" " When set, the blob contents will be filtered as if it had this\n" " filename (used for attribute lookups).\n" "\n" "flags : enums.BlobFilter\n" " A combination of BlobFilter constants (only applicable when `as_path` is set).\n" "\n" "commit_id : oid\n" " Commit to load attributes from when ATTRIBUTES_FROM_COMMIT is\n" " specified in `flags` (only applicable when `as_path` is set).\n"); PyObject * Blob__write_to_queue(Blob *self, PyObject *args, PyObject *kwds) { PyObject *py_queue = NULL; PyObject *py_ready = NULL; PyObject *py_closed = NULL; Py_ssize_t chunk_size = STREAM_CHUNK_SIZE; char *as_path = NULL; PyObject *py_oid = NULL; int err; char *keywords[] = {"queue", "ready", "closed", "chunk_size", "as_path", "flags", "commit_id", NULL}; git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT; git_filter_list *fl = NULL; git_blob *blob = NULL; const git_oid *blob_oid; struct blob_filter_stream writer; if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOO|nzIO", keywords, &py_queue, &py_ready, &py_closed, &chunk_size, &as_path, &opts.flags, &py_oid)) return NULL; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load /* we load our own copy of this blob since libgit2 objects are not * thread-safe */ blob_oid = Object__id((Object*)self); err = git_blob_lookup(&blob, git_blob_owner(self->blob), blob_oid); if (err < 0) return Error_set(err); if (as_path != NULL && !((opts.flags & GIT_BLOB_FILTER_CHECK_FOR_BINARY) != 0 && git_blob_is_binary(blob))) { if (py_oid != NULL && py_oid != Py_None) { err = py_oid_to_git_oid(py_oid, &opts.attr_commit_id); if (err < 0) return Error_set(err); } if ((opts.flags & GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) filter_opts.flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES; if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD) != 0) filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD; if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_COMMIT; git_oid_cpy(&filter_opts.attr_commit_id, &opts.attr_commit_id); err = git_filter_list_load_ext(&fl, git_blob_owner(blob), blob, as_path, GIT_FILTER_TO_WORKTREE, &filter_opts); if (err < 0) { if (blob != NULL) git_blob_free(blob); return Error_set(err); } } memset(&writer, 0, sizeof(struct blob_filter_stream)); writer.stream.write = blob_filter_stream_write; writer.stream.close = blob_filter_stream_close; writer.stream.free = blob_filter_stream_free; writer.py_queue = py_queue; writer.py_ready = py_ready; writer.py_closed = py_closed; writer.chunk_size = chunk_size; Py_INCREF(writer.py_queue); Py_INCREF(writer.py_ready); Py_INCREF(writer.py_closed); Py_BEGIN_ALLOW_THREADS; err = git_filter_list_stream_blob(fl, blob, &writer.stream); Py_END_ALLOW_THREADS; git_filter_list_free(fl); if (writer.py_queue != NULL) Py_DECREF(writer.py_queue); if (writer.py_ready != NULL) Py_DECREF(writer.py_ready); if (writer.py_closed != NULL) Py_DECREF(writer.py_closed); if (blob != NULL) git_blob_free(blob); if (err < 0) return Error_set(err); Py_RETURN_NONE; } static PyMethodDef Blob_methods[] = { METHOD(Blob, diff, METH_VARARGS | METH_KEYWORDS), METHOD(Blob, diff_to_buffer, METH_VARARGS | METH_KEYWORDS), METHOD(Blob, _write_to_queue, METH_VARARGS | METH_KEYWORDS), {NULL} }; PyDoc_STRVAR(Blob_size__doc__, "Size in bytes.\n" "\n" "Example:\n" "\n" " >>> print(blob.size)\n" " 130\n"); PyObject * Blob_size__get__(Blob *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load return PyLong_FromLongLong(git_blob_rawsize(self->blob)); } PyDoc_STRVAR(Blob_is_binary__doc__, "True if binary data, False if not."); PyObject * Blob_is_binary__get__(Blob *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load if (git_blob_is_binary(self->blob)) Py_RETURN_TRUE; Py_RETURN_FALSE; } PyDoc_STRVAR(Blob_data__doc__, "The contents of the blob, a byte string. This is the same as\n" "Blob.read_raw().\n" "\n" "Example, print the contents of the ``.gitignore`` file:\n" "\n" " >>> blob = repo['d8022420bf6db02e906175f64f66676df539f2fd']\n" " >>> print(blob.data)\n" " MANIFEST\n" " build\n" " dist\n"); PyGetSetDef Blob_getseters[] = { GETTER(Blob, size), GETTER(Blob, is_binary), {"data", (getter)Object_read_raw, NULL, Blob_data__doc__, NULL}, {NULL} }; static int Blob_getbuffer(Blob *self, Py_buffer *view, int flags) { if (Object__load((Object*)self) == NULL) { return -1; } // Lazy load return PyBuffer_FillInfo(view, (PyObject *) self, (void *) git_blob_rawcontent(self->blob), git_blob_rawsize(self->blob), 1, flags); } static PyBufferProcs Blob_as_buffer = { (getbufferproc)Blob_getbuffer, }; PyDoc_STRVAR(Blob__doc__, "Blob object.\n" "\n" "Blobs implement the buffer interface, which means you can get access\n" "to its data via `memoryview(blob)` without the need to create a copy." ); PyTypeObject BlobType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Blob", /* tp_name */ sizeof(Blob), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Object_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ &Blob_as_buffer, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Blob__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Blob_methods, /* tp_methods */ 0, /* tp_members */ Blob_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/branch.c000066400000000000000000000241071473744024100170050ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "types.h" #include "branch.h" #include "error.h" #include "reference.h" #include "utils.h" extern PyObject *GitError; extern PyTypeObject ReferenceType; PyDoc_STRVAR(Branch_delete__doc__, "delete()\n" "\n" "Delete this branch. It will no longer be valid!"); PyObject * Branch_delete(Branch *self, PyObject *args) { int err; CHECK_REFERENCE(self); /* Delete the branch */ err = git_branch_delete(self->reference); if (err < 0) return Error_set(err); git_reference_free(self->reference); self->reference = NULL; /* Invalidate the pointer */ Py_RETURN_NONE; } PyDoc_STRVAR(Branch_is_head__doc__, "is_head() -> bool\n" "\n" "True if HEAD points at the branch, False otherwise."); PyObject * Branch_is_head(Branch *self) { int err; CHECK_REFERENCE(self); err = git_branch_is_head(self->reference); if (err == 1) Py_RETURN_TRUE; else if (err == 0) Py_RETURN_FALSE; else return Error_set(err); } PyDoc_STRVAR(Branch_is_checked_out__doc__, "is_checked_out() -> bool\n" "\n" "True if branch is checked out by any repo connected to the current one, " " False otherwise."); PyObject * Branch_is_checked_out(Branch *self) { int err; CHECK_REFERENCE(self); err = git_branch_is_checked_out(self->reference); if (err == 1) Py_RETURN_TRUE; else if (err == 0) Py_RETURN_FALSE; else return Error_set(err); } PyDoc_STRVAR(Branch_rename__doc__, "rename(name: str, force: bool = False)\n" "\n" "Move/rename an existing local branch reference. The new branch name will be " "checked for validity.\n" "Returns the new branch."); PyObject * Branch_rename(Branch *self, PyObject *args) { int err, force = 0; git_reference *c_out; const char *c_name; CHECK_REFERENCE(self); if (!PyArg_ParseTuple(args, "s|i", &c_name, &force)) return NULL; err = git_branch_move(&c_out, self->reference, c_name, force); if (err == GIT_OK) return wrap_branch(c_out, self->repo); else return Error_set(err); } PyDoc_STRVAR(Branch_branch_name__doc__, "The name of the local or remote branch."); PyObject * Branch_branch_name__get__(Branch *self) { int err; const char *c_name; CHECK_REFERENCE(self); err = git_branch_name(&c_name, self->reference); if (err == GIT_OK) return to_unicode(c_name, NULL, NULL); else return Error_set(err); } PyDoc_STRVAR(Branch_raw_branch_name__doc__, "The name of the local or remote branch (bytes)."); PyObject * Branch_raw_branch_name__get__(Branch *self) { int err; const char *c_name; CHECK_REFERENCE(self); err = git_branch_name(&c_name, self->reference); if (err == GIT_OK) return PyBytes_FromString(c_name); else return Error_set(err); } PyDoc_STRVAR(Branch_remote_name__doc__, "Find the remote name of a remote-tracking branch.\n" "\n" "This will return the name of the remote whose fetch refspec is matching " "the given branch. E.g. given a branch 'refs/remotes/test/master', it will " "extract the 'test' part. If refspecs from multiple remotes match, the " "function will raise ValueError."); PyObject * Branch_remote_name__get__(Branch *self) { int err; git_buf name = {NULL}; const char *branch_name; PyObject *py_name; CHECK_REFERENCE(self); branch_name = git_reference_name(self->reference); err = git_branch_remote_name(&name, self->repo->repo, branch_name); if (err < GIT_OK) return Error_set(err); py_name = to_unicode_n(name.ptr, name.size, NULL, NULL); git_buf_dispose(&name); return py_name; } PyDoc_STRVAR(Branch_upstream__doc__, "The branch's upstream branch or None if this branch does not have an upstream set. " "Set to None to unset the upstream configuration."); PyObject * Branch_upstream__get__(Branch *self) { int err; git_reference *c_reference; CHECK_REFERENCE(self); err = git_branch_upstream(&c_reference, self->reference); if (err == GIT_ENOTFOUND) Py_RETURN_NONE; else if (err < GIT_OK) return Error_set(err); return wrap_branch(c_reference, self->repo); } int Branch_upstream__set__(Branch *self, Reference *py_ref) { int err; const char *branch_name = NULL; CHECK_REFERENCE_INT(self); if ((PyObject *)py_ref != Py_None) { if (!PyObject_TypeCheck(py_ref, (PyTypeObject *)&ReferenceType)) { PyErr_SetObject(PyExc_TypeError, (PyObject *)py_ref); return -1; } CHECK_REFERENCE_INT(py_ref); err = git_branch_name(&branch_name, py_ref->reference); if (err < GIT_OK) { Error_set(err); return -1; } } err = git_branch_set_upstream(self->reference, branch_name); if (err < GIT_OK) { Error_set(err); return -1; } return 0; } PyDoc_STRVAR(Branch_upstream_name__doc__, "The name of the reference set to be the upstream of this one"); PyObject * Branch_upstream_name__get__(Branch *self) { int err; git_buf name = {NULL}; const char *branch_name; PyObject *py_name; CHECK_REFERENCE(self); branch_name = git_reference_name(self->reference); err = git_branch_upstream_name(&name, self->repo->repo, branch_name); if (err < GIT_OK) return Error_set(err); py_name = to_unicode_n(name.ptr, name.size, NULL, NULL); git_buf_dispose(&name); return py_name; } PyMethodDef Branch_methods[] = { METHOD(Branch, delete, METH_NOARGS), METHOD(Branch, is_head, METH_NOARGS), METHOD(Branch, is_checked_out, METH_NOARGS), METHOD(Branch, rename, METH_VARARGS), {NULL} }; PyGetSetDef Branch_getseters[] = { GETTER(Branch, branch_name), GETTER(Branch, raw_branch_name), GETTER(Branch, remote_name), GETSET(Branch, upstream), GETTER(Branch, upstream_name), {NULL} }; PyDoc_STRVAR(Branch__doc__, "Branch."); PyTypeObject BranchType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Branch", /* tp_name */ sizeof(Branch), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Branch__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Branch_methods, /* tp_methods */ 0, /* tp_members */ Branch_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * wrap_branch(git_reference *c_reference, Repository *repo) { Branch *py_branch=NULL; py_branch = PyObject_New(Branch, &BranchType); if (py_branch) { py_branch->reference = c_reference; if (repo) { py_branch->repo = repo; Py_INCREF(repo); } } return (PyObject *)py_branch; } libgit2-pygit2-a011e26/src/branch.h000066400000000000000000000031301473744024100170030ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_branch_h #define INCLUDE_pygit2_branch_h #define PY_SSIZE_T_CLEAN #include #include PyObject* Branch_delete(Branch *self, PyObject *args); PyObject* Branch_is_head(Branch *self); PyObject* Branch_is_checked_out(Branch *self); PyObject* Branch_rename(Branch *self, PyObject *args); PyObject* wrap_branch(git_reference *c_reference, Repository *repo); #endif libgit2-pygit2-a011e26/src/commit.c000066400000000000000000000266151473744024100170460ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "utils.h" #include "signature.h" #include "object.h" #include "oid.h" extern PyTypeObject TreeType; extern PyObject *GitError; PyDoc_STRVAR(Commit_message_encoding__doc__, "Message encoding."); PyObject * Commit_message_encoding__get__(Commit *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const char *encoding = git_commit_message_encoding(self->commit); if (encoding == NULL) Py_RETURN_NONE; return to_encoding(encoding); } PyDoc_STRVAR(Commit_message__doc__, "The commit message, a text string."); PyObject * Commit_message__get__(Commit *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const char *message = git_commit_message(self->commit); const char *encoding = git_commit_message_encoding(self->commit); return to_unicode(message, encoding, NULL); } PyDoc_STRVAR(Commit_gpg_signature__doc__, "A tuple with the GPG signature and the signed payload."); PyObject * Commit_gpg_signature__get__(Commit *self) { git_buf gpg_signature = { NULL }, signed_data = { NULL }; PyObject *py_gpg_signature, *py_signed_data; const git_oid *oid = Object__id((Object*)self); int err = git_commit_extract_signature( &gpg_signature, &signed_data, self->repo->repo, (git_oid*) oid, NULL ); if (err != GIT_OK){ git_buf_dispose(&gpg_signature); git_buf_dispose(&signed_data); if (err == GIT_ENOTFOUND){ return Py_BuildValue("OO", Py_None, Py_None); } return Error_set(err); } py_gpg_signature = PyBytes_FromString(gpg_signature.ptr); py_signed_data = PyBytes_FromString(signed_data.ptr); git_buf_dispose(&gpg_signature); git_buf_dispose(&signed_data); return Py_BuildValue("NN", py_gpg_signature, py_signed_data); } PyDoc_STRVAR(Commit_message_trailers__doc__, "Returns commit message trailers (e.g., Bug: 1234) as a dictionary." ); PyObject * Commit_message_trailers__get__(Commit *self) { git_message_trailer_array gmt_arr; int i, trailer_count, err; PyObject *dict; PyObject *py_val; const char *message = git_commit_message(self->commit); const char *encoding = git_commit_message_encoding(self->commit); err = git_message_trailers(&gmt_arr, message); if (err < 0) return Error_set(err); dict = PyDict_New(); if (dict == NULL) goto error; trailer_count = gmt_arr.count; for (i=0; i < trailer_count; i++) { py_val = to_unicode(gmt_arr.trailers[i].value, encoding, NULL); err = PyDict_SetItemString(dict, gmt_arr.trailers[i].key, py_val); Py_DECREF(py_val); if (err < 0) goto error; } git_message_trailer_array_free(&gmt_arr); return dict; error: git_message_trailer_array_free(&gmt_arr); Py_CLEAR(dict); return NULL; } PyDoc_STRVAR(Commit_raw_message__doc__, "Message (bytes)."); PyObject * Commit_raw_message__get__(Commit *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load return PyBytes_FromString(git_commit_message(self->commit)); } PyDoc_STRVAR(Commit_commit_time__doc__, "Commit time."); PyObject * Commit_commit_time__get__(Commit *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load return PyLong_FromLongLong(git_commit_time(self->commit)); } PyDoc_STRVAR(Commit_commit_time_offset__doc__, "Commit time offset."); PyObject * Commit_commit_time_offset__get__(Commit *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load return PyLong_FromLong(git_commit_time_offset(self->commit)); } PyDoc_STRVAR(Commit_committer__doc__, "The committer of the commit."); PyObject * Commit_committer__get__(Commit *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const git_signature *signature = git_commit_committer(self->commit); const char *encoding = git_commit_message_encoding(self->commit); return build_signature((Object*)self, signature, encoding); } PyDoc_STRVAR(Commit_author__doc__, "The author of the commit."); PyObject * Commit_author__get__(Commit *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const git_signature *signature = git_commit_author(self->commit); const char *encoding = git_commit_message_encoding(self->commit); return build_signature((Object*)self, signature, encoding); } PyDoc_STRVAR(Commit_tree__doc__, "The tree object attached to the commit."); PyObject * Commit_tree__get__(Commit *self) { git_tree *tree; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load int err = git_commit_tree(&tree, self->commit); if (err == GIT_ENOTFOUND) { char tree_id[GIT_OID_HEXSZ + 1] = { 0 }; git_oid_fmt(tree_id, git_commit_tree_id(self->commit)); return PyErr_Format(GitError, "Unable to read tree %s", tree_id); } if (err < 0) return Error_set(err); return wrap_object((git_object*)tree, self->repo, NULL); } PyDoc_STRVAR(Commit_tree_id__doc__, "The id of the tree attached to the commit."); PyObject * Commit_tree_id__get__(Commit *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load return git_oid_to_python(git_commit_tree_id(self->commit)); } PyDoc_STRVAR(Commit_parents__doc__, "The list of parent commits."); PyObject * Commit_parents__get__(Commit *self) { Repository *py_repo; unsigned int i, parent_count; const git_oid *parent_oid; git_commit *parent; int err; PyObject *py_parent; PyObject *list; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load parent_count = git_commit_parentcount(self->commit); list = PyList_New(parent_count); if (!list) return NULL; py_repo = self->repo; for (i=0; i < parent_count; i++) { parent_oid = git_commit_parent_id(self->commit, i); if (parent_oid == NULL) { Py_DECREF(list); Error_set(GIT_ENOTFOUND); return NULL; } err = git_commit_lookup(&parent, py_repo->repo, parent_oid); if (err < 0) { Py_DECREF(list); return Error_set_oid(err, parent_oid, GIT_OID_HEXSZ); } py_parent = wrap_object((git_object*)parent, py_repo, NULL); if (py_parent == NULL) { Py_DECREF(list); return NULL; } PyList_SET_ITEM(list, i, py_parent); } return list; } PyDoc_STRVAR(Commit_parent_ids__doc__, "The list of parent commits' ids."); PyObject * Commit_parent_ids__get__(Commit *self) { unsigned int i, parent_count; const git_oid *id; PyObject *list; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load parent_count = git_commit_parentcount(self->commit); list = PyList_New(parent_count); if (!list) return NULL; for (i=0; i < parent_count; i++) { id = git_commit_parent_id(self->commit, i); PyList_SET_ITEM(list, i, git_oid_to_python(id)); } return list; } PyGetSetDef Commit_getseters[] = { GETTER(Commit, message_encoding), GETTER(Commit, message), GETTER(Commit, raw_message), GETTER(Commit, commit_time), GETTER(Commit, commit_time_offset), GETTER(Commit, committer), GETTER(Commit, author), GETTER(Commit, gpg_signature), GETTER(Commit, tree), GETTER(Commit, tree_id), GETTER(Commit, parents), GETTER(Commit, parent_ids), GETTER(Commit, message_trailers), {NULL} }; PyDoc_STRVAR(Commit__doc__, "Commit objects."); PyTypeObject CommitType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Commit", /* tp_name */ sizeof(Commit), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Object_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Commit__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ Commit_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/diff.c000066400000000000000000001210441473744024100164560ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "diff.h" #include "error.h" #include "oid.h" #include "patch.h" #include "types.h" #include "utils.h" extern PyObject *GitError; extern PyTypeObject TreeType; extern PyTypeObject IndexType; extern PyTypeObject DiffType; extern PyTypeObject DiffDeltaType; extern PyTypeObject DiffFileType; extern PyTypeObject DiffHunkType; extern PyTypeObject DiffLineType; extern PyTypeObject DiffStatsType; extern PyTypeObject RepositoryType; extern PyObject *DeltaStatusEnum; extern PyObject *DiffFlagEnum; extern PyObject *FileModeEnum; PyObject * wrap_diff(git_diff *diff, Repository *repo) { Diff *py_diff; py_diff = PyObject_New(Diff, &DiffType); if (py_diff) { Py_XINCREF(repo); py_diff->repo = repo; py_diff->diff = diff; } return (PyObject*) py_diff; } PyObject * wrap_diff_file(const git_diff_file *file) { DiffFile *py_file; if (!file) Py_RETURN_NONE; py_file = PyObject_New(DiffFile, &DiffFileType); if (py_file) { py_file->id = git_oid_to_python(&file->id); if (file->path) { py_file->path = strdup(file->path); py_file->raw_path = PyBytes_FromString(file->path); } else { py_file->path = NULL; py_file->raw_path = NULL; } py_file->size = file->size; py_file->flags = file->flags; py_file->mode = file->mode; } return (PyObject *) py_file; } PyObject * wrap_diff_delta(const git_diff_delta *delta) { DiffDelta *py_delta; if (!delta) Py_RETURN_NONE; py_delta = PyObject_New(DiffDelta, &DiffDeltaType); if (py_delta) { py_delta->status = delta->status; py_delta->flags = delta->flags; py_delta->similarity = delta->similarity; py_delta->nfiles = delta->nfiles; py_delta->old_file = wrap_diff_file(&delta->old_file); py_delta->new_file = wrap_diff_file(&delta->new_file); } return (PyObject *) py_delta; } PyObject * wrap_diff_hunk(Patch *patch, size_t idx) { DiffHunk *py_hunk; const git_diff_hunk *hunk; size_t lines_in_hunk; int err; err = git_patch_get_hunk(&hunk, &lines_in_hunk, patch->patch, idx); if (err < 0) return Error_set(err); py_hunk = PyObject_New(DiffHunk, &DiffHunkType); if (py_hunk) { Py_INCREF(patch); py_hunk->patch = patch; py_hunk->hunk = hunk; py_hunk->idx = idx; py_hunk->n_lines = lines_in_hunk; } return (PyObject *) py_hunk; } PyObject * wrap_diff_stats(git_diff *diff) { git_diff_stats *stats; DiffStats *py_stats; int err; err = git_diff_get_stats(&stats, diff); if (err < 0) return Error_set(err); py_stats = PyObject_New(DiffStats, &DiffStatsType); if (!py_stats) { git_diff_stats_free(stats); return NULL; } py_stats->stats = stats; return (PyObject *) py_stats; } PyObject * wrap_diff_line(const git_diff_line *line, DiffHunk *hunk) { DiffLine *py_line; py_line = PyObject_New(DiffLine, &DiffLineType); if (py_line) { Py_INCREF(hunk); py_line->hunk = hunk; py_line->line = line; } return (PyObject *) py_line; } static void DiffFile_dealloc(DiffFile *self) { Py_CLEAR(self->id); Py_CLEAR(self->raw_path); free(self->path); PyObject_Del(self); } PyDoc_STRVAR(DiffFile_from_c__doc__, "Method exposed for _checkout_notify_cb to hook into"); /* Expose wrap_diff_file to python so we can call it in callbacks.py. */ PyObject * DiffFile_from_c(DiffFile *dummy, PyObject *py_diff_file_ptr) { const git_diff_file *diff_file; char *buffer; Py_ssize_t length; /* Here we need to do the opposite conversion from the _pointer getters */ if (PyBytes_AsStringAndSize(py_diff_file_ptr, &buffer, &length)) return NULL; if (length != sizeof(git_diff_file *)) { PyErr_SetString(PyExc_TypeError, "passed value is not a pointer"); return NULL; } /* the "buffer" contains the pointer */ diff_file = *((const git_diff_file **) buffer); return wrap_diff_file(diff_file); } PyDoc_STRVAR(DiffFile_flags__doc__, "A combination of enums.DiffFlag constants." ); PyObject * DiffFile_flags__get__(DiffFile *self) { return pygit2_enum(DiffFlagEnum, self->flags); } PyDoc_STRVAR(DiffFile_mode__doc__, "Mode of the entry (an enums.FileMode constant)." ); PyObject * DiffFile_mode__get__(DiffFile *self) { return pygit2_enum(FileModeEnum, self->mode); } PyMemberDef DiffFile_members[] = { MEMBER(DiffFile, id, T_OBJECT, "Oid of the item."), MEMBER(DiffFile, path, T_STRING, "Path to the entry."), MEMBER(DiffFile, raw_path, T_OBJECT, "Path to the entry (bytes)."), MEMBER(DiffFile, size, T_LONG, "Size of the entry."), {NULL} }; PyMethodDef DiffFile_methods[] = { METHOD(DiffFile, from_c, METH_STATIC | METH_O), {NULL}, }; PyGetSetDef DiffFile_getsetters[] = { GETTER(DiffFile, flags), GETTER(DiffFile, mode), {NULL}, }; PyDoc_STRVAR(DiffFile__doc__, "DiffFile object."); PyTypeObject DiffFileType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.DiffFile", /* tp_name */ sizeof(DiffFile), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)DiffFile_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ DiffFile__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ DiffFile_methods, /* tp_methods */ DiffFile_members, /* tp_members */ DiffFile_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyDoc_STRVAR(DiffDelta_status_char__doc__, "status_char() -> str\n" "\n" "Return the single character abbreviation for a delta status code." ); PyObject * DiffDelta_status_char(DiffDelta *self) { char status = git_diff_status_char(self->status); return Py_BuildValue("C", status); } PyDoc_STRVAR(DiffDelta_is_binary__doc__, "True if binary data, False if text, None if not (yet) known." ); PyObject * DiffDelta_is_binary__get__(DiffDelta *self) { if (self->flags & GIT_DIFF_FLAG_BINARY) Py_RETURN_TRUE; if (self->flags & GIT_DIFF_FLAG_NOT_BINARY) Py_RETURN_FALSE; // This means the file has not been loaded, so we don't know whether it's // binary or text Py_RETURN_NONE; } PyDoc_STRVAR(DiffDelta_status__doc__, "An enums.DeltaStatus constant." ); PyObject * DiffDelta_status__get__(DiffDelta *self) { return pygit2_enum(DeltaStatusEnum, self->status); } PyDoc_STRVAR(DiffDelta_flags__doc__, "A combination of enums.DiffFlag constants." ); PyObject * DiffDelta_flags__get__(DiffDelta *self) { return pygit2_enum(DiffFlagEnum, self->flags); } static void DiffDelta_dealloc(DiffDelta *self) { Py_CLEAR(self->old_file); Py_CLEAR(self->new_file); PyObject_Del(self); } static PyMethodDef DiffDelta_methods[] = { METHOD(DiffDelta, status_char, METH_NOARGS), {NULL} }; PyMemberDef DiffDelta_members[] = { MEMBER(DiffDelta, similarity, T_USHORT, "For renamed and copied."), MEMBER(DiffDelta, nfiles, T_USHORT, "Number of files in the delta."), MEMBER(DiffDelta, old_file, T_OBJECT, "\"from\" side of the diff."), MEMBER(DiffDelta, new_file, T_OBJECT, "\"to\" side of the diff."), {NULL} }; PyGetSetDef DiffDelta_getsetters[] = { GETTER(DiffDelta, is_binary), GETTER(DiffDelta, status), GETTER(DiffDelta, flags), {NULL} }; PyDoc_STRVAR(DiffDelta__doc__, "DiffDelta object."); PyTypeObject DiffDeltaType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.DiffDelta", /* tp_name */ sizeof(DiffDelta), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)DiffDelta_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ DiffDelta__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ DiffDelta_methods, /* tp_methods */ DiffDelta_members, /* tp_members */ DiffDelta_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; static void DiffLine_dealloc(DiffLine *self) { Py_CLEAR(self->hunk); PyObject_Del(self); } PyDoc_STRVAR(DiffLine_origin__doc__, "Type of the diff line"); PyObject * DiffLine_origin__get__(DiffLine *self) { return PyUnicode_FromStringAndSize(&(self->line->origin), 1); } PyDoc_STRVAR(DiffLine_old_lineno__doc__, "Line number in old file or -1 for added line"); PyObject * DiffLine_old_lineno__get__(DiffLine *self) { return PyLong_FromLong(self->line->old_lineno); } PyDoc_STRVAR(DiffLine_new_lineno__doc__, "Line number in new file or -1 for deleted line"); PyObject * DiffLine_new_lineno__get__(DiffLine *self) { return PyLong_FromLong(self->line->new_lineno); } PyDoc_STRVAR(DiffLine_num_lines__doc__, "Number of newline characters in content"); PyObject * DiffLine_num_lines__get__(DiffLine *self) { return PyLong_FromLong(self->line->num_lines); } PyDoc_STRVAR(DiffLine_content_offset__doc__, "Offset in the original file to the content"); PyObject * DiffLine_content_offset__get__(DiffLine *self) { return PyLong_FromLongLong(self->line->content_offset); } PyDoc_STRVAR(DiffLine_content__doc__, "Content of the diff line"); PyObject * DiffLine_content__get__(DiffLine *self) { return to_unicode_n(self->line->content, self->line->content_len, NULL, NULL); } PyDoc_STRVAR(DiffLine_raw_content__doc__, "Content of the diff line (byte string)"); PyObject * DiffLine_raw_content__get__(DiffLine *self) { return PyBytes_FromStringAndSize(self->line->content, self->line->content_len); } PyGetSetDef DiffLine_getsetters[] = { GETTER(DiffLine, origin), GETTER(DiffLine, old_lineno), GETTER(DiffLine, new_lineno), GETTER(DiffLine, num_lines), GETTER(DiffLine, content_offset), GETTER(DiffLine, content), GETTER(DiffLine, raw_content), {NULL} }; PyDoc_STRVAR(DiffLine__doc__, "DiffLine object."); PyTypeObject DiffLineType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.DiffLine", /* tp_name */ sizeof(DiffLine), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)DiffLine_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ DiffLine__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ DiffLine_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * diff_get_patch_byindex(git_diff *diff, size_t idx) { git_patch *patch = NULL; int err; err = git_patch_from_diff(&patch, diff, idx); if (err < 0) return Error_set(err); return (PyObject*) wrap_patch(patch, NULL, NULL); } PyObject * DiffIter_iternext(DiffIter *self) { if (self->i < self->n) return diff_get_patch_byindex(self->diff->diff, self->i++); PyErr_SetNone(PyExc_StopIteration); return NULL; } void DiffIter_dealloc(DiffIter *self) { Py_CLEAR(self->diff); PyObject_Del(self); } PyDoc_STRVAR(DiffIter__doc__, "Diff iterator object."); PyTypeObject DiffIterType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.DiffIter", /* tp_name */ sizeof(DiffIter), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)DiffIter_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ DiffIter__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ (iternextfunc) DiffIter_iternext, /* tp_iternext */ }; PyObject * diff_get_delta_byindex(git_diff *diff, size_t idx) { const git_diff_delta *delta = git_diff_get_delta(diff, idx); if (delta == NULL) { PyErr_SetObject(PyExc_IndexError, PyLong_FromSize_t(idx)); return NULL; } return (PyObject*) wrap_diff_delta(delta); } PyObject * DeltasIter_iternext(DeltasIter *self) { if (self->i < self->n) return diff_get_delta_byindex(self->diff->diff, self->i++); PyErr_SetNone(PyExc_StopIteration); return NULL; } void DeltasIter_dealloc(DeltasIter *self) { Py_CLEAR(self->diff); PyObject_Del(self); } PyDoc_STRVAR(DeltasIter__doc__, "Deltas iterator object."); PyTypeObject DeltasIterType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.DeltasIter", /* tp_name */ sizeof(DeltasIter), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)DeltasIter_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ DeltasIter__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ (iternextfunc) DeltasIter_iternext, /* tp_iternext */ }; Py_ssize_t Diff_len(Diff *self) { assert(self->diff); return (Py_ssize_t)git_diff_num_deltas(self->diff); } PyDoc_STRVAR(Diff_patchid__doc__, "Corresponding patchid."); PyObject * Diff_patchid__get__(Diff *self) { git_oid oid; int err; err = git_diff_patchid(&oid, self->diff, NULL); if (err < 0) return Error_set(err); return git_oid_to_python(&oid); } PyDoc_STRVAR(Diff_deltas__doc__, "Iterate over the diff deltas."); PyObject * Diff_deltas__get__(Diff *self) { DeltasIter *iter; iter = PyObject_New(DeltasIter, &DeltasIterType); if (iter != NULL) { Py_INCREF(self); iter->diff = self; iter->i = 0; iter->n = git_diff_num_deltas(self->diff); } return (PyObject*)iter; } PyDoc_STRVAR(Diff_patch__doc__, "Patch diff string. Can be None in some cases, such as empty commits."); PyObject * Diff_patch__get__(Diff *self) { git_buf buf = {NULL}; int err = git_diff_to_buf(&buf, self->diff, GIT_DIFF_FORMAT_PATCH); if (err < 0) return Error_set(err); PyObject *py_patch = to_unicode_n(buf.ptr, buf.size, NULL, NULL); git_buf_dispose(&buf); return py_patch; } static void DiffHunk_dealloc(DiffHunk *self) { Py_CLEAR(self->patch); PyObject_Del(self); } PyDoc_STRVAR(DiffHunk_old_start__doc__, "Old start."); PyObject * DiffHunk_old_start__get__(DiffHunk *self) { return PyLong_FromLong(self->hunk->old_start); } PyDoc_STRVAR(DiffHunk_old_lines__doc__, "Old lines."); PyObject * DiffHunk_old_lines__get__(DiffHunk *self) { return PyLong_FromLong(self->hunk->old_lines); } PyDoc_STRVAR(DiffHunk_new_start__doc__, "New start."); PyObject * DiffHunk_new_start__get__(DiffHunk *self) { return PyLong_FromLong(self->hunk->new_start); } PyDoc_STRVAR(DiffHunk_new_lines__doc__, "New lines."); PyObject * DiffHunk_new_lines__get__(DiffHunk *self) { return PyLong_FromLong(self->hunk->new_lines); } PyDoc_STRVAR(DiffHunk_header__doc__, "Header."); PyObject * DiffHunk_header__get__(DiffHunk *self) { return to_unicode_n((const char *) &self->hunk->header, self->hunk->header_len, NULL, NULL); } PyDoc_STRVAR(DiffHunk_lines__doc__, "Lines."); PyObject * DiffHunk_lines__get__(DiffHunk *self) { PyObject *py_lines; PyObject *py_line; const git_diff_line *line; size_t i; int err; // TODO Replace by an iterator py_lines = PyList_New(self->n_lines); for (i = 0; i < self->n_lines; ++i) { err = git_patch_get_line_in_hunk(&line, self->patch->patch, self->idx, i); if (err < 0) return Error_set(err); py_line = wrap_diff_line(line, self); if (py_line == NULL) return NULL; PyList_SetItem(py_lines, i, py_line); } return py_lines; } PyGetSetDef DiffHunk_getsetters[] = { GETTER(DiffHunk, old_start), GETTER(DiffHunk, old_lines), GETTER(DiffHunk, new_start), GETTER(DiffHunk, new_lines), GETTER(DiffHunk, header), GETTER(DiffHunk, lines), {NULL} }; PyDoc_STRVAR(DiffHunk__doc__, "DiffHunk object."); PyTypeObject DiffHunkType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.DiffHunk", /* tp_name */ sizeof(DiffHunk), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)DiffHunk_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ DiffHunk__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ DiffHunk_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyDoc_STRVAR(DiffStats_insertions__doc__, "Total number of insertions"); PyObject * DiffStats_insertions__get__(DiffStats *self) { return PyLong_FromSize_t(git_diff_stats_insertions(self->stats)); } PyDoc_STRVAR(DiffStats_deletions__doc__, "Total number of deletions"); PyObject * DiffStats_deletions__get__(DiffStats *self) { return PyLong_FromSize_t(git_diff_stats_deletions(self->stats)); } PyDoc_STRVAR(DiffStats_files_changed__doc__, "Total number of files changed"); PyObject * DiffStats_files_changed__get__(DiffStats *self) { return PyLong_FromSize_t(git_diff_stats_files_changed(self->stats)); } PyDoc_STRVAR(DiffStats_format__doc__, "format(format: enums.DiffStatsFormat, width: int) -> str\n" "\n" "Format the stats as a string.\n" "\n" "Returns: str.\n" "\n" "Parameters:\n" "\n" "format\n" " The format to use. A combination of DiffStatsFormat constants.\n" "\n" "width\n" " The width of the output. The output will be scaled to fit."); PyObject * DiffStats_format(DiffStats *self, PyObject *args, PyObject *kwds) { int err, format; git_buf buf = { 0 }; Py_ssize_t width; PyObject *str; char *keywords[] = {"format", "width", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "in", keywords, &format, &width)) return NULL; if (width <= 0) { PyErr_SetString(PyExc_ValueError, "width must be positive"); return NULL; } err = git_diff_stats_to_buf(&buf, self->stats, format, width); if (err < 0) return Error_set(err); str = to_unicode_n(buf.ptr, buf.size, NULL, NULL); git_buf_dispose(&buf); return str; } static void DiffStats_dealloc(DiffStats *self) { git_diff_stats_free(self->stats); PyObject_Del(self); } PyMethodDef DiffStats_methods[] = { METHOD(DiffStats, format, METH_VARARGS | METH_KEYWORDS), {NULL} }; PyGetSetDef DiffStats_getsetters[] = { GETTER(DiffStats, insertions), GETTER(DiffStats, deletions), GETTER(DiffStats, files_changed), {NULL} }; PyDoc_STRVAR(DiffStats__doc__, "DiffStats object."); PyTypeObject DiffStatsType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.DiffStats", /* tp_name */ sizeof(DiffStats), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)DiffStats_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ DiffStats__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ DiffStats_methods, /* tp_methods */ 0, /* tp_members */ DiffStats_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyDoc_STRVAR(Diff_from_c__doc__, "Method exposed for Index to hook into"); PyObject * Diff_from_c(Diff *dummy, PyObject *args) { PyObject *py_diff, *py_repository; git_diff *diff; char *buffer; Py_ssize_t length; if (!PyArg_ParseTuple(args, "OO!", &py_diff, &RepositoryType, &py_repository)) return NULL; /* Here we need to do the opposite conversion from the _pointer getters */ if (PyBytes_AsStringAndSize(py_diff, &buffer, &length)) return NULL; if (length != sizeof(git_diff *)) { PyErr_SetString(PyExc_TypeError, "passed value is not a pointer"); return NULL; } /* the "buffer" contains the pointer */ diff = *((git_diff **) buffer); return wrap_diff(diff, (Repository *) py_repository); } PyDoc_STRVAR(Diff_merge__doc__, "merge(diff: Diff)\n" "\n" "Merge one diff into another."); PyObject * Diff_merge(Diff *self, PyObject *args) { Diff *py_diff; int err; if (!PyArg_ParseTuple(args, "O!", &DiffType, &py_diff)) return NULL; err = git_diff_merge(self->diff, py_diff->diff); if (err < 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Diff_find_similar__doc__, "find_similar(flags: enums.DiffFind = enums.DiffFind.FIND_BY_CONFIG, rename_threshold: int = 50, copy_threshold: int = 50, rename_from_rewrite_threshold: int = 50, break_rewrite_threshold: int = 60, rename_limit: int = 1000)\n" "\n" "Transform a diff marking file renames, copies, etc.\n" "\n" "This modifies a diff in place, replacing old entries that look like\n" "renames or copies with new entries reflecting those changes. This also " "will, if requested, break modified files into add/remove pairs if the " "amount of change is above a threshold.\n" "\n" "flags - Combination of enums.DiffFind.FIND_* and enums.DiffFind.BREAK_* constants." ); PyObject * Diff_find_similar(Diff *self, PyObject *args, PyObject *kwds) { int err; git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; char *keywords[] = {"flags", "rename_threshold", "copy_threshold", "rename_from_rewrite_threshold", "break_rewrite_threshold", "rename_limit", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iHHHHI", keywords, &opts.flags, &opts.rename_threshold, &opts.copy_threshold, &opts.rename_from_rewrite_threshold, &opts.break_rewrite_threshold, &opts.rename_limit)) return NULL; err = git_diff_find_similar(self->diff, &opts); if (err < 0) return Error_set(err); Py_RETURN_NONE; } PyObject * Diff_iter(Diff *self) { DiffIter *iter; iter = PyObject_New(DiffIter, &DiffIterType); if (iter != NULL) { Py_INCREF(self); iter->diff = self; iter->i = 0; iter->n = git_diff_num_deltas(self->diff); } return (PyObject*)iter; } PyObject * Diff_getitem(Diff *self, PyObject *value) { size_t i; if (!PyLong_Check(value)) return NULL; /* FIXME Raise error */ i = PyLong_AsSize_t(value); return diff_get_patch_byindex(self->diff, i); } PyDoc_STRVAR(Diff_stats__doc__, "Accumulate diff statistics for all patches."); PyObject * Diff_stats__get__(Diff *self) { return wrap_diff_stats(self->diff); } PyDoc_STRVAR(Diff_parse_diff__doc__, "parse_diff(git_diff: str | bytes) -> Diff\n" "\n" "Parses a git unified diff into a diff object without a repository"); static PyObject * Diff_parse_diff(PyObject *self, PyObject *py_str) { /* A wrapper around git_diff_from_buffer */ git_diff *diff; const char *content = pgit_borrow(py_str); if (content == NULL) return NULL; int err = git_diff_from_buffer(&diff, content, strlen(content)); if (err < 0) return Error_set(err); return wrap_diff(diff, NULL); } static void Diff_dealloc(Diff *self) { git_diff_free(self->diff); Py_CLEAR(self->repo); PyObject_Del(self); } PyGetSetDef Diff_getsetters[] = { GETTER(Diff, deltas), GETTER(Diff, patch), GETTER(Diff, stats), GETTER(Diff, patchid), {NULL} }; PyMappingMethods Diff_as_mapping = { (lenfunc)Diff_len, /* mp_length */ (binaryfunc)Diff_getitem, /* mp_subscript */ 0, /* mp_ass_subscript */ }; static PyMethodDef Diff_methods[] = { METHOD(Diff, merge, METH_VARARGS), METHOD(Diff, find_similar, METH_VARARGS | METH_KEYWORDS), METHOD(Diff, from_c, METH_STATIC | METH_VARARGS), {"parse_diff", (PyCFunction) Diff_parse_diff, METH_O | METH_STATIC, Diff_parse_diff__doc__}, {NULL} }; /* TODO Implement Diff.patches, deprecate Diff_iter and Diff_getitem */ PyDoc_STRVAR(Diff__doc__, "Diff objects."); PyTypeObject DiffType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Diff", /* tp_name */ sizeof(Diff), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Diff_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ &Diff_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ Diff__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)Diff_iter, /* tp_iter */ 0, /* tp_iternext */ Diff_methods, /* tp_methods */ 0, /* tp_members */ Diff_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/diff.h000066400000000000000000000031711473744024100164630ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_diff_h #define INCLUDE_pygit2_diff_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject* wrap_diff(git_diff *diff, Repository *repo); PyObject* wrap_diff_delta(const git_diff_delta *delta); PyObject* wrap_diff_file(const git_diff_file *file); PyObject* wrap_diff_hunk(Patch *patch, size_t idx); PyObject* wrap_diff_line(const git_diff_line *line, DiffHunk *hunk); #endif libgit2-pygit2-a011e26/src/error.c000066400000000000000000000106351473744024100167020ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "error.h" extern PyObject *GitError; extern PyObject *AlreadyExistsError; extern PyObject *InvalidSpecError; PyObject * Error_type(int type) { const git_error* error; /* Expected */ switch (type) { /* Input does not exist in the scope searched. */ case GIT_ENOTFOUND: return PyExc_KeyError; /* A reference with this name already exists */ case GIT_EEXISTS: return AlreadyExistsError; /* The given short oid is ambiguous */ case GIT_EAMBIGUOUS: return PyExc_ValueError; /* The buffer is too short to satisfy the request */ case GIT_EBUFS: return PyExc_ValueError; /* Invalid input spec */ case GIT_EINVALIDSPEC: return InvalidSpecError; /* Skip and passthrough the given ODB backend */ case GIT_PASSTHROUGH: return GitError; /* No entries left in ref walker */ case GIT_ITEROVER: return PyExc_StopIteration; } /* Critical */ error = git_error_last(); if (error != NULL) { switch (error->klass) { case GITERR_NOMEMORY: return PyExc_MemoryError; case GITERR_OS: return PyExc_OSError; case GITERR_INVALID: return PyExc_ValueError; } } return GitError; } PyObject * Error_set(int err) { assert(err < 0); return Error_set_exc(Error_type(err)); } PyObject * Error_set_exc(PyObject* exception) { const git_error* error = git_error_last(); char* message = (error == NULL) ? "(No error information given)" : error->message; PyErr_SetString(exception, message); return NULL; } PyObject * Error_set_str(int err, const char *str) { if (err == GIT_ENOTFOUND) { /* KeyError expects the arg to be the missing key. */ PyErr_SetString(PyExc_KeyError, str); return NULL; } const git_error *error = git_error_last(); if (error == NULL) /* Expected error - no error msg set */ return PyErr_Format(Error_type(err), "%s", str); return PyErr_Format(Error_type(err), "%s: %s", str, error->message); } PyObject * Error_set_oid(int err, const git_oid *oid, size_t len) { char hex[GIT_OID_HEXSZ + 1]; git_oid_fmt(hex, oid); hex[len] = '\0'; return Error_set_str(err, hex); } PyObject * Error_type_error(const char *format, PyObject *value) { PyErr_Format(PyExc_TypeError, format, Py_TYPE(value)->tp_name); return NULL; } int git_error_for_exc(void) { PyObject *err = PyErr_Occurred(); if (err) { // FIXME Here we're masking exception, if the Python implementation has // a genuine Key or Value error. We should have an explicit way for the // Python callbacks to signal ENOTFOUND (and EAMBIGUOUS?) // Not found is an expected condition (the ODB will try with the next // backend), so we clear the exception. if (PyErr_GivenExceptionMatches(err, PyExc_KeyError)) { PyErr_Clear(); return GIT_ENOTFOUND; } if (PyErr_GivenExceptionMatches(err, PyExc_ValueError)) return GIT_EAMBIGUOUS; return GIT_EUSER; } return 0; } libgit2-pygit2-a011e26/src/error.h000066400000000000000000000032121473744024100167000ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_error_h #define INCLUDE_pygit2_error_h #define PY_SSIZE_T_CLEAN #include #include PyObject* Error_type(int type); PyObject* Error_set(int err); PyObject* Error_set_exc(PyObject* exception); PyObject* Error_set_str(int err, const char *str); PyObject* Error_set_oid(int err, const git_oid *oid, size_t len); PyObject* Error_type_error(const char *format, PyObject *value); int git_error_for_exc(void); #endif libgit2-pygit2-a011e26/src/filter.c000066400000000000000000000401001473744024100170240ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include "diff.h" #include "error.h" #include "object.h" #include "oid.h" #include "patch.h" #include "utils.h" #include "filter.h" extern PyObject *GitError; extern PyTypeObject FilterSourceType; extern PyTypeObject RepositoryType; PyDoc_STRVAR(FilterSource_repo__doc__, "Repository the source data is from\n"); PyObject * FilterSource_repo__get__(FilterSource *self) { git_repository *repo = git_filter_source_repo(self->src); Repository *py_repo; if (repo == NULL) Py_RETURN_NONE; py_repo = PyObject_New(Repository, &RepositoryType); if (py_repo == NULL) return NULL; py_repo->repo = repo; py_repo->config = NULL; py_repo->index = NULL; py_repo->owned = 0; Py_INCREF(py_repo); return (PyObject *)py_repo; } PyDoc_STRVAR(FilterSource_path__doc__, "File path the source data is from.\n"); PyObject * FilterSource_path__get__(FilterSource *self) { return to_unicode_safe(git_filter_source_path(self->src), NULL); } PyDoc_STRVAR(FilterSource_filemode__doc__, "Mode of the source file. If this is unknown, `filemode` will be 0.\n"); PyObject * FilterSource_filemode__get__(FilterSource *self) { return PyLong_FromUnsignedLong(git_filter_source_filemode(self->src)); } PyDoc_STRVAR(FilterSource_oid__doc__, "Oid of the source object. If the oid is unknown " "(often the case with FilterMode.CLEAN) then `oid` will be None.\n"); PyObject * FilterSource_oid__get__(FilterSource *self) { const git_oid *oid = git_filter_source_id(self->src); if (oid == NULL) Py_RETURN_NONE; return git_oid_to_python(oid); } PyDoc_STRVAR(FilterSource_mode__doc__, "Filter mode (either FilterMode.CLEAN or FilterMode.SMUDGE).\n"); PyObject * FilterSource_mode__get__(FilterSource *self) { return PyLong_FromUnsignedLong(git_filter_source_mode(self->src)); } PyDoc_STRVAR(FilterSource_flags__doc__, "A combination of filter flags (enums.FilterFlag) to be applied to the data.\n"); PyObject * FilterSource_flags__get__(FilterSource *self) { return PyLong_FromUnsignedLong(git_filter_source_flags(self->src)); } PyGetSetDef FilterSource_getseters[] = { GETTER(FilterSource, repo), GETTER(FilterSource, path), GETTER(FilterSource, filemode), GETTER(FilterSource, oid), GETTER(FilterSource, mode), GETTER(FilterSource, flags), {NULL} }; PyDoc_STRVAR(FilterSource__doc__, "A filter source represents the file/blob to be processed.\n"); PyTypeObject FilterSourceType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.FilterSource", /* tp_name */ sizeof(FilterSource), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ FilterSource__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ FilterSource_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyDoc_STRVAR(filter__write_next__doc__, "Write to the next writestream in a filter list.\n"); static PyObject * filter__write_next(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *py_next; git_writestream *next; const char *buf; Py_ssize_t size; char *keywords[] = {"next", "data", NULL}; int err; if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oy#", keywords, &py_next, &buf, &size)) return NULL; next = (git_writestream *)PyCapsule_GetPointer(py_next, NULL); if (next == NULL) goto done; Py_BEGIN_ALLOW_THREADS; err = next->write(next, buf, size); Py_END_ALLOW_THREADS; if (err < 0) return Error_set(err); done: Py_RETURN_NONE; } static PyMethodDef filter__write_next_method = { "_write_next", (PyCFunction)filter__write_next, METH_VARARGS | METH_KEYWORDS, filter__write_next__doc__ }; struct pygit2_filter_stream { git_writestream stream; git_writestream *next; PyObject *py_filter; FilterSource *py_src; PyObject *py_write_next; }; struct pygit2_filter_payload { PyObject *py_filter; FilterSource *src; struct pygit2_filter_stream *stream; }; static void pygit2_filter_payload_free( struct pygit2_filter_payload *payload) { if (payload == NULL) return; if (payload->py_filter != NULL) Py_DECREF(payload->py_filter); if (payload->src != NULL) Py_DECREF(payload->src); if (payload->stream != NULL) free(payload->stream); free(payload); } static struct pygit2_filter_payload * pygit2_filter_payload_new( PyObject *py_filter_cls, const git_filter_source *src) { struct pygit2_filter_payload *payload = NULL; payload = malloc(sizeof(struct pygit2_filter_payload)); if (payload == NULL) return NULL; memset(payload, 0, sizeof(struct pygit2_filter_payload)); payload->py_filter = PyObject_CallFunction(py_filter_cls, NULL); if (payload->py_filter == NULL) { PyErr_Clear(); goto error; } payload->src = PyObject_New(FilterSource, &FilterSourceType); if (payload->src == NULL) { PyErr_Clear(); goto error; } payload->src->src = src; goto done; error: pygit2_filter_payload_free(payload); payload = NULL; done: return payload; } static int pygit2_filter_stream_write( git_writestream *s, const char *buffer, size_t len) { struct pygit2_filter_stream *stream = (struct pygit2_filter_stream *)s; PyObject *result = NULL; PyGILState_STATE gil = PyGILState_Ensure(); int err = 0; result = PyObject_CallMethod(stream->py_filter, "write", "y#OO", buffer, len, stream->py_src, stream->py_write_next); if (result == NULL) { PyErr_Clear(); git_error_set(GIT_ERROR_OS, "failed to write to filter stream"); err = GIT_ERROR; goto done; } Py_DECREF(result); done: PyGILState_Release(gil); return err; } static int pygit2_filter_stream_close(git_writestream *s) { struct pygit2_filter_stream *stream = (struct pygit2_filter_stream *)s; PyObject *result = NULL; PyGILState_STATE gil = PyGILState_Ensure(); int err = 0; int nexterr; result = PyObject_CallMethod(stream->py_filter, "close", "O", stream->py_write_next); if (result == NULL) { PyErr_Clear(); git_error_set(GIT_ERROR_OS, "failed to close filter stream"); err = GIT_ERROR; goto done; } Py_DECREF(result); done: if (stream->py_write_next != NULL) Py_DECREF(stream->py_write_next); PyGILState_Release(gil); if (stream->next != NULL) { nexterr = stream->next->close(stream->next); if (err == 0) err = nexterr; } return err; } static void pygit2_filter_stream_free(git_writestream *s) { } static int pygit2_filter_stream_init( struct pygit2_filter_stream *stream, git_writestream *next, PyObject *py_filter, FilterSource *py_src) { int err = 0; PyObject *py_next = NULL; PyObject *py_functools = NULL; PyObject *py_write_next = NULL; PyObject *py_partial_write = NULL; PyGILState_STATE gil = PyGILState_Ensure(); memset(stream, 0, sizeof(struct pygit2_filter_stream)); stream->stream.write = pygit2_filter_stream_write; stream->stream.close = pygit2_filter_stream_close; stream->stream.free = pygit2_filter_stream_free; stream->next = next; stream->py_filter = py_filter; stream->py_src = py_src; py_functools = PyImport_ImportModule("functools"); if (py_functools == NULL) { PyErr_Clear(); git_error_set(GIT_ERROR_OS, "failed to import module"); err = GIT_ERROR; goto error; } py_next = PyCapsule_New(stream->next, NULL, NULL); if (py_next == NULL) { PyErr_Clear(); giterr_set_oom(); err = GIT_ERROR; goto error; } py_write_next = PyCFunction_New(&filter__write_next_method, NULL); if (py_write_next == NULL) { PyErr_Clear(); err = GIT_ERROR; goto error; } py_partial_write = PyObject_CallMethod(py_functools, "partial", "OO", py_write_next, py_next); if (py_partial_write == NULL) { PyErr_Clear(); err = GIT_ERROR; goto error; } stream->py_write_next = py_partial_write; goto done; error: if (py_partial_write != NULL) Py_DECREF(py_partial_write); done: if (py_write_next != NULL) Py_DECREF(py_write_next); if (py_functools != NULL) Py_DECREF(py_functools); if (py_next != NULL) Py_DECREF(py_next); PyGILState_Release(gil); return err; } static PyObject * get_passthrough() { PyObject *py_passthrough; PyObject *py_errors = PyImport_ImportModule("pygit2.errors"); if (py_errors == NULL) return NULL; py_passthrough = PyObject_GetAttrString(py_errors, "Passthrough"); Py_DECREF(py_errors); return py_passthrough; } int pygit2_filter_check( git_filter *self, void **payload, const git_filter_source *src, const char **attr_values) { pygit2_filter *filter = (pygit2_filter *)self; struct pygit2_filter_payload *pl = NULL; PyObject *py_attrs = NULL; Py_ssize_t nattrs; Py_ssize_t i; PyObject *result; PyObject *py_passthrough = NULL; PyGILState_STATE gil = PyGILState_Ensure(); int err = 0; py_passthrough = get_passthrough(); if (py_passthrough == NULL) { PyErr_Clear(); err = GIT_ERROR; goto error; } pl = pygit2_filter_payload_new(filter->py_filter_cls, src); if (pl == NULL) { giterr_set_oom(); err = GIT_ERROR; goto done; } result = PyObject_CallMethod(pl->py_filter, "nattrs", NULL); if (result == NULL) { PyErr_Clear(); err = GIT_ERROR; goto error; } nattrs = PyLong_AsSsize_t(result); Py_DECREF(result); py_attrs = PyList_New(nattrs); if (py_attrs == NULL) { PyErr_Clear(); err = GIT_ERROR; goto error; } for (i = 0; i < nattrs; ++i) { if (attr_values[i] == NULL) { if (PyList_SetItem(py_attrs, i, Py_None) < 0) { PyErr_Clear(); err = GIT_ERROR; goto error; } } else if (PyList_SetItem(py_attrs, i, to_unicode_safe(attr_values[i], NULL)) < 0) { PyErr_Clear(); err = GIT_ERROR; goto error; } } result = PyObject_CallMethod(pl->py_filter, "check", "OO", pl->src, py_attrs); if (result == NULL) { if (PyErr_ExceptionMatches(py_passthrough)) { PyErr_Clear(); err = GIT_PASSTHROUGH; } else { PyErr_Clear(); err = GIT_ERROR; goto error; } } else { Py_DECREF(result); *payload = pl; } goto done; error: if (pl != NULL) pygit2_filter_payload_free(pl); done: if (py_attrs != NULL) Py_DECREF(py_attrs); if (py_passthrough != NULL) Py_DECREF(py_passthrough); PyGILState_Release(gil); return err; } int pygit2_filter_stream( git_writestream **out, git_filter *self, void **payload, const git_filter_source *src, git_writestream *next) { pygit2_filter *filter = (pygit2_filter *)self; struct pygit2_filter_stream *stream = NULL; struct pygit2_filter_payload *pl = NULL; PyGILState_STATE gil = PyGILState_Ensure(); int err = 0; if (*payload == NULL) { pl = pygit2_filter_payload_new(filter->py_filter_cls, src); if (pl == NULL) { giterr_set_oom(); err = GIT_ERROR; goto done; } *payload = pl; } else { pl = *payload; } stream = malloc(sizeof(struct pygit2_filter_stream)); if ((err = pygit2_filter_stream_init(stream, next, pl->py_filter, pl->src)) < 0) goto error; *out = &stream->stream; goto done; error: if (stream != NULL) free(stream); done: PyGILState_Release(gil); return err; } void pygit2_filter_cleanup(git_filter *self, void *payload) { struct pygit2_filter_payload *pl = (struct pygit2_filter_payload *)payload; PyGILState_STATE gil = PyGILState_Ensure(); pygit2_filter_payload_free(pl); PyGILState_Release(gil); } void pygit2_filter_shutdown(git_filter *self) { pygit2_filter *filter = (pygit2_filter *)self; PyGILState_STATE gil = PyGILState_Ensure(); Py_DECREF(filter->py_filter_cls); free(filter); PyGILState_Release(gil); } libgit2-pygit2-a011e26/src/filter.h000066400000000000000000000035231473744024100170410ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_filter_h #define INCLUDE_pygit2_filter_h #define PY_SSIZE_T_CLEAN #include #include #include #include "types.h" typedef struct pygit2_filter { git_filter filter; PyObject *py_filter_cls; } pygit2_filter; int pygit2_filter_check( git_filter *self, void **payload, const git_filter_source *src, const char **attr_values); int pygit2_filter_stream( git_writestream **out, git_filter *self, void **payload, const git_filter_source *src, git_writestream *next); void pygit2_filter_cleanup(git_filter *self, void *payload); void pygit2_filter_shutdown(git_filter *self); #endif libgit2-pygit2-a011e26/src/mailmap.c000066400000000000000000000216551473744024100171750ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "error.h" #include "utils.h" #include "types.h" #include "mailmap.h" #include "signature.h" extern PyTypeObject SignatureType; extern PyTypeObject RepositoryType; int Mailmap_init(Mailmap *self, PyObject *args, PyObject *kwargs) { char *keywords[] = {NULL}; git_mailmap *mm; int error; /* Our init method does not handle parameters */ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", keywords)) return -1; error = git_mailmap_new(&mm); if (error < 0) { Error_set(error); return -1; } self->mailmap = mm; return 0; } PyDoc_STRVAR(Mailmap_from_repository__doc__, "from_repository(repository: Repository) -> Mailmap\n" "\n" "Create a new mailmap instance from a repository, loading mailmap files based on the repository's configuration.\n" "\n" "Mailmaps are loaded in the following order:\n" " 1. '.mailmap' in the root of the repository's working directory, if present.\n" " 2. The blob object identified by the 'mailmap.blob' config entry, if set.\n" " [NOTE: 'mailmap.blob' defaults to 'HEAD:.mailmap' in bare repositories]\n" " 3. The path in the 'mailmap.file' config entry, if set."); PyObject * Mailmap_from_repository(Mailmap *dummy, PyObject *args) { Repository *repo = NULL; git_mailmap *mm = NULL; int error; if (!PyArg_ParseTuple(args, "O!", &RepositoryType, &repo)) return NULL; error = git_mailmap_from_repository(&mm, repo->repo); if (error < 0) return Error_set(error); return wrap_mailmap(mm); } PyDoc_STRVAR(Mailmap_from_buffer__doc__, "from_buffer(buffer: str) -> Mailmap\n" "\n" "Parse a passed-in buffer and construct a mailmap object."); PyObject * Mailmap_from_buffer(Mailmap *dummy, PyObject *args) { char *buffer = NULL; Py_ssize_t size = 0; git_mailmap *mm = NULL; int error; if (!PyArg_ParseTuple(args, "s#", &buffer, &size)) return NULL; error = git_mailmap_from_buffer(&mm, buffer, size); if (error < 0) return Error_set(error); return wrap_mailmap(mm); } PyDoc_STRVAR(Mailmap_add_entry__doc__, "add_entry(real_name: str = None, real_email: str = None, replace_name: str = None, replace_email: str)\n" "\n" "Add a new entry to the mailmap, overriding existing entries."); PyObject * Mailmap_add_entry(Mailmap *self, PyObject *args, PyObject *kwargs) { char *keywords[] = {"real_name", "real_email", "replace_name", "replace_email", NULL}; char *real_name = NULL, *real_email = NULL; char *replace_name = NULL, *replace_email = NULL; int error; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|zzzs", keywords, &real_name, &real_email, &replace_name, &replace_email)) return NULL; /* replace_email cannot be null */ if (!replace_email) { PyErr_BadArgument(); return NULL; } error = git_mailmap_add_entry(self->mailmap, real_name, real_email, replace_name, replace_email); if (error < 0) return Error_set(error); Py_RETURN_NONE; } PyDoc_STRVAR(Mailmap_resolve__doc__, "resolve(name: str, email: str) -> tuple[str, str]\n" "\n" "Resolve name & email to a real name and email."); PyObject * Mailmap_resolve(Mailmap *self, PyObject *args) { const char *name = NULL, *email = NULL; const char *real_name = NULL, *real_email = NULL; int error; if (!PyArg_ParseTuple(args, "ss", &name, &email)) return NULL; error = git_mailmap_resolve(&real_name, &real_email, self->mailmap, name, email); if (error < 0) return Error_set(error); return Py_BuildValue("ss", real_name, real_email); } PyDoc_STRVAR(Mailmap_resolve_signature__doc__, "resolve_signature(sig: Signature) -> Signature\n" "\n" "Resolve signature to real name and email."); PyObject * Mailmap_resolve_signature(Mailmap *self, PyObject *args) { Signature *sig = NULL; git_signature *resolved = NULL; int error; if (!PyArg_ParseTuple(args, "O!", &SignatureType, &sig)) return NULL; error = git_mailmap_resolve_signature(&resolved, self->mailmap, sig->signature); if (error < 0) return Error_set(error); return build_signature(sig->obj, resolved, sig->encoding); } static void Mailmap_dealloc(Mailmap *self) { git_mailmap_free(self->mailmap); PyObject_Del(self); } PyMethodDef Mailmap_methods[] = { METHOD(Mailmap, add_entry, METH_VARARGS | METH_KEYWORDS), METHOD(Mailmap, resolve, METH_VARARGS), METHOD(Mailmap, resolve_signature, METH_VARARGS), METHOD(Mailmap, from_repository, METH_VARARGS | METH_STATIC), METHOD(Mailmap, from_buffer, METH_VARARGS | METH_STATIC), {NULL} }; PyDoc_STRVAR(Mailmap__doc__, "Mailmap object."); PyTypeObject MailmapType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Mailmap", /* tp_name */ sizeof(Mailmap), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Mailmap_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Mailmap__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Mailmap_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Mailmap_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * wrap_mailmap(git_mailmap* mm) { Mailmap* py_mm = NULL; py_mm = PyObject_New(Mailmap, &MailmapType); if (py_mm == NULL) { PyErr_NoMemory(); return NULL; } py_mm->mailmap = mm; return (PyObject*) py_mm; } libgit2-pygit2-a011e26/src/mailmap.h000066400000000000000000000026211473744024100171720ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_mailmap_h #define INCLUDE_pygit2_mailmap_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject* wrap_mailmap(git_mailmap *c_object); #endif libgit2-pygit2-a011e26/src/note.c000066400000000000000000000221231473744024100165110ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "error.h" #include "utils.h" #include "types.h" #include "oid.h" #include "note.h" extern PyTypeObject SignatureType; PyDoc_STRVAR(Note_remove__doc__, "remove(author: Signature, committer: Signature, ref: str = \"refs/notes/commits\")\n" "\n" "Removes a note for an annotated object"); PyObject * Note_remove(Note *self, PyObject* args) { char *ref = "refs/notes/commits"; int err = GIT_ERROR; Signature *py_author, *py_committer; Oid *id; if (!PyArg_ParseTuple(args, "O!O!|s", &SignatureType, &py_author, &SignatureType, &py_committer, &ref)) return NULL; id = (Oid *) self->annotated_id; err = git_note_remove(self->repo->repo, ref, py_author->signature, py_committer->signature, &id->oid); if (err < 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Note_message__doc__, "Gets message of the note\n"); PyObject * Note_message__get__(Note *self) { int err; // Lazy load if (self->note == NULL) { err = git_note_read(&self->note, self->repo->repo, self->ref, &((Oid *)self->annotated_id)->oid); if (err < 0) return Error_set(err); } return to_unicode(git_note_message(self->note), NULL, NULL); } static void Note_dealloc(Note *self) { Py_CLEAR(self->repo); Py_CLEAR(self->annotated_id); Py_CLEAR(self->id); if (self->note != NULL) git_note_free(self->note); PyObject_Del(self); } PyMethodDef Note_methods[] = { METHOD(Note, remove, METH_VARARGS), {NULL} }; PyMemberDef Note_members[] = { MEMBER(Note, id, T_OBJECT, "id of the note object."), MEMBER(Note, annotated_id, T_OBJECT, "id of the annotated object."), {NULL} }; PyGetSetDef Note_getseters[] = { GETTER(Note, message), {NULL} }; PyDoc_STRVAR(Note__doc__, "Note object."); PyTypeObject NoteType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Note", /* tp_name */ sizeof(Note), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Note_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Note__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Note_methods, /* tp_methods */ Note_members, /* tp_members */ Note_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * NoteIter_iternext(NoteIter *self) { int err; git_oid note_id, annotated_id; err = git_note_next(¬e_id, &annotated_id, self->iter); if (err < 0) return Error_set(err); return (PyObject*) wrap_note(self->repo, ¬e_id, &annotated_id, self->ref); } void NoteIter_dealloc(NoteIter *self) { Py_CLEAR(self->repo); git_note_iterator_free(self->iter); PyObject_Del(self); } PyDoc_STRVAR(NoteIter__doc__, "Note iterator object."); PyTypeObject NoteIterType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.NoteIter", /* tp_name */ sizeof(NoteIter), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)NoteIter_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ NoteIter__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ (iternextfunc) NoteIter_iternext, /* tp_iternext */ }; PyObject * wrap_note(Repository* repo, git_oid* note_id, git_oid* annotated_id, const char* ref) { Note* py_note = NULL; int err = GIT_ERROR; py_note = PyObject_New(Note, &NoteType); if (py_note == NULL) { PyErr_NoMemory(); return NULL; } Py_INCREF(repo); py_note->repo = repo; py_note->ref = ref; py_note->annotated_id = git_oid_to_python(annotated_id); py_note->id = NULL; py_note->note = NULL; /* If the note has been provided, defer the git_note_read() call */ if (note_id != NULL) { py_note->id = git_oid_to_python(note_id); } else { err = git_note_read(&py_note->note, repo->repo, ref, annotated_id); if (err < 0) { Py_DECREF(py_note); return Error_set(err); } py_note->id = git_oid_to_python(git_note_id(py_note->note)); } return (PyObject*) py_note; } libgit2-pygit2-a011e26/src/note.h000066400000000000000000000026761473744024100165310ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_note_h #define INCLUDE_pygit2_note_h #define PY_SSIZE_T_CLEAN #include #include PyObject* wrap_note(Repository* repo, git_oid* note_id, git_oid* annotated_id, const char* ref); #endif libgit2-pygit2-a011e26/src/object.c000066400000000000000000000264761473744024100170310ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "error.h" #include "types.h" #include "utils.h" #include "odb.h" #include "oid.h" #include "repository.h" #include "object.h" extern PyTypeObject TreeType; extern PyTypeObject CommitType; extern PyTypeObject BlobType; extern PyTypeObject TagType; extern PyObject *FileModeEnum; PyTypeObject ObjectType; void Object_dealloc(Object* self) { Py_CLEAR(self->repo); git_object_free(self->obj); git_tree_entry_free((git_tree_entry*)self->entry); Py_TYPE(self)->tp_free(self); } git_object* Object__load(Object *self) { if (self->obj == NULL) { int err = git_tree_entry_to_object(&self->obj, self->repo->repo, self->entry); if (err < 0) { Error_set(err); return NULL; } } return self->obj; } const git_oid* Object__id(Object *self) { return (self->obj) ? git_object_id(self->obj) : git_tree_entry_id(self->entry); } git_object_t Object__type(Object *self) { return (self->obj) ? git_object_type(self->obj) : git_tree_entry_type(self->entry); } PyDoc_STRVAR(Object_id__doc__, "The object id, an instance of the Oid type."); PyObject * Object_id__get__(Object *self) { return git_oid_to_python(Object__id(self)); } PyDoc_STRVAR(Object_short_id__doc__, "An unambiguous short (abbreviated) hex Oid string for the object."); PyObject * Object_short_id__get__(Object *self) { if (Object__load(self) == NULL) { return NULL; } // Lazy load git_buf short_id = { NULL, 0, 0 }; int err = git_object_short_id(&short_id, self->obj); if (err != GIT_OK) return Error_set(err); PyObject *py_short_id = to_unicode_n(short_id.ptr, short_id.size, NULL, "strict"); git_buf_dispose(&short_id); return py_short_id; } PyDoc_STRVAR(Object_type__doc__, "One of the enums.ObjectType.COMMIT, TREE, BLOB or TAG constants."); PyObject * Object_type__get__(Object *self) { return PyLong_FromLong(Object__type(self)); } PyDoc_STRVAR(Object_type_str__doc__, "One of the 'commit', 'tree', 'blob' or 'tag' strings."); PyObject * Object_type_str__get__(Object *self) { return PyUnicode_DecodeFSDefault(git_object_type2string(Object__type(self))); } PyDoc_STRVAR(Object__pointer__doc__, "Get the object's pointer. For internal use only."); PyObject * Object__pointer__get__(Object *self) { /* Bytes means a raw buffer */ if (Object__load(self) == NULL) { return NULL; } // Lazy load return PyBytes_FromStringAndSize((char *) &self->obj, sizeof(git_object *)); } PyDoc_STRVAR(Object_name__doc__, "Name (or None if the object was not reached through a tree)"); PyObject * Object_name__get__(Object *self) { if (self->entry == NULL) Py_RETURN_NONE; return PyUnicode_DecodeFSDefault(git_tree_entry_name(self->entry)); } PyDoc_STRVAR(Object_raw_name__doc__, "Name (bytes)."); PyObject * Object_raw_name__get__(Object *self) { if (self->entry == NULL) Py_RETURN_NONE; return PyBytes_FromString(git_tree_entry_name(self->entry)); } PyDoc_STRVAR(Object_filemode__doc__, "An enums.FileMode constant (or None if the object was not reached through a tree)"); PyObject * Object_filemode__get__(Object *self) { if (self->entry == NULL) Py_RETURN_NONE; return pygit2_enum(FileModeEnum, git_tree_entry_filemode(self->entry)); } PyDoc_STRVAR(Object_read_raw__doc__, "read_raw() -> bytes\n" "\n" "Returns the byte string with the raw contents of the object."); PyObject * Object_read_raw(Object *self) { int err; git_odb *odb; PyObject *aux; err = git_repository_odb(&odb, self->repo->repo); if (err < 0) return Error_set(err); const git_oid *oid = Object__id(self); git_odb_object *obj = Odb_read_raw(odb, oid, GIT_OID_HEXSZ); git_odb_free(odb); if (obj == NULL) return NULL; aux = PyBytes_FromStringAndSize( git_odb_object_data(obj), git_odb_object_size(obj)); git_odb_object_free(obj); return aux; } PyDoc_STRVAR(Object_peel__doc__, "peel(target_type) -> Object\n" "\n" "Peel the current object and returns the first object of the given type.\n" "\n" "If you pass None as the target type, then the object will be peeled\n" "until the type changes. A tag will be peeled until the referenced object\n" "is no longer a tag, and a commit will be peeled to a tree. Any other\n" "object type will raise InvalidSpecError.\n"); PyObject * Object_peel(Object *self, PyObject *py_type) { int err; git_otype otype; git_object *peeled; if (Object__load(self) == NULL) { return NULL; } // Lazy load otype = py_object_to_otype(py_type); if (otype == GIT_OBJECT_INVALID) return NULL; err = git_object_peel(&peeled, self->obj, otype); if (err < 0) return Error_set(err); return wrap_object(peeled, self->repo, NULL); } Py_hash_t Object_hash(Object *self) { const git_oid *oid = Object__id(self); PyObject *py_oid = git_oid_to_py_str(oid); Py_hash_t ret = PyObject_Hash(py_oid); Py_DECREF(py_oid); return ret; } PyObject * Object_repr(Object *self) { char hex[GIT_OID_HEXSZ + 1]; git_oid_fmt(hex, Object__id(self)); hex[GIT_OID_HEXSZ] = '\0'; return PyUnicode_FromFormat("", git_object_type2string(Object__type(self)), hex ); } PyObject * Object_richcompare(PyObject *o1, PyObject *o2, int op) { PyObject *res; if (!PyObject_TypeCheck(o2, &ObjectType)) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } int equal = git_oid_equal(Object__id((Object *)o1), Object__id((Object *)o2)); switch (op) { case Py_NE: res = (equal) ? Py_False : Py_True; break; case Py_EQ: res = (equal) ? Py_True : Py_False; break; case Py_LT: case Py_LE: case Py_GT: case Py_GE: Py_INCREF(Py_NotImplemented); return Py_NotImplemented; default: PyErr_Format(PyExc_RuntimeError, "Unexpected '%d' op", op); return NULL; } Py_INCREF(res); return res; } PyGetSetDef Object_getseters[] = { GETTER(Object, id), GETTER(Object, short_id), GETTER(Object, type), GETTER(Object, type_str), GETTER(Object, _pointer), // These come from git_tree_entry GETTER(Object, name), GETTER(Object, raw_name), GETTER(Object, filemode), {NULL} }; PyMethodDef Object_methods[] = { METHOD(Object, read_raw, METH_NOARGS), METHOD(Object, peel, METH_O), {NULL} }; PyDoc_STRVAR(Object__doc__, "Base class for Git objects."); PyTypeObject ObjectType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Object", /* tp_name */ sizeof(Object), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Object_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Object_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)Object_hash, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ Object__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ (richcmpfunc)Object_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Object_methods, /* tp_methods */ 0, /* tp_members */ Object_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * wrap_object(git_object *c_object, Repository *repo, const git_tree_entry *entry) { Object *py_obj = NULL; git_object_t obj_type = (c_object) ? git_object_type(c_object) : git_tree_entry_type(entry); switch (obj_type) { case GIT_OBJECT_COMMIT: py_obj = PyObject_New(Object, &CommitType); break; case GIT_OBJECT_TREE: py_obj = PyObject_New(Object, &TreeType); break; case GIT_OBJECT_BLOB: py_obj = PyObject_New(Object, &BlobType); break; case GIT_OBJECT_TAG: py_obj = PyObject_New(Object, &TagType); break; default: assert(0); } if (py_obj) { py_obj->obj = c_object; if (repo) { py_obj->repo = repo; Py_INCREF(repo); } py_obj->entry = entry; } return (PyObject *)py_obj; } libgit2-pygit2-a011e26/src/object.h000066400000000000000000000031341473744024100170200ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_object_h #define INCLUDE_pygit2_object_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" git_object* Object__load(Object *self); const git_oid* Object__id(Object *self); PyObject* Object_read_raw(Object *self); PyObject* Object_repr(Object *self); PyObject* wrap_object(git_object *c_object, Repository *repo, const git_tree_entry *entry); #endif libgit2-pygit2-a011e26/src/odb.c000066400000000000000000000274751473744024100163270ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "object.h" #include "odb_backend.h" #include "oid.h" #include "types.h" #include "utils.h" #include extern PyTypeObject OdbBackendType; static git_otype int_to_loose_object_type(int type_id) { switch((git_otype)type_id) { case GIT_OBJECT_COMMIT: case GIT_OBJECT_TREE: case GIT_OBJECT_BLOB: case GIT_OBJECT_TAG: return (git_otype)type_id; default: return GIT_OBJECT_INVALID; } } int Odb_init(Odb *self, PyObject *args, PyObject *kwds) { if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "Odb takes no keyword arguments"); return -1; } PyObject *py_path = NULL; if (!PyArg_ParseTuple(args, "|O", &py_path)) return -1; int err; if (py_path) { PyObject *tvalue; char *path = pgit_borrow_fsdefault(py_path, &tvalue); if (path == NULL) return -1; err = git_odb_open(&self->odb, path); Py_DECREF(tvalue); } else { err = git_odb_new(&self->odb); } if (err) { Error_set(err); return -1; } return 0; } void Odb_dealloc(Odb *self) { git_odb_free(self->odb); Py_TYPE(self)->tp_free((PyObject *) self); } static int Odb_build_as_iter(const git_oid *oid, void *accum) { int err; PyObject *py_oid = git_oid_to_python(oid); if (py_oid == NULL) return GIT_EUSER; err = PyList_Append((PyObject*)accum, py_oid); Py_DECREF(py_oid); if (err < 0) return GIT_EUSER; return 0; } PyObject * Odb_as_iter(Odb *self) { int err; PyObject *accum = PyList_New(0); PyObject *ret = NULL; err = git_odb_foreach(self->odb, Odb_build_as_iter, (void*)accum); if (err == GIT_EUSER) goto exit; if (err < 0) { ret = Error_set(err); goto exit; } ret = PyObject_GetIter(accum); exit: Py_DECREF(accum); return ret; } PyDoc_STRVAR(Odb_add_disk_alternate__doc__, "add_disk_alternate(path: str)\n" "\n" "Adds a path on disk as an alternate backend for objects.\n" "Alternate backends are checked for objects only *after* the main backends\n" "are checked. Writing is disabled on alternate backends.\n"); PyObject * Odb_add_disk_alternate(Odb *self, PyObject *py_path) { PyObject *tvalue; char *path = pgit_borrow_fsdefault(py_path, &tvalue); if (path == NULL) return NULL; int err = git_odb_add_disk_alternate(self->odb, path); Py_DECREF(tvalue); if (err) return Error_set(err); Py_RETURN_NONE; } git_odb_object * Odb_read_raw(git_odb *odb, const git_oid *oid, size_t len) { git_odb_object *obj; int err; err = git_odb_read_prefix(&obj, odb, oid, (unsigned int)len); if (err < 0 && err != GIT_EUSER) { Error_set_oid(err, oid, len); return NULL; } return obj; } PyDoc_STRVAR(Odb_read__doc__, "read(oid) -> type, data, size\n" "\n" "Read raw object data from the object db."); PyObject * Odb_read(Odb *self, PyObject *py_hex) { git_oid oid; git_odb_object *obj; size_t len; PyObject* tuple; len = py_oid_to_git_oid(py_hex, &oid); if (len == 0) return NULL; obj = Odb_read_raw(self->odb, &oid, len); if (obj == NULL) return NULL; tuple = Py_BuildValue( "(ny#)", git_odb_object_type(obj), git_odb_object_data(obj), git_odb_object_size(obj)); git_odb_object_free(obj); return tuple; } PyDoc_STRVAR(Odb_write__doc__, "write(type: int, data: bytes) -> Oid\n" "\n" "Write raw object data into the object db. First arg is the object\n" "type, the second one a buffer with data. Return the Oid of the created\n" "object."); PyObject * Odb_write(Odb *self, PyObject *args) { int err; git_oid oid; git_odb_stream* stream; int type_id; const char* buffer; Py_ssize_t buflen; git_otype type; if (!PyArg_ParseTuple(args, "Is#", &type_id, &buffer, &buflen)) return NULL; type = int_to_loose_object_type(type_id); if (type == GIT_OBJECT_INVALID) return PyErr_Format(PyExc_ValueError, "%d", type_id); err = git_odb_open_wstream(&stream, self->odb, buflen, type); if (err < 0) return Error_set(err); err = git_odb_stream_write(stream, buffer, buflen); if (err) { git_odb_stream_free(stream); return Error_set(err); } err = git_odb_stream_finalize_write(&oid, stream); git_odb_stream_free(stream); if (err) return Error_set(err); return git_oid_to_python(&oid); } PyDoc_STRVAR(Odb_exists__doc__, "exists(oid: Oid) -> bool\n" "\n" "Returns true if the given oid can be found in this odb."); PyObject * Odb_exists(Odb *self, PyObject *py_hex) { git_oid oid; size_t len; int result; len = py_oid_to_git_oid(py_hex, &oid); if (len == 0) return NULL; result = git_odb_exists(self->odb, &oid); if (result < 0) return Error_set(result); else if (result == 0) Py_RETURN_FALSE; else Py_RETURN_TRUE; } PyDoc_STRVAR(Odb_add_backend__doc__, "add_backend(backend: OdbBackend, priority: int)\n" "\n" "Adds an OdbBackend to the list of backends for this object database.\n"); PyObject * Odb_add_backend(Odb *self, PyObject *args) { int err, priority; OdbBackend *backend; if (!PyArg_ParseTuple(args, "OI", &backend, &priority)) return NULL; if (!PyObject_IsInstance((PyObject *)backend, (PyObject *)&OdbBackendType)) { PyErr_SetString(PyExc_TypeError, "add_backend expects an instance of pygit2.OdbBackend"); return NULL; } err = git_odb_add_backend(self->odb, backend->odb_backend, priority); if (err != 0) return Error_set(err); Py_INCREF(backend); Py_RETURN_NONE; } PyMethodDef Odb_methods[] = { METHOD(Odb, add_disk_alternate, METH_O), METHOD(Odb, read, METH_O), METHOD(Odb, write, METH_VARARGS), METHOD(Odb, exists, METH_O), METHOD(Odb, add_backend, METH_VARARGS), {NULL} }; PyDoc_STRVAR(Odb_backends__doc__, "Return an iterable of backends for this object database."); PyObject * Odb_backends__get__(Odb *self) { int err; git_odb_backend *backend; PyObject *ret = NULL; PyObject *py_backend; PyObject *accum = PyList_New(0); if (accum == NULL) return NULL; size_t nbackends = git_odb_num_backends(self->odb); for (size_t i = 0; i < nbackends; ++i) { err = git_odb_get_backend(&backend, self->odb, i); if (err != 0) { ret = Error_set(err); goto exit; } // XXX This won't return the correct class for custom backends (add a // test and fix) py_backend = wrap_odb_backend(backend); if (py_backend == NULL) goto exit; err = PyList_Append(accum, py_backend); if (err != 0) goto exit; } ret = PyObject_GetIter(accum); exit: Py_DECREF(accum); return ret; } PyGetSetDef Odb_getseters[] = { GETTER(Odb, backends), {NULL} }; int Odb_contains(Odb *self, PyObject *py_name) { git_oid oid; size_t len; len = py_oid_to_git_oid(py_name, &oid); if (len == 0) { PyErr_SetString(PyExc_TypeError, "name must be an oid"); return -1; } return git_odb_exists(self->odb, &oid); } PySequenceMethods Odb_as_sequence = { 0, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ (objobjproc)Odb_contains, /* sq_contains */ }; PyDoc_STRVAR(Odb__doc__, "Object database."); PyTypeObject OdbType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Odb", /* tp_name */ sizeof(Odb), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Odb_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ &Odb_as_sequence, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Odb__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)Odb_as_iter, /* tp_iter */ 0, /* tp_iternext */ Odb_methods, /* tp_methods */ 0, /* tp_members */ Odb_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Odb_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * wrap_odb(git_odb *c_odb) { Odb *py_odb = PyObject_New(Odb, &OdbType); if (py_odb) py_odb->odb = c_odb; return (PyObject *)py_odb; } libgit2-pygit2-a011e26/src/odb.h000066400000000000000000000027751473744024100163300ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_odb_h #define INCLUDE_pygit2_odb_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject *wrap_odb(git_odb *c_odb); git_odb_object *Odb_read_raw(git_odb *odb, const git_oid *oid, size_t len); PyObject *Odb_read(Odb *self, PyObject *py_hex); #endif libgit2-pygit2-a011e26/src/odb_backend.c000066400000000000000000000617411473744024100177700ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * * TODO This still needs much work to make it usable, and maintanable! * - Create OdbBackendCustomType that inherits from OdbBackendType. * OdbBackendType should not be subclassed, instead subclass * OdbBackendCustomType to develop custom backends in Python. * Implement this new type in src/odb_backend_custom.c */ #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "object.h" #include "oid.h" #include "types.h" #include "utils.h" #include #include #include /* * pgit_odb_backend_t is a container for the state associated with a custom * implementation of git_odb_backend. The git_odb_backend field's function * pointers are assigned to the pgit_odb_backend_* functions, which handle * translating between the libgit2 ABI and the Python ABI. * It holds a pointer to the subclass, which must implement * the callbacks in Python. */ typedef struct { git_odb_backend backend; PyObject *py_backend; } pgit_odb_backend; static int pgit_odb_backend_read(void **ptr, size_t *sz, git_object_t *type, git_odb_backend *_be, const git_oid *oid) { pgit_odb_backend *be = (pgit_odb_backend *)_be; PyObject *py_oid = git_oid_to_python(oid); if (py_oid == NULL) return GIT_EUSER; PyObject *result = PyObject_CallMethod(be->py_backend, "read_cb", "N", py_oid); if (result == NULL) return git_error_for_exc(); const char *bytes; Py_ssize_t type_value; if (!PyArg_ParseTuple(result, "ny#", &type_value, &bytes, sz) || !bytes) { Py_DECREF(result); return GIT_EUSER; } *type = (git_object_t)type_value; *ptr = git_odb_backend_data_alloc(_be, *sz); if (!*ptr) { Py_DECREF(result); return GIT_EUSER; } memcpy(*ptr, bytes, *sz); Py_DECREF(result); return 0; } static int pgit_odb_backend_read_prefix(git_oid *oid_out, void **ptr, size_t *sz, git_object_t *type, git_odb_backend *_be, const git_oid *short_id, size_t len) { // short_id to hex char short_id_hex[GIT_OID_HEXSZ]; git_oid_nfmt(short_id_hex, len, short_id); // Call callback pgit_odb_backend *be = (pgit_odb_backend *)_be; PyObject *result = PyObject_CallMethod(be->py_backend, "read_prefix_cb", "s#", short_id_hex, len); if (result == NULL) return git_error_for_exc(); // Parse output from calback PyObject *py_oid_out; Py_ssize_t type_value; const char *bytes; if (!PyArg_ParseTuple(result, "ny#O", &type_value, &bytes, sz, &py_oid_out) || !bytes) { Py_DECREF(result); return GIT_EUSER; } *type = (git_object_t)type_value; *ptr = git_odb_backend_data_alloc(_be, *sz); if (!*ptr) { Py_DECREF(result); return GIT_EUSER; } memcpy(*ptr, bytes, *sz); py_oid_to_git_oid(py_oid_out, oid_out); Py_DECREF(result); return 0; } static int pgit_odb_backend_read_header(size_t *len, git_object_t *type, git_odb_backend *_be, const git_oid *oid) { pgit_odb_backend *be = (pgit_odb_backend *)_be; PyObject *py_oid = git_oid_to_python(oid); if (py_oid == NULL) return GIT_EUSER; PyObject *result = PyObject_CallMethod(be->py_backend, "read_header_cb", "N", py_oid); if (result == NULL) return git_error_for_exc(); Py_ssize_t type_value; if (!PyArg_ParseTuple(result, "nn", &type_value, len)) { Py_DECREF(result); return GIT_EUSER; } *type = (git_object_t)type_value; Py_DECREF(result); return 0; } static int pgit_odb_backend_write(git_odb_backend *_be, const git_oid *oid, const void *data, size_t sz, git_object_t typ) { pgit_odb_backend *be = (pgit_odb_backend *)_be; PyObject *py_oid = git_oid_to_python(oid); if (py_oid == NULL) return GIT_EUSER; PyObject *result = PyObject_CallMethod(be->py_backend, "write_cb", "Ny#n", py_oid, data, sz, typ); if (result == NULL) return git_error_for_exc(); Py_DECREF(result); return 0; } static int pgit_odb_backend_exists(git_odb_backend *_be, const git_oid *oid) { pgit_odb_backend *be = (pgit_odb_backend *)_be; PyObject *py_oid = git_oid_to_python(oid); if (py_oid == NULL) return GIT_EUSER; PyObject *result = PyObject_CallMethod(be->py_backend, "exists_cb", "N", py_oid); if (result == NULL) return git_error_for_exc(); int r = PyObject_IsTrue(result); Py_DECREF(result); return r; } static int pgit_odb_backend_exists_prefix(git_oid *out, git_odb_backend *_be, const git_oid *short_id, size_t len) { // short_id to hex char short_id_hex[GIT_OID_HEXSZ]; git_oid_nfmt(short_id_hex, len, short_id); // Call callback pgit_odb_backend *be = (pgit_odb_backend *)_be; PyObject *py_oid = PyObject_CallMethod(be->py_backend, "exists_prefix_cb", "s#", short_id_hex, len); if (py_oid == NULL) return git_error_for_exc(); py_oid_to_git_oid(py_oid, out); Py_DECREF(py_oid); return 0; } static int pgit_odb_backend_refresh(git_odb_backend *_be) { pgit_odb_backend *be = (pgit_odb_backend *)_be; PyObject_CallMethod(be->py_backend, "refresh_cb", NULL); return git_error_for_exc(); } static int pgit_odb_backend_foreach(git_odb_backend *_be, git_odb_foreach_cb cb, void *payload) { PyObject *item; git_oid oid; pgit_odb_backend *be = (pgit_odb_backend *)_be; PyObject *iterator = PyObject_GetIter((PyObject *)be->py_backend); assert(iterator); while ((item = PyIter_Next(iterator))) { py_oid_to_git_oid(item, &oid); cb(&oid, payload); Py_DECREF(item); } return git_error_for_exc(); } static void pgit_odb_backend_free(git_odb_backend *backend) { pgit_odb_backend *custom_backend = (pgit_odb_backend *)backend; Py_DECREF(custom_backend->py_backend); } int OdbBackend_init(OdbBackend *self, PyObject *args, PyObject *kwds) { // Check input arguments if (args && PyTuple_Size(args) > 0) { PyErr_SetString(PyExc_TypeError, "OdbBackend takes no arguments"); return -1; } if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "OdbBackend takes no keyword arguments"); return -1; } // Create the C backend pgit_odb_backend *custom_backend = calloc(1, sizeof(pgit_odb_backend)); custom_backend->backend.version = GIT_ODB_BACKEND_VERSION; // Fill the member methods custom_backend->backend.free = pgit_odb_backend_free; custom_backend->backend.read = pgit_odb_backend_read; custom_backend->backend.read_prefix = pgit_odb_backend_read_prefix; custom_backend->backend.read_header = pgit_odb_backend_read_header; custom_backend->backend.write = pgit_odb_backend_write; custom_backend->backend.exists = pgit_odb_backend_exists; custom_backend->backend.exists_prefix = pgit_odb_backend_exists_prefix; custom_backend->backend.refresh = pgit_odb_backend_refresh; // custom_backend->backend.writepack = pgit_odb_backend_writepack; // custom_backend->backend.freshen = pgit_odb_backend_freshen; // custom_backend->backend.writestream = pgit_odb_backend_writestream; // custom_backend->backend.readstream = pgit_odb_backend_readstream; if (PyIter_Check((PyObject *)self)) custom_backend->backend.foreach = pgit_odb_backend_foreach; // Cross reference (don't incref because it's something internal) custom_backend->py_backend = (PyObject *)self; self->odb_backend = (git_odb_backend *)custom_backend; return 0; } void OdbBackend_dealloc(OdbBackend *self) { if (self->odb_backend && self->odb_backend->read == pgit_odb_backend_read) { pgit_odb_backend *custom_backend = (pgit_odb_backend *)self->odb_backend; free(custom_backend); } Py_TYPE(self)->tp_free((PyObject *) self); } static int OdbBackend_build_as_iter(const git_oid *oid, void *accum) { int err; PyObject *py_oid = git_oid_to_python(oid); if (py_oid == NULL) return GIT_EUSER; err = PyList_Append((PyObject*)accum, py_oid); Py_DECREF(py_oid); if (err < 0) return GIT_EUSER; return 0; } PyObject * OdbBackend_as_iter(OdbBackend *self) { PyObject *accum = PyList_New(0); PyObject *iter = NULL; int err = self->odb_backend->foreach(self->odb_backend, OdbBackend_build_as_iter, (void*)accum); if (err == GIT_EUSER) goto exit; if (err < 0) { Error_set(err); goto exit; } iter = PyObject_GetIter(accum); exit: Py_DECREF(accum); return iter; } PyDoc_STRVAR(OdbBackend_read__doc__, "read(oid) -> (type, data)\n" "\n" "Read raw object data from this odb backend.\n"); PyObject * OdbBackend_read(OdbBackend *self, PyObject *py_hex) { int err; git_oid oid; git_object_t type; size_t len, size; void *data; if (self->odb_backend->read == NULL) Py_RETURN_NOTIMPLEMENTED; len = py_oid_to_git_oid(py_hex, &oid); if (len == 0) return NULL; err = self->odb_backend->read(&data, &size, &type, self->odb_backend, &oid); if (err != 0) { Error_set_oid(err, &oid, len); return NULL; } PyObject *tuple = Py_BuildValue("(ny#)", type, data, size); git_odb_backend_data_free(self->odb_backend, data); return tuple; } PyDoc_STRVAR(OdbBackend_read_prefix__doc__, "read_prefix(oid: Oid) -> tuple[int, bytes, Oid]\n" "\n" "Read raw object data from this odb backend based on an oid prefix.\n" "The returned tuple contains (type, data, oid)."); PyObject * OdbBackend_read_prefix(OdbBackend *self, PyObject *py_hex) { int err; git_oid oid, oid_out; git_object_t type; size_t len, size; void *data; if (self->odb_backend->read_prefix == NULL) Py_RETURN_NOTIMPLEMENTED; len = py_oid_to_git_oid(py_hex, &oid); if (len == 0) return NULL; err = self->odb_backend->read_prefix(&oid_out, &data, &size, &type, self->odb_backend, &oid, len); if (err != 0) { Error_set_oid(err, &oid, len); return NULL; } PyObject *py_oid_out = git_oid_to_python(&oid_out); if (py_oid_out == NULL) return Error_set_exc(PyExc_MemoryError); PyObject *tuple = Py_BuildValue("(ny#N)", type, data, size, py_oid_out); git_odb_backend_data_free(self->odb_backend, data); return tuple; } PyDoc_STRVAR(OdbBackend_read_header__doc__, "read_header(oid) -> (type, len)\n" "\n" "Read raw object header from this odb backend."); PyObject * OdbBackend_read_header(OdbBackend *self, PyObject *py_hex) { int err; size_t len; git_object_t type; git_oid oid; if (self->odb_backend->read_header == NULL) Py_RETURN_NOTIMPLEMENTED; len = py_oid_to_git_oid(py_hex, &oid); if (len == 0) return NULL; err = self->odb_backend->read_header(&len, &type, self->odb_backend, &oid); if (err != 0) { Error_set_oid(err, &oid, len); return NULL; } return Py_BuildValue("(ni)", type, len); } PyDoc_STRVAR(OdbBackend_exists__doc__, "exists(oid: Oid) -> bool\n" "\n" "Returns true if the given oid can be found in this odb."); PyObject * OdbBackend_exists(OdbBackend *self, PyObject *py_hex) { int result; size_t len; git_oid oid; if (self->odb_backend->exists == NULL) Py_RETURN_NOTIMPLEMENTED; len = py_oid_to_git_oid(py_hex, &oid); if (len == 0) return NULL; result = self->odb_backend->exists(self->odb_backend, &oid); if (result < 0) return Error_set(result); else if (result == 0) Py_RETURN_FALSE; else Py_RETURN_TRUE; } PyDoc_STRVAR(OdbBackend_exists_prefix__doc__, "exists_prefix(partial_id: Oid) -> Oid\n" "\n" "Given a partial oid, returns the full oid. Raises KeyError if not found,\n" "or ValueError if ambiguous."); PyObject * OdbBackend_exists_prefix(OdbBackend *self, PyObject *py_hex) { int result; size_t len; git_oid oid; if (self->odb_backend->exists_prefix == NULL) Py_RETURN_NOTIMPLEMENTED; len = py_oid_to_git_oid(py_hex, &oid); if (len == 0) return NULL; git_oid out; result = self->odb_backend->exists_prefix(&out, self->odb_backend, &oid, len); if (result < 0) return Error_set(result); return git_oid_to_python(&out); } PyDoc_STRVAR(OdbBackend_refresh__doc__, "refresh()\n" "\n" "If the backend supports a refreshing mechanism, this function will invoke\n" "it. However, the backend implementation should try to stay up-to-date as\n" "much as possible by itself as libgit2 will not automatically invoke this\n" "function. For instance, a potential strategy for the backend\n" "implementation to utilize this could be internally calling the refresh\n" "function on failed lookups."); PyObject * OdbBackend_refresh(OdbBackend *self) { if (self->odb_backend->refresh == NULL) Py_RETURN_NOTIMPLEMENTED; self->odb_backend->refresh(self->odb_backend); Py_RETURN_NONE; } /* * TODO: * - write * - writepack * - writestream * - readstream * - freshen */ PyMethodDef OdbBackend_methods[] = { METHOD(OdbBackend, read, METH_O), METHOD(OdbBackend, read_prefix, METH_O), METHOD(OdbBackend, read_header, METH_O), METHOD(OdbBackend, exists, METH_O), METHOD(OdbBackend, exists_prefix, METH_O), METHOD(OdbBackend, refresh, METH_NOARGS), {NULL} }; PyDoc_STRVAR(OdbBackend__doc__, "Object database backend."); PyTypeObject OdbBackendType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.OdbBackend", /* tp_name */ sizeof(OdbBackend), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)OdbBackend_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ OdbBackend__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)OdbBackend_as_iter, /* tp_iter */ 0, /* tp_iternext */ OdbBackend_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)OdbBackend_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * wrap_odb_backend(git_odb_backend *odb_backend) { OdbBackend *py_backend = PyObject_New(OdbBackend, &OdbBackendType); if (py_backend) py_backend->odb_backend = odb_backend; return (PyObject *)py_backend; } PyDoc_STRVAR(OdbBackendPack__doc__, "Object database backend for packfiles."); int OdbBackendPack_init(OdbBackendPack *self, PyObject *args, PyObject *kwds) { if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "OdbBackendPack takes no keyword arguments"); return -1; } PyObject *py_path; if (!PyArg_ParseTuple(args, "O", &py_path)) return -1; PyObject *tvalue; char *path = pgit_borrow_fsdefault(py_path, &tvalue); if (path == NULL) return -1; int err = git_odb_backend_pack(&self->super.odb_backend, path); Py_DECREF(tvalue); if (err) { Error_set(err); return -1; } return 0; } PyTypeObject OdbBackendPackType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.OdbBackendPack", /* tp_name */ sizeof(OdbBackendPack), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ OdbBackendPack__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ &OdbBackendType, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)OdbBackendPack_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyDoc_STRVAR(OdbBackendLoose__doc__, "OdbBackendLoose(objects_dir, compression_level," " do_fsync, dir_mode=0, file_mode=0)\n" "\n" "Object database backend for loose objects.\n" "\n" "Parameters:\n" "\n" "objects_dir\n" " path to top-level object dir on disk\n" "\n" "compression_level\n" " zlib compression level to use\n" "\n" "do_fsync\n" " true to fsync() after writing\n" "\n" "dir_mode\n" " mode for new directories, or 0 for default\n" "\n" "file_mode\n" " mode for new files, or 0 for default"); int OdbBackendLoose_init(OdbBackendLoose *self, PyObject *args, PyObject *kwds) { if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "OdbBackendLoose takes no keyword arguments"); return -1; } PyObject *py_path; int compression_level, do_fsync; unsigned int dir_mode = 0, file_mode = 0; if (!PyArg_ParseTuple(args, "Oip|II", &py_path, &compression_level, &do_fsync, &dir_mode, &file_mode)) return -1; PyObject *tvalue; char *path = pgit_borrow_fsdefault(py_path, &tvalue); if (path == NULL) return -1; int err = git_odb_backend_loose(&self->super.odb_backend, path, compression_level, do_fsync, dir_mode, file_mode); Py_DECREF(tvalue); if (err) { Error_set(err); return -1; } return 0; } PyTypeObject OdbBackendLooseType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.OdbBackendLoose", /* tp_name */ sizeof(OdbBackendLoose), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ OdbBackendLoose__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ &OdbBackendType, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)OdbBackendLoose_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/odb_backend.h000066400000000000000000000026461473744024100177740ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_odb_backend_h #define INCLUDE_pygit2_odb_backend_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject *wrap_odb_backend(git_odb_backend *c_odb_backend); #endif libgit2-pygit2-a011e26/src/oid.c000066400000000000000000000215741473744024100163300ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "utils.h" #include "error.h" #include "oid.h" PyTypeObject OidType; PyObject * git_oid_to_python(const git_oid *oid) { Oid *py_oid; py_oid = PyObject_New(Oid, &OidType); if (py_oid == NULL) return NULL; git_oid_cpy(&(py_oid->oid), oid); return (PyObject*)py_oid; } size_t py_hex_to_git_oid(PyObject *py_oid, git_oid *oid) { PyObject *py_hex; int err; char *hex; Py_ssize_t len; /* Unicode */ if (PyUnicode_Check(py_oid)) { py_hex = PyUnicode_AsASCIIString(py_oid); if (py_hex == NULL) return 0; err = PyBytes_AsStringAndSize(py_hex, &hex, &len); if (err) { Py_DECREF(py_hex); return 0; } err = git_oid_fromstrn(oid, hex, len); Py_DECREF(py_hex); if (err < 0) { PyErr_SetObject(Error_type(err), py_oid); return 0; } return (size_t)len; } /* Type error */ PyErr_SetObject(PyExc_TypeError, py_oid); return 0; } size_t py_oid_to_git_oid(PyObject *py_oid, git_oid *oid) { /* Oid */ if (PyObject_TypeCheck(py_oid, (PyTypeObject*)&OidType)) { git_oid_cpy(oid, &((Oid*)py_oid)->oid); return GIT_OID_HEXSZ; } /* Hex */ return py_hex_to_git_oid(py_oid, oid); } int py_oid_to_git_oid_expand(git_repository *repo, PyObject *py_str, git_oid *oid) { int err; size_t len; git_odb *odb = NULL; git_oid tmp; len = py_oid_to_git_oid(py_str, oid); if (len == 0) return -1; if (len == GIT_OID_HEXSZ) return 0; /* Short oid */ err = git_repository_odb(&odb, repo); if (err < 0) goto error; err = git_odb_exists_prefix(&tmp, odb, oid, len); if (err < 0) goto error; git_oid_cpy(oid, &tmp); git_odb_free(odb); return 0; error: git_odb_free(odb); Error_set(err); return -1; } PyObject * git_oid_to_py_str(const git_oid *oid) { char hex[GIT_OID_HEXSZ]; git_oid_fmt(hex, oid); return to_unicode_n(hex, GIT_OID_HEXSZ, "utf-8", "strict"); } int Oid_init(Oid *self, PyObject *args, PyObject *kw) { char *keywords[] = {"raw", "hex", NULL}; PyObject *raw = NULL, *hex = NULL; int err; char *bytes; Py_ssize_t len; if (!PyArg_ParseTupleAndKeywords(args, kw, "|OO", keywords, &raw, &hex)) return -1; /* We expect one or the other, but not both. */ if (raw == NULL && hex == NULL) { PyErr_SetString(PyExc_ValueError, "Expected raw or hex."); return -1; } if (raw != NULL && hex != NULL) { PyErr_SetString(PyExc_ValueError, "Expected raw or hex, not both."); return -1; } /* Case 1: raw */ if (raw != NULL) { err = PyBytes_AsStringAndSize(raw, &bytes, &len); if (err) return -1; if (len > GIT_OID_RAWSZ) { PyErr_SetObject(PyExc_ValueError, raw); return -1; } memcpy(self->oid.id, (const unsigned char*)bytes, len); return 0; } /* Case 2: hex */ len = py_hex_to_git_oid(hex, &self->oid); if (len == 0) return -1; return 0; } Py_hash_t Oid_hash(PyObject *oid) { PyObject *py_oid = git_oid_to_py_str(&((Oid *)oid)->oid); Py_hash_t ret = PyObject_Hash(py_oid); Py_DECREF(py_oid); return ret; } PyObject * Oid_richcompare(PyObject *self, PyObject *other, int op) { git_oid *oid = &((Oid*)self)->oid; int cmp; // Can compare an oid against another oid or a unicode string if (PyObject_TypeCheck(other, &OidType)) { cmp = git_oid_cmp(oid, &((Oid*)other)->oid); } else if (PyObject_TypeCheck(other, &PyUnicode_Type)) { const char * str = PyUnicode_AsUTF8(other); if (str == NULL) { return NULL; } cmp = git_oid_strcmp(oid, str); } else { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } // Return boolean PyObject *res; switch (op) { case Py_LT: res = (cmp <= 0) ? Py_True: Py_False; break; case Py_LE: res = (cmp < 0) ? Py_True: Py_False; break; case Py_EQ: res = (cmp == 0) ? Py_True: Py_False; break; case Py_NE: res = (cmp != 0) ? Py_True: Py_False; break; case Py_GT: res = (cmp > 0) ? Py_True: Py_False; break; case Py_GE: res = (cmp >= 0) ? Py_True: Py_False; break; default: PyErr_Format(PyExc_RuntimeError, "Unexpected '%d' op", op); return NULL; } Py_INCREF(res); return res; } PyObject * Oid__str__(Oid *self) { return git_oid_to_py_str(&self->oid); } PyDoc_STRVAR(Oid_raw__doc__, "Raw oid, a 20 bytes string."); PyObject * Oid_raw__get__(Oid *self) { return PyBytes_FromStringAndSize((const char*)self->oid.id, GIT_OID_RAWSZ); } PyGetSetDef Oid_getseters[] = { GETTER(Oid, raw), {NULL}, }; PyDoc_STRVAR(Oid__doc__, "Object id."); PyTypeObject OidType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Oid", /* tp_name */ sizeof(Oid), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Oid__str__, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)Oid_hash, /* tp_hash */ 0, /* tp_call */ (reprfunc)Oid__str__, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Oid__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ (richcmpfunc)Oid_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ Oid_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Oid_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/oid.h000066400000000000000000000031241473744024100163240ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_oid_h #define INCLUDE_pygit2_oid_h #define PY_SSIZE_T_CLEAN #include #include size_t py_oid_to_git_oid(PyObject *py_str, git_oid *oid); int py_oid_to_git_oid_expand(git_repository *repo, PyObject *py_str, git_oid *oid); PyObject* git_oid_to_python(const git_oid *oid); PyObject* git_oid_to_py_str(const git_oid *oid); #endif libgit2-pygit2-a011e26/src/options.c000066400000000000000000000216431473744024100172450ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "error.h" #include "types.h" #include "utils.h" extern PyObject *GitError; static PyObject * get_search_path(long level) { git_buf buf = {NULL}; PyObject *py_path; int err; err = git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, &buf); if (err < 0) return Error_set(err); py_path = to_unicode_n(buf.ptr, buf.size, NULL, NULL); git_buf_dispose(&buf); if (!py_path) return NULL; return py_path; } PyObject * option(PyObject *self, PyObject *args) { long option; int error; PyObject *py_option; py_option = PyTuple_GetItem(args, 0); if (!py_option) return NULL; if (!PyLong_Check(py_option)) return Error_type_error( "option should be an integer, got %.200s", py_option); option = PyLong_AsLong(py_option); switch (option) { case GIT_OPT_GET_MWINDOW_FILE_LIMIT: case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: case GIT_OPT_GET_MWINDOW_SIZE: { size_t value; error = git_libgit2_opts(option, &value); if (error < 0) return Error_set(error); return PyLong_FromSize_t(value); } case GIT_OPT_SET_MWINDOW_FILE_LIMIT: case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: case GIT_OPT_SET_MWINDOW_SIZE: { PyObject *py_value = PyTuple_GetItem(args, 1); if (!py_value) return NULL; if (!PyLong_Check(py_value)) return Error_type_error("expected integer, got %.200s", py_value); size_t value = PyLong_AsSize_t(py_value); error = git_libgit2_opts(option, value); if (error < 0) return Error_set(error); Py_RETURN_NONE; } case GIT_OPT_GET_SEARCH_PATH: { PyObject *py_level = PyTuple_GetItem(args, 1); if (!py_level) return NULL; if (!PyLong_Check(py_level)) return Error_type_error("level should be an integer, got %.200s", py_level); return get_search_path(PyLong_AsLong(py_level)); } case GIT_OPT_SET_SEARCH_PATH: { PyObject *py_level = PyTuple_GetItem(args, 1); if (!py_level) return NULL; PyObject *py_path = PyTuple_GetItem(args, 2); if (!py_path) return NULL; if (!PyLong_Check(py_level)) return Error_type_error("level should be an integer, got %.200s", py_level); const char *path = pgit_borrow(py_path); if (!path) return NULL; int err = git_libgit2_opts(option, PyLong_AsLong(py_level), path); if (err < 0) return Error_set(err); Py_RETURN_NONE; } case GIT_OPT_SET_CACHE_OBJECT_LIMIT: { size_t limit; int object_type; PyObject *py_object_type, *py_limit; py_object_type = PyTuple_GetItem(args, 1); if (!py_object_type) return NULL; py_limit = PyTuple_GetItem(args, 2); if (!py_limit) return NULL; if (!PyLong_Check(py_limit)) return Error_type_error( "limit should be an integer, got %.200s", py_limit); object_type = PyLong_AsLong(py_object_type); limit = PyLong_AsSize_t(py_limit); error = git_libgit2_opts(option, object_type, limit); if (error < 0) return Error_set(error); Py_RETURN_NONE; } case GIT_OPT_SET_CACHE_MAX_SIZE: { size_t max_size; PyObject *py_max_size; py_max_size = PyTuple_GetItem(args, 1); if (!py_max_size) return NULL; if (!PyLong_Check(py_max_size)) return Error_type_error( "max_size should be an integer, got %.200s", py_max_size); max_size = PyLong_AsSize_t(py_max_size); error = git_libgit2_opts(option, max_size); if (error < 0) return Error_set(error); Py_RETURN_NONE; } case GIT_OPT_GET_CACHED_MEMORY: { size_t current; size_t allowed; PyObject* tup = PyTuple_New(2); error = git_libgit2_opts(option, ¤t, &allowed); if (error < 0) return Error_set(error); PyTuple_SetItem(tup, 0, PyLong_FromLong(current)); PyTuple_SetItem(tup, 1, PyLong_FromLong(allowed)); return tup; } case GIT_OPT_GET_TEMPLATE_PATH: case GIT_OPT_SET_TEMPLATE_PATH: { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } case GIT_OPT_SET_SSL_CERT_LOCATIONS: { PyObject *py_file, *py_dir; char *file_path=NULL, *dir_path=NULL; int err; py_file = PyTuple_GetItem(args, 1); if (!py_file) return NULL; py_dir = PyTuple_GetItem(args, 2); if (!py_dir) return NULL; /* py_file and py_dir are only valid if they are strings */ PyObject *tvalue_file = NULL; if (PyUnicode_Check(py_file) || PyBytes_Check(py_file)) file_path = pgit_borrow_fsdefault(py_file, &tvalue_file); PyObject *tvalue_dir = NULL; if (PyUnicode_Check(py_dir) || PyBytes_Check(py_dir)) dir_path = pgit_borrow_fsdefault(py_dir, &tvalue_dir); err = git_libgit2_opts(option, file_path, dir_path); Py_XDECREF(tvalue_file); Py_XDECREF(tvalue_dir); if (err) return Error_set(err); Py_RETURN_NONE; } case GIT_OPT_SET_USER_AGENT: { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } // int enabled case GIT_OPT_ENABLE_CACHING: case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: case GIT_OPT_ENABLE_OFS_DELTA: case GIT_OPT_ENABLE_FSYNC_GITDIR: case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: case GIT_OPT_SET_OWNER_VALIDATION: { PyObject *py_value = PyTuple_GetItem(args, 1); if (!py_value) return NULL; if (!PyLong_Check(py_value)) return Error_type_error("expected integer, got %.200s", py_value); int value = PyLong_AsSize_t(py_value); error = git_libgit2_opts(option, value); if (error < 0) return Error_set(error); Py_RETURN_NONE; } // int enabled getter case GIT_OPT_GET_OWNER_VALIDATION: { int enabled; error = git_libgit2_opts(option, &enabled); if (error < 0) return Error_set(error); return PyLong_FromLong(enabled); } // Not implemented case GIT_OPT_SET_SSL_CIPHERS: case GIT_OPT_GET_USER_AGENT: case GIT_OPT_GET_WINDOWS_SHAREMODE: case GIT_OPT_SET_WINDOWS_SHAREMODE: case GIT_OPT_SET_ALLOCATOR: case GIT_OPT_GET_PACK_MAX_OBJECTS: case GIT_OPT_SET_PACK_MAX_OBJECTS: { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } } PyErr_SetString(PyExc_ValueError, "unknown/unsupported option value"); return NULL; } libgit2-pygit2-a011e26/src/options.h000066400000000000000000000047371473744024100172570ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_blame_h #define INCLUDE_pygit2_blame_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyDoc_STRVAR(option__doc__, "option(option, ...)\n" "\n" "Get or set a libgit2 option.\n" "\n" "Parameters:\n" "\n" "GIT_OPT_GET_SEARCH_PATH, level\n" " Get the config search path for the given level.\n" "\n" "GIT_OPT_SET_SEARCH_PATH, level, path\n" " Set the config search path for the given level.\n" "\n" "GIT_OPT_GET_MWINDOW_SIZE\n" " Get the maximum mmap window size.\n" "\n" "GIT_OPT_SET_MWINDOW_SIZE, size\n" " Set the maximum mmap window size.\n" "\n" "GIT_OPT_GET_MWINDOW_FILE_LIMIT\n" " Get the maximum number of files that will be mapped at any time by the library.\n" "\n" "GIT_OPT_SET_MWINDOW_FILE_LIMIT, size\n" " Set the maximum number of files that can be mapped at any time by the library. The default (0) is unlimited.\n" "\n" "GIT_OPT_GET_OWNER_VALIDATION\n" " Gets the owner validation setting for repository directories.\n" "\n" "GIT_OPT_SET_OWNER_VALIDATION, enabled\n" " Set that repository directories should be owned by the current user.\n" " The default is to validate ownership.\n" ); PyObject *option(PyObject *self, PyObject *args); #endif libgit2-pygit2-a011e26/src/patch.c000066400000000000000000000232021473744024100166420ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "diff.h" #include "error.h" #include "object.h" #include "oid.h" #include "types.h" #include "utils.h" extern PyTypeObject DiffHunkType; extern PyTypeObject BlobType; PyTypeObject PatchType; PyObject * wrap_patch(git_patch *patch, Blob *oldblob, Blob *newblob) { Patch *py_patch; assert(patch); py_patch = PyObject_New(Patch, &PatchType); if (py_patch) { py_patch->patch = patch; Py_XINCREF(oldblob); py_patch->oldblob = oldblob; Py_XINCREF(newblob); py_patch->newblob = newblob; } return (PyObject*) py_patch; } static void Patch_dealloc(Patch *self) { Py_CLEAR(self->oldblob); Py_CLEAR(self->newblob); git_patch_free(self->patch); PyObject_Del(self); } PyDoc_STRVAR(Patch_delta__doc__, "Get the delta associated with a patch."); PyObject * Patch_delta__get__(Patch *self) { assert(self->patch); return wrap_diff_delta(git_patch_get_delta(self->patch)); } PyDoc_STRVAR(Patch_line_stats__doc__, "Get line counts of each type in a patch (context, additions, deletions)."); PyObject * Patch_line_stats__get__(Patch *self) { size_t context, additions, deletions; int err; assert(self->patch); err = git_patch_line_stats(&context, &additions, &deletions, self->patch); if (err < 0) return Error_set(err); return Py_BuildValue("III", context, additions, deletions); } PyDoc_STRVAR(Patch_create_from__doc__, "Create a patch from blobs, buffers, or a blob and a buffer"); static PyObject * Patch_create_from(PyObject *self, PyObject *args, PyObject *kwds) { /* A generic wrapper around * git_patch_from_blob_and_buffer * git_patch_from_buffers * git_patch_from_blobs */ git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_patch *patch; char *old_as_path = NULL, *new_as_path = NULL; PyObject *oldobj = NULL, *newobj = NULL; Blob *oldblob = NULL, *newblob = NULL; const char *oldbuf = NULL, *newbuf = NULL; Py_ssize_t oldbuflen, newbuflen; int err; char *keywords[] = {"old", "new", "old_as_path", "new_as_path", "flag", "context_lines", "interhunk_lines", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|zzIHH", keywords, &oldobj, &newobj, &old_as_path, &new_as_path, &opts.flags, &opts.context_lines, &opts.interhunk_lines)) return NULL; if (oldobj != Py_None && PyObject_TypeCheck(oldobj, &BlobType)) { /* The old object exists and is a blob */ oldblob = (Blob*)oldobj; if (Object__load((Object*)oldblob) == NULL) { return NULL; } // Lazy load if (newobj != Py_None && PyObject_TypeCheck(newobj, &BlobType)) { /* The new object exists and is a blob */ newblob = (Blob*)newobj; if (Object__load((Object*)newblob) == NULL) { return NULL; } // Lazy load err = git_patch_from_blobs(&patch, oldblob->blob, old_as_path, newblob->blob, new_as_path, &opts); } else { /* The new object does not exist or is a buffer */ if (!PyArg_Parse(newobj, "z#", &newbuf, &newbuflen)) return NULL; err = git_patch_from_blob_and_buffer(&patch, oldblob->blob, old_as_path, newbuf, newbuflen, new_as_path, &opts); } } else { /* The old object does exist and is a buffer */ if (!PyArg_Parse(oldobj, "z#", &oldbuf, &oldbuflen)) return NULL; if (!PyArg_Parse(newobj, "z#", &newbuf, &newbuflen)) return NULL; err = git_patch_from_buffers(&patch, oldbuf, oldbuflen, old_as_path, newbuf, newbuflen, new_as_path, &opts); } if (err < 0) return Error_set(err); return wrap_patch(patch, oldblob, newblob); } PyDoc_STRVAR(Patch_data__doc__, "The raw bytes of the patch's contents."); PyObject * Patch_data__get__(Patch *self) { git_buf buf = {NULL}; int err; PyObject *bytes; assert(self->patch); err = git_patch_to_buf(&buf, self->patch); if (err < 0) return Error_set(err); bytes = PyBytes_FromStringAndSize(buf.ptr, buf.size); git_buf_dispose(&buf); return bytes; } PyDoc_STRVAR(Patch_text__doc__, "Patch diff string. Can be None in some cases, such as empty commits.\n" "Note that this decodes the content to Unicode assuming UTF-8 encoding. " "For non-UTF-8 content that can lead be a lossy, non-reversible process. " "To access the raw, un-decoded patch, use `patch.data`."); PyObject * Patch_text__get__(Patch *self) { git_buf buf = {NULL}; int err; PyObject *text; assert(self->patch); err = git_patch_to_buf(&buf, self->patch); if (err < 0) return Error_set(err); text = to_unicode_n(buf.ptr, buf.size, NULL, NULL); git_buf_dispose(&buf); return text; } PyDoc_STRVAR(Patch_hunks__doc__, "hunks"); PyObject * Patch_hunks__get__(Patch *self) { size_t i, hunk_amounts; PyObject *py_hunks; PyObject *py_hunk; hunk_amounts = git_patch_num_hunks(self->patch); py_hunks = PyList_New(hunk_amounts); for (i = 0; i < hunk_amounts; i++) { py_hunk = wrap_diff_hunk(self, i); if (py_hunk == NULL) return NULL; PyList_SET_ITEM((PyObject*) py_hunks, i, py_hunk); } return py_hunks; } PyMethodDef Patch_methods[] = { {"create_from", (PyCFunction) Patch_create_from, METH_KEYWORDS | METH_VARARGS | METH_STATIC, Patch_create_from__doc__}, {NULL} }; PyGetSetDef Patch_getsetters[] = { GETTER(Patch, delta), GETTER(Patch, line_stats), GETTER(Patch, data), GETTER(Patch, text), GETTER(Patch, hunks), {NULL} }; PyDoc_STRVAR(Patch__doc__, "Diff patch object."); PyTypeObject PatchType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Patch", /* tp_name */ sizeof(Patch), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Patch_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Patch__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Patch_methods, /* tp_methods */ 0, /* tp_members */ Patch_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/patch.h000066400000000000000000000026211473744024100166510ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_patch_h #define INCLUDE_pygit2_patch_h #define PY_SSIZE_T_CLEAN #include #include PyObject* wrap_patch(git_patch *patch, Blob *oldblob, Blob *newblob); #endif libgit2-pygit2-a011e26/src/pygit2.c000066400000000000000000000733031473744024100167700ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "error.h" #include "types.h" #include "utils.h" #include "repository.h" #include "oid.h" #include "options.h" #include "filter.h" PyObject *GitError; PyObject *AlreadyExistsError; PyObject *InvalidSpecError; PyObject *DeltaStatusEnum; PyObject *DiffFlagEnum; PyObject *FileModeEnum; PyObject *FileStatusEnum; PyObject *MergeAnalysisEnum; PyObject *MergePreferenceEnum; PyObject *ReferenceTypeEnum; extern PyTypeObject RepositoryType; extern PyTypeObject OdbType; extern PyTypeObject OdbBackendType; extern PyTypeObject OdbBackendPackType; extern PyTypeObject OdbBackendLooseType; extern PyTypeObject OidType; extern PyTypeObject ObjectType; extern PyTypeObject CommitType; extern PyTypeObject DiffType; extern PyTypeObject DeltasIterType; extern PyTypeObject DiffIterType; extern PyTypeObject DiffDeltaType; extern PyTypeObject DiffFileType; extern PyTypeObject DiffHunkType; extern PyTypeObject DiffLineType; extern PyTypeObject DiffStatsType; extern PyTypeObject PatchType; extern PyTypeObject TreeType; extern PyTypeObject TreeBuilderType; extern PyTypeObject TreeIterType; extern PyTypeObject BlobType; extern PyTypeObject TagType; extern PyTypeObject WalkerType; extern PyTypeObject RefdbType; extern PyTypeObject RefdbBackendType; extern PyTypeObject RefdbFsBackendType; extern PyTypeObject ReferenceType; extern PyTypeObject RevSpecType; extern PyTypeObject RefLogIterType; extern PyTypeObject RefLogEntryType; extern PyTypeObject BranchType; extern PyTypeObject SignatureType; extern PyTypeObject RemoteType; extern PyTypeObject RefspecType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; extern PyTypeObject WorktreeType; extern PyTypeObject MailmapType; extern PyTypeObject StashType; extern PyTypeObject RefsIteratorType; extern PyTypeObject FilterSourceType; PyDoc_STRVAR(discover_repository__doc__, "discover_repository(path: str, across_fs: bool = False[, ceiling_dirs: str]) -> str\n" "\n" "Look for a git repository and return its path. If not found returns None."); PyObject * discover_repository(PyObject *self, PyObject *args) { git_buf repo_path = {NULL}; const char *path = NULL; PyBytesObject *py_path = NULL; int across_fs = 0; PyBytesObject *py_ceiling_dirs = NULL; const char *ceiling_dirs = NULL; PyObject *py_repo_path = NULL; int err; PyObject *result = NULL; if (!PyArg_ParseTuple(args, "O&|IO&", PyUnicode_FSConverter, &py_path, &across_fs, PyUnicode_FSConverter, &py_ceiling_dirs)) return NULL; if (py_path != NULL) path = PyBytes_AS_STRING(py_path); if (py_ceiling_dirs != NULL) ceiling_dirs = PyBytes_AS_STRING(py_ceiling_dirs); memset(&repo_path, 0, sizeof(git_buf)); err = git_repository_discover(&repo_path, path, across_fs, ceiling_dirs); if (err == GIT_OK) { py_repo_path = PyUnicode_DecodeFSDefault(repo_path.ptr); git_buf_dispose(&repo_path); result = py_repo_path; } else if (err == GIT_ENOTFOUND) { result = Py_None; } else { result = Error_set_str(err, path); } Py_XDECREF(py_ceiling_dirs); Py_XDECREF(py_path); return result; }; PyDoc_STRVAR(hashfile__doc__, "hashfile(path: str) -> Oid\n" "\n" "Returns the oid of a new blob from a file path without actually writing\n" "to the odb."); PyObject * hashfile(PyObject *self, PyObject *args) { git_oid oid; PyBytesObject *py_path = NULL; const char* path = NULL; int err; if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter, &py_path)) return NULL; if (py_path != NULL) path = PyBytes_AS_STRING(py_path); err = git_odb_hashfile(&oid, path, GIT_OBJECT_BLOB); Py_XDECREF(py_path); if (err < 0) return Error_set(err); return git_oid_to_python(&oid); } PyDoc_STRVAR(hash__doc__, "hash(data: bytes) -> Oid\n" "\n" "Returns the oid of a new blob from a string without actually writing to\n" "the odb."); PyObject * hash(PyObject *self, PyObject *args) { git_oid oid; const char *data; Py_ssize_t size; int err; if (!PyArg_ParseTuple(args, "s#", &data, &size)) return NULL; err = git_odb_hash(&oid, data, size, GIT_OBJECT_BLOB); if (err < 0) { return Error_set(err); } return git_oid_to_python(&oid); } PyDoc_STRVAR(init_file_backend__doc__, "init_file_backend(path: str, flags: int = 0) -> object\n" "\n" "Open repo backend given path."); PyObject * init_file_backend(PyObject *self, PyObject *args) { PyBytesObject *py_path = NULL; const char* path = NULL; unsigned int flags = 0; int err = GIT_OK; git_repository *repository = NULL; PyObject *result = NULL; if (!PyArg_ParseTuple(args, "O&|I", PyUnicode_FSConverter, &py_path, &flags)) return NULL; if (py_path != NULL) path = PyBytes_AS_STRING(py_path); err = git_repository_open_ext(&repository, path, flags, NULL); if (err == GIT_OK) { result = PyCapsule_New(repository, "backend", NULL); } else { result = NULL; Error_set_str(err, path); if (repository) { git_repository_free(repository); } if (err == GIT_ENOTFOUND) { PyErr_Format(GitError, "Repository not found at %s", path); } } Py_XDECREF(py_path); return result; } PyDoc_STRVAR(reference_is_valid_name__doc__, "reference_is_valid_name(refname: str) -> bool\n" "\n" "Check if the passed string is a valid reference name."); PyObject * reference_is_valid_name(PyObject *self, PyObject *py_refname) { const char *refname = pgit_borrow(py_refname); if (refname == NULL) return NULL; int result = git_reference_is_valid_name(refname); return PyBool_FromLong(result); } PyDoc_STRVAR(tree_entry_cmp__doc__, "tree_entry_cmp(a: Object, b: Object) -> int\n" "\n" "Rich comparison for objects, only available when the objects have been\n" "obtained through a tree. The sort criteria is the one Git uses to sort\n" "tree entries in a tree object. This function wraps git_tree_entry_cmp.\n" "\n" "Returns < 0 if a is before b, > 0 if a is after b, and 0 if a and b are\n" "the same."); PyObject * tree_entry_cmp(PyObject *self, PyObject *args) { Object *a, *b; int cmp; if (!PyArg_ParseTuple(args, "O!O!", &ObjectType, &a, &ObjectType, &b)) return NULL; if (a->entry == NULL || b->entry == NULL) { PyErr_SetString(PyExc_ValueError, "objects lack entry information"); return NULL; } cmp = git_tree_entry_cmp(a->entry, b->entry); return PyLong_FromLong(cmp); } PyDoc_STRVAR(filter_register__doc__, "filter_register(name: str, filter_cls: Type[Filter], [priority: int = C.GIT_FILTER_DRIVER_PRIORITY]) -> None\n" "\n" "Register a filter under the given name.\n" "\n" "Filters will be run in order of `priority` on smudge (to workdir) and in\n" "reverse order of priority on clean (to odb).\n" "\n" "Two filters are preregistered with libgit2:\n" " - GIT_FILTER_CRLF with priority 0\n" " - GIT_FILTER_IDENT with priority 100\n" "\n" "`priority` defaults to GIT_FILTER_DRIVER_PRIORITY which imitates a core\n" "Git filter driver that will be run last on checkout (smudge) and first \n" "on checkin (clean).\n" "\n" "Note that the filter registry is not thread safe. Any registering or\n" "deregistering of filters should be done outside of any possible usage\n" "of the filters.\n"); PyObject * filter_register(PyObject *self, PyObject *args, PyObject *kwds) { const char *name; Py_ssize_t size; PyObject *py_filter_cls; int priority = GIT_FILTER_DRIVER_PRIORITY; char *keywords[] = {"name", "filter_cls", "priority", NULL}; pygit2_filter *filter; PyObject *py_attrs; PyObject *result = Py_None; int err; if (!PyArg_ParseTupleAndKeywords(args, kwds, "s#O|i", keywords, &name, &size, &py_filter_cls, &priority)) return NULL; py_attrs = PyObject_GetAttrString(py_filter_cls, "attributes"); if (py_attrs == NULL) return NULL; filter = malloc(sizeof(pygit2_filter)); if (filter == NULL) { return PyExc_MemoryError; } memset(filter, 0, sizeof(pygit2_filter)); git_filter_init(&filter->filter, GIT_FILTER_VERSION); filter->filter.attributes = PyUnicode_AsUTF8(py_attrs); filter->filter.shutdown = pygit2_filter_shutdown; filter->filter.check = pygit2_filter_check; filter->filter.stream = pygit2_filter_stream; filter->filter.cleanup = pygit2_filter_cleanup; filter->py_filter_cls = py_filter_cls; Py_INCREF(py_filter_cls); if ((err = git_filter_register(name, &filter->filter, priority)) < 0) goto error; goto done; error: Py_DECREF(py_filter_cls); free(filter); done: Py_DECREF(py_attrs); return result; } PyDoc_STRVAR(filter_unregister__doc__, "filter_unregister(name: str) -> None\n" "\n" "Unregister the given filter.\n" "\n" "Note that the filter registry is not thread safe. Any registering or\n" "deregistering of filters should be done outside of any possible usage\n" "of the filters.\n"); PyObject * filter_unregister(PyObject *self, PyObject *args) { const char *name; Py_ssize_t size; int err; if (!PyArg_ParseTuple(args, "s#", &name, &size)) return NULL; if ((err = git_filter_unregister(name)) < 0) return Error_set(err); Py_RETURN_NONE; } static void forget_enums(void) { Py_CLEAR(DeltaStatusEnum); Py_CLEAR(DiffFlagEnum); Py_CLEAR(FileModeEnum); Py_CLEAR(FileStatusEnum); Py_CLEAR(MergeAnalysisEnum); Py_CLEAR(MergePreferenceEnum); Py_CLEAR(ReferenceTypeEnum); } PyDoc_STRVAR(_cache_enums__doc__, "_cache_enums()\n" "\n" "For internal use only. Do not call this from user code.\n" "\n" "Let the _pygit2 C module cache references to Python enums\n" "defined in pygit2.enums.\n"); PyObject * _cache_enums(PyObject *self, PyObject *args) { (void) args; /* In case this is somehow being called several times, let go of old references */ forget_enums(); PyObject *enums = PyImport_ImportModule("pygit2.enums"); if (enums == NULL) { return NULL; } #define CACHE_PYGIT2_ENUM(name) do { \ name##Enum = PyObject_GetAttrString(enums, #name); \ if (name##Enum == NULL) { goto fail; } \ } while (0) CACHE_PYGIT2_ENUM(DeltaStatus); CACHE_PYGIT2_ENUM(DiffFlag); CACHE_PYGIT2_ENUM(FileMode); CACHE_PYGIT2_ENUM(FileStatus); CACHE_PYGIT2_ENUM(MergeAnalysis); CACHE_PYGIT2_ENUM(MergePreference); CACHE_PYGIT2_ENUM(ReferenceType); #undef CACHE_PYGIT2_ENUM Py_RETURN_NONE; fail: Py_DECREF(enums); forget_enums(); return NULL; } void free_module(void *self) { forget_enums(); } PyMethodDef module_methods[] = { {"discover_repository", discover_repository, METH_VARARGS, discover_repository__doc__}, {"hash", hash, METH_VARARGS, hash__doc__}, {"hashfile", hashfile, METH_VARARGS, hashfile__doc__}, {"init_file_backend", init_file_backend, METH_VARARGS, init_file_backend__doc__}, {"option", option, METH_VARARGS, option__doc__}, {"reference_is_valid_name", reference_is_valid_name, METH_O, reference_is_valid_name__doc__}, {"tree_entry_cmp", tree_entry_cmp, METH_VARARGS, tree_entry_cmp__doc__}, {"filter_register", (PyCFunction)filter_register, METH_VARARGS | METH_KEYWORDS, filter_register__doc__}, {"filter_unregister", filter_unregister, METH_VARARGS, filter_unregister__doc__}, {"_cache_enums", _cache_enums, METH_NOARGS, _cache_enums__doc__}, {NULL} }; struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_pygit2", /* m_name */ "Python bindings for libgit2.", /* m_doc */ -1, /* m_size */ module_methods, /* m_methods */ NULL, /* m_reload */ NULL, /* m_traverse */ NULL, /* m_clear */ free_module, /* m_free */ }; PyMODINIT_FUNC PyInit__pygit2(void) { PyObject *m = PyModule_Create(&moduledef); if (m == NULL) return NULL; /* libgit2 version info */ ADD_CONSTANT_INT(m, LIBGIT2_VER_MAJOR) ADD_CONSTANT_INT(m, LIBGIT2_VER_MINOR) ADD_CONSTANT_INT(m, LIBGIT2_VER_REVISION) ADD_CONSTANT_STR(m, LIBGIT2_VERSION) /* libgit2 options */ ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_SIZE); ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_SIZE); ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_MAPPED_LIMIT); ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_MAPPED_LIMIT); ADD_CONSTANT_INT(m, GIT_OPT_GET_SEARCH_PATH); ADD_CONSTANT_INT(m, GIT_OPT_SET_SEARCH_PATH); ADD_CONSTANT_INT(m, GIT_OPT_SET_CACHE_OBJECT_LIMIT); ADD_CONSTANT_INT(m, GIT_OPT_SET_CACHE_MAX_SIZE); ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_CACHING); ADD_CONSTANT_INT(m, GIT_OPT_GET_CACHED_MEMORY); ADD_CONSTANT_INT(m, GIT_OPT_GET_TEMPLATE_PATH); ADD_CONSTANT_INT(m, GIT_OPT_SET_TEMPLATE_PATH); ADD_CONSTANT_INT(m, GIT_OPT_SET_SSL_CERT_LOCATIONS); ADD_CONSTANT_INT(m, GIT_OPT_SET_USER_AGENT); ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_STRICT_OBJECT_CREATION); ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION); ADD_CONSTANT_INT(m, GIT_OPT_SET_SSL_CIPHERS); ADD_CONSTANT_INT(m, GIT_OPT_GET_USER_AGENT); ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_OFS_DELTA); ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_FSYNC_GITDIR); ADD_CONSTANT_INT(m, GIT_OPT_GET_WINDOWS_SHAREMODE); ADD_CONSTANT_INT(m, GIT_OPT_SET_WINDOWS_SHAREMODE); ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION); ADD_CONSTANT_INT(m, GIT_OPT_SET_ALLOCATOR); ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY); ADD_CONSTANT_INT(m, GIT_OPT_GET_PACK_MAX_OBJECTS); ADD_CONSTANT_INT(m, GIT_OPT_SET_PACK_MAX_OBJECTS); ADD_CONSTANT_INT(m, GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS); ADD_CONSTANT_INT(m, GIT_OPT_GET_OWNER_VALIDATION); ADD_CONSTANT_INT(m, GIT_OPT_SET_OWNER_VALIDATION); ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_FILE_LIMIT); ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_FILE_LIMIT); /* Exceptions */ ADD_EXC(m, GitError, NULL); ADD_EXC(m, AlreadyExistsError, PyExc_ValueError); ADD_EXC(m, InvalidSpecError, PyExc_ValueError); /* Repository */ INIT_TYPE(RepositoryType, NULL, PyType_GenericNew) ADD_TYPE(m, Repository) /* Odb */ INIT_TYPE(OdbType, NULL, PyType_GenericNew) ADD_TYPE(m, Odb) INIT_TYPE(OdbBackendType, NULL, PyType_GenericNew) ADD_TYPE(m, OdbBackend) INIT_TYPE(OdbBackendPackType, &OdbBackendType, PyType_GenericNew) ADD_TYPE(m, OdbBackendPack) INIT_TYPE(OdbBackendLooseType, &OdbBackendType, PyType_GenericNew) ADD_TYPE(m, OdbBackendLoose) /* Oid */ INIT_TYPE(OidType, NULL, PyType_GenericNew) ADD_TYPE(m, Oid) ADD_CONSTANT_INT(m, GIT_OID_RAWSZ) ADD_CONSTANT_INT(m, GIT_OID_HEXSZ) ADD_CONSTANT_STR(m, GIT_OID_HEX_ZERO) ADD_CONSTANT_INT(m, GIT_OID_MINPREFIXLEN) /* * Objects */ INIT_TYPE(ObjectType, NULL, NULL) INIT_TYPE(CommitType, &ObjectType, NULL) INIT_TYPE(SignatureType, NULL, PyType_GenericNew) INIT_TYPE(TreeType, &ObjectType, NULL) INIT_TYPE(TreeIterType, NULL, NULL) INIT_TYPE(TreeBuilderType, NULL, NULL) INIT_TYPE(BlobType, &ObjectType, NULL) INIT_TYPE(TagType, &ObjectType, NULL) INIT_TYPE(RefsIteratorType, NULL, NULL) ADD_TYPE(m, Object) ADD_TYPE(m, Commit) ADD_TYPE(m, Signature) ADD_TYPE(m, Tree) ADD_TYPE(m, TreeBuilder) ADD_TYPE(m, Blob) ADD_TYPE(m, Tag) ADD_CONSTANT_INT(m, GIT_OBJECT_ANY) ADD_CONSTANT_INT(m, GIT_OBJECT_INVALID) ADD_CONSTANT_INT(m, GIT_OBJECT_COMMIT) ADD_CONSTANT_INT(m, GIT_OBJECT_TREE) ADD_CONSTANT_INT(m, GIT_OBJECT_BLOB) ADD_CONSTANT_INT(m, GIT_OBJECT_TAG) ADD_CONSTANT_INT(m, GIT_OBJECT_OFS_DELTA) ADD_CONSTANT_INT(m, GIT_OBJECT_REF_DELTA) /* Valid modes for index and tree entries. */ ADD_CONSTANT_INT(m, GIT_FILEMODE_UNREADABLE) ADD_CONSTANT_INT(m, GIT_FILEMODE_TREE) ADD_CONSTANT_INT(m, GIT_FILEMODE_BLOB) ADD_CONSTANT_INT(m, GIT_FILEMODE_BLOB_EXECUTABLE) ADD_CONSTANT_INT(m, GIT_FILEMODE_LINK) ADD_CONSTANT_INT(m, GIT_FILEMODE_COMMIT) /* * Log */ INIT_TYPE(WalkerType, NULL, NULL) ADD_TYPE(m, Walker); ADD_CONSTANT_INT(m, GIT_SORT_NONE) ADD_CONSTANT_INT(m, GIT_SORT_TOPOLOGICAL) ADD_CONSTANT_INT(m, GIT_SORT_TIME) ADD_CONSTANT_INT(m, GIT_SORT_REVERSE) /* * Reset */ ADD_CONSTANT_INT(m, GIT_RESET_SOFT) ADD_CONSTANT_INT(m, GIT_RESET_MIXED) ADD_CONSTANT_INT(m, GIT_RESET_HARD) /* Refdb */ INIT_TYPE(RefdbType, NULL, PyType_GenericNew) ADD_TYPE(m, Refdb) INIT_TYPE(RefdbBackendType, NULL, PyType_GenericNew) ADD_TYPE(m, RefdbBackend) INIT_TYPE(RefdbFsBackendType, &RefdbBackendType, PyType_GenericNew) ADD_TYPE(m, RefdbFsBackend) /* * References */ INIT_TYPE(ReferenceType, NULL, PyType_GenericNew) INIT_TYPE(RefLogEntryType, NULL, NULL) INIT_TYPE(RefLogIterType, NULL, NULL) INIT_TYPE(NoteType, NULL, NULL) INIT_TYPE(NoteIterType, NULL, NULL) ADD_TYPE(m, Reference) ADD_TYPE(m, RefLogEntry) ADD_TYPE(m, Note) ADD_CONSTANT_INT(m, GIT_REFERENCES_ALL) ADD_CONSTANT_INT(m, GIT_REFERENCES_BRANCHES) ADD_CONSTANT_INT(m, GIT_REFERENCES_TAGS) /* * RevSpec */ INIT_TYPE(RevSpecType, NULL, NULL) ADD_TYPE(m, RevSpec) ADD_CONSTANT_INT(m, GIT_REVSPEC_SINGLE) ADD_CONSTANT_INT(m, GIT_REVSPEC_RANGE) ADD_CONSTANT_INT(m, GIT_REVSPEC_MERGE_BASE) /* * Worktree */ INIT_TYPE(WorktreeType, NULL, NULL) ADD_TYPE(m, Worktree) /* * Branches */ INIT_TYPE(BranchType, &ReferenceType, NULL); ADD_TYPE(m, Branch) ADD_CONSTANT_INT(m, GIT_BRANCH_LOCAL) ADD_CONSTANT_INT(m, GIT_BRANCH_REMOTE) ADD_CONSTANT_INT(m, GIT_BRANCH_ALL) /* * Index & Working copy */ /* Status */ ADD_CONSTANT_INT(m, GIT_STATUS_CURRENT) ADD_CONSTANT_INT(m, GIT_STATUS_INDEX_NEW) ADD_CONSTANT_INT(m, GIT_STATUS_INDEX_MODIFIED) ADD_CONSTANT_INT(m, GIT_STATUS_INDEX_DELETED) ADD_CONSTANT_INT(m, GIT_STATUS_INDEX_RENAMED) ADD_CONSTANT_INT(m, GIT_STATUS_INDEX_TYPECHANGE) ADD_CONSTANT_INT(m, GIT_STATUS_WT_NEW) ADD_CONSTANT_INT(m, GIT_STATUS_WT_MODIFIED) ADD_CONSTANT_INT(m, GIT_STATUS_WT_DELETED) ADD_CONSTANT_INT(m, GIT_STATUS_WT_TYPECHANGE) ADD_CONSTANT_INT(m, GIT_STATUS_WT_RENAMED) ADD_CONSTANT_INT(m, GIT_STATUS_WT_UNREADABLE) ADD_CONSTANT_INT(m, GIT_STATUS_IGNORED) /* Flags for ignored files */ ADD_CONSTANT_INT(m, GIT_STATUS_CONFLICTED) /* Different checkout strategies */ ADD_CONSTANT_INT(m, GIT_CHECKOUT_NONE) ADD_CONSTANT_INT(m, GIT_CHECKOUT_SAFE) ADD_CONSTANT_INT(m, GIT_CHECKOUT_FORCE) ADD_CONSTANT_INT(m, GIT_CHECKOUT_RECREATE_MISSING) ADD_CONSTANT_INT(m, GIT_CHECKOUT_ALLOW_CONFLICTS) ADD_CONSTANT_INT(m, GIT_CHECKOUT_REMOVE_UNTRACKED) ADD_CONSTANT_INT(m, GIT_CHECKOUT_REMOVE_IGNORED) ADD_CONSTANT_INT(m, GIT_CHECKOUT_UPDATE_ONLY) ADD_CONSTANT_INT(m, GIT_CHECKOUT_DONT_UPDATE_INDEX) ADD_CONSTANT_INT(m, GIT_CHECKOUT_NO_REFRESH) ADD_CONSTANT_INT(m, GIT_CHECKOUT_SKIP_UNMERGED) ADD_CONSTANT_INT(m, GIT_CHECKOUT_USE_OURS) ADD_CONSTANT_INT(m, GIT_CHECKOUT_USE_THEIRS) ADD_CONSTANT_INT(m, GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) ADD_CONSTANT_INT(m, GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) ADD_CONSTANT_INT(m, GIT_CHECKOUT_DONT_OVERWRITE_IGNORED) ADD_CONSTANT_INT(m, GIT_CHECKOUT_CONFLICT_STYLE_MERGE) ADD_CONSTANT_INT(m, GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) ADD_CONSTANT_INT(m, GIT_CHECKOUT_DONT_REMOVE_EXISTING) ADD_CONSTANT_INT(m, GIT_CHECKOUT_DONT_WRITE_INDEX) ADD_CONSTANT_INT(m, GIT_CHECKOUT_DRY_RUN) ADD_CONSTANT_INT(m, GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3) /* * Diff */ INIT_TYPE(DiffType, NULL, NULL) INIT_TYPE(DeltasIterType, NULL, NULL) INIT_TYPE(DiffIterType, NULL, NULL) INIT_TYPE(DiffDeltaType, NULL, NULL) INIT_TYPE(DiffFileType, NULL, NULL) INIT_TYPE(DiffHunkType, NULL, NULL) INIT_TYPE(DiffLineType, NULL, NULL) INIT_TYPE(DiffStatsType, NULL, NULL) INIT_TYPE(PatchType, NULL, NULL) ADD_TYPE(m, Diff) ADD_TYPE(m, DiffDelta) ADD_TYPE(m, DiffFile) ADD_TYPE(m, DiffHunk) ADD_TYPE(m, DiffLine) ADD_TYPE(m, DiffStats) ADD_TYPE(m, Patch) /* (git_diff_options in libgit2) */ ADD_CONSTANT_INT(m, GIT_DIFF_NORMAL) ADD_CONSTANT_INT(m, GIT_DIFF_REVERSE) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_IGNORED) ADD_CONSTANT_INT(m, GIT_DIFF_RECURSE_IGNORED_DIRS) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNTRACKED) ADD_CONSTANT_INT(m, GIT_DIFF_RECURSE_UNTRACKED_DIRS) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNMODIFIED) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_TYPECHANGE) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_FILEMODE) ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_SUBMODULES) ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_CASE) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_CASECHANGE) ADD_CONSTANT_INT(m, GIT_DIFF_DISABLE_PATHSPEC_MATCH) ADD_CONSTANT_INT(m, GIT_DIFF_SKIP_BINARY_CHECK) ADD_CONSTANT_INT(m, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS) ADD_CONSTANT_INT(m, GIT_DIFF_UPDATE_INDEX) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNREADABLE) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED) ADD_CONSTANT_INT(m, GIT_DIFF_INDENT_HEURISTIC) ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_BLANK_LINES) ADD_CONSTANT_INT(m, GIT_DIFF_FORCE_TEXT) ADD_CONSTANT_INT(m, GIT_DIFF_FORCE_BINARY) ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_WHITESPACE) ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_WHITESPACE_CHANGE) ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_WHITESPACE_EOL) ADD_CONSTANT_INT(m, GIT_DIFF_SHOW_UNTRACKED_CONTENT) ADD_CONSTANT_INT(m, GIT_DIFF_SHOW_UNMODIFIED) ADD_CONSTANT_INT(m, GIT_DIFF_PATIENCE) ADD_CONSTANT_INT(m, GIT_DIFF_MINIMAL) ADD_CONSTANT_INT(m, GIT_DIFF_SHOW_BINARY) /* Formatting options for diff stats (git_diff_stats_format_t in libgit2) */ ADD_CONSTANT_INT(m, GIT_DIFF_STATS_NONE) ADD_CONSTANT_INT(m, GIT_DIFF_STATS_FULL) ADD_CONSTANT_INT(m, GIT_DIFF_STATS_SHORT) ADD_CONSTANT_INT(m, GIT_DIFF_STATS_NUMBER) ADD_CONSTANT_INT(m, GIT_DIFF_STATS_INCLUDE_SUMMARY) /* Flags for Diff.find_similar (git_diff_find_t in libgit2) */ ADD_CONSTANT_INT(m, GIT_DIFF_FIND_BY_CONFIG) /** Obey diff.renames */ ADD_CONSTANT_INT(m, GIT_DIFF_FIND_RENAMES) /* --find-renames */ ADD_CONSTANT_INT(m, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) /* --break-rewrites=N */ ADD_CONSTANT_INT(m, GIT_DIFF_FIND_COPIES) /* --find-copies */ ADD_CONSTANT_INT(m, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED) /* --find-copies-harder */ ADD_CONSTANT_INT(m, GIT_DIFF_FIND_REWRITES) /* --break-rewrites=/M */ ADD_CONSTANT_INT(m, GIT_DIFF_BREAK_REWRITES) ADD_CONSTANT_INT(m, GIT_DIFF_FIND_AND_BREAK_REWRITES) ADD_CONSTANT_INT(m, GIT_DIFF_FIND_FOR_UNTRACKED) ADD_CONSTANT_INT(m, GIT_DIFF_FIND_ALL) /* Turn on all finding features. */ ADD_CONSTANT_INT(m, GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE) ADD_CONSTANT_INT(m, GIT_DIFF_FIND_IGNORE_WHITESPACE) ADD_CONSTANT_INT(m, GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE) ADD_CONSTANT_INT(m, GIT_DIFF_FIND_EXACT_MATCH_ONLY) ADD_CONSTANT_INT(m, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY) ADD_CONSTANT_INT(m, GIT_DIFF_FIND_REMOVE_UNMODIFIED) /* DiffDelta and DiffFile flags (git_diff_flag_t in libgit2) */ ADD_CONSTANT_INT(m, GIT_DIFF_FLAG_BINARY) ADD_CONSTANT_INT(m, GIT_DIFF_FLAG_NOT_BINARY) ADD_CONSTANT_INT(m, GIT_DIFF_FLAG_VALID_ID) ADD_CONSTANT_INT(m, GIT_DIFF_FLAG_EXISTS) ADD_CONSTANT_INT(m, GIT_DIFF_FLAG_VALID_SIZE) /* DiffDelta.status (git_delta_t in libgit2) */ ADD_CONSTANT_INT(m, GIT_DELTA_UNMODIFIED) ADD_CONSTANT_INT(m, GIT_DELTA_ADDED) ADD_CONSTANT_INT(m, GIT_DELTA_DELETED) ADD_CONSTANT_INT(m, GIT_DELTA_MODIFIED) ADD_CONSTANT_INT(m, GIT_DELTA_RENAMED) ADD_CONSTANT_INT(m, GIT_DELTA_COPIED) ADD_CONSTANT_INT(m, GIT_DELTA_IGNORED) ADD_CONSTANT_INT(m, GIT_DELTA_UNTRACKED) ADD_CONSTANT_INT(m, GIT_DELTA_TYPECHANGE) ADD_CONSTANT_INT(m, GIT_DELTA_UNREADABLE) ADD_CONSTANT_INT(m, GIT_DELTA_CONFLICTED) /* Config */ ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_PROGRAMDATA); ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_SYSTEM); ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_XDG); ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_GLOBAL); ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_LOCAL); ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_WORKTREE); ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_APP); ADD_CONSTANT_INT(m, GIT_CONFIG_HIGHEST_LEVEL); /* Blame */ ADD_CONSTANT_INT(m, GIT_BLAME_NORMAL) ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_FILE) ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES) ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES) ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES) ADD_CONSTANT_INT(m, GIT_BLAME_FIRST_PARENT) ADD_CONSTANT_INT(m, GIT_BLAME_USE_MAILMAP) ADD_CONSTANT_INT(m, GIT_BLAME_IGNORE_WHITESPACE) /* Merge */ ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_NONE) ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_NORMAL) ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_UP_TO_DATE) ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_FASTFORWARD) ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_UNBORN) ADD_CONSTANT_INT(m, GIT_MERGE_PREFERENCE_NONE) ADD_CONSTANT_INT(m, GIT_MERGE_PREFERENCE_NO_FASTFORWARD) ADD_CONSTANT_INT(m, GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) /* Describe */ ADD_CONSTANT_INT(m, GIT_DESCRIBE_DEFAULT); ADD_CONSTANT_INT(m, GIT_DESCRIBE_TAGS); ADD_CONSTANT_INT(m, GIT_DESCRIBE_ALL); /* Stash */ ADD_CONSTANT_INT(m, GIT_STASH_DEFAULT); ADD_CONSTANT_INT(m, GIT_STASH_KEEP_INDEX); ADD_CONSTANT_INT(m, GIT_STASH_INCLUDE_UNTRACKED); ADD_CONSTANT_INT(m, GIT_STASH_INCLUDE_IGNORED); ADD_CONSTANT_INT(m, GIT_STASH_KEEP_ALL); ADD_CONSTANT_INT(m, GIT_STASH_APPLY_DEFAULT); ADD_CONSTANT_INT(m, GIT_STASH_APPLY_REINSTATE_INDEX); /* Apply location */ ADD_CONSTANT_INT(m, GIT_APPLY_LOCATION_WORKDIR); ADD_CONSTANT_INT(m, GIT_APPLY_LOCATION_INDEX); ADD_CONSTANT_INT(m, GIT_APPLY_LOCATION_BOTH); /* Submodule */ ADD_CONSTANT_INT(m, GIT_SUBMODULE_IGNORE_UNSPECIFIED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_IGNORE_NONE); ADD_CONSTANT_INT(m, GIT_SUBMODULE_IGNORE_UNTRACKED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_IGNORE_DIRTY); ADD_CONSTANT_INT(m, GIT_SUBMODULE_IGNORE_ALL); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_IN_HEAD); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_IN_INDEX); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_IN_CONFIG); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_IN_WD); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_INDEX_ADDED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_INDEX_DELETED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_INDEX_MODIFIED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_WD_ADDED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_WD_DELETED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_WD_MODIFIED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_WD_WD_MODIFIED); ADD_CONSTANT_INT(m, GIT_SUBMODULE_STATUS_WD_UNTRACKED); /* Mailmap */ INIT_TYPE(MailmapType, NULL, PyType_GenericNew) ADD_TYPE(m, Mailmap) /* Stash */ INIT_TYPE(StashType, NULL, NULL) ADD_TYPE(m, Stash) /* Blob filter */ ADD_CONSTANT_INT(m, GIT_BLOB_FILTER_CHECK_FOR_BINARY); ADD_CONSTANT_INT(m, GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES); ADD_CONSTANT_INT(m, GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD); ADD_CONSTANT_INT(m, GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT); ADD_CONSTANT_INT(m, GIT_FILTER_DRIVER_PRIORITY); ADD_CONSTANT_INT(m, GIT_FILTER_TO_WORKTREE); ADD_CONSTANT_INT(m, GIT_FILTER_SMUDGE); ADD_CONSTANT_INT(m, GIT_FILTER_TO_ODB); ADD_CONSTANT_INT(m, GIT_FILTER_CLEAN); ADD_CONSTANT_INT(m, GIT_FILTER_DEFAULT); ADD_CONSTANT_INT(m, GIT_FILTER_ALLOW_UNSAFE); ADD_CONSTANT_INT(m, GIT_FILTER_NO_SYSTEM_ATTRIBUTES); ADD_CONSTANT_INT(m, GIT_FILTER_ATTRIBUTES_FROM_HEAD); ADD_CONSTANT_INT(m, GIT_FILTER_ATTRIBUTES_FROM_COMMIT); INIT_TYPE(FilterSourceType, NULL, NULL); ADD_TYPE(m, FilterSource); /* Global initialization of libgit2 */ git_libgit2_init(); return m; fail: Py_DECREF(m); return NULL; } libgit2-pygit2-a011e26/src/refdb.c000066400000000000000000000154561473744024100166410ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "refdb.h" #include "types.h" #include "utils.h" #include #include extern PyTypeObject RepositoryType; extern PyTypeObject RefdbType; void Refdb_dealloc(Refdb *self) { git_refdb_free(self->refdb); Py_TYPE(self)->tp_free((PyObject *) self); } PyDoc_STRVAR(Refdb_compress__doc__, "compress()\n" "\n" "Suggests that the given refdb compress or optimize its references.\n" "This mechanism is implementation specific. For on-disk reference\n" "databases, for example, this may pack all loose references."); PyObject * Refdb_compress(Refdb *self) { int err = git_refdb_compress(self->refdb); if (err != 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Refdb_set_backend__doc__, "set_backend(backend: RefdbBackend)\n" "\n" "Sets a custom RefdbBackend for this Refdb."); PyObject * Refdb_set_backend(Refdb *self, RefdbBackend *backend) { int err; err = git_refdb_set_backend(self->refdb, backend->refdb_backend); if (err != 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Refdb_new__doc__, "Refdb.new(repo: Repository) -> Refdb\n" "Creates a new refdb with no backend."); PyObject * Refdb_new(PyObject *self, Repository *repo) { if (!PyObject_IsInstance((PyObject *)repo, (PyObject *)&RepositoryType)) { PyErr_SetString(PyExc_TypeError, "Refdb.new expects an object of type " "pygit2.Repository"); return NULL; } git_refdb *refdb; int err = git_refdb_new(&refdb, repo->repo); if (err) { Error_set(err); return NULL; } return wrap_refdb(refdb); } PyDoc_STRVAR(Refdb_open__doc__, "open(repo: Repository) -> Refdb\n" "\n" "Create a new reference database and automatically add\n" "the default backends, assuming the repository dir as the folder."); PyObject * Refdb_open(PyObject *self, Repository *repo) { if (!PyObject_IsInstance((PyObject *)repo, (PyObject *)&RepositoryType)) { PyErr_SetString(PyExc_TypeError, "Refdb.open expects an object of type " "pygit2.Repository"); return NULL; } git_refdb *refdb; int err = git_refdb_open(&refdb, repo->repo); if (err) { Error_set(err); return NULL; } return wrap_refdb(refdb); } PyMethodDef Refdb_methods[] = { METHOD(Refdb, compress, METH_NOARGS), METHOD(Refdb, set_backend, METH_O), {"new", (PyCFunction) Refdb_new, METH_O | METH_STATIC, Refdb_new__doc__}, {"open", (PyCFunction) Refdb_open, METH_O | METH_STATIC, Refdb_open__doc__}, {NULL} }; PyDoc_STRVAR(Refdb__doc__, "Reference database."); PyTypeObject RefdbType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Refdb", /* tp_name */ sizeof(Refdb), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Refdb_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Refdb__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Refdb_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * wrap_refdb(git_refdb *c_refdb) { Refdb *py_refdb = PyObject_New(Refdb, &RefdbType); if (py_refdb) py_refdb->refdb = c_refdb; return (PyObject *)py_refdb; } libgit2-pygit2-a011e26/src/refdb.h000066400000000000000000000026101473744024100166320ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_refdb_h #define INCLUDE_pygit2_refdb_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject *wrap_refdb(git_refdb *c_refdb); #endif libgit2-pygit2-a011e26/src/refdb_backend.c000066400000000000000000000707361473744024100203120ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "types.h" #include "oid.h" #include "reference.h" #include "signature.h" #include "utils.h" #include "wildmatch.h" #include #include extern PyTypeObject ReferenceType; extern PyTypeObject RepositoryType; extern PyTypeObject SignatureType; struct pygit2_refdb_backend { git_refdb_backend backend; PyObject *RefdbBackend; PyObject *exists, *lookup, *iterator, *write, *rename, *delete, *compress, *has_log, *ensure_log, *reflog_read, *reflog_write, *reflog_rename, *reflog_delete, *lock, *unlock; }; struct pygit2_refdb_iterator { struct git_reference_iterator base; PyObject *iterator; char *glob; }; static Reference * iterator_get_next(struct pygit2_refdb_iterator *iter) { Reference *ref; while ((ref = (Reference *)PyIter_Next(iter->iterator)) != NULL) { if (!iter->glob) { return ref; } const char *name = git_reference_name(ref->reference); if (wildmatch(iter->glob, name, 0) != WM_NOMATCH) { return ref; } } return NULL; } static int pygit2_refdb_iterator_next(git_reference **out, git_reference_iterator *_iter) { struct pygit2_refdb_iterator *iter = (struct pygit2_refdb_iterator *)_iter; Reference *ref = iterator_get_next(iter); if (ref == NULL) { *out = NULL; return GIT_ITEROVER; } if (!PyObject_IsInstance((PyObject *)ref, (PyObject *)&ReferenceType)) { PyErr_SetString(PyExc_TypeError, "RefdbBackend iterator must yield References"); return GIT_EUSER; } *out = ref->reference; return 0; } static int pygit2_refdb_iterator_next_name(const char **ref_name, git_reference_iterator *_iter) { struct pygit2_refdb_iterator *iter = (struct pygit2_refdb_iterator *)_iter; Reference *ref = iterator_get_next(iter); if (ref == NULL) { *ref_name = NULL; return GIT_ITEROVER; } if (!PyObject_IsInstance((PyObject *)ref, (PyObject *)&ReferenceType)) { PyErr_SetString(PyExc_TypeError, "RefdbBackend iterator must yield References"); return GIT_EUSER; } *ref_name = git_reference_name(ref->reference); return 0; } static void pygit2_refdb_iterator_free(git_reference_iterator *_iter) { struct pygit2_refdb_iterator *iter = (struct pygit2_refdb_iterator *)_iter; Py_DECREF(iter->iterator); free(iter->glob); } static int pygit2_refdb_backend_iterator(git_reference_iterator **iter, struct git_refdb_backend *_be, const char *glob) { struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; PyObject *iterator = PyObject_GetIter((PyObject *)be->RefdbBackend); assert(iterator); struct pygit2_refdb_iterator *pyiter = calloc(1, sizeof(struct pygit2_refdb_iterator)); *iter = (git_reference_iterator *)pyiter; pyiter->iterator = iterator; pyiter->base.next = pygit2_refdb_iterator_next; pyiter->base.next_name = pygit2_refdb_iterator_next_name; pyiter->base.free = pygit2_refdb_iterator_free; pyiter->glob = strdup(glob); return 0; } static int pygit2_refdb_backend_exists(int *exists, git_refdb_backend *_be, const char *ref_name) { int err; PyObject *args, *result; struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; if ((args = Py_BuildValue("(s)", ref_name)) == NULL) return GIT_EUSER; result = PyObject_CallObject(be->exists, args); Py_DECREF(args); if ((err = git_error_for_exc()) != 0) goto out; *exists = PyObject_IsTrue(result); out: Py_DECREF(result); return 0; } static int pygit2_refdb_backend_lookup(git_reference **out, git_refdb_backend *_be, const char *ref_name) { int err; PyObject *args; Reference *result; struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; if ((args = Py_BuildValue("(s)", ref_name)) == NULL) return GIT_EUSER; result = (Reference *)PyObject_CallObject(be->lookup, args); Py_DECREF(args); if ((err = git_error_for_exc()) != 0) goto out; if (!PyObject_IsInstance((PyObject *)result, (PyObject *)&ReferenceType)) { PyErr_SetString(PyExc_TypeError, "Expected object of type pygit2.Reference"); err = GIT_EUSER; goto out; } *out = result->reference; out: return err; } static int pygit2_refdb_backend_write(git_refdb_backend *_be, const git_reference *_ref, int force, const git_signature *_who, const char *message, const git_oid *_old, const char *old_target) { int err; PyObject *args = NULL, *ref = NULL, *who = NULL, *old = NULL; struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; // XXX: Drops const if ((ref = wrap_reference((git_reference *)_ref, NULL)) == NULL) goto euser; if ((who = build_signature(NULL, _who, "utf-8")) == NULL) goto euser; if ((old = git_oid_to_python(_old)) == NULL) goto euser; if ((args = Py_BuildValue("(NNNsNs)", ref, force ? Py_True : Py_False, who, message, old, old_target)) == NULL) goto euser; PyObject_CallObject(be->write, args); err = git_error_for_exc(); out: Py_DECREF(ref); Py_DECREF(who); Py_DECREF(old); Py_DECREF(args); return err; euser: err = GIT_EUSER; goto out; } static int pygit2_refdb_backend_rename(git_reference **out, git_refdb_backend *_be, const char *old_name, const char *new_name, int force, const git_signature *_who, const char *message) { int err; PyObject *args, *who; struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; if ((who = build_signature(NULL, _who, "utf-8")) != NULL) return GIT_EUSER; if ((args = Py_BuildValue("(ssNNs)", old_name, new_name, force ? Py_True : Py_False, who, message)) == NULL) { Py_DECREF(who); return GIT_EUSER; } Reference *ref = (Reference *)PyObject_CallObject(be->rename, args); Py_DECREF(who); Py_DECREF(args); if ((err = git_error_for_exc()) != 0) return err; if (!PyObject_IsInstance((PyObject *)ref, (PyObject *)&ReferenceType)) { PyErr_SetString(PyExc_TypeError, "Expected object of type pygit2.Reference"); return GIT_EUSER; } git_reference_dup(out, ref->reference); Py_DECREF(ref); return 0; } static int pygit2_refdb_backend_del(git_refdb_backend *_be, const char *ref_name, const git_oid *_old, const char *old_target) { PyObject *args, *old; struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; old = git_oid_to_python(_old); if ((args = Py_BuildValue("(sOs)", ref_name, old, old_target)) == NULL) { Py_DECREF(old); return GIT_EUSER; } PyObject_CallObject(be->rename, args); Py_DECREF(old); Py_DECREF(args); return git_error_for_exc(); } static int pygit2_refdb_backend_compress(git_refdb_backend *_be) { struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; PyObject_CallObject(be->rename, NULL); return git_error_for_exc(); } static int pygit2_refdb_backend_has_log(git_refdb_backend *_be, const char *refname) { int err; PyObject *args, *result; struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; if ((args = Py_BuildValue("(s)", refname)) == NULL) { return GIT_EUSER; } result = PyObject_CallObject(be->has_log, args); Py_DECREF(args); if ((err = git_error_for_exc()) != 0) { return err; } if (PyObject_IsTrue(result)) { Py_DECREF(result); return 1; } Py_DECREF(result); return 0; } static int pygit2_refdb_backend_ensure_log(git_refdb_backend *_be, const char *refname) { int err; PyObject *args, *result; struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; if ((args = Py_BuildValue("(s)", refname)) == NULL) { return GIT_EUSER; } result = PyObject_CallObject(be->ensure_log, args); Py_DECREF(args); if ((err = git_error_for_exc()) != 0) { return err; } if (PyObject_IsTrue(result)) { Py_DECREF(result); return 1; } Py_DECREF(result); return 0; } static int pygit2_refdb_backend_reflog_read(git_reflog **out, git_refdb_backend *backend, const char *name) { /* TODO: Implement first-class pygit2 reflog support * These stubs are here because libgit2 requires refdb_backend to implement * them. libgit2 doesn't actually use them as of 0.99; it assumes the refdb * backend will update the reflogs itself. */ return GIT_EUSER; } static int pygit2_refdb_backend_reflog_write(git_refdb_backend *backend, git_reflog *reflog) { /* TODO: Implement first-class pygit2 reflog support */ return GIT_EUSER; } static int pygit2_refdb_backend_reflog_rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) { /* TODO: Implement first-class pygit2 reflog support */ return GIT_EUSER; } static int pygit2_refdb_backend_reflog_delete(git_refdb_backend *backend, const char *name) { /* TODO: Implement first-class pygit2 reflog support */ return GIT_EUSER; } static void pygit2_refdb_backend_free(git_refdb_backend *_be) { struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)_be; Py_DECREF(be->RefdbBackend); } int RefdbBackend_init(RefdbBackend *self, PyObject *args, PyObject *kwds) { if (args && PyTuple_Size(args) > 0) { PyErr_SetString(PyExc_TypeError, "RefdbBackend takes no arguments"); return -1; } if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "RefdbBackend takes no keyword arguments"); return -1; } struct pygit2_refdb_backend *be = calloc(1, sizeof(struct pygit2_refdb_backend)); git_refdb_init_backend(&be->backend, GIT_REFDB_BACKEND_VERSION); be->RefdbBackend = (PyObject *)self; if (PyIter_Check((PyObject *)self)) { be->backend.iterator = pygit2_refdb_backend_iterator; } if (PyObject_HasAttrString((PyObject *)self, "exists")) { be->exists = PyObject_GetAttrString((PyObject *)self, "exists"); be->backend.exists = pygit2_refdb_backend_exists; } if (PyObject_HasAttrString((PyObject *)self, "lookup")) { be->lookup = PyObject_GetAttrString((PyObject *)self, "lookup"); be->backend.lookup = pygit2_refdb_backend_lookup; } if (PyObject_HasAttrString((PyObject *)self, "write")) { be->write = PyObject_GetAttrString((PyObject *)self, "write"); be->backend.write = pygit2_refdb_backend_write; } if (PyObject_HasAttrString((PyObject *)self, "rename")) { be->rename = PyObject_GetAttrString((PyObject *)self, "rename"); be->backend.rename = pygit2_refdb_backend_rename; } if (PyObject_HasAttrString((PyObject *)self, "delete")) { be->delete = PyObject_GetAttrString((PyObject *)self, "delete"); be->backend.del = pygit2_refdb_backend_del; } if (PyObject_HasAttrString((PyObject *)self, "compress")) { be->compress = PyObject_GetAttrString((PyObject *)self, "compress"); be->backend.compress = pygit2_refdb_backend_compress; } if (PyObject_HasAttrString((PyObject *)self, "has_log")) { be->has_log = PyObject_GetAttrString((PyObject *)self, "has_log"); be->backend.has_log = pygit2_refdb_backend_has_log; } if (PyObject_HasAttrString((PyObject *)self, "ensure_log")) { be->ensure_log = PyObject_GetAttrString((PyObject *)self, "ensure_log"); be->backend.ensure_log = pygit2_refdb_backend_ensure_log; } /* TODO: First-class reflog support */ be->backend.reflog_read = pygit2_refdb_backend_reflog_read; be->backend.reflog_write = pygit2_refdb_backend_reflog_write; be->backend.reflog_rename = pygit2_refdb_backend_reflog_rename; be->backend.reflog_delete = pygit2_refdb_backend_reflog_delete; /* TODO: transactions if (PyObject_HasAttrString((PyObject *)self, "lock")) { be->lock = PyObject_GetAttrString((PyObject *)self, "lock"); be->backend.lock = pygit2_refdb_backend_lock; } if (PyObject_HasAttrString((PyObject *)self, "unlock")) { be->unlock = PyObject_GetAttrString((PyObject *)self, "unlock"); be->backend.unlock = pygit2_refdb_backend_unlock; } */ Py_INCREF((PyObject *)self); be->backend.free = pygit2_refdb_backend_free; self->refdb_backend = (git_refdb_backend *)be; return 0; } void RefdbBackend_dealloc(RefdbBackend *self) { if (self->refdb_backend && self->refdb_backend->free == pygit2_refdb_backend_free) { struct pygit2_refdb_backend *be = (struct pygit2_refdb_backend *)self->refdb_backend; Py_CLEAR(be->exists); Py_CLEAR(be->lookup); Py_CLEAR(be->iterator); Py_CLEAR(be->write); Py_CLEAR(be->rename); Py_CLEAR(be->delete); Py_CLEAR(be->compress); Py_CLEAR(be->has_log); Py_CLEAR(be->ensure_log); Py_CLEAR(be->reflog_read); Py_CLEAR(be->reflog_write); Py_CLEAR(be->reflog_rename); Py_CLEAR(be->reflog_delete); Py_CLEAR(be->lock); Py_CLEAR(be->unlock); free(be); } Py_TYPE(self)->tp_free((PyObject *) self); } PyDoc_STRVAR(RefdbBackend_exists__doc__, "exists(refname: str) -> bool\n" "\n" "Returns True if a ref by this name exists, or False otherwise."); PyObject * RefdbBackend_exists(RefdbBackend *self, PyObject *py_str) { int err, exists; const char *ref_name; if (self->refdb_backend->exists == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } if (!PyUnicode_Check(py_str)) { PyErr_SetString(PyExc_TypeError, "RefdbBackend.exists takes a string argument"); return NULL; } ref_name = PyUnicode_AsUTF8(py_str); err = self->refdb_backend->exists(&exists, self->refdb_backend, ref_name); if (err != 0) return Error_set(err); if (exists) Py_RETURN_TRUE; else Py_RETURN_FALSE; } PyDoc_STRVAR(RefdbBackend_lookup__doc__, "lookup(refname: str) -> Reference\n" "\n" "Looks up a reference and returns it, or None if not found."); PyObject * RefdbBackend_lookup(RefdbBackend *self, PyObject *py_str) { int err; git_reference *ref; const char *ref_name; if (self->refdb_backend->lookup == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } if (!PyUnicode_Check(py_str)) { PyErr_SetString(PyExc_TypeError, "RefdbBackend.lookup takes a string argument"); return NULL; } ref_name = PyUnicode_AsUTF8(py_str); err = self->refdb_backend->lookup(&ref, self->refdb_backend, ref_name); if (err == GIT_ENOTFOUND) { Py_RETURN_NONE; } else if (err != 0) { return Error_set(err); } return wrap_reference(ref, NULL); } PyDoc_STRVAR(RefdbBackend_write__doc__, "write(ref: Reference, force: bool, who: Signature, message: str, old: Oid, old_target: str)\n" "\n" "Writes a new reference to the reference database."); // TODO: Better docs? libgit2 is scant on documentation for this, too. PyObject * RefdbBackend_write(RefdbBackend *self, PyObject *args) { int err; Reference *ref; int force; Signature *who; const git_signature *sig = NULL; char *message, *old_target; PyObject *py_old; git_oid _old, *old = NULL; if (self->refdb_backend->write == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } if (!PyArg_ParseTuple(args, "O!pOzOz", &ReferenceType, &ref, &force, &who, &message, &py_old, &old_target)) return NULL; if ((PyObject *)py_old != Py_None) { py_oid_to_git_oid(py_old, &_old); old = &_old; } if ((PyObject *)who != Py_None) { if (!PyObject_IsInstance((PyObject *)who, (PyObject *)&SignatureType)) { PyErr_SetString(PyExc_TypeError, "Signature must be type pygit2.Signature"); return NULL; } sig = who->signature; } err = self->refdb_backend->write(self->refdb_backend, ref->reference, force, sig, message, old, old_target); if (err != 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(RefdbBackend_rename__doc__, "rename(old_name: str, new_name: str, force: bool, who: Signature, message: str) -> Reference\n" "\n" "Renames a reference."); PyObject * RefdbBackend_rename(RefdbBackend *self, PyObject *args) { int err; int force; Signature *who; char *old_name, *new_name, *message; git_reference *out; if (self->refdb_backend->rename == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } if (!PyArg_ParseTuple(args, "sspO!s", &old_name, &new_name, &force, &SignatureType, &who, &message)) return NULL; err = self->refdb_backend->rename(&out, self->refdb_backend, old_name, new_name, force, who->signature, message); if (err != 0) return Error_set(err); return (PyObject *)wrap_reference(out, NULL); } PyDoc_STRVAR(RefdbBackend_delete__doc__, "delete(ref_name: str, old_id: Oid, old_target: str)\n" "\n" "Deletes a reference."); PyObject * RefdbBackend_delete(RefdbBackend *self, PyObject *args) { int err; PyObject *py_old_id; git_oid old_id; char *ref_name, *old_target; if (self->refdb_backend->del == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } if (!PyArg_ParseTuple(args, "sOz", &ref_name, &py_old_id, &old_target)) return NULL; if (py_old_id != Py_None) { py_oid_to_git_oid(py_old_id, &old_id); err = self->refdb_backend->del(self->refdb_backend, ref_name, &old_id, old_target); } else { err = self->refdb_backend->del(self->refdb_backend, ref_name, NULL, old_target); } if (err != 0) { return Error_set(err); } Py_RETURN_NONE; } PyDoc_STRVAR(RefdbBackend_compress__doc__, "compress()\n" "\n" "Suggests that the implementation compress or optimize its references.\n" "This behavior is implementation-specific."); PyObject * RefdbBackend_compress(RefdbBackend *self) { int err; if (self->refdb_backend->compress == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } err = self->refdb_backend->compress(self->refdb_backend); if (err != 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(RefdbBackend_has_log__doc__, "has_log(ref_name: str) -> bool\n" "\n" "Returns True if a ref log is available for this reference.\n" "It may be empty even if it exists."); PyObject * RefdbBackend_has_log(RefdbBackend *self, PyObject *_ref_name) { int err; const char *ref_name; if (self->refdb_backend->has_log == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } if (!PyUnicode_Check(_ref_name)) { PyErr_SetString(PyExc_TypeError, "RefdbBackend.has_log takes a string argument"); return NULL; } ref_name = PyUnicode_AsUTF8(_ref_name); err = self->refdb_backend->has_log(self->refdb_backend, ref_name); if (err < 0) { return Error_set(err); } if (err == 1) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } PyDoc_STRVAR(RefdbBackend_ensure_log__doc__, "ensure_log(ref_name: str) -> bool\n" "\n" "Ensure that a particular reference will have a reflog which will be\n" "appended to on writes."); PyObject * RefdbBackend_ensure_log(RefdbBackend *self, PyObject *_ref_name) { int err; const char *ref_name; if (self->refdb_backend->ensure_log == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } if (!PyUnicode_Check(_ref_name)) { PyErr_SetString(PyExc_TypeError, "RefdbBackend.ensure_log takes a string argument"); return NULL; } ref_name = PyUnicode_AsUTF8(_ref_name); err = self->refdb_backend->ensure_log(self->refdb_backend, ref_name); if (err < 0) { return Error_set(err); } if (err == 0) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } PyMethodDef RefdbBackend_methods[] = { METHOD(RefdbBackend, exists, METH_O), METHOD(RefdbBackend, lookup, METH_O), METHOD(RefdbBackend, write, METH_VARARGS), METHOD(RefdbBackend, rename, METH_VARARGS), METHOD(RefdbBackend, delete, METH_VARARGS), METHOD(RefdbBackend, compress, METH_NOARGS), METHOD(RefdbBackend, has_log, METH_O), METHOD(RefdbBackend, ensure_log, METH_O), {NULL} }; PyDoc_STRVAR(RefdbBackend__doc__, "Reference database backend."); PyTypeObject RefdbBackendType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.RefdbBackend", /* tp_name */ sizeof(RefdbBackend), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)RefdbBackend_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ RefdbBackend__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0 /* TODO: Wrap git_reference_iterator */, /* tp_iter */ 0, /* tp_iternext */ RefdbBackend_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)RefdbBackend_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * wrap_refdb_backend(git_refdb_backend *c_refdb_backend) { RefdbBackend *pygit2_refdb_backend = PyObject_New(RefdbBackend, &RefdbBackendType); if (pygit2_refdb_backend) pygit2_refdb_backend->refdb_backend = c_refdb_backend; return (PyObject *)pygit2_refdb_backend; } PyDoc_STRVAR(RefdbFsBackend__doc__, "RefdbFsBackend(repo: Repository)\n" "\n" "Reference database filesystem backend. The path to the repository\n" "is used as the basis of the reference database."); int RefdbFsBackend_init(RefdbFsBackend *self, PyObject *args, PyObject *kwds) { if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "RefdbFsBackend takes no keyword arguments"); return -1; } Repository *repo = NULL; if (!PyArg_ParseTuple(args, "O!", &RepositoryType, &repo)) return -1; int err = git_refdb_backend_fs(&self->super.refdb_backend, repo->repo); if (err) { Error_set(err); return -1; } return 0; } PyTypeObject RefdbFsBackendType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.RefdbFsBackend", /* tp_name */ sizeof(RefdbFsBackend), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ RefdbFsBackend__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ &RefdbBackendType, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)RefdbFsBackend_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/refdb_backend.h000066400000000000000000000026601473744024100203060ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_refdb_backend_h #define INCLUDE_pygit2_refdb_backend_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject *wrap_refdb_backend(git_refdb_backend *c_refdb_backend); #endif libgit2-pygit2-a011e26/src/reference.c000066400000000000000000000550461473744024100175140ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "object.h" #include "error.h" #include "types.h" #include "utils.h" #include "oid.h" #include "signature.h" #include "reference.h" #include extern PyObject *GitError; extern PyTypeObject RefLogEntryType; extern PyTypeObject RepositoryType; extern PyTypeObject SignatureType; extern PyObject *ReferenceTypeEnum; PyTypeObject ReferenceType; void RefLogIter_dealloc(RefLogIter *self) { git_reflog_free(self->reflog); PyObject_Del(self); } PyObject * RefLogIter_iternext(RefLogIter *self) { const git_reflog_entry *entry; const char * entry_message; RefLogEntry *py_entry; int err; if (self->i < self->size) { entry = git_reflog_entry_byindex(self->reflog, self->i); py_entry = PyObject_New(RefLogEntry, &RefLogEntryType); if (py_entry == NULL) return NULL; py_entry->oid_old = git_oid_to_python(git_reflog_entry_id_old(entry)); py_entry->oid_new = git_oid_to_python(git_reflog_entry_id_new(entry)); entry_message = git_reflog_entry_message(entry); py_entry->message = (entry_message != NULL) ? strdup(entry_message) : NULL; err = git_signature_dup(&py_entry->signature, git_reflog_entry_committer(entry)); if (err < 0) return Error_set(err); ++(self->i); return (PyObject*) py_entry; } PyErr_SetNone(PyExc_StopIteration); return NULL; } PyDoc_STRVAR(RefLogIterType__doc__, "Internal reflog iterator object."); PyTypeObject RefLogIterType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.RefLogIter", /* tp_name */ sizeof(RefLogIter), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)RefLogIter_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ RefLogIterType__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ (iternextfunc)RefLogIter_iternext, /* tp_iternext */ }; PyDoc_STRVAR(Reference__doc__, "Reference(name: str, target: str): create a symbolic reference\n" "\n" "Reference(name: str, oid: Oid, peel: Oid): create a direct reference\n" "\n" "'peel' is the first non-tag object's OID, or None.\n" "\n" "The purpose of this constructor is for use in custom refdb backends.\n" "References created with this function are unlikely to work as\n" "expected in other contexts.\n"); static int Reference_init_symbolic(Reference *self, PyObject *args, PyObject *kwds) { const char *name, *target; if (!PyArg_ParseTuple(args, "ss", &name, &target)) { return -1; } self->reference = git_reference__alloc_symbolic(name, target); return 0; } int Reference_init(Reference *self, PyObject *args, PyObject *kwds) { const char *name; git_oid oid, peel; PyObject *py_oid, *py_peel; if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "Reference takes no keyword arguments"); return -1; } Py_ssize_t nargs = PyTuple_Size(args); if (nargs == 2) { return Reference_init_symbolic(self, args, kwds); } else if (nargs != 3) { PyErr_SetString(PyExc_TypeError, "Invalid arguments to Reference constructor"); return -1; } if (!PyArg_ParseTuple(args, "sOO", &name, &py_oid, &py_peel)) { return -1; } py_oid_to_git_oid(py_oid, &oid); if (py_peel != Py_None) { py_oid_to_git_oid(py_peel, &peel); } self->reference = git_reference__alloc(name, &oid, &peel); return 0; } void Reference_dealloc(Reference *self) { Py_CLEAR(self->repo); git_reference_free(self->reference); PyObject_Del(self); } PyDoc_STRVAR(Reference_delete__doc__, "delete()\n" "\n" "Delete this reference. It will no longer be valid!"); PyObject * Reference_delete(Reference *self, PyObject *args) { int err; CHECK_REFERENCE(self); /* Delete the reference */ err = git_reference_delete(self->reference); if (err < 0) return Error_set(err); git_reference_free(self->reference); self->reference = NULL; /* Invalidate the pointer */ Py_RETURN_NONE; } PyDoc_STRVAR(Reference_rename__doc__, "rename(new_name: str)\n" "\n" "Rename the reference."); PyObject * Reference_rename(Reference *self, PyObject *py_name) { CHECK_REFERENCE(self); // Get the C name PyObject *tvalue; char *c_name = pgit_borrow_fsdefault(py_name, &tvalue); if (c_name == NULL) return NULL; // Rename git_reference *new_reference; int err = git_reference_rename(&new_reference, self->reference, c_name, 0, NULL); Py_DECREF(tvalue); if (err) return Error_set(err); // Upadate reference git_reference_free(self->reference); self->reference = new_reference; Py_RETURN_NONE; } PyDoc_STRVAR(Reference_resolve__doc__, "resolve() -> Reference\n" "\n" "Resolve a symbolic reference and return a direct reference."); PyObject * Reference_resolve(Reference *self, PyObject *args) { git_reference *c_reference; int err; CHECK_REFERENCE(self); /* Direct: return myself */ if (git_reference_type(self->reference) == GIT_REF_OID) { Py_INCREF(self); return (PyObject *)self; } /* Symbolic: resolve */ err = git_reference_resolve(&c_reference, self->reference); if (err < 0) return Error_set(err); return wrap_reference(c_reference, self->repo); } static PyObject * Reference_target_impl(Reference *self, const char ** c_name) { CHECK_REFERENCE(self); /* Case 1: Direct */ if (GIT_REF_OID == git_reference_type(self->reference)) return git_oid_to_python(git_reference_target(self->reference)); /* Case 2: Symbolic */ *c_name = git_reference_symbolic_target(self->reference); if (*c_name == NULL) PyErr_SetString(PyExc_ValueError, "no target available"); return NULL; } PyDoc_STRVAR(Reference_target__doc__, "The reference target: If direct the value will be an Oid object, if it\n" "is symbolic it will be an string with the full name of the target\n" "reference.\n"); PyObject * Reference_target__get__(Reference *self) { const char * c_name = NULL; PyObject * ret; ret = Reference_target_impl(self, &c_name); if (ret != NULL) return ret; if (c_name != NULL) return PyUnicode_DecodeFSDefault(c_name); return NULL; } PyDoc_STRVAR(Reference_raw_target__doc__, "The raw reference target: If direct the value will be an Oid object, if it\n" "is symbolic it will be bytes with the full name of the target\n" "reference.\n"); PyObject * Reference_raw_target__get__(Reference *self) { const char * c_name = NULL; PyObject * ret; ret = Reference_target_impl(self, &c_name); if (ret != NULL) return ret; if (c_name != NULL) return PyBytes_FromString(c_name); return NULL; } PyDoc_STRVAR(Reference_set_target__doc__, "set_target(target[, message: str])\n" "\n" "Set the target of this reference. Creates a new entry in the reflog.\n" "\n" "Parameters:\n" "\n" "target\n" " The new target for this reference\n" "\n" "message\n" " Message to use for the reflog.\n"); PyObject * Reference_set_target(Reference *self, PyObject *args, PyObject *kwds) { git_oid oid; int err; git_reference *new_ref; PyObject *py_target = NULL; const char *message = NULL; char *keywords[] = {"target", "message", NULL}; CHECK_REFERENCE(self); if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|s", keywords, &py_target, &message)) return NULL; /* Case 1: Direct */ if (GIT_REF_OID == git_reference_type(self->reference)) { err = py_oid_to_git_oid_expand(self->repo->repo, py_target, &oid); if (err < 0) goto error; err = git_reference_set_target(&new_ref, self->reference, &oid, message); if (err < 0) goto error; git_reference_free(self->reference); self->reference = new_ref; Py_RETURN_NONE; } /* Case 2: Symbolic */ PyObject *tvalue; char *c_name = pgit_borrow_fsdefault(py_target, &tvalue); if (c_name == NULL) return NULL; err = git_reference_symbolic_set_target(&new_ref, self->reference, c_name, message); Py_DECREF(tvalue); if (err < 0) goto error; git_reference_free(self->reference); self->reference = new_ref; Py_RETURN_NONE; error: Error_set(err); return NULL; } PyDoc_STRVAR(Reference_name__doc__, "The full name of the reference."); PyObject * Reference_name__get__(Reference *self) { CHECK_REFERENCE(self); return PyUnicode_DecodeFSDefault(git_reference_name(self->reference)); } PyDoc_STRVAR(Reference_raw_name__doc__, "The full name of the reference (Bytes)."); PyObject * Reference_raw_name__get__(Reference *self) { CHECK_REFERENCE(self); return PyBytes_FromString(git_reference_name(self->reference)); } PyDoc_STRVAR(Reference_shorthand__doc__, "The shorthand \"human-readable\" name of the reference."); PyObject * Reference_shorthand__get__(Reference *self) { CHECK_REFERENCE(self); return PyUnicode_DecodeFSDefault(git_reference_shorthand(self->reference)); } PyDoc_STRVAR(Reference_raw_shorthand__doc__, "The shorthand \"human-readable\" name of the reference (Bytes)."); PyObject * Reference_raw_shorthand__get__(Reference *self) { CHECK_REFERENCE(self); return PyBytes_FromString(git_reference_shorthand(self->reference)); } PyDoc_STRVAR(Reference_type__doc__, "An enums.ReferenceType constant (either OID or SYMBOLIC)."); PyObject * Reference_type__get__(Reference *self) { git_reference_t c_type; CHECK_REFERENCE(self); c_type = git_reference_type(self->reference); return pygit2_enum(ReferenceTypeEnum, c_type); } PyDoc_STRVAR(Reference_log__doc__, "log() -> RefLogIter\n" "\n" "Retrieves the current reference log."); PyObject * Reference_log(Reference *self) { int err; RefLogIter *iter; git_repository *repo; CHECK_REFERENCE(self); repo = git_reference_owner(self->reference); iter = PyObject_New(RefLogIter, &RefLogIterType); if (iter != NULL) { err = git_reflog_read(&iter->reflog, repo, git_reference_name(self->reference)); if (err < 0) return Error_set(err); iter->size = git_reflog_entrycount(iter->reflog); iter->i = 0; } return (PyObject*)iter; } PyDoc_STRVAR(Reference_peel__doc__, "peel(type=None) -> Object\n" "\n" "Retrieve an object of the given type by recursive peeling.\n" "\n" "If no type is provided, the first non-tag object will be returned."); PyObject * Reference_peel(Reference *self, PyObject *args) { int err; git_otype otype; git_object *obj; PyObject *py_type = Py_None; CHECK_REFERENCE(self); if (!PyArg_ParseTuple(args, "|O", &py_type)) return NULL; otype = py_object_to_otype(py_type); if (otype == GIT_OBJECT_INVALID) return NULL; err = git_reference_peel(&obj, self->reference, otype); if (err < 0) return Error_set(err); return wrap_object(obj, self->repo, NULL); } PyObject * Reference_richcompare(PyObject *o1, PyObject *o2, int op) { PyObject *res; Reference *obj1; Reference *obj2; const char *name1; const char *name2; if (!PyObject_TypeCheck(o2, &ReferenceType)) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } switch (op) { case Py_NE: obj1 = (Reference *) o1; obj2 = (Reference *) o2; CHECK_REFERENCE(obj1); CHECK_REFERENCE(obj2); name1 = git_reference_name(obj1->reference); name2 = git_reference_name(obj2->reference); if (strcmp(name1, name2) != 0) { res = Py_True; break; } res = Py_False; break; case Py_EQ: obj1 = (Reference *) o1; obj2 = (Reference *) o2; CHECK_REFERENCE(obj1); CHECK_REFERENCE(obj2); name1 = git_reference_name(obj1->reference); name2 = git_reference_name(obj2->reference); if (strcmp(name1, name2) != 0) { res = Py_False; break; } res = Py_True; break; case Py_LT: case Py_LE: case Py_GT: case Py_GE: Py_INCREF(Py_NotImplemented); return Py_NotImplemented; default: PyErr_Format(PyExc_RuntimeError, "Unexpected '%d' op", op); return NULL; } Py_INCREF(res); return res; } PyDoc_STRVAR(RefLogEntry_committer__doc__, "Committer."); PyObject * RefLogEntry_committer__get__(RefLogEntry *self) { return build_signature((Object*) self, self->signature, "utf-8"); } static int RefLogEntry_init(RefLogEntry *self, PyObject *args, PyObject *kwds) { self->oid_old = NULL; self->oid_new = NULL; self->message = NULL; self->signature = NULL; return 0; } static void RefLogEntry_dealloc(RefLogEntry *self) { Py_CLEAR(self->oid_old); Py_CLEAR(self->oid_new); free(self->message); git_signature_free(self->signature); PyObject_Del(self); } PyMemberDef RefLogEntry_members[] = { MEMBER(RefLogEntry, oid_new, T_OBJECT, "New oid."), MEMBER(RefLogEntry, oid_old, T_OBJECT, "Old oid."), MEMBER(RefLogEntry, message, T_STRING, "Message."), {NULL} }; PyGetSetDef RefLogEntry_getseters[] = { GETTER(RefLogEntry, committer), {NULL} }; PyDoc_STRVAR(RefLogEntry__doc__, "Reference log object."); PyTypeObject RefLogEntryType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.RefLogEntry", /* tp_name */ sizeof(RefLogEntry), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)RefLogEntry_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ RefLogEntry__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ RefLogEntry_members, /* tp_members */ RefLogEntry_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)RefLogEntry_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyMethodDef Reference_methods[] = { METHOD(Reference, delete, METH_NOARGS), METHOD(Reference, rename, METH_O), METHOD(Reference, resolve, METH_NOARGS), METHOD(Reference, log, METH_NOARGS), METHOD(Reference, set_target, METH_VARARGS | METH_KEYWORDS), METHOD(Reference, peel, METH_VARARGS), {NULL} }; PyGetSetDef Reference_getseters[] = { GETTER(Reference, name), GETTER(Reference, raw_name), GETTER(Reference, shorthand), GETTER(Reference, raw_shorthand), GETTER(Reference, target), GETTER(Reference, raw_target), GETTER(Reference, type), {NULL} }; PyTypeObject ReferenceType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Reference", /* tp_name */ sizeof(Reference), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Reference_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Reference__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ Reference_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Reference_methods, /* tp_methods */ 0, /* tp_members */ Reference_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Reference_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * wrap_reference(git_reference * c_reference, Repository *repo) { Reference *py_reference=NULL; py_reference = PyObject_New(Reference, &ReferenceType); if (py_reference) { py_reference->reference = c_reference; py_reference->repo = repo; if (repo) { Py_INCREF(repo); } } return (PyObject *)py_reference; } libgit2-pygit2-a011e26/src/reference.h000066400000000000000000000031261473744024100175110ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_reference_h #define INCLUDE_pygit2_reference_h #define PY_SSIZE_T_CLEAN #include #include PyObject* Reference_delete(Reference *self, PyObject *args); PyObject* Reference_rename(Reference *self, PyObject *py_name); PyObject* Reference_resolve(Reference *self, PyObject *args); PyObject* wrap_reference(git_reference *c_reference, Repository *repo); #endif libgit2-pygit2-a011e26/src/repository.c000066400000000000000000002176631473744024100200020ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "types.h" #include "reference.h" #include "revspec.h" #include "utils.h" #include "odb.h" #include "object.h" #include "oid.h" #include "note.h" #include "refdb.h" #include "repository.h" #include "diff.h" #include "branch.h" #include "signature.h" #include "worktree.h" #include #include extern PyObject *GitError; extern PyTypeObject IndexType; extern PyTypeObject WalkerType; extern PyTypeObject SignatureType; extern PyTypeObject ObjectType; extern PyTypeObject OidType; extern PyTypeObject CommitType; extern PyTypeObject TreeType; extern PyTypeObject TreeBuilderType; extern PyTypeObject ConfigType; extern PyTypeObject DiffType; extern PyTypeObject ReferenceType; extern PyTypeObject RevSpecType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; extern PyTypeObject StashType; extern PyTypeObject RefsIteratorType; extern PyObject *FileStatusEnum; extern PyObject *MergeAnalysisEnum; extern PyObject *MergePreferenceEnum; /* forward-declaration for Repsository._from_c() */ PyTypeObject RepositoryType; PyObject * wrap_repository(git_repository *c_repo) { Repository *py_repo = PyObject_GC_New(Repository, &RepositoryType); if (py_repo) { py_repo->repo = c_repo; py_repo->config = NULL; py_repo->index = NULL; py_repo->owned = 1; } return (PyObject *)py_repo; } int Repository_init(Repository *self, PyObject *args, PyObject *kwds) { PyObject *backend = NULL; if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "Repository takes no keyword arguments"); return -1; } if (!PyArg_ParseTuple(args, "|O", &backend)) { return -1; } if (backend == NULL) { /* Create repository without odb/refdb */ int err = git_repository_new(&self->repo); if (err != 0) { Error_set(err); return -1; } self->owned = 1; self->config = NULL; self->index = NULL; return 0; } self->repo = PyCapsule_GetPointer(backend, "backend"); if (self->repo == NULL) { PyErr_SetString(PyExc_TypeError, "Repository unable to unpack backend."); return -1; } self->owned = 1; self->config = NULL; self->index = NULL; return 0; } PyDoc_STRVAR(Repository__from_c__doc__, "Init a Repository from a pointer. For internal use only."); PyObject * Repository__from_c(Repository *py_repo, PyObject *args) { PyObject *py_pointer, *py_free; char *buffer; Py_ssize_t len; int err; py_repo->repo = NULL; py_repo->config = NULL; py_repo->index = NULL; if (!PyArg_ParseTuple(args, "OO!", &py_pointer, &PyBool_Type, &py_free)) return NULL; err = PyBytes_AsStringAndSize(py_pointer, &buffer, &len); if (err < 0) return NULL; if (len != sizeof(git_repository *)) { PyErr_SetString(PyExc_TypeError, "invalid pointer length"); return NULL; } py_repo->repo = *((git_repository **) buffer); py_repo->owned = py_free == Py_True; Py_RETURN_NONE; } PyDoc_STRVAR(Repository__disown__doc__, "Mark the object as not-owned by us. For internal use only."); PyObject * Repository__disown(Repository *py_repo) { py_repo->owned = 0; Py_RETURN_NONE; } void Repository_dealloc(Repository *self) { PyObject_GC_UnTrack(self); Py_CLEAR(self->index); Py_CLEAR(self->config); if (self->owned) git_repository_free(self->repo); Py_TYPE(self)->tp_free(self); } int Repository_traverse(Repository *self, visitproc visit, void *arg) { Py_VISIT(self->index); return 0; } int Repository_clear(Repository *self) { Py_CLEAR(self->index); return 0; } PyDoc_STRVAR(Repository_head__doc__, "Current head reference of the repository."); PyObject * Repository_head__get__(Repository *self) { git_reference *head; int err; err = git_repository_head(&head, self->repo); if (err < 0) { if (err == GIT_ENOTFOUND) PyErr_SetString(GitError, "head reference does not exist"); else Error_set(err); return NULL; } return wrap_reference(head, self); } PyDoc_STRVAR(Repository_head_is_detached__doc__, "A repository's HEAD is detached when it points directly to a commit\n" "instead of a branch."); PyObject * Repository_head_is_detached__get__(Repository *self) { if (git_repository_head_detached(self->repo) > 0) Py_RETURN_TRUE; Py_RETURN_FALSE; } PyDoc_STRVAR(Repository_head_is_unborn__doc__, "An unborn branch is one named from HEAD but which doesn't exist in the\n" "refs namespace, because it doesn't have any commit to point to."); PyObject * Repository_head_is_unborn__get__(Repository *self) { if (git_repository_head_unborn(self->repo) > 0) Py_RETURN_TRUE; Py_RETURN_FALSE; } PyDoc_STRVAR(Repository_is_empty__doc__, "Check if a repository is empty."); PyObject * Repository_is_empty__get__(Repository *self) { if (git_repository_is_empty(self->repo) > 0) Py_RETURN_TRUE; Py_RETURN_FALSE; } PyDoc_STRVAR(Repository_is_bare__doc__, "Check if a repository is a bare repository."); PyObject * Repository_is_bare__get__(Repository *self) { if (git_repository_is_bare(self->repo) > 0) Py_RETURN_TRUE; Py_RETURN_FALSE; } PyDoc_STRVAR(Repository_is_shallow__doc__, "Check if a repository is a shallow repository."); PyObject * Repository_is_shallow__get__(Repository *self) { if (git_repository_is_shallow(self->repo) > 0) Py_RETURN_TRUE; Py_RETURN_FALSE; } PyDoc_STRVAR(Repository_git_object_lookup_prefix__doc__, "git_object_lookup_prefix(oid: Oid) -> Object\n" "\n" "Returns the Git object with the given oid."); PyObject * Repository_git_object_lookup_prefix(Repository *self, PyObject *key) { int err; size_t len; git_oid oid; git_object *obj; len = py_oid_to_git_oid(key, &oid); if (len == 0) return NULL; err = git_object_lookup_prefix(&obj, self->repo, &oid, len, GIT_OBJECT_ANY); if (err == 0) return wrap_object(obj, self, NULL); if (err == GIT_ENOTFOUND) Py_RETURN_NONE; return Error_set_oid(err, &oid, len); } PyDoc_STRVAR(Repository_lookup_branch__doc__, "lookup_branch(branch_name: str, branch_type: BranchType = BranchType.LOCAL) -> Branch\n" "\n" "Returns the Git reference for the given branch name (local or remote).\n" "If branch_type is BranchType.REMOTE, you must include the remote name\n" "in the branch name (eg 'origin/master')."); PyObject * Repository_lookup_branch(Repository *self, PyObject *args) { git_reference *c_reference; const char *c_name; Py_ssize_t c_name_len; git_branch_t branch_type = GIT_BRANCH_LOCAL; int err; if (!PyArg_ParseTuple(args, "s#|I", &c_name, &c_name_len, &branch_type)) return NULL; err = git_branch_lookup(&c_reference, self->repo, c_name, branch_type); if (err == 0) return wrap_branch(c_reference, self); if (err == GIT_ENOTFOUND) Py_RETURN_NONE; return Error_set(err); } PyDoc_STRVAR(Repository_path_is_ignored__doc__, "path_is_ignored(path: str) -> bool\n" "\n" "Check if a path is ignored in the repository."); PyObject * Repository_path_is_ignored(Repository *self, PyObject *args) { int ignored; char *path; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; git_ignore_path_is_ignored(&ignored, self->repo, path); if (ignored == 1) Py_RETURN_TRUE; Py_RETURN_FALSE; } PyDoc_STRVAR(Repository_revparse_single__doc__, "revparse_single(revision: str) -> Object\n" "\n" "Find an object, as specified by a revision string. See\n" "`man gitrevisions`, or the documentation for `git rev-parse` for\n" "information on the syntax accepted."); PyObject * Repository_revparse_single(Repository *self, PyObject *py_spec) { /* Get the C revision spec */ const char *c_spec = pgit_borrow(py_spec); if (c_spec == NULL) return NULL; /* Lookup */ git_object *c_obj; int err = git_revparse_single(&c_obj, self->repo, c_spec); if (err) return Error_set_str(err, c_spec); return wrap_object(c_obj, self, NULL); } PyDoc_STRVAR(Repository_revparse_ext__doc__, "revparse_ext(revision: str) -> tuple[Object, Reference]\n" "\n" "Find a single object and intermediate reference, as specified by a revision\n" "string. See `man gitrevisions`, or the documentation for `git rev-parse`\n" "for information on the syntax accepted.\n" "\n" "In some cases (@{<-n>} or @{upstream}), the expression may\n" "point to an intermediate reference, which is returned in the second element\n" "of the result tuple."); PyObject * Repository_revparse_ext(Repository *self, PyObject *py_spec) { /* Get the C revision spec */ const char *c_spec = pgit_borrow(py_spec); if (c_spec == NULL) return NULL; /* Lookup */ git_object *c_obj = NULL; git_reference *c_ref = NULL; int err = git_revparse_ext(&c_obj, &c_ref, self->repo, c_spec); if (err) return Error_set_str(err, c_spec); PyObject *py_obj = wrap_object(c_obj, self, NULL); PyObject *py_ref = NULL; if (c_ref != NULL) { py_ref = wrap_reference(c_ref, self); } else { py_ref = Py_None; Py_INCREF(Py_None); } return Py_BuildValue("NN", py_obj, py_ref); } PyDoc_STRVAR(Repository_revparse__doc__, "revparse(revspec: str) -> RevSpec\n" "\n" "Parse a revision string for from, to, and intent. See `man gitrevisions`,\n" "or the documentation for `git rev-parse` for information on the syntax\n" "accepted."); PyObject * Repository_revparse(Repository *self, PyObject *py_spec) { /* Get the C revision spec */ const char *c_spec = pgit_borrow(py_spec); if (c_spec == NULL) return NULL; /* Lookup */ git_revspec revspec; int err = git_revparse(&revspec, self->repo, c_spec); if (err) { return Error_set_str(err, c_spec); } return wrap_revspec(&revspec, self); } PyDoc_STRVAR(Repository_path__doc__, "The normalized path to the git repository."); PyObject * Repository_path__get__(Repository *self, void *closure) { const char *c_path; if (self->repo == NULL) Py_RETURN_NONE; c_path = git_repository_path(self->repo); if (c_path == NULL) Py_RETURN_NONE; return PyUnicode_DecodeFSDefault(c_path); } PyDoc_STRVAR(Repository_workdir__doc__, "The normalized path to the working directory of the repository. If the\n" "repository is bare, None will be returned."); PyObject * Repository_workdir__get__(Repository *self, void *closure) { const char *c_path; c_path = git_repository_workdir(self->repo); if (c_path == NULL) Py_RETURN_NONE; return PyUnicode_DecodeFSDefault(c_path); } int Repository_workdir__set__(Repository *self, PyObject *py_workdir) { const char *workdir = pgit_borrow(py_workdir); if (workdir == NULL) return -1; int err = git_repository_set_workdir(self->repo, workdir, 0 /* update_gitlink */); if (err) { Error_set_str(err, workdir); return -1; } return 0; } PyDoc_STRVAR(Repository_descendant_of__doc__, "descendant_of(oid1: Oid, oid2: Oid) -> bool\n" "\n" "Determine if the first commit is a descendant of the second commit.\n" "Note that a commit is not considered a descendant of itself."); PyObject * Repository_descendant_of(Repository *self, PyObject *args) { PyObject *value1; PyObject *value2; git_oid oid1; git_oid oid2; int err; if (!PyArg_ParseTuple(args, "OO", &value1, &value2)) return NULL; err = py_oid_to_git_oid_expand(self->repo, value1, &oid1); if (err < 0) return NULL; err = py_oid_to_git_oid_expand(self->repo, value2, &oid2); if (err < 0) return NULL; // err < 0 => error, see source code of `git_graph_descendant_of` err = git_graph_descendant_of(self->repo, &oid1, &oid2); if (err < 0) return Error_set(err); return PyBool_FromLong(err); } PyDoc_STRVAR(Repository_merge_base__doc__, "merge_base(oid1: Oid, oid2: Oid) -> Oid\n" "\n" "Find as good common ancestors as possible for a merge.\n" "Returns None if there is no merge base between the commits"); PyObject * Repository_merge_base(Repository *self, PyObject *args) { PyObject *value1; PyObject *value2; git_oid oid; git_oid oid1; git_oid oid2; int err; if (!PyArg_ParseTuple(args, "OO", &value1, &value2)) return NULL; err = py_oid_to_git_oid_expand(self->repo, value1, &oid1); if (err < 0) return NULL; err = py_oid_to_git_oid_expand(self->repo, value2, &oid2); if (err < 0) return NULL; err = git_merge_base(&oid, self->repo, &oid1, &oid2); if (err == GIT_ENOTFOUND) Py_RETURN_NONE; if (err < 0) return Error_set(err); return git_oid_to_python(&oid); } typedef int (*git_merge_base_xxx_t)(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]); static PyObject * merge_base_xxx(Repository *self, PyObject *args, git_merge_base_xxx_t git_merge_base_xxx) { PyObject *py_result = NULL; PyObject *py_commit_oid; PyObject *py_commit_oids; git_oid oid; int commit_oid_count; git_oid *commit_oids = NULL; int i = 0; int err; if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &py_commit_oids)) return NULL; commit_oid_count = (int)PyList_Size(py_commit_oids); commit_oids = malloc(commit_oid_count * sizeof(git_oid)); if (commit_oids == NULL) { PyErr_SetNone(PyExc_MemoryError); goto out; } for (; i < commit_oid_count; i++) { py_commit_oid = PyList_GET_ITEM(py_commit_oids, i); err = py_oid_to_git_oid_expand(self->repo, py_commit_oid, &commit_oids[i]); if (err < 0) goto out; } err = (*git_merge_base_xxx)(&oid, self->repo, commit_oid_count, (const git_oid*)commit_oids); if (err == GIT_ENOTFOUND) { Py_INCREF(Py_None); py_result = Py_None; goto out; } if (err < 0) { py_result = Error_set(err); goto out; } py_result = git_oid_to_python(&oid); out: free(commit_oids); return py_result; } PyDoc_STRVAR(Repository_merge_base_many__doc__, "merge_base_many(oids: list[Oid]) -> Oid\n" "\n" "Find as good common ancestors as possible for an n-way merge.\n" "Returns None if there is no merge base between the commits"); PyObject * Repository_merge_base_many(Repository *self, PyObject *args) { return merge_base_xxx(self, args, &git_merge_base_many); } PyDoc_STRVAR(Repository_merge_base_octopus__doc__, "merge_base_octopus(oids: list[Oid]) -> Oid\n" "\n" "Find as good common ancestors as possible for an n-way octopus merge.\n" "Returns None if there is no merge base between the commits"); PyObject * Repository_merge_base_octopus(Repository *self, PyObject *args) { return merge_base_xxx(self, args, &git_merge_base_octopus); } PyDoc_STRVAR(Repository_merge_analysis__doc__, "merge_analysis(their_head: Oid, our_ref: str = \"HEAD\") -> tuple[MergeAnalysis, MergePreference]\n" "\n" "Analyzes the given branch and determines the opportunities for\n" "merging it into a reference (defaults to HEAD).\n" "\n" "Parameters:\n" "\n" "our_ref\n" " The reference name (String) to perform the analysis from\n" "\n" "their_head\n" " Head (commit Oid) to merge into\n" "\n" "The first returned value is a mixture of the MergeAnalysis.NONE, NORMAL,\n" "UP_TO_DATE, FASTFORWARD and UNBORN flags.\n" "The second value is the user's preference from 'merge.ff'"); PyObject * Repository_merge_analysis(Repository *self, PyObject *args) { char *our_ref_name = "HEAD"; PyObject *py_their_head; PyObject *py_result = NULL; git_oid head_id; git_reference *our_ref; git_annotated_commit *commit; git_merge_analysis_t analysis; git_merge_preference_t preference; int err = 0; if (!PyArg_ParseTuple(args, "O|z", &py_their_head, &our_ref_name)) return NULL; err = git_reference_lookup(&our_ref, self->repo, our_ref_name); if (err < 0) { PyObject *py_err = Error_set_str(err, our_ref_name); return py_err; } err = py_oid_to_git_oid_expand(self->repo, py_their_head, &head_id); if (err < 0) goto out; err = git_annotated_commit_lookup(&commit, self->repo, &head_id); if (err < 0) { py_result = Error_set(err); goto out; } err = git_merge_analysis_for_ref(&analysis, &preference, self->repo, our_ref, (const git_annotated_commit **) &commit, 1); git_annotated_commit_free(commit); if (err < 0) { py_result = Error_set(err); goto out; } // Convert analysis to MergeAnalysis enum PyObject *analysis_enum = pygit2_enum(MergeAnalysisEnum, analysis); if (!analysis_enum) { py_result = NULL; goto out; } // Convert preference to MergePreference enum PyObject *preference_enum = pygit2_enum(MergePreferenceEnum, preference); if (!preference_enum) { py_result = NULL; Py_DECREF(analysis_enum); goto out; } py_result = Py_BuildValue("(OO)", analysis_enum, preference_enum); out: git_reference_free(our_ref); return py_result; } PyDoc_STRVAR(Repository_cherrypick__doc__, "cherrypick(id: Oid)\n" "\n" "Cherry-pick the given oid, producing changes in the index and working directory.\n" "\n" "Merges the given commit into HEAD as a cherrypick, writing the results into the\n" "working directory. Any changes are staged for commit and any conflicts\n" "are written to the index. Callers should inspect the repository's\n" "index after this completes, resolve any conflicts and prepare a\n" "commit."); PyObject * Repository_cherrypick(Repository *self, PyObject *py_oid) { git_commit *commit; git_oid oid; int err; size_t len; git_cherrypick_options cherrypick_opts = GIT_CHERRYPICK_OPTIONS_INIT; len = py_oid_to_git_oid(py_oid, &oid); if (len == 0) return NULL; err = git_commit_lookup(&commit, self->repo, &oid); if (err < 0) return Error_set(err); cherrypick_opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; err = git_cherrypick(self->repo, commit, (const git_cherrypick_options *)&cherrypick_opts); git_commit_free(commit); if (err < 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Repository_walk__doc__, "walk(oid: Oid | None, sort_mode: enums.SortMode = enums.SortMode.NONE) -> Walker\n" "\n" "Start traversing the history from the given commit.\n" "The following SortMode values can be used to control the walk:\n" "\n" "* NONE. Sort the output with the same default method from\n" " `git`: reverse chronological order. This is the default sorting for\n" " new walkers.\n" "* TOPOLOGICAL. Sort the repository contents in topological order\n" " (no parents before all of its children are shown); this sorting mode\n" " can be combined with time sorting to produce `git`'s `--date-order``.\n" "* TIME. Sort the repository contents by commit time; this sorting\n" " mode can be combined with topological sorting.\n" "* REVERSE. Iterate through the repository contents in reverse\n" " order; this sorting mode can be combined with any of the above.\n" "\n" "Example:\n" "\n" " >>> from pygit2 import Repository\n" " >>> from pygit2.enums import SortMode\n" " >>> repo = Repository('.git')\n" " >>> for commit in repo.walk(repo.head.target, SortMode.TOPOLOGICAL):\n" " ... print(commit.message)\n" " >>> for commit in repo.walk(repo.head.target, SortMode.TOPOLOGICAL | SortMode.REVERSE):\n" " ... print(commit.message)\n" " >>>\n"); PyObject * Repository_walk(Repository *self, PyObject *args) { PyObject *value; unsigned int sort = GIT_SORT_NONE; int err; git_oid oid; git_revwalk *walk; Walker *py_walker; if (!PyArg_ParseTuple(args, "O|I", &value, &sort)) return NULL; err = git_revwalk_new(&walk, self->repo); if (err < 0) return Error_set(err); /* Sort */ git_revwalk_sorting(walk, sort); /* Push */ if (value != Py_None) { err = py_oid_to_git_oid_expand(self->repo, value, &oid); if (err < 0) goto error; err = git_revwalk_push(walk, &oid); if (err < 0) { Error_set(err); goto error; } } py_walker = PyObject_New(Walker, &WalkerType); if (py_walker) { Py_INCREF(self); py_walker->repo = self; py_walker->walk = walk; return (PyObject*)py_walker; } error: git_revwalk_free(walk); return NULL; } PyDoc_STRVAR(Repository_create_blob__doc__, "create_blob(data: bytes) -> Oid\n" "\n" "Create a new blob from a bytes string. The blob is added to the Git\n" "object database. Returns the oid of the blob."); PyObject * Repository_create_blob(Repository *self, PyObject *args) { git_oid oid; const char *raw; Py_ssize_t size; int err; if (!PyArg_ParseTuple(args, "s#", &raw, &size)) return NULL; err = git_blob_create_frombuffer(&oid, self->repo, (const void*)raw, size); if (err < 0) return Error_set(err); return git_oid_to_python(&oid); } PyDoc_STRVAR(Repository_create_blob_fromworkdir__doc__, "create_blob_fromworkdir(path: str) -> Oid\n" "\n" "Create a new blob from a file within the working directory. The given\n" "path must be relative to the working directory, if it is not an error\n" "is raised."); PyObject * Repository_create_blob_fromworkdir(Repository *self, PyObject *value) { PyObject *tvalue; char *path = pgit_borrow_fsdefault(value, &tvalue); if (path == NULL) return NULL; git_oid oid; int err = git_blob_create_fromworkdir(&oid, self->repo, path); Py_DECREF(tvalue); if (err < 0) return Error_set(err); return git_oid_to_python(&oid); } PyDoc_STRVAR(Repository_create_blob_fromdisk__doc__, "create_blob_fromdisk(path: str) -> Oid\n" "\n" "Create a new blob from a file anywhere (no working directory check)."); PyObject * Repository_create_blob_fromdisk(Repository *self, PyObject *value) { PyObject *tvalue; char *path = pgit_borrow_fsdefault(value, &tvalue); if (path == NULL) return NULL; git_oid oid; int err = git_blob_create_fromdisk(&oid, self->repo, path); Py_DECREF(tvalue); if (err < 0) return Error_set(err); return git_oid_to_python(&oid); } #define BUFSIZE 4096 PyDoc_STRVAR(Repository_create_blob_fromiobase__doc__, "create_blob_fromiobase(io.IOBase) -> Oid\n" "\n" "Create a new blob from an IOBase object."); PyObject * Repository_create_blob_fromiobase(Repository *self, PyObject *py_file) { git_writestream *stream; git_oid oid; PyObject *py_is_readable; int is_readable; int err; py_is_readable = PyObject_CallMethod(py_file, "readable", NULL); if (!py_is_readable) { if (PyErr_ExceptionMatches(PyExc_AttributeError)) PyErr_SetObject(PyExc_TypeError, py_file); return NULL; } is_readable = PyObject_IsTrue(py_is_readable); Py_DECREF(py_is_readable); if (!is_readable) { Py_DECREF(py_file); PyErr_SetString(PyExc_TypeError, "expected readable IO type"); return NULL; } err = git_blob_create_fromstream(&stream, self->repo, NULL); if (err < 0) return Error_set(err); for (;;) { PyObject *py_bytes; char *bytes; Py_ssize_t size; py_bytes = PyObject_CallMethod(py_file, "read", "i", 4096); if (!py_bytes) return NULL; if (py_bytes == Py_None) { Py_DECREF(py_bytes); goto cleanup; } if (PyBytes_AsStringAndSize(py_bytes, &bytes, &size)) { Py_DECREF(py_bytes); return NULL; } if (size == 0) { Py_DECREF(py_bytes); break; } err = stream->write(stream, bytes, size); Py_DECREF(py_bytes); if (err < 0) goto cleanup; } cleanup: if (err < 0) { stream->free(stream); return Error_set(err); } err = git_blob_create_fromstream_commit(&oid, stream); if (err < 0) return Error_set(err); return git_oid_to_python(&oid); } PyDoc_STRVAR(Repository_create_commit__doc__, "create_commit(reference_name: str, author: Signature, committer: Signature, message: bytes | str, tree: Oid, parents: list[Oid][, encoding: str]) -> Oid\n" "\n" "Create a new commit object, return its oid."); PyObject * Repository_create_commit(Repository *self, PyObject *args) { Signature *py_author, *py_committer; PyObject *py_oid, *py_message, *py_parents, *py_parent; PyObject *py_result = NULL; char *update_ref = NULL; char *encoding = NULL; git_oid oid; git_tree *tree = NULL; int parent_count; git_commit **parents = NULL; int i = 0; if (!PyArg_ParseTuple(args, "zO!O!OOO!|s", &update_ref, &SignatureType, &py_author, &SignatureType, &py_committer, &py_message, &py_oid, &PyList_Type, &py_parents, &encoding)) return NULL; size_t len = py_oid_to_git_oid(py_oid, &oid); if (len == 0) return NULL; PyObject *tmessage; const char *message = pgit_borrow_encoding(py_message, encoding, NULL, &tmessage); if (message == NULL) return NULL; int err = git_tree_lookup_prefix(&tree, self->repo, &oid, len); if (err < 0) { Error_set(err); goto out; } parent_count = (int)PyList_Size(py_parents); parents = malloc(parent_count * sizeof(git_commit*)); if (parents == NULL) { PyErr_SetNone(PyExc_MemoryError); goto out; } for (; i < parent_count; i++) { py_parent = PyList_GET_ITEM(py_parents, i); len = py_oid_to_git_oid(py_parent, &oid); if (len == 0) goto out; err = git_commit_lookup_prefix(&parents[i], self->repo, &oid, len); if (err < 0) { Error_set(err); goto out; } } err = git_commit_create(&oid, self->repo, update_ref, py_author->signature, py_committer->signature, encoding, message, tree, parent_count, (const git_commit **)parents); if (err < 0) { Error_set(err); goto out; } py_result = git_oid_to_python(&oid); out: Py_DECREF(tmessage); git_tree_free(tree); while (i > 0) { i--; git_commit_free(parents[i]); } free(parents); return py_result; } PyDoc_STRVAR(Repository_create_commit_string__doc__, "create_commit_string(author: Signature, committer: Signature, message: bytes | str, tree: Oid, parents: list[Oid][, encoding: str]) -> str\n" "\n" "Create a new commit but return it as a string."); PyObject * Repository_create_commit_string(Repository *self, PyObject *args) { Signature *py_author, *py_committer; PyObject *py_oid, *py_message, *py_parents, *py_parent; PyObject *str = NULL; char *encoding = NULL; git_oid oid; git_tree *tree = NULL; int parent_count; git_commit **parents = NULL; git_buf buf = { 0 }; int i = 0; if (!PyArg_ParseTuple(args, "O!O!OOO!|s", &SignatureType, &py_author, &SignatureType, &py_committer, &py_message, &py_oid, &PyList_Type, &py_parents, &encoding)) return NULL; size_t len = py_oid_to_git_oid(py_oid, &oid); if (len == 0) return NULL; PyObject *tmessage; const char *message = pgit_borrow_encoding(py_message, encoding, NULL, &tmessage); if (message == NULL) return NULL; int err = git_tree_lookup_prefix(&tree, self->repo, &oid, len); if (err < 0) { Error_set(err); goto out; } parent_count = (int)PyList_Size(py_parents); parents = malloc(parent_count * sizeof(git_commit*)); if (parents == NULL) { PyErr_SetNone(PyExc_MemoryError); goto out; } for (; i < parent_count; i++) { py_parent = PyList_GET_ITEM(py_parents, i); len = py_oid_to_git_oid(py_parent, &oid); if (len == 0) goto out; err = git_commit_lookup_prefix(&parents[i], self->repo, &oid, len); if (err < 0) { Error_set(err); goto out; } } err = git_commit_create_buffer(&buf, self->repo, py_author->signature, py_committer->signature, encoding, message, tree, parent_count, (const git_commit **)parents); if (err < 0) { Error_set(err); goto out; } str = to_unicode_n(buf.ptr, buf.size, NULL, NULL); git_buf_dispose(&buf); out: Py_DECREF(tmessage); git_tree_free(tree); while (i > 0) { i--; git_commit_free(parents[i]); } free(parents); return str; } PyDoc_STRVAR(Repository_create_commit_with_signature__doc__, "create_commit_with_signature(content: str, signature: str[, field_name: str]) -> Oid\n" "\n" "Create a new signed commit object, return its oid."); PyObject * Repository_create_commit_with_signature(Repository *self, PyObject *args) { git_oid oid; char *content, *signature; char *signature_field = NULL; if (!PyArg_ParseTuple(args, "ss|s", &content, &signature, &signature_field)) return NULL; int err = git_commit_create_with_signature(&oid, self->repo, content, signature, signature_field); if (err < 0) { Error_set(err); return NULL; } return git_oid_to_python(&oid); } PyDoc_STRVAR(Repository_create_tag__doc__, "create_tag(name: str, oid: Oid, type: enums.ObjectType, tagger: Signature[, message: str]) -> Oid\n" "\n" "Create a new tag object, return its oid."); PyObject * Repository_create_tag(Repository *self, PyObject *args) { PyObject *py_oid; Signature *py_tagger; char *tag_name, *message; git_oid oid; git_object *target = NULL; int err, target_type; size_t len; if (!PyArg_ParseTuple(args, "sOiO!s", &tag_name, &py_oid, &target_type, &SignatureType, &py_tagger, &message)) return NULL; len = py_oid_to_git_oid(py_oid, &oid); if (len == 0) return NULL; err = git_object_lookup_prefix(&target, self->repo, &oid, len, target_type); err = err < 0 ? err : git_tag_create(&oid, self->repo, tag_name, target, py_tagger->signature, message, 0); git_object_free(target); if (err < 0) return Error_set_oid(err, &oid, len); return git_oid_to_python(&oid); } PyDoc_STRVAR(Repository_create_branch__doc__, "create_branch(name: str, commit: Commit, force: bool = False) -> Branch\n" "\n" "Create a new branch \"name\" which points to a commit.\n" "\n" "Returns: Branch\n" "\n" "Parameters:\n" "\n" "force\n" " If True branches will be overridden, otherwise (the default) an\n" " exception is raised.\n" "\n" "Examples::\n" "\n" " repo.create_branch('foo', repo.head.peel(), force=False)"); PyObject * Repository_create_branch(Repository *self, PyObject *args) { Commit *py_commit; git_reference *c_reference; char *c_name; int err, force = 0; if (!PyArg_ParseTuple(args, "sO!|i", &c_name, &CommitType, &py_commit, &force)) return NULL; err = git_branch_create(&c_reference, self->repo, c_name, py_commit->commit, force); if (err < 0) return Error_set(err); return wrap_branch(c_reference, self); } static PyObject * to_path_f(const char * x) { return PyUnicode_DecodeFSDefault(x); } PyDoc_STRVAR(Repository_raw_listall_references__doc__, "raw_listall_references() -> list[bytes]\n" "\n" "Return a list with all the references in the repository."); static PyObject * Repository_raw_listall_references(Repository *self, PyObject *args) { git_strarray c_result; PyObject *py_result, *py_string; unsigned index; int err; /* Get the C result */ err = git_reference_list(&c_result, self->repo); if (err < 0) return Error_set(err); /* Create a new PyTuple */ py_result = PyList_New(c_result.count); if (py_result == NULL) goto out; /* Fill it */ for (index=0; index < c_result.count; index++) { py_string = PyBytes_FromString(c_result.strings[index]); if (py_string == NULL) { Py_CLEAR(py_result); goto out; } PyList_SET_ITEM(py_result, index, py_string); } out: git_strarray_dispose(&c_result); return py_result; } PyObject * wrap_references_iterator(git_reference_iterator *iter) { RefsIterator *py_refs_iter = PyObject_New(RefsIterator , &ReferenceType); if (py_refs_iter) py_refs_iter->iterator = iter; return (PyObject *)py_refs_iter; } void References_iterator_dealloc(RefsIterator *iter) { git_reference_iterator_free(iter->iterator); Py_TYPE(iter)->tp_free((PyObject *)iter); } PyDoc_STRVAR(Repository_references_iterator_init__doc__, "references_iterator_init() -> git_reference_iterator\n" "\n" "Creates and returns an iterator for references."); PyObject * Repository_references_iterator_init(Repository *self, PyObject *args) { int err; git_reference_iterator *iter; RefsIterator *refs_iter; refs_iter = PyObject_New(RefsIterator, &RefsIteratorType); if (refs_iter == NULL) { return NULL; } if ((err = git_reference_iterator_new(&iter, self->repo)) < 0) return Error_set(err); refs_iter->iterator = iter; return (PyObject*)refs_iter; } PyDoc_STRVAR(Repository_references_iterator_next__doc__, "references_iterator_next(iter: Iterator[Reference], references_return_type: ReferenceFilter = ReferenceFilter.ALL) -> Reference\n" "\n" "Returns next reference object for repository. Optionally, can filter \n" "based on value of references_return_type.\n" "Acceptable values of references_return_type:\n" "ReferenceFilter.ALL -> returns all refs, this is the default\n" "ReferenceFilter.BRANCHES -> returns all branches\n" "ReferenceFilter.TAGS -> returns all tags\n" "all other values -> will return None"); PyObject * Repository_references_iterator_next(Repository *self, PyObject *args) { git_reference *ref; git_reference_iterator *git_iter; PyObject *iter; int references_return_type = GIT_REFERENCES_ALL; if (!PyArg_ParseTuple(args, "O|i", &iter, &references_return_type)) return NULL; git_iter = ((RefsIterator *) iter)->iterator; int err; while (0 == (err = git_reference_next(&ref, git_iter))) { switch(references_return_type) { case GIT_REFERENCES_ALL: return wrap_reference(ref, self); case GIT_REFERENCES_BRANCHES: if (git_reference_is_branch(ref)) { return wrap_reference(ref, self); } break; case GIT_REFERENCES_TAGS: if (git_reference_is_tag(ref)) { return wrap_reference(ref, self); } break; } } if (err == GIT_ITEROVER) { Py_RETURN_NONE; } return Error_set(err); } static PyObject * Repository_listall_branches_impl(Repository *self, PyObject *args, PyObject *(*item_trans)(const char *)) { git_branch_t list_flags = GIT_BRANCH_LOCAL; git_branch_iterator *iter; git_reference *ref = NULL; int err; git_branch_t type; PyObject *list; /* 1- Get list_flags */ if (!PyArg_ParseTuple(args, "|I", &list_flags)) return NULL; list = PyList_New(0); if (list == NULL) return NULL; if ((err = git_branch_iterator_new(&iter, self->repo, list_flags)) < 0) return Error_set(err); while ((err = git_branch_next(&ref, &type, iter)) == 0) { PyObject *py_branch_name = item_trans(git_reference_shorthand(ref)); git_reference_free(ref); if (py_branch_name == NULL) goto error; err = PyList_Append(list, py_branch_name); Py_DECREF(py_branch_name); if (err < 0) goto error; } git_branch_iterator_free(iter); if (err == GIT_ITEROVER) err = 0; if (err < 0) { Py_CLEAR(list); return Error_set(err); } return list; error: git_branch_iterator_free(iter); Py_CLEAR(list); return NULL; } PyDoc_STRVAR(Repository_listall_branches__doc__, "listall_branches(flag: BranchType = BranchType.LOCAL) -> list[str]\n" "\n" "Return a list with all the branches in the repository.\n" "\n" "The *flag* may be:\n" "\n" "- BranchType.LOCAL - return all local branches (set by default)\n" "- BranchType.REMOTE - return all remote-tracking branches\n" "- BranchType.ALL - return local branches and remote-tracking branches"); PyObject * Repository_listall_branches(Repository *self, PyObject *args) { return Repository_listall_branches_impl(self, args, to_path_f); } PyDoc_STRVAR(Repository_raw_listall_branches__doc__, "raw_listall_branches(flag: BranchType = BranchType.LOCAL) -> list[bytes]\n" "\n" "Return a list with all the branches in the repository.\n" "\n" "The *flag* may be:\n" "\n" "- BranchType.LOCAL - return all local branches (set by default)\n" "- BranchType.REMOTE - return all remote-tracking branches\n" "- BranchType.ALL - return local branches and remote-tracking branches"); PyObject * Repository_raw_listall_branches(Repository *self, PyObject *args) { return Repository_listall_branches_impl(self, args, PyBytes_FromString); } PyDoc_STRVAR(Repository_listall_submodules__doc__, "listall_submodules() -> list[str]\n" "\n" "Return a list with all submodule paths in the repository.\n"); static int foreach_path_cb(git_submodule *submodule, const char *name, void *payload) { PyObject *list = (PyObject *)payload; PyObject *path = to_unicode(git_submodule_path(submodule), NULL, NULL); int err = PyList_Append(list, path); Py_DECREF(path); return err; } PyObject * Repository_listall_submodules(Repository *self, PyObject *args) { PyObject *list = PyList_New(0); if (list == NULL) return NULL; int err = git_submodule_foreach(self->repo, foreach_path_cb, list); if (err) { Py_DECREF(list); if (PyErr_Occurred()) return NULL; return Error_set(err); } return list; } PyDoc_STRVAR(Repository_lookup_reference__doc__, "lookup_reference(name: str) -> Reference\n" "\n" "Lookup a reference by its name in a repository."); PyObject * Repository_lookup_reference(Repository *self, PyObject *py_name) { /* 1- Get the C name */ PyObject *tvalue; char *c_name = pgit_borrow_fsdefault(py_name, &tvalue); if (c_name == NULL) return NULL; /* 2- Lookup */ git_reference *c_reference; int err = git_reference_lookup(&c_reference, self->repo, c_name); if (err) { PyObject *err_obj = Error_set_str(err, c_name); Py_DECREF(tvalue); return err_obj; } Py_DECREF(tvalue); /* 3- Make an instance of Reference and return it */ return wrap_reference(c_reference, self); } PyDoc_STRVAR(Repository_lookup_reference_dwim__doc__, "lookup_reference_dwim(name: str) -> Reference\n" "\n" "Lookup a reference by doing-what-i-mean'ing its short name."); PyObject * Repository_lookup_reference_dwim(Repository *self, PyObject *py_name) { /* 1- Get the C name */ PyObject *tvalue; char *c_name = pgit_borrow_fsdefault(py_name, &tvalue); if (c_name == NULL) return NULL; /* 2- Lookup */ git_reference *c_reference; int err = git_reference_dwim(&c_reference, self->repo, c_name); if (err) { PyObject *err_obj = Error_set_str(err, c_name); Py_DECREF(tvalue); return err_obj; } Py_DECREF(tvalue); /* 3- Make an instance of Reference and return it */ return wrap_reference(c_reference, self); } PyDoc_STRVAR(Repository_create_reference_direct__doc__, "create_reference_direct(name: str, target: Oid, force: bool, message=None) -> Reference\n" "\n" "Create a new reference \"name\" which points to an object.\n" "\n" "Returns: Reference\n" "\n" "Parameters:\n" "\n" "force\n" " If True references will be overridden, otherwise (the default) an\n" " exception is raised.\n" "\n" "message\n" " Optional message to use for the reflog.\n" "\n" "Examples::\n" "\n" " repo.create_reference_direct('refs/heads/foo', repo.head.target, False)"); PyObject * Repository_create_reference_direct(Repository *self, PyObject *args, PyObject *kw) { PyObject *py_obj; git_reference *c_reference; char *c_name; git_oid oid; int err, force; const char *message = NULL; char *keywords[] = {"name", "target", "force", "message", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "sOi|z", keywords, &c_name, &py_obj, &force, &message)) return NULL; err = py_oid_to_git_oid_expand(self->repo, py_obj, &oid); if (err < 0) return NULL; err = git_reference_create(&c_reference, self->repo, c_name, &oid, force, message); if (err < 0) return Error_set(err); return wrap_reference(c_reference, self); } PyDoc_STRVAR(Repository_create_reference_symbolic__doc__, "create_reference_symbolic(name: str, target: str, force: bool, message: str = None) -> Reference\n" "\n" "Create a new reference \"name\" which points to another reference.\n" "\n" "Returns: Reference\n" "\n" "Parameters:\n" "\n" "force\n" " If True references will be overridden, otherwise (the default) an\n" " exception is raised.\n" "\n" "message\n" " Optional message to use for the reflog.\n" "\n" "Examples::\n" "\n" " repo.create_reference_symbolic('refs/tags/foo', 'refs/heads/master', False)"); PyObject * Repository_create_reference_symbolic(Repository *self, PyObject *args, PyObject *kw) { git_reference *c_reference; char *c_name, *c_target; int err, force; const char *message = NULL; char *keywords[] = {"name", "target", "force", "message", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "ssi|z", keywords, &c_name, &c_target, &force, &message)) return NULL; err = git_reference_symbolic_create(&c_reference, self->repo, c_name, c_target, force, message); if (err < 0) return Error_set(err); return wrap_reference(c_reference, self); } PyDoc_STRVAR(Repository_compress_references__doc__, "compress_references()\n" "\n" "Suggest that the repository compress or optimize its references.\n" "This mechanism is implementation-specific. For on-disk reference\n" "databases, for example, this may pack all loose references."); PyObject * Repository_compress_references(Repository *self) { git_refdb *refdb; int err; err = git_repository_refdb(&refdb, self->repo); if (err < 0) return Error_set(err); err = git_refdb_compress(refdb); git_refdb_free(refdb); if (err < 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Repository_status__doc__, "status(untracked_files: str = \"all\", ignored: bool = False) -> dict[str, enums.FileStatus]\n" "\n" "Reads the status of the repository and returns a dictionary with file\n" "paths as keys and FileStatus flags as values.\n" "\n" "Parameters:\n" "\n" "untracked_files\n" " How to handle untracked files, defaults to \"all\":\n" "\n" " - \"no\": do not return untracked files\n" " - \"normal\": include untracked files/directories but do not recurse subdirectories\n" " - \"all\": include all files in untracked directories\n" "\n" " Using `untracked_files=\"no\"` or \"normal\"can be faster than \"all\" when the worktree\n" " contains many untracked files/directories.\n" "\n" "ignored\n" " Whether to show ignored files with untracked files. Ignored when untracked_files == \"no\"\n" " Defaults to False.\n"); PyObject * Repository_status(Repository *self, PyObject *args, PyObject *kw) { int err; size_t len, i; git_status_list *list; char *untracked_files = "all"; static char *kwlist[] = {"untracked_files", "ignored", NULL}; PyObject* ignored = Py_False; if (!PyArg_ParseTupleAndKeywords(args, kw, "|sO", kwlist, &untracked_files, &ignored)) return NULL; git_status_options opts = GIT_STATUS_OPTIONS_INIT; opts.flags = GIT_STATUS_OPT_DEFAULTS; if (!strcmp(untracked_files, "no")) { opts.flags &= ~(GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS); } else if (!strcmp(untracked_files, "normal")){ opts.flags &= ~GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; } else if (strcmp(untracked_files, "all") ){ return PyErr_Format( PyExc_ValueError, "untracked_files must be one of \"all\", \"normal\" or \"one\""); }; if (!PyBool_Check(ignored)) { return PyErr_Format(PyExc_TypeError, "ignored must be True or False"); } if (!PyObject_IsTrue(ignored)) { opts.flags &= ~GIT_STATUS_OPT_INCLUDE_IGNORED; } err = git_status_list_new(&list, self->repo, &opts); if (err < 0) return Error_set(err); PyObject *dict = PyDict_New(); if (dict == NULL) goto error; len = git_status_list_entrycount(list); for (i = 0; i < len; i++) { const git_status_entry *entry; const char *path; PyObject *status; entry = git_status_byindex(list, i); if (entry == NULL) goto error; /* We need to choose one of the strings */ if (entry->head_to_index) path = entry->head_to_index->old_file.path; else path = entry->index_to_workdir->old_file.path; /* Get corresponding entry in enums.FileStatus for status int */ status = pygit2_enum(FileStatusEnum, entry->status); if (status == NULL) goto error; err = PyDict_SetItemString(dict, path, status); Py_CLEAR(status); if (err < 0) goto error; } git_status_list_free(list); return dict; error: git_status_list_free(list); Py_CLEAR(dict); return NULL; } PyDoc_STRVAR(Repository_status_file__doc__, "status_file(path: str) -> enums.FileStatus\n" "\n" "Returns the status of the given file path."); PyObject * Repository_status_file(Repository *self, PyObject *value) { PyObject *tvalue; char *path = pgit_borrow_fsdefault(value, &tvalue); if (!path) return NULL; unsigned int status; int err = git_status_file(&status, self->repo, path); if (err) { PyObject *err_obj = Error_set_str(err, path); Py_DECREF(tvalue); return err_obj; } Py_DECREF(tvalue); return pygit2_enum(FileStatusEnum, (int) status); } PyDoc_STRVAR(Repository_TreeBuilder__doc__, "TreeBuilder([tree]) -> TreeBuilder\n" "\n" "Create a TreeBuilder object for this repository."); PyObject * Repository_TreeBuilder(Repository *self, PyObject *args) { TreeBuilder *builder; git_treebuilder *bld; PyObject *py_src = NULL; git_oid oid; git_tree *tree = NULL; git_tree *must_free = NULL; int err; if (!PyArg_ParseTuple(args, "|O", &py_src)) return NULL; if (py_src) { if (PyObject_TypeCheck(py_src, &TreeType)) { Tree *py_tree = (Tree *)py_src; if (py_tree->repo->repo != self->repo) { /* return Error_set(GIT_EINVALIDARGS); */ return Error_set(GIT_ERROR); } if (Object__load((Object*)py_tree) == NULL) { return NULL; } // Lazy load tree = py_tree->tree; } else { err = py_oid_to_git_oid_expand(self->repo, py_src, &oid); if (err < 0) return NULL; err = git_tree_lookup(&tree, self->repo, &oid); if (err < 0) return Error_set(err); must_free = tree; } } err = git_treebuilder_new(&bld, self->repo, tree); if (must_free != NULL) git_tree_free(must_free); if (err < 0) return Error_set(err); builder = PyObject_New(TreeBuilder, &TreeBuilderType); if (builder) { builder->repo = self; builder->bld = bld; Py_INCREF(self); } return (PyObject*)builder; } PyDoc_STRVAR(Repository_default_signature__doc__, "Return the signature according to the repository's configuration"); PyObject * Repository_default_signature__get__(Repository *self) { git_signature *sig; int err; if ((err = git_signature_default(&sig, self->repo)) < 0) return Error_set(err); return build_signature(NULL, sig, "utf-8"); } PyDoc_STRVAR(Repository_odb__doc__, "Return the object database for this repository"); PyObject * Repository_odb__get__(Repository *self) { git_odb *odb; int err; err = git_repository_odb(&odb, self->repo); if (err < 0) return Error_set(err); return wrap_odb(odb); } PyDoc_STRVAR(Repository_refdb__doc__, "Return the reference database for this repository"); PyObject * Repository_refdb__get__(Repository *self) { git_refdb *refdb; int err; err = git_repository_refdb(&refdb, self->repo); if (err < 0) return Error_set(err); return wrap_refdb(refdb); } PyDoc_STRVAR(Repository__pointer__doc__, "Get the repo's pointer. For internal use only."); PyObject * Repository__pointer__get__(Repository *self) { /* Bytes means a raw buffer */ return PyBytes_FromStringAndSize((char *) &self->repo, sizeof(git_repository *)); } PyDoc_STRVAR(Repository_notes__doc__, ""); PyObject * Repository_notes(Repository *self, PyObject *args) { char *ref = "refs/notes/commits"; if (!PyArg_ParseTuple(args, "|s", &ref)) return NULL; NoteIter *iter = PyObject_New(NoteIter, &NoteIterType); if (iter == NULL) return NULL; Py_INCREF(self); iter->repo = self; iter->ref = ref; iter->iter = NULL; int err = git_note_iterator_new(&iter->iter, self->repo, iter->ref); if (err != GIT_OK) { Py_DECREF(iter); return Error_set(err); } return (PyObject*)iter; } PyDoc_STRVAR(Repository_create_note__doc__, "create_note(message: str, author: Signature, committer: Signature, annotated_id: str, ref: str = \"refs/notes/commits\", force: bool = False) -> Oid\n" "\n" "Create a new note for an object, return its SHA-ID." "If no ref is given 'refs/notes/commits' will be used."); PyObject * Repository_create_note(Repository *self, PyObject* args) { git_oid note_id, annotated_id; char *annotated = NULL, *message = NULL, *ref = "refs/notes/commits"; int err = GIT_ERROR; unsigned int force = 0; Signature *py_author, *py_committer; if (!PyArg_ParseTuple(args, "sO!O!s|si", &message, &SignatureType, &py_author, &SignatureType, &py_committer, &annotated, &ref, &force)) return NULL; err = git_oid_fromstr(&annotated_id, annotated); if (err < 0) return Error_set(err); err = git_note_create(¬e_id, self->repo, ref, py_author->signature, py_committer->signature, &annotated_id, message, force); if (err < 0) return Error_set(err); return git_oid_to_python(¬e_id); } PyDoc_STRVAR(Repository_lookup_note__doc__, "lookup_note(annotated_id: str, ref: str = \"refs/notes/commits\") -> Note\n" "\n" "Lookup a note for an annotated object in a repository."); PyObject * Repository_lookup_note(Repository *self, PyObject* args) { git_oid annotated_id; char* annotated = NULL, *ref = "refs/notes/commits"; int err; if (!PyArg_ParseTuple(args, "s|s", &annotated, &ref)) return NULL; err = git_oid_fromstr(&annotated_id, annotated); if (err < 0) return Error_set(err); return (PyObject*) wrap_note(self, NULL, &annotated_id, ref); } PyDoc_STRVAR(Repository_reset__doc__, "reset(oid: Oid, reset_mode: enums.ResetMode)\n" "\n" "Resets the current head.\n" "\n" "Parameters:\n" "\n" "oid\n" " The oid of the commit to reset to.\n" "\n" "reset_mode\n" " * SOFT: resets head to point to oid, but does not modify\n" " working copy, and leaves the changes in the index.\n" " * MIXED: resets head to point to oid, but does not modify\n" " working copy. It empties the index too.\n" " * HARD: resets head to point to oid, and resets too the\n" " working copy and the content of the index.\n"); PyObject * Repository_reset(Repository *self, PyObject* args) { PyObject *py_oid; git_oid oid; git_object *target = NULL; int err, reset_type; size_t len; if (!PyArg_ParseTuple(args, "Oi", &py_oid, &reset_type )) return NULL; len = py_oid_to_git_oid(py_oid, &oid); if (len == 0) return NULL; err = git_object_lookup_prefix(&target, self->repo, &oid, len, GIT_OBJECT_ANY); err = err < 0 ? err : git_reset(self->repo, target, reset_type, NULL); git_object_free(target); if (err < 0) return Error_set_oid(err, &oid, len); Py_RETURN_NONE; } PyDoc_STRVAR(Repository_free__doc__, "free()\n" "\n" "Releases handles to the Git database without deallocating the repository.\n"); PyObject * Repository_free(Repository *self) { if (self->owned) git_repository__cleanup(self->repo); Py_RETURN_NONE; } PyDoc_STRVAR(Repository_expand_id__doc__, "expand_id(hex: str) -> Oid\n" "\n" "Expand a string into a full Oid according to the objects in this repsitory.\n"); PyObject * Repository_expand_id(Repository *self, PyObject *py_hex) { git_oid oid; int err; err = py_oid_to_git_oid_expand(self->repo, py_hex, &oid); if (err < 0) return NULL; return git_oid_to_python(&oid); } PyDoc_STRVAR(Repository_add_worktree__doc__, "add_worktree(name: str, path: str | bytes[, ref: Reference]) -> Worktree\n" "\n" "Create a new worktree for this repository. If ref is specified, no new \ branch will be created and the provided ref will be checked out instead."); PyObject * Repository_add_worktree(Repository *self, PyObject *args) { char *c_name; PyBytesObject *py_path = NULL; char *c_path = NULL; Reference *py_reference = NULL; git_worktree *wt; git_worktree_add_options add_opts = GIT_WORKTREE_ADD_OPTIONS_INIT; int err; if (!PyArg_ParseTuple(args, "sO&|O!", &c_name, PyUnicode_FSConverter, &py_path, &ReferenceType, &py_reference)) return NULL; if (py_path != NULL) c_path = PyBytes_AS_STRING(py_path); if(py_reference != NULL) add_opts.ref = py_reference->reference; err = git_worktree_add(&wt, self->repo, c_name, c_path, &add_opts); Py_XDECREF(py_path); if (err < 0) return Error_set(err); return wrap_worktree(self, wt); } PyDoc_STRVAR(Repository_lookup_worktree__doc__, "lookup_worktree(name: str) -> Worktree\n" "\n" "Lookup a worktree from its name."); PyObject * Repository_lookup_worktree(Repository *self, PyObject *args) { char *c_name; git_worktree *wt; int err; if (!PyArg_ParseTuple(args, "s", &c_name)) return NULL; err = git_worktree_lookup(&wt, self->repo, c_name); if (err < 0) return Error_set(err); return wrap_worktree(self, wt); } PyDoc_STRVAR(Repository_list_worktrees__doc__, "list_worktrees() -> list[str]\n" "\n" "Return a list with all the worktrees of this repository."); PyObject * Repository_list_worktrees(Repository *self, PyObject *args) { git_strarray c_result; PyObject *py_result, *py_string; unsigned index; int err; /* Get the C result */ err = git_worktree_list(&c_result, self->repo); if (err < 0) return Error_set(err); /* Create a new PyTuple */ py_result = PyList_New(c_result.count); if (py_result == NULL) goto out; /* Fill it */ for (index=0; index < c_result.count; index++) { py_string = PyUnicode_DecodeFSDefault(c_result.strings[index]); if (py_string == NULL) { Py_CLEAR(py_result); goto out; } PyList_SET_ITEM(py_result, index, py_string); } out: git_strarray_dispose(&c_result); return py_result; } PyDoc_STRVAR(Repository_apply__doc__, "apply(diff: Diff, location: ApplyLocation = ApplyLocation.WORKDIR)\n" "\n" "Applies the given Diff object to HEAD, writing the results into the\n" "working directory, the index, or both.\n" "\n" "Parameters:\n" "\n" "diff\n" " The Diff to apply.\n" "\n" "location\n" " The location to apply: ApplyLocation.WORKDIR (default),\n" " ApplyLocation.INDEX, or ApplyLocation.BOTH.\n" ); PyObject * Repository_apply(Repository *self, PyObject *args, PyObject *kwds) { Diff *py_diff; int location = GIT_APPLY_LOCATION_WORKDIR; git_apply_options options = GIT_APPLY_OPTIONS_INIT; char* keywords[] = {"diff", "location", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|i", keywords, &DiffType, &py_diff, &location)) return NULL; int err = git_apply(self->repo, py_diff->diff, location, &options); if (err != 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Repository_applies__doc__, "applies(diff: Diff, location: int = GIT_APPLY_LOCATION_INDEX, raise_error: bool = False) -> bool\n" "\n" "Tests if the given patch will apply to HEAD, without writing it.\n" "\n" "Parameters:\n" "\n" "diff\n" " The Diff to apply.\n" "\n" "location\n" " The location to apply: GIT_APPLY_LOCATION_WORKDIR,\n" " GIT_APPLY_LOCATION_INDEX (default), or GIT_APPLY_LOCATION_BOTH.\n" "\n" "raise_error\n" " If the patch doesn't apply, raise an exception containing more details\n" " about the failure instead of returning False.\n" ); PyObject * Repository_applies(Repository *self, PyObject *args, PyObject *kwds) { Diff *py_diff; int location = GIT_APPLY_LOCATION_INDEX; int raise_error = 0; git_apply_options options = GIT_APPLY_OPTIONS_INIT; options.flags |= GIT_APPLY_CHECK; char* keywords[] = {"diff", "location", "raise_error", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|ii", keywords, &DiffType, &py_diff, &location, &raise_error)) return NULL; int err = git_apply(self->repo, ((Diff*)py_diff)->diff, location, &options); if (err != 0) { if (raise_error) return Error_set(err); else Py_RETURN_FALSE; } Py_RETURN_TRUE; } PyDoc_STRVAR(Repository_set_odb__doc__, "set_odb(odb: Odb)\n" "\n" "Sets the object database for this repository.\n" "This is a low-level function, most users won't need it.\n"); PyObject * Repository_set_odb(Repository *self, Odb *odb) { int err; err = git_repository_set_odb(self->repo, odb->odb); if (err < 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Repository_set_refdb__doc__, "set_refdb(refdb: Refdb)\n" "\n" "Sets the reference database for this repository.\n" "This is a low-level function, most users won't need it.\n"); PyObject * Repository_set_refdb(Repository *self, Refdb *refdb) { int err; err = git_repository_set_refdb(self->repo, refdb->refdb); if (err < 0) return Error_set(err); Py_RETURN_NONE; } static int foreach_stash_cb(size_t index, const char *message, const git_oid *stash_id, void *payload) { int err; Stash *py_stash; py_stash = PyObject_New(Stash, &StashType); if (py_stash == NULL) return GIT_EUSER; assert(message != NULL); assert(stash_id != NULL); py_stash->commit_id = git_oid_to_python(stash_id); if (py_stash->commit_id == NULL) return GIT_EUSER; py_stash->message = strdup(message); if (py_stash->message == NULL) { PyErr_NoMemory(); return GIT_EUSER; } PyObject* list = (PyObject*) payload; err = PyList_Append(list, (PyObject*) py_stash); Py_DECREF(py_stash); if (err < 0) return GIT_EUSER; return 0; } PyDoc_STRVAR(Repository_listall_stashes__doc__, "listall_stashes() -> list[Stash]\n" "\n" "Return a list with all stashed commits in the repository.\n"); PyObject * Repository_listall_stashes(Repository *self, PyObject *args) { int err; PyObject *list = PyList_New(0); if (list == NULL) return NULL; err = git_stash_foreach(self->repo, foreach_stash_cb, (void*)list); if (err == 0) { return list; } else { Py_CLEAR(list); if (PyErr_Occurred()) return NULL; return Error_set(err); } } static int foreach_mergehead_cb(const git_oid *oid, void *payload) { PyObject* py_oid = git_oid_to_python(oid); if (py_oid == NULL) return GIT_EUSER; PyObject* list = (PyObject*) payload; int err = PyList_Append(list, (PyObject*) py_oid); Py_DECREF(py_oid); if (err < 0) return GIT_EUSER; return 0; } PyDoc_STRVAR(Repository_listall_mergeheads__doc__, "listall_mergeheads() -> list[Oid]\n" "\n" "If a merge is in progress, return a list of all commit oids in the MERGE_HEAD file.\n" "Return an empty list if there is no MERGE_HEAD file (no merge in progress)."); PyObject * Repository_listall_mergeheads(Repository *self, PyObject *args) { int err; PyObject *list = PyList_New(0); if (list == NULL) return NULL; err = git_repository_mergehead_foreach(self->repo, foreach_mergehead_cb, (void*)list); if (err == 0) { return list; } else if (err == GIT_ENOTFOUND) { /* MERGE_HEAD not found - return empty list */ return list; } else { Py_CLEAR(list); if (PyErr_Occurred()) return NULL; return Error_set(err); } } PyMethodDef Repository_methods[] = { METHOD(Repository, create_blob, METH_VARARGS), METHOD(Repository, create_blob_fromworkdir, METH_O), METHOD(Repository, create_blob_fromdisk, METH_O), METHOD(Repository, create_blob_fromiobase, METH_O), METHOD(Repository, create_commit, METH_VARARGS), METHOD(Repository, create_commit_string, METH_VARARGS), METHOD(Repository, create_commit_with_signature, METH_VARARGS), METHOD(Repository, create_tag, METH_VARARGS), METHOD(Repository, TreeBuilder, METH_VARARGS), METHOD(Repository, walk, METH_VARARGS), METHOD(Repository, descendant_of, METH_VARARGS), METHOD(Repository, merge_base, METH_VARARGS), METHOD(Repository, merge_base_many, METH_VARARGS), METHOD(Repository, merge_base_octopus, METH_VARARGS), METHOD(Repository, merge_analysis, METH_VARARGS), METHOD(Repository, cherrypick, METH_O), METHOD(Repository, apply, METH_VARARGS | METH_KEYWORDS), METHOD(Repository, applies, METH_VARARGS | METH_KEYWORDS), METHOD(Repository, create_reference_direct, METH_VARARGS | METH_KEYWORDS), METHOD(Repository, create_reference_symbolic, METH_VARARGS | METH_KEYWORDS), METHOD(Repository, compress_references, METH_NOARGS), METHOD(Repository, raw_listall_references, METH_NOARGS), METHOD(Repository, references_iterator_init, METH_NOARGS), METHOD(Repository, references_iterator_next, METH_VARARGS), METHOD(Repository, listall_submodules, METH_NOARGS), METHOD(Repository, lookup_reference, METH_O), METHOD(Repository, lookup_reference_dwim, METH_O), METHOD(Repository, revparse_single, METH_O), METHOD(Repository, revparse_ext, METH_O), METHOD(Repository, revparse, METH_O), METHOD(Repository, status, METH_VARARGS | METH_KEYWORDS), METHOD(Repository, status_file, METH_O), METHOD(Repository, notes, METH_VARARGS), METHOD(Repository, create_note, METH_VARARGS), METHOD(Repository, lookup_note, METH_VARARGS), METHOD(Repository, git_object_lookup_prefix, METH_O), METHOD(Repository, lookup_branch, METH_VARARGS), METHOD(Repository, path_is_ignored, METH_VARARGS), METHOD(Repository, listall_branches, METH_VARARGS), METHOD(Repository, raw_listall_branches, METH_VARARGS), METHOD(Repository, create_branch, METH_VARARGS), METHOD(Repository, reset, METH_VARARGS), METHOD(Repository, free, METH_NOARGS), METHOD(Repository, expand_id, METH_O), METHOD(Repository, add_worktree, METH_VARARGS), METHOD(Repository, lookup_worktree, METH_VARARGS), METHOD(Repository, list_worktrees, METH_VARARGS), METHOD(Repository, _from_c, METH_VARARGS), METHOD(Repository, _disown, METH_NOARGS), METHOD(Repository, set_odb, METH_O), METHOD(Repository, set_refdb, METH_O), METHOD(Repository, listall_stashes, METH_NOARGS), METHOD(Repository, listall_mergeheads, METH_NOARGS), {NULL} }; PyGetSetDef Repository_getseters[] = { GETTER(Repository, path), GETTER(Repository, head), GETTER(Repository, head_is_detached), GETTER(Repository, head_is_unborn), GETTER(Repository, is_empty), GETTER(Repository, is_bare), GETTER(Repository, is_shallow), GETSET(Repository, workdir), GETTER(Repository, default_signature), GETTER(Repository, odb), GETTER(Repository, refdb), GETTER(Repository, _pointer), {NULL} }; PyDoc_STRVAR(Repository__doc__, "Repository(backend) -> Repository\n" "\n" "Git repository."); PyTypeObject RepositoryType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Repository", /* tp_name */ sizeof(Repository), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Repository_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ Repository__doc__, /* tp_doc */ (traverseproc)Repository_traverse, /* tp_traverse */ (inquiry)Repository_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Repository_methods, /* tp_methods */ 0, /* tp_members */ Repository_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Repository_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyDoc_STRVAR(RefsIterator__doc__, "References iterator."); PyTypeObject RefsIteratorType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.RefsIterator", /* tp_name */ sizeof(Repository), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)References_iterator_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ RefsIterator__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ (iternextfunc)Repository_references_iterator_next, /* tp_iternext */ }; libgit2-pygit2-a011e26/src/repository.h000077500000000000000000000067271473744024100200070ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_repository_h #define INCLUDE_pygit2_repository_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" typedef enum { GIT_REFERENCES_ALL = 0, GIT_REFERENCES_BRANCHES = 1, GIT_REFERENCES_TAGS = 2, } git_reference_iterator_return_t; PyObject *wrap_repository(git_repository *c_repo); int Repository_init(Repository *self, PyObject *args, PyObject *kwds); int Repository_traverse(Repository *self, visitproc visit, void *arg); int Repository_clear(Repository *self); PyObject* Repository_walk(Repository *self, PyObject *args); PyObject* Repository_create_blob(Repository *self, PyObject *args); PyObject* Repository_create_blob_fromdisk(Repository *self, PyObject *args); PyObject* Repository_create_commit(Repository *self, PyObject *args); PyObject* Repository_create_commit_string(Repository *self, PyObject *args); PyObject* Repository_create_commit_with_signature(Repository *self, PyObject *args); PyObject* Repository_create_tag(Repository *self, PyObject *args); PyObject* Repository_create_branch(Repository *self, PyObject *args); PyObject* Repository_references_iterator_init(Repository *self, PyObject *args); PyObject* Repository_references_iterator_next(Repository *self, PyObject *args); PyObject* Repository_listall_branches(Repository *self, PyObject *args); PyObject* Repository_lookup_reference(Repository *self, PyObject *py_name); PyObject* Repository_add_worktree(Repository *self, PyObject *args); PyObject* Repository_lookup_worktree(Repository *self, PyObject *py_name); PyObject* Repository_list_worktrees(Repository *self, PyObject *args); PyObject* Repository_create_reference_direct(Repository *self, PyObject *args, PyObject* kw); PyObject* Repository_create_reference_symbolic(Repository *self, PyObject *args, PyObject* kw); PyObject* Repository_compress_references(Repository *self); PyObject* Repository_status(Repository *self, PyObject *args, PyObject *kw); PyObject* Repository_status_file(Repository *self, PyObject *value); PyObject* Repository_TreeBuilder(Repository *self, PyObject *args); PyObject* Repository_cherrypick(Repository *self, PyObject *py_oid); PyObject* Repository_apply(Repository *self, PyObject *py_diff, PyObject *kwds); PyObject* Repository_merge_analysis(Repository *self, PyObject *args); #endif libgit2-pygit2-a011e26/src/revspec.c000066400000000000000000000135441473744024100172220ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "object.h" #include "types.h" #include "utils.h" extern PyTypeObject RevSpecType; PyObject* wrap_revspec(git_revspec *revspec, Repository *repo) { RevSpec *py_revspec; py_revspec = PyObject_New(RevSpec, &RevSpecType); if (py_revspec) { py_revspec->flags = revspec->flags; if (revspec->from != NULL) { py_revspec->from = wrap_object(revspec->from, repo, NULL); } else { py_revspec->from = NULL; } if (revspec->to != NULL) { py_revspec->to = wrap_object(revspec->to, repo, NULL); } else { py_revspec->to = NULL; } } return (PyObject*) py_revspec; } PyDoc_STRVAR(RevSpec_from_object__doc__, "From revision"); PyObject * RevSpec_from_object__get__(RevSpec *self) { if (self->from == NULL) Py_RETURN_NONE; Py_INCREF(self->from); return self->from; } PyDoc_STRVAR(RevSpec_to_object__doc__, "To revision"); PyObject * RevSpec_to_object__get__(RevSpec *self) { if (self->to == NULL) Py_RETURN_NONE; Py_INCREF(self->to); return self->to; } PyDoc_STRVAR(RevSpec_flags__doc__, "A combination of enums.RevSpecFlag constants indicating the\n" "intended behavior of the spec passed to Repository.revparse()"); PyObject * RevSpec_flags__get__(RevSpec *self) { return PyLong_FromLong(self->flags); } static PyObject * RevSpec_repr(RevSpec *self) { return PyUnicode_FromFormat("", (self->from != NULL) ? self->from : Py_None, (self->to != NULL) ? self->to : Py_None); } static void RevSpec_dealloc(RevSpec *self) { Py_XDECREF(self->from); Py_XDECREF(self->to); PyObject_Del(self); } PyGetSetDef RevSpec_getsetters[] = { GETTER(RevSpec, from_object), GETTER(RevSpec, to_object), GETTER(RevSpec, flags), {NULL} }; PyDoc_STRVAR(RevSpec__doc__, "RevSpec object, output from Repository.revparse()."); PyTypeObject RevSpecType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.RevSpec", /* tp_name */ sizeof(RevSpec), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)RevSpec_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)RevSpec_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ RevSpec__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ RevSpec_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/revspec.h000066400000000000000000000026421473744024100172240ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_revspec_h #define INCLUDE_pygit2_revspec_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject* wrap_revspec(git_revspec *revspec, Repository *repo); #endif libgit2-pygit2-a011e26/src/signature.c000066400000000000000000000243761473744024100175610ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "types.h" #include "utils.h" #include "oid.h" #include "signature.h" extern PyTypeObject SignatureType; int Signature_init(Signature *self, PyObject *args, PyObject *kwds) { char *keywords[] = {"name", "email", "time", "offset", "encoding", NULL}; PyObject *py_name; char *email, *encoding = NULL; long long time = -1; int offset = 0; if (!PyArg_ParseTupleAndKeywords( args, kwds, "Os|Liz", keywords, &py_name, &email, &time, &offset, &encoding)) return -1; PyObject *tname; const char *name = pgit_borrow_encoding( py_name, value_or_default(encoding, "utf-8"), NULL, &tname); if (name == NULL) return -1; git_signature *signature; int err = (time == -1) ? git_signature_now(&signature, name, email) : git_signature_new(&signature, name, email, time, offset); Py_DECREF(tname); if (err < 0) { Error_set(err); return -1; } self->obj = NULL; self->signature = signature; if (encoding) { self->encoding = strdup(encoding); if (self->encoding == NULL) { PyErr_NoMemory(); return -1; } } return 0; } void Signature_dealloc(Signature *self) { /* self->obj is the owner of the git_signature C structure, so we musn't free it */ if (self->obj) { Py_CLEAR(self->obj); } else { git_signature_free((git_signature *) self->signature); } /* we own self->encoding */ free(self->encoding); PyObject_Del(self); } PyDoc_STRVAR(Signature__pointer__doc__, "Get the signature's pointer. For internal use only."); PyObject * Signature__pointer__get__(Signature *self) { /* Bytes means a raw buffer */ return PyBytes_FromStringAndSize((char *) &self->signature, sizeof(git_signature *)); } PyDoc_STRVAR(Signature__encoding__doc__, "Encoding."); PyObject * Signature__encoding__get__(Signature *self) { const char *encoding = self->encoding; if (encoding == NULL) { encoding = "utf-8"; } return to_encoding(encoding); } PyDoc_STRVAR(Signature_raw_name__doc__, "Name (bytes)."); PyObject * Signature_raw_name__get__(Signature *self) { return PyBytes_FromString(self->signature->name); } PyDoc_STRVAR(Signature_raw_email__doc__, "Email (bytes)."); PyObject * Signature_raw_email__get__(Signature *self) { return PyBytes_FromString(self->signature->email); } PyDoc_STRVAR(Signature_name__doc__, "Name."); PyObject * Signature_name__get__(Signature *self) { return to_unicode(self->signature->name, self->encoding, NULL); } PyDoc_STRVAR(Signature_email__doc__, "Email address."); PyObject * Signature_email__get__(Signature *self) { return to_unicode(self->signature->email, self->encoding, NULL); } PyDoc_STRVAR(Signature_time__doc__, "Unix time."); PyObject * Signature_time__get__(Signature *self) { return PyLong_FromLongLong(self->signature->when.time); } PyDoc_STRVAR(Signature_offset__doc__, "Offset from UTC in minutes."); PyObject * Signature_offset__get__(Signature *self) { return PyLong_FromLong(self->signature->when.offset); } PyGetSetDef Signature_getseters[] = { GETTER(Signature, _encoding), GETTER(Signature, raw_name), GETTER(Signature, raw_email), GETTER(Signature, name), GETTER(Signature, email), GETTER(Signature, time), GETTER(Signature, offset), GETTER(Signature, _pointer), {NULL} }; PyObject * Signature_richcompare(PyObject *a, PyObject *b, int op) { int eq; Signature *sa, *sb; /* We only support comparing to another signature */ if (!PyObject_TypeCheck(b, &SignatureType)) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } sa = (Signature *)a; sb = (Signature *)b; eq = ( strcmp(sa->signature->name, sb->signature->name) == 0 && strcmp(sa->signature->email, sb->signature->email) == 0 && sa->signature->when.time == sb->signature->when.time && sa->signature->when.offset == sb->signature->when.offset && sa->signature->when.sign == sb->signature->when.sign && strcmp(value_or_default(sa->encoding, "utf-8"), value_or_default(sb->encoding, "utf-8")) == 0); switch (op) { case Py_EQ: if (eq) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } case Py_NE: if (eq) { Py_RETURN_FALSE; } else { Py_RETURN_TRUE; } default: Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } } static PyObject * Signature__str__(Signature *self) { PyObject *name, *email, *str; name = to_unicode_safe(self->signature->name, self->encoding); email = to_unicode_safe(self->signature->email, self->encoding); assert(name); assert(email); str = PyUnicode_FromFormat("%U <%U>", name, email); Py_DECREF(name); Py_DECREF(email); return str; } static PyObject * Signature__repr__(Signature *self) { PyObject *name, *email, *encoding, *str; name = to_unicode_safe(self->signature->name, self->encoding); email = to_unicode_safe(self->signature->email, self->encoding); if (self->encoding) { encoding = to_unicode_safe(self->encoding, self->encoding); } else { encoding = Py_None; Py_INCREF(Py_None); } assert(name); assert(email); assert(encoding); str = PyUnicode_FromFormat( "pygit2.Signature(%R, %R, %lld, %ld, %R)", name, email, self->signature->when.time, self->signature->when.offset, encoding); Py_DECREF(name); Py_DECREF(email); Py_DECREF(encoding); return str; } PyDoc_STRVAR(Signature__doc__, "Signature."); PyTypeObject SignatureType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Signature", /* tp_name */ sizeof(Signature), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Signature_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Signature__repr__, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ (reprfunc)Signature__str__, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Signature__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ (richcmpfunc)Signature_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ Signature_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Signature_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * build_signature(Object *obj, const git_signature *signature, const char *encoding) { Signature *py_signature; py_signature = PyObject_New(Signature, &SignatureType); if (!py_signature) goto on_error; py_signature->encoding = NULL; if (encoding) { py_signature->encoding = strdup(encoding); if (!py_signature->encoding) goto on_error; } Py_XINCREF(obj); py_signature->obj = obj; py_signature->signature = signature; return (PyObject*)py_signature; on_error: git_signature_free((git_signature *) signature); return NULL; } libgit2-pygit2-a011e26/src/signature.h000066400000000000000000000027241473744024100175570ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_signature_h #define INCLUDE_pygit2_signature_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject* build_signature(Object *obj, const git_signature *signature, const char *encoding); #endif libgit2-pygit2-a011e26/src/stash.c000066400000000000000000000135171473744024100166750ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "object.h" #include "error.h" #include "types.h" #include "utils.h" #include "oid.h" PyTypeObject StashType; PyDoc_STRVAR(Stash_commit_id__doc__, "The commit id of the stashed state."); PyObject * Stash_commit_id__get__(Stash *self) { Py_INCREF(self->commit_id); return self->commit_id; } PyDoc_STRVAR(Stash_message__doc__, "Stash message."); PyObject * Stash_message__get__(Stash *self) { return to_unicode(self->message, "utf-8", "strict"); } PyDoc_STRVAR(Stash_raw_message__doc__, "Stash message (bytes)."); PyObject * Stash_raw_message__get__(Stash *self) { return PyBytes_FromString(self->message); } static void Stash_dealloc(Stash *self) { Py_CLEAR(self->commit_id); free(self->message); PyObject_Del(self); } static PyObject * Stash_repr(Stash *self) { return PyUnicode_FromFormat("", self->commit_id); } PyObject * Stash_richcompare(PyObject *o1, PyObject *o2, int op) { int eq = 0; Stash *s1, *s2; git_oid *oid1, *oid2; /* We only support comparing to another stash */ if (!PyObject_TypeCheck(o2, &StashType)) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } s1 = (Stash *)o1; s2 = (Stash *)o2; oid1 = &((Oid *)s1->commit_id)->oid; oid2 = &((Oid *)s2->commit_id)->oid; eq = git_oid_equal(oid1, oid2) && (0 == strcmp(s1->message, s2->message)); switch (op) { case Py_EQ: if (eq) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } case Py_NE: if (eq) { Py_RETURN_FALSE; } else { Py_RETURN_TRUE; } default: Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } } PyGetSetDef Stash_getseters[] = { GETTER(Stash, commit_id), GETTER(Stash, message), GETTER(Stash, raw_message), {NULL} }; PyDoc_STRVAR(Stash__doc__, "Stashed state."); PyTypeObject StashType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Stash", /* tp_name */ sizeof(Stash), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Stash_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Stash_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Stash__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ Stash_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ Stash_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/tag.c000066400000000000000000000150311473744024100163170ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "object.h" #include "error.h" #include "types.h" #include "utils.h" #include "signature.h" #include "oid.h" PyDoc_STRVAR(Tag_target__doc__, "Tagged object."); PyObject * Tag_target__get__(Tag *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const git_oid *oid = git_tag_target_id(self->tag); return git_oid_to_python(oid); } PyDoc_STRVAR(Tag_get_object__doc__, "get_object() -> Object\n" "\n" "Retrieves the object the current tag is pointing to."); PyObject * Tag_get_object(Tag *self) { git_object* obj; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load int err = git_tag_peel(&obj, self->tag); if (err < 0) return Error_set(err); return wrap_object(obj, self->repo, NULL); } PyDoc_STRVAR(Tag_name__doc__, "Tag name."); PyObject * Tag_name__get__(Tag *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const char *name = git_tag_name(self->tag); if (!name) Py_RETURN_NONE; return to_unicode(name, "utf-8", "strict"); } PyDoc_STRVAR(Tag_raw_name__doc__, "Tag name (bytes)."); PyObject * Tag_raw_name__get__(Tag *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const char *name = git_tag_name(self->tag); if (!name) Py_RETURN_NONE; return PyBytes_FromString(name); } PyDoc_STRVAR(Tag_tagger__doc__, "Tagger."); PyObject * Tag_tagger__get__(Tag *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const git_signature *signature = git_tag_tagger(self->tag); if (!signature) Py_RETURN_NONE; return build_signature((Object*)self, signature, "utf-8"); } PyDoc_STRVAR(Tag_message__doc__, "Tag message."); PyObject * Tag_message__get__(Tag *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const char *message = git_tag_message(self->tag); if (!message) Py_RETURN_NONE; return to_unicode(message, "utf-8", "strict"); } PyDoc_STRVAR(Tag_raw_message__doc__, "Tag message (bytes)."); PyObject * Tag_raw_message__get__(Tag *self) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load const char *message = git_tag_message(self->tag); if (!message) Py_RETURN_NONE; return PyBytes_FromString(message); } PyMethodDef Tag_methods[] = { METHOD(Tag, get_object, METH_NOARGS), {NULL} }; PyGetSetDef Tag_getseters[] = { GETTER(Tag, target), GETTER(Tag, name), GETTER(Tag, raw_name), GETTER(Tag, tagger), GETTER(Tag, message), GETTER(Tag, raw_message), {NULL} }; PyDoc_STRVAR(Tag__doc__, "Tag objects."); PyTypeObject TagType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Tag", /* tp_name */ sizeof(Tag), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Tag__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Tag_methods, /* tp_methods */ 0, /* tp_members */ Tag_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/tree.c000066400000000000000000000452431473744024100165130ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "error.h" #include "utils.h" #include "repository.h" #include "object.h" #include "oid.h" #include "tree.h" #include "diff.h" extern PyTypeObject TreeType; extern PyTypeObject DiffType; extern PyTypeObject TreeIterType; extern PyTypeObject IndexType; PyObject * treeentry_to_object(const git_tree_entry *entry, Repository *repo) { if (repo == NULL) { PyErr_SetString(PyExc_ValueError, "expected repository"); return NULL; } return wrap_object(NULL, repo, entry); } Py_ssize_t Tree_len(Tree *self) { if (Object__load((Object*)self) == NULL) { return -1; } // Lazy load return (Py_ssize_t)git_tree_entrycount(self->tree); } int Tree_contains(Tree *self, PyObject *py_name) { if (Object__load((Object*)self) == NULL) { return -1; } // Lazy load PyObject *tvalue; char *name = pgit_borrow_fsdefault(py_name, &tvalue); if (name == NULL) return -1; git_tree_entry *entry; int err = git_tree_entry_bypath(&entry, self->tree, name); Py_DECREF(tvalue); if (err == GIT_ENOTFOUND) { return 0; } else if (err < 0) { Error_set(err); return -1; } git_tree_entry_free(entry); return 1; } int Tree_fix_index(const git_tree *tree, PyObject *py_index) { long index; size_t len; long slen; index = PyLong_AsLong(py_index); if (PyErr_Occurred()) return -1; len = git_tree_entrycount(tree); slen = (long)len; if (index >= slen) { PyErr_SetObject(PyExc_IndexError, py_index); return -1; } else if (index < -slen) { PyErr_SetObject(PyExc_IndexError, py_index); return -1; } /* This function is called via mp_subscript, which doesn't do negative * index rewriting, so we have to do it manually. */ if (index < 0) index = len + index; return (int)index; } PyObject * Tree_iter(Tree *self) { TreeIter *iter; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load iter = PyObject_New(TreeIter, &TreeIterType); if (iter) { Py_INCREF(self); iter->owner = self; iter->i = 0; } return (PyObject*)iter; } PyObject* tree_getentry_by_index(const git_tree *tree, Repository *repo, PyObject *py_index) { int index; const git_tree_entry *entry_src; git_tree_entry *entry; index = Tree_fix_index(tree, py_index); if (PyErr_Occurred()) return NULL; entry_src = git_tree_entry_byindex(tree, index); if (!entry_src) { PyErr_SetObject(PyExc_IndexError, py_index); return NULL; } if (git_tree_entry_dup(&entry, entry_src) < 0) { PyErr_SetNone(PyExc_MemoryError); return NULL; } return treeentry_to_object(entry, repo); } PyObject* tree_getentry_by_path(const git_tree *tree, Repository *repo, PyObject *py_path) { PyObject *tvalue; char *path = pgit_borrow_fsdefault(py_path, &tvalue); if (path == NULL) { PyErr_SetString(PyExc_TypeError, "Value must be a path string"); return NULL; } git_tree_entry *entry; int err = git_tree_entry_bypath(&entry, tree, path); Py_DECREF(tvalue); if (err == GIT_ENOTFOUND) { PyErr_SetObject(PyExc_KeyError, py_path); return NULL; } if (err < 0) return Error_set(err); /* git_tree_entry_dup is already done in git_tree_entry_bypath */ return treeentry_to_object(entry, repo); } PyObject* Tree_subscript(Tree *self, PyObject *value) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load /* Case 1: integer */ if (PyLong_Check(value)) return tree_getentry_by_index(self->tree, self->repo, value); /* Case 2: byte or text string */ return tree_getentry_by_path(self->tree, self->repo, value); } PyObject * Tree_divide(Tree *self, PyObject *value) { if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load return tree_getentry_by_path(self->tree, self->repo, value); } PyDoc_STRVAR(Tree_diff_to_workdir__doc__, "diff_to_workdir(flags: enums.DiffOption = enums.DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0) -> Diff\n" "\n" "Show the changes between the :py:class:`~pygit2.Tree` and the workdir.\n" "\n" "Parameters:\n" "\n" "flags\n" " A combination of enums.DiffOption constants.\n" "\n" "context_lines\n" " The number of unchanged lines that define the boundary of a hunk\n" " (and to display before and after).\n" "\n" "interhunk_lines\n" " The maximum number of unchanged lines between hunk boundaries before\n" " the hunks will be merged into a one.\n"); PyObject * Tree_diff_to_workdir(Tree *self, PyObject *args) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff *diff; int err; if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, &opts.interhunk_lines)) return NULL; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load err = git_diff_tree_to_workdir(&diff, self->repo->repo, self->tree, &opts); if (err < 0) return Error_set(err); return wrap_diff(diff, self->repo); } PyDoc_STRVAR(Tree_diff_to_index__doc__, "diff_to_index(index: Index, flags: enums.DiffOption = enums.DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0) -> Diff\n" "\n" "Show the changes between the index and a given :py:class:`~pygit2.Tree`.\n" "\n" "Parameters:\n" "\n" "index : :py:class:`~pygit2.Index`\n" " The index to diff.\n" "\n" "flags\n" " A combination of enums.DiffOption constants.\n" "\n" "context_lines\n" " The number of unchanged lines that define the boundary of a hunk\n" " (and to display before and after).\n" "\n" "interhunk_lines\n" " The maximum number of unchanged lines between hunk boundaries before\n" " the hunks will be merged into a one.\n"); PyObject * Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff *diff; git_index *index; char *buffer; Py_ssize_t length; PyObject *py_idx; int err; if (!PyArg_ParseTuple(args, "O|IHH", &py_idx, &opts.flags, &opts.context_lines, &opts.interhunk_lines)) return NULL; /* Check whether the first argument is an index. * FIXME Uses duck typing. This would be easy and correct if we had * _pygit2.Index. */ PyObject *pygit2_index = PyObject_GetAttrString(py_idx, "_index"); if (!pygit2_index) { PyErr_SetString(PyExc_TypeError, "argument must be an Index"); return NULL; } Py_DECREF(pygit2_index); /* Get git_index from cffi's pointer */ PyObject *py_idx_ptr = PyObject_GetAttrString(py_idx, "_pointer"); if (!py_idx_ptr) return NULL; /* Here we need to do the opposite conversion from the _pointer getters */ if (PyBytes_AsStringAndSize(py_idx_ptr, &buffer, &length)) goto error; if (length != sizeof(git_index *)) { PyErr_SetString(PyExc_TypeError, "passed value is not a pointer"); goto error; } index = *((git_index **) buffer); /* the "buffer" contains the pointer */ /* Call git_diff_tree_to_index */ if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load err = git_diff_tree_to_index(&diff, self->repo->repo, self->tree, index, &opts); Py_DECREF(py_idx_ptr); if (err < 0) return Error_set(err); return wrap_diff(diff, self->repo); error: Py_DECREF(py_idx_ptr); return NULL; } PyDoc_STRVAR(Tree_diff_to_tree__doc__, "diff_to_tree([tree: Tree, flags: enums.DiffOption = enums.DiffOption.NORMAL, context_lines: int = 3, interhunk_lines: int = 0, swap: bool = False]) -> Diff\n" "\n" "Show the changes between two trees.\n" "\n" "Parameters:\n" "\n" "tree: :py:class:`~pygit2.Tree`\n" " The tree to diff. If no tree is given the empty tree will be used\n" " instead.\n" "\n" "flags\n" " A combination of enums.DiffOption constants.\n" "\n" "context_lines\n" " The number of unchanged lines that define the boundary of a hunk\n" " (and to display before and after).\n" "\n" "interhunk_lines\n" " The maximum number of unchanged lines between hunk boundaries before\n" " the hunks will be merged into a one.\n" "\n" "swap\n" " Instead of diffing a to b. Diff b to a.\n"); PyObject * Tree_diff_to_tree(Tree *self, PyObject *args, PyObject *kwds) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff *diff; git_tree *from, *to = NULL, *tmp; int err, swap = 0; char *keywords[] = {"obj", "flags", "context_lines", "interhunk_lines", "swap", NULL}; Tree *other = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!IHHi", keywords, &TreeType, &other, &opts.flags, &opts.context_lines, &opts.interhunk_lines, &swap)) return NULL; if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load from = self->tree; if (other) { if (Object__load((Object*)other) == NULL) { return NULL; } // Lazy load to = other->tree; } if (swap > 0) { tmp = from; from = to; to = tmp; } err = git_diff_tree_to_tree(&diff, self->repo->repo, from, to, &opts); if (err < 0) return Error_set(err); return wrap_diff(diff, self->repo); } PySequenceMethods Tree_as_sequence = { 0, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ (objobjproc)Tree_contains, /* sq_contains */ }; PyMappingMethods Tree_as_mapping = { (lenfunc)Tree_len, /* mp_length */ (binaryfunc)Tree_subscript, /* mp_subscript */ 0, /* mp_ass_subscript */ }; PyMethodDef Tree_methods[] = { METHOD(Tree, diff_to_tree, METH_VARARGS | METH_KEYWORDS), METHOD(Tree, diff_to_workdir, METH_VARARGS), METHOD(Tree, diff_to_index, METH_VARARGS | METH_KEYWORDS), {NULL} }; /* Py2/3 compatible structure * see https://py3c.readthedocs.io/en/latest/ext-types.html#pynumbermethods */ PyNumberMethods Tree_as_number = { 0, /* nb_add */ 0, /* nb_subtract */ 0, /* nb_multiply */ 0, /* nb_remainder */ 0, /* nb_divmod */ 0, /* nb_power */ 0, /* nb_negative */ 0, /* nb_positive */ 0, /* nb_absolute */ 0, /* nb_bool (Py2: nb_nonzero) */ 0, /* nb_invert */ 0, /* nb_lshift */ 0, /* nb_rshift */ 0, /* nb_and */ 0, /* nb_xor */ 0, /* nb_or */ 0, /* nb_int */ 0, /* nb_reserved (Py2: nb_long) */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ 0, /* nb_floor_divide */ (binaryfunc)Tree_divide, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ 0, /* nb_index */ 0, /* nb_matrix_multiply */ 0, /* nb_inplace_matrix_multiply */ }; PyDoc_STRVAR(Tree__doc__, "Tree objects."); PyTypeObject TreeType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Tree", /* tp_name */ sizeof(Tree), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ (reprfunc)Object_repr, /* tp_repr */ &Tree_as_number, /* tp_as_number */ &Tree_as_sequence, /* tp_as_sequence */ &Tree_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ Tree__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)Tree_iter, /* tp_iter */ 0, /* tp_iternext */ Tree_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; void TreeIter_dealloc(TreeIter *self) { Py_CLEAR(self->owner); PyObject_Del(self); } PyObject* TreeIter_iternext(TreeIter *self) { const git_tree_entry *entry_src; git_tree_entry *entry; entry_src = git_tree_entry_byindex(self->owner->tree, self->i); if (!entry_src) return NULL; self->i += 1; if (git_tree_entry_dup(&entry, entry_src) < 0) { PyErr_SetNone(PyExc_MemoryError); return NULL; } return treeentry_to_object(entry, self->owner->repo); } PyDoc_STRVAR(TreeIter__doc__, "Tree iterator."); PyTypeObject TreeIterType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.TreeIter", /* tp_name */ sizeof(TreeIter), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)TreeIter_dealloc , /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ TreeIter__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ (iternextfunc)TreeIter_iternext, /* tp_iternext */ }; libgit2-pygit2-a011e26/src/tree.h000066400000000000000000000026531473744024100165160ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_tree_h #define INCLUDE_pygit2_tree_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject* treeentry_to_object(const git_tree_entry *entry, Repository *repo); #endif libgit2-pygit2-a011e26/src/treebuilder.c000066400000000000000000000164361473744024100200640ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "error.h" #include "utils.h" #include "oid.h" #include "treebuilder.h" #include "tree.h" void TreeBuilder_dealloc(TreeBuilder *self) { Py_CLEAR(self->repo); git_treebuilder_free(self->bld); PyObject_Del(self); } PyDoc_STRVAR(TreeBuilder_insert__doc__, "insert(name: str, oid: Oid, attr: FileMode)\n" "\n" "Insert or replace an entry in the treebuilder.\n" "\n" "Parameters:\n" "\n" "attr\n" " Available values are FileMode.BLOB, FileMode.BLOB_EXECUTABLE,\n" " FileMode.TREE, FileMode.LINK and FileMode.COMMIT.\n"); PyObject * TreeBuilder_insert(TreeBuilder *self, PyObject *args) { PyObject *py_oid; size_t len; int err, attr; git_oid oid; const char *fname; if (!PyArg_ParseTuple(args, "sOi", &fname, &py_oid, &attr)) return NULL; len = py_oid_to_git_oid(py_oid, &oid); if (len == 0) return NULL; err = git_treebuilder_insert(NULL, self->bld, fname, &oid, attr); if (err < 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(TreeBuilder_write__doc__, "write() -> Oid\n" "\n" "Write the tree to the given repository."); PyObject * TreeBuilder_write(TreeBuilder *self) { int err; git_oid oid; err = git_treebuilder_write(&oid, self->bld); if (err < 0) return Error_set(err); return git_oid_to_python(&oid); } PyDoc_STRVAR(TreeBuilder_get__doc__, "get(name: str) -> Object\n" "\n" "Return the Object for the given name, or None if there is not."); PyObject * TreeBuilder_get(TreeBuilder *self, PyObject *py_filename) { PyObject *tvalue; char *filename = pgit_borrow_fsdefault(py_filename, &tvalue); if (filename == NULL) return NULL; const git_tree_entry *entry_src = git_treebuilder_get(self->bld, filename); Py_DECREF(tvalue); if (entry_src == NULL) Py_RETURN_NONE; git_tree_entry *entry; if (git_tree_entry_dup(&entry, entry_src) < 0) { PyErr_SetNone(PyExc_MemoryError); return NULL; } return treeentry_to_object(entry, self->repo); } PyDoc_STRVAR(TreeBuilder_remove__doc__, "remove(name: str)\n" "\n" "Remove an entry from the builder."); PyObject * TreeBuilder_remove(TreeBuilder *self, PyObject *py_filename) { PyObject *tvalue; char *filename = pgit_borrow_fsdefault(py_filename, &tvalue); if (filename == NULL) return NULL; int err = git_treebuilder_remove(self->bld, filename); Py_DECREF(tvalue); if (err) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(TreeBuilder_clear__doc__, "clear()\n" "\n" "Clear all the entries in the builder."); PyObject * TreeBuilder_clear(TreeBuilder *self) { git_treebuilder_clear(self->bld); Py_RETURN_NONE; } PyMethodDef TreeBuilder_methods[] = { METHOD(TreeBuilder, clear, METH_NOARGS), METHOD(TreeBuilder, get, METH_O), METHOD(TreeBuilder, insert, METH_VARARGS), METHOD(TreeBuilder, remove, METH_O), METHOD(TreeBuilder, write, METH_NOARGS), {NULL} }; Py_ssize_t TreeBuilder_len(TreeBuilder *self) { return (Py_ssize_t)git_treebuilder_entrycount(self->bld); } PyMappingMethods TreeBuilder_as_mapping = { (lenfunc)TreeBuilder_len, /* mp_length */ 0, /* mp_subscript */ 0, /* mp_ass_subscript */ }; PyDoc_STRVAR(TreeBuilder__doc__, "TreeBuilder objects."); PyTypeObject TreeBuilderType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.TreeBuilder", /* tp_name */ sizeof(TreeBuilder), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)TreeBuilder_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ &TreeBuilder_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ TreeBuilder__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ TreeBuilder_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/treebuilder.h000066400000000000000000000031231473744024100200560ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_treebuilder_h #define INCLUDE_pygit2_treebuilder_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" PyObject* TreeBuilder_insert(TreeBuilder *self, PyObject *args); PyObject* TreeBuilder_write(TreeBuilder *self); PyObject* TreeBuilder_remove(TreeBuilder *self, PyObject *py_filename); PyObject* TreeBuilder_clear(TreeBuilder *self); #endif libgit2-pygit2-a011e26/src/types.h000066400000000000000000000131171473744024100167200ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_objects_h #define INCLUDE_pygit2_objects_h #define PY_SSIZE_T_CLEAN #include #include #include #if !(LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 9) #error You need a compatible libgit2 version (1.9.x) #endif /* * Python objects * **/ /* git_repository */ typedef struct { PyObject_HEAD git_repository *repo; PyObject *index; /* It will be None for a bare repository */ PyObject *config; /* It will be None for a bare repository */ int owned; /* _from_c() sometimes means we don't own the C pointer */ } Repository; typedef struct { PyObject_HEAD git_oid oid; } Oid; typedef struct { PyObject_HEAD git_odb *odb; } Odb; typedef struct { PyObject_HEAD git_odb_backend *odb_backend; } OdbBackend; typedef struct { OdbBackend super; } OdbBackendPack; typedef struct { OdbBackend super; } OdbBackendLoose; typedef struct { PyObject_HEAD git_refdb *refdb; } Refdb; typedef struct { PyObject_HEAD git_refdb_backend *refdb_backend; } RefdbBackend; typedef struct { RefdbBackend super; } RefdbFsBackend; typedef struct { PyObject_HEAD git_reference_iterator *iterator; } RefsIterator; #define SIMPLE_TYPE(_name, _ptr_type, _ptr_name) \ typedef struct {\ PyObject_HEAD\ Repository *repo;\ _ptr_type *_ptr_name;\ } _name; #define OBJECT_TYPE(_name, _ptr_type, _ptr_name) \ typedef struct {\ PyObject_HEAD\ Repository *repo;\ _ptr_type *_ptr_name;\ const git_tree_entry *entry;\ } _name; /* git object types * * The structs for some of the object subtypes are identical except for * the type of their object pointers. */ OBJECT_TYPE(Object, git_object, obj) OBJECT_TYPE(Commit, git_commit, commit) OBJECT_TYPE(Tree, git_tree, tree) OBJECT_TYPE(Blob, git_blob, blob) OBJECT_TYPE(Tag, git_tag, tag) SIMPLE_TYPE(Worktree, git_worktree, worktree) /* git_note */ typedef struct { PyObject_HEAD Repository *repo; const char *ref; PyObject *annotated_id; PyObject *id; git_note *note; } Note; typedef struct { PyObject_HEAD Repository *repo; git_note_iterator* iter; char* ref; } NoteIter; /* git_patch */ typedef struct { PyObject_HEAD git_patch *patch; Blob* oldblob; Blob* newblob; } Patch; /* git_diff */ SIMPLE_TYPE(Diff, git_diff, diff) typedef struct { PyObject_HEAD Diff *diff; size_t i; size_t n; } DeltasIter; typedef struct { PyObject_HEAD Diff *diff; size_t i; size_t n; } DiffIter; typedef struct { PyObject_HEAD PyObject *id; char *path; PyObject *raw_path; git_off_t size; uint32_t flags; uint16_t mode; } DiffFile; typedef struct { PyObject_HEAD git_delta_t status; uint32_t flags; uint16_t similarity; uint16_t nfiles; PyObject *old_file; PyObject *new_file; } DiffDelta; typedef struct { PyObject_HEAD Patch *patch; const git_diff_hunk *hunk; size_t idx; size_t n_lines; } DiffHunk; typedef struct { PyObject_HEAD DiffHunk *hunk; const git_diff_line *line; } DiffLine; SIMPLE_TYPE(DiffStats, git_diff_stats, stats); /* git_tree_walk , git_treebuilder*/ SIMPLE_TYPE(TreeBuilder, git_treebuilder, bld) typedef struct { PyObject_HEAD Tree *owner; int i; } TreeIter; /* git_index */ typedef struct { PyObject_HEAD git_index_entry entry; } IndexEntry; /* git_reference, git_reflog */ SIMPLE_TYPE(Walker, git_revwalk, walk) SIMPLE_TYPE(Reference, git_reference, reference) typedef Reference Branch; typedef struct { PyObject_HEAD git_signature *signature; PyObject *oid_old; PyObject *oid_new; char *message; } RefLogEntry; typedef struct { PyObject_HEAD git_reflog *reflog; size_t i; size_t size; } RefLogIter; /* git_revspec */ typedef struct { PyObject_HEAD PyObject *from; PyObject *to; unsigned int flags; } RevSpec; /* git_signature */ typedef struct { PyObject_HEAD Object *obj; const git_signature *signature; char *encoding; } Signature; /* git_mailmap */ typedef struct { PyObject_HEAD git_mailmap *mailmap; } Mailmap; typedef struct { PyObject_HEAD PyObject *commit_id; char *message; } Stash; typedef struct { PyObject_HEAD const git_filter_source *src; } FilterSource; #endif libgit2-pygit2-a011e26/src/utils.c000066400000000000000000000131661473744024100167130ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "utils.h" extern PyTypeObject ReferenceType; extern PyTypeObject TreeType; extern PyTypeObject CommitType; extern PyTypeObject BlobType; extern PyTypeObject TagType; /** * Attempt to convert a C string to a Python string with the given encoding. * If the conversion fails, return a fallback string. */ PyObject * to_unicode_safe(const char *value, const char *encoding) { PyObject *py_str; if (!value) { py_str = PyUnicode_FromString("None"); } else { py_str = to_unicode(value, encoding, "replace"); if (!py_str) { assert(PyErr_Occurred()); py_str = PyUnicode_FromString("(error)"); PyErr_Clear(); } } assert(!PyErr_Occurred()); assert(py_str); return py_str; } char* pgit_borrow_fsdefault(PyObject *value, PyObject **tvalue) { PyObject *str = PyOS_FSPath(value); if (str == NULL) { return NULL; } PyObject *bytes = PyUnicode_EncodeFSDefault(str); if (bytes == NULL) { return NULL; } *tvalue = bytes; return PyBytes_AS_STRING(bytes); } /** * Return a pointer to the underlying C string in 'value'. The pointer is * guaranteed by 'tvalue', decrease its refcount when done with the string. */ const char* pgit_borrow_encoding(PyObject *value, const char *encoding, const char *errors, PyObject **tvalue) { PyObject *py_value = NULL; PyObject *py_str = NULL; py_value = PyOS_FSPath(value); if (py_value == NULL) { Error_type_error("unexpected %.200s", value); return NULL; } // Get new PyBytes reference from value if (PyUnicode_Check(py_value)) { // Text string py_str = PyUnicode_AsEncodedString( py_value, encoding ? encoding : "utf-8", errors ? errors : "strict" ); Py_DECREF(py_value); if (py_str == NULL) return NULL; } else if (PyBytes_Check(py_value)) { // Byte string py_str = py_value; } else { // Type error Error_type_error("unexpected %.200s", value); Py_DECREF(py_value); return NULL; } // Borrow c string from the new PyBytes reference char *c_str = PyBytes_AsString(py_str); if (c_str == NULL) { Py_DECREF(py_str); return NULL; } // Return the borrowed c string and the new PyBytes reference *tvalue = py_str; return c_str; } /** * Return a borrowed c string with the representation of the given Unicode or * Bytes object: * - If value is Unicode return the UTF-8 representation * - If value is Bytes return the raw sttring * In both cases the returned string is owned by value and must not be * modified, nor freed. */ const char* pgit_borrow(PyObject *value) { if (PyUnicode_Check(value)) { // Text string return PyUnicode_AsUTF8(value); } else if (PyBytes_Check(value)) { // Byte string return PyBytes_AsString(value); } // Type error Error_type_error("unexpected %.200s", value); return NULL; } static git_otype py_type_to_git_type(PyTypeObject *py_type) { if (py_type == &CommitType) return GIT_OBJECT_COMMIT; else if (py_type == &TreeType) return GIT_OBJECT_TREE; else if (py_type == &BlobType) return GIT_OBJECT_BLOB; else if (py_type == &TagType) return GIT_OBJECT_TAG; PyErr_SetString(PyExc_ValueError, "invalid target type"); return GIT_OBJECT_INVALID; /* -1 */ } git_otype py_object_to_otype(PyObject *py_type) { long value; if (py_type == Py_None) return GIT_OBJECT_ANY; if (PyLong_Check(py_type)) { value = PyLong_AsLong(py_type); if (value == -1 && PyErr_Occurred()) return GIT_OBJECT_INVALID; /* TODO Check whether the value is a valid value */ return (git_otype)value; } if (PyType_Check(py_type)) return py_type_to_git_type((PyTypeObject *) py_type); PyErr_SetString(PyExc_ValueError, "invalid target type"); return GIT_OBJECT_INVALID; /* -1 */ } /** * Convert an integer to a reference to an IntEnum or IntFlag in pygit2.enums. */ PyObject * pygit2_enum(PyObject *enum_type, int value) { if (!enum_type) { PyErr_SetString(PyExc_TypeError, "an enum has not been cached in _pygit2.cache_enums()"); return NULL; } PyObject *enum_instance = PyObject_CallFunction(enum_type, "(i)", value); return enum_instance; } libgit2-pygit2-a011e26/src/utils.h000066400000000000000000000123641473744024100167170ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_utils_h #define INCLUDE_pygit2_utils_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" #ifdef __GNUC__ # define PYGIT2_FN_UNUSED __attribute__((unused)) #else # define PYGIT2_FN_UNUSED #endif #if defined(PYPY_VERSION) #define Py_FileSystemDefaultEncodeErrors "surrogateescape" #endif #define to_encoding(x) PyUnicode_DecodeASCII(x, strlen(x), "strict") #define CHECK_REFERENCE(self)\ if (self->reference == NULL) {\ PyErr_SetString(GitError, "deleted reference");\ return NULL;\ } #define CHECK_REFERENCE_INT(self)\ if (self->reference == NULL) {\ PyErr_SetString(GitError, "deleted reference");\ return -1;\ } /* Utilities */ #define to_unicode(x, encoding, errors) to_unicode_n(x, strlen(x), encoding, errors) PyObject *to_unicode_safe(const char *value, const char *encoding); PYGIT2_FN_UNUSED Py_LOCAL_INLINE(PyObject*) to_unicode_n(const char *value, size_t len, const char *encoding, const char *errors) { if (encoding == NULL) { encoding = "utf-8"; // Default to UTF-8 /* If the encoding is not explicit, it may not be UTF-8, so it is not * safe to decode it strictly. This is rare in the wild, but does * occur in old commits to git itself (e.g. c31820c2). * https://github.com/libgit2/pygit2/issues/77 */ if (errors == NULL) { errors = "replace"; } } return PyUnicode_Decode(value, len, encoding, errors); } #define value_or_default(x, _default) ((x) == NULL ? (_default) : (x)) const char* pgit_borrow(PyObject *value); const char* pgit_borrow_encoding(PyObject *value, const char *encoding, const char *errors, PyObject **tvalue); char* pgit_borrow_fsdefault(PyObject *value, PyObject **tvalue); //PyObject * get_pylist_from_git_strarray(git_strarray *strarray); //int get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist); git_otype py_object_to_otype(PyObject *py_type); /* Enum utilities (pygit2.enums) */ PyObject *pygit2_enum(PyObject *enum_type, int value); /* Helpers to make shorter PyMethodDef and PyGetSetDef blocks */ #define METHOD(type, name, args)\ {#name, (PyCFunction) type ## _ ## name, args, type ## _ ## name ## __doc__} #define GETTER(type, attr)\ { #attr,\ (getter) type ## _ ## attr ## __get__,\ NULL,\ type ## _ ## attr ## __doc__,\ NULL} #define GETSET(type, attr)\ { #attr,\ (getter) type ## _ ## attr ## __get__,\ (setter) type ## _ ## attr ## __set__,\ type ## _ ## attr ## __doc__,\ NULL} #define MEMBER(type, attr, attr_type, docstr)\ {#attr, attr_type, offsetof(type, attr), 0, PyDoc_STR(docstr)} #define RMEMBER(type, attr, attr_type, docstr)\ {#attr, attr_type, offsetof(type, attr), READONLY, PyDoc_STR(docstr)} /* Helpers for memory allocation */ #define CALLOC(ptr, num, size, label) \ ptr = calloc((num), size);\ if (ptr == NULL) {\ err = GIT_ERROR;\ giterr_set_oom();\ goto label;\ } #define MALLOC(ptr, size, label) \ ptr = malloc(size);\ if (ptr == NULL) {\ err = GIT_ERROR;\ giterr_set_oom();\ goto label;\ } /* Helpers to make type init shorter. */ #define INIT_TYPE(type, base, new) \ type.tp_base = base; \ type.tp_new = new; \ if (PyType_Ready(&type) < 0) return NULL; #define ADD_TYPE(module, type) \ Py_INCREF(& type ## Type);\ if (PyModule_AddObject(module, #type, (PyObject*) & type ## Type) == -1)\ return NULL; #define ADD_EXC(m, name, base)\ name = PyErr_NewException("_pygit2." #name, base, NULL);\ if (name == NULL) goto fail;\ Py_INCREF(name);\ if (PyModule_AddObject(m, #name, name)) {\ Py_DECREF(name);\ goto fail;\ } #define ADD_CONSTANT_INT(m, name) \ if (PyModule_AddIntConstant(m, #name, name) == -1) return NULL; #define ADD_CONSTANT_STR(m, name) \ if (PyModule_AddStringConstant(m, #name, name) == -1) return NULL; #endif libgit2-pygit2-a011e26/src/walker.c000066400000000000000000000151751473744024100170420ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include "error.h" #include "object.h" #include "oid.h" #include "tree.h" #include "utils.h" #include "walker.h" extern PyTypeObject CommitType; void Walker_dealloc(Walker *self) { Py_CLEAR(self->repo); git_revwalk_free(self->walk); PyObject_Del(self); } PyDoc_STRVAR(Walker_hide__doc__, "hide(oid: Oid)\n" "\n" "Mark a commit (and its ancestors) uninteresting for the output."); PyObject * Walker_hide(Walker *self, PyObject *py_hex) { int err; git_oid oid; err = py_oid_to_git_oid_expand(self->repo->repo, py_hex, &oid); if (err < 0) return NULL; err = git_revwalk_hide(self->walk, &oid); if (err < 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Walker_push__doc__, "push(oid: Oid)\n" "\n" "Mark a commit to start traversal from."); PyObject * Walker_push(Walker *self, PyObject *py_hex) { int err; git_oid oid; err = py_oid_to_git_oid_expand(self->repo->repo, py_hex, &oid); if (err < 0) return NULL; err = git_revwalk_push(self->walk, &oid); if (err < 0) return Error_set(err); Py_RETURN_NONE; } PyDoc_STRVAR(Walker_sort__doc__, "sort(mode: enums.SortMode)\n" "\n" "Change the sorting mode (this resets the walker)."); PyObject * Walker_sort(Walker *self, PyObject *py_sort_mode) { long sort_mode; sort_mode = PyLong_AsLong(py_sort_mode); if (sort_mode == -1 && PyErr_Occurred()) return NULL; git_revwalk_sorting(self->walk, (unsigned int)sort_mode); Py_RETURN_NONE; } PyDoc_STRVAR(Walker_reset__doc__, "reset()\n" "\n" "Reset the walking machinery for reuse."); PyObject * Walker_reset(Walker *self) { git_revwalk_reset(self->walk); Py_RETURN_NONE; } PyDoc_STRVAR(Walker_simplify_first_parent__doc__, "simplify_first_parent()\n" "\n" "Simplify the history by first-parent."); PyObject * Walker_simplify_first_parent(Walker *self) { git_revwalk_simplify_first_parent(self->walk); Py_RETURN_NONE; } PyObject * Walker_iter(Walker *self) { Py_INCREF(self); return (PyObject*)self; } PyObject * Walker_iternext(Walker *self) { int err; git_commit *commit; git_oid oid; Py_BEGIN_ALLOW_THREADS err = git_revwalk_next(&oid, self->walk); Py_END_ALLOW_THREADS if (err < 0) return Error_set(err); err = git_commit_lookup(&commit, self->repo->repo, &oid); if (err < 0) return Error_set(err); return wrap_object((git_object*)commit, self->repo, NULL); } PyMethodDef Walker_methods[] = { METHOD(Walker, hide, METH_O), METHOD(Walker, push, METH_O), METHOD(Walker, reset, METH_NOARGS), METHOD(Walker, simplify_first_parent, METH_NOARGS), METHOD(Walker, sort, METH_O), {NULL} }; PyDoc_STRVAR(Walker__doc__, "Revision walker."); PyTypeObject WalkerType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Walker", /* tp_name */ sizeof(Walker), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Walker_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Walker__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)Walker_iter, /* tp_iter */ (iternextfunc)Walker_iternext, /* tp_iternext */ Walker_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; libgit2-pygit2-a011e26/src/walker.h000066400000000000000000000032421473744024100170370ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_walker_h #define INCLUDE_pygit2_walker_h #define PY_SSIZE_T_CLEAN #include #include #include "types.h" void Walker_dealloc(Walker *self); PyObject* Walker_hide(Walker *self, PyObject *py_hex); PyObject* Walker_push(Walker *self, PyObject *py_hex); PyObject* Walker_sort(Walker *self, PyObject *py_sort_mode); PyObject* Walker_reset(Walker *self); PyObject* Walker_iter(Walker *self); PyObject* Walker_iternext(Walker *self); #endif libgit2-pygit2-a011e26/src/wildmatch.c000066400000000000000000000224201473744024100175200ustar00rootroot00000000000000/* * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. * * Do shell-style pattern matching for ?, \, [], and * characters. * It is 8bit clean. * * Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. * Rich $alz is now . * * Modified by Wayne Davison to special-case '/' matching, to make '**' * work differently than '*', and to fix the character-class code. * * Imported from git.git. */ #include #include #include "wildmatch.h" #define GIT_SPACE 0x01 #define GIT_DIGIT 0x02 #define GIT_ALPHA 0x04 #define GIT_GLOB_SPECIAL 0x08 #define GIT_REGEX_SPECIAL 0x10 #define GIT_PATHSPEC_MAGIC 0x20 #define GIT_CNTRL 0x40 #define GIT_PUNCT 0x80 enum { S = GIT_SPACE, A = GIT_ALPHA, D = GIT_DIGIT, G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ X = GIT_CNTRL, U = GIT_PUNCT, Z = GIT_CNTRL | GIT_SPACE }; static const unsigned char sane_ctype[256] = { X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ /* Nothing in the 128.. range */ }; #define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) #define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) typedef unsigned char uchar; /* What character marks an inverted character class? */ #define NEGATE_CLASS '!' #define NEGATE_CLASS2 '^' #define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ && *(class) == *(litmatch) \ && strncmp((char*)class, litmatch, len) == 0) #if defined STDC_HEADERS || !defined isascii # define ISASCII(c) 1 #else # define ISASCII(c) isascii(c) #endif #ifdef isblank # define ISBLANK(c) (ISASCII(c) && isblank(c)) #else # define ISBLANK(c) ((c) == ' ' || (c) == '\t') #endif #ifdef isgraph # define ISGRAPH(c) (ISASCII(c) && isgraph(c)) #else # define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) #endif #define ISPRINT(c) (ISASCII(c) && isprint(c)) #define ISDIGIT(c) (ISASCII(c) && isdigit(c)) #define ISALNUM(c) (ISASCII(c) && isalnum(c)) #define ISALPHA(c) (ISASCII(c) && isalpha(c)) #define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) #define ISLOWER(c) (ISASCII(c) && islower(c)) #define ISPUNCT(c) (ISASCII(c) && ispunct(c)) #define ISSPACE(c) (ISASCII(c) && isspace(c)) #define ISUPPER(c) (ISASCII(c) && isupper(c)) #define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) /* Match pattern "p" against "text" */ static int dowild(const uchar *p, const uchar *text, unsigned int flags) { uchar p_ch; const uchar *pattern = p; for ( ; (p_ch = *p) != '\0'; text++, p++) { int matched, match_slash, negated; uchar t_ch, prev_ch; if ((t_ch = *text) == '\0' && p_ch != '*') return WM_ABORT_ALL; if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) t_ch = tolower(t_ch); if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) p_ch = tolower(p_ch); switch (p_ch) { case '\\': /* Literal match with following character. Note that the test * in "default" handles the p[1] == '\0' failure case. */ p_ch = *++p; /* FALLTHROUGH */ default: if (t_ch != p_ch) return WM_NOMATCH; continue; case '?': /* Match anything but '/'. */ if ((flags & WM_PATHNAME) && t_ch == '/') return WM_NOMATCH; continue; case '*': if (*++p == '*') { const uchar *prev_p = p - 2; while (*++p == '*') {} if (!(flags & WM_PATHNAME)) /* without WM_PATHNAME, '*' == '**' */ match_slash = 1; else if ((prev_p < pattern || *prev_p == '/') && (*p == '\0' || *p == '/' || (p[0] == '\\' && p[1] == '/'))) { /* * Assuming we already match 'foo/' and are at * , just assume it matches * nothing and go ahead match the rest of the * pattern with the remaining string. This * helps make foo/<*><*>/bar (<> because * otherwise it breaks C comment syntax) match * both foo/bar and foo/a/bar. */ if (p[0] == '/' && dowild(p + 1, text, flags) == WM_MATCH) return WM_MATCH; match_slash = 1; } else /* WM_PATHNAME is set */ match_slash = 0; } else /* without WM_PATHNAME, '*' == '**' */ match_slash = flags & WM_PATHNAME ? 0 : 1; if (*p == '\0') { /* Trailing "**" matches everything. Trailing "*" matches * only if there are no more slash characters. */ if (!match_slash) { if (strchr((char*)text, '/') != NULL) return WM_NOMATCH; } return WM_MATCH; } else if (!match_slash && *p == '/') { /* * _one_ asterisk followed by a slash * with WM_PATHNAME matches the next * directory */ const char *slash = strchr((char*)text, '/'); if (!slash) return WM_NOMATCH; text = (const uchar*)slash; /* the slash is consumed by the top-level for loop */ break; } while (1) { if (t_ch == '\0') break; /* * Try to advance faster when an asterisk is * followed by a literal. We know in this case * that the string before the literal * must belong to "*". * If match_slash is false, do not look past * the first slash as it cannot belong to '*'. */ if (!is_glob_special(*p)) { p_ch = *p; if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) p_ch = tolower(p_ch); while ((t_ch = *text) != '\0' && (match_slash || t_ch != '/')) { if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) t_ch = tolower(t_ch); if (t_ch == p_ch) break; text++; } if (t_ch != p_ch) return WM_NOMATCH; } if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { if (!match_slash || matched != WM_ABORT_TO_STARSTAR) return matched; } else if (!match_slash && t_ch == '/') return WM_ABORT_TO_STARSTAR; t_ch = *++text; } return WM_ABORT_ALL; case '[': p_ch = *++p; #ifdef NEGATE_CLASS2 if (p_ch == NEGATE_CLASS2) p_ch = NEGATE_CLASS; #endif /* Assign literal 1/0 because of "matched" comparison. */ negated = p_ch == NEGATE_CLASS ? 1 : 0; if (negated) { /* Inverted character class. */ p_ch = *++p; } prev_ch = 0; matched = 0; do { if (!p_ch) return WM_ABORT_ALL; if (p_ch == '\\') { p_ch = *++p; if (!p_ch) return WM_ABORT_ALL; if (t_ch == p_ch) matched = 1; } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { p_ch = *++p; if (p_ch == '\\') { p_ch = *++p; if (!p_ch) return WM_ABORT_ALL; } if (t_ch <= p_ch && t_ch >= prev_ch) matched = 1; else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) { uchar t_ch_upper = toupper(t_ch); if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch) matched = 1; } p_ch = 0; /* This makes "prev_ch" get set to 0. */ } else if (p_ch == '[' && p[1] == ':') { const uchar *s; int i; for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ if (!p_ch) return WM_ABORT_ALL; i = (int)(p - s - 1); if (i < 0 || p[-1] != ':') { /* Didn't find ":]", so treat like a normal set. */ p = s - 2; p_ch = '['; if (t_ch == p_ch) matched = 1; continue; } if (CC_EQ(s,i, "alnum")) { if (ISALNUM(t_ch)) matched = 1; } else if (CC_EQ(s,i, "alpha")) { if (ISALPHA(t_ch)) matched = 1; } else if (CC_EQ(s,i, "blank")) { if (ISBLANK(t_ch)) matched = 1; } else if (CC_EQ(s,i, "cntrl")) { if (ISCNTRL(t_ch)) matched = 1; } else if (CC_EQ(s,i, "digit")) { if (ISDIGIT(t_ch)) matched = 1; } else if (CC_EQ(s,i, "graph")) { if (ISGRAPH(t_ch)) matched = 1; } else if (CC_EQ(s,i, "lower")) { if (ISLOWER(t_ch)) matched = 1; } else if (CC_EQ(s,i, "print")) { if (ISPRINT(t_ch)) matched = 1; } else if (CC_EQ(s,i, "punct")) { if (ISPUNCT(t_ch)) matched = 1; } else if (CC_EQ(s,i, "space")) { if (ISSPACE(t_ch)) matched = 1; } else if (CC_EQ(s,i, "upper")) { if (ISUPPER(t_ch)) matched = 1; else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) matched = 1; } else if (CC_EQ(s,i, "xdigit")) { if (ISXDIGIT(t_ch)) matched = 1; } else /* malformed [:class:] string */ return WM_ABORT_ALL; p_ch = 0; /* This makes "prev_ch" get set to 0. */ } else if (t_ch == p_ch) matched = 1; } while (prev_ch = p_ch, (p_ch = *++p) != ']'); if (matched == negated || ((flags & WM_PATHNAME) && t_ch == '/')) return WM_NOMATCH; continue; } } return *text ? WM_NOMATCH : WM_MATCH; } /* Match the "pattern" against the "text" string. */ int wildmatch(const char *pattern, const char *text, unsigned int flags) { return dowild((const uchar*)pattern, (const uchar*)text, flags); } libgit2-pygit2-a011e26/src/wildmatch.h000066400000000000000000000007651473744024100175350ustar00rootroot00000000000000/* * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #ifndef INCLUDE_wildmatch_h__ #define INCLUDE_wildmatch_h__ #define WM_CASEFOLD 1 #define WM_PATHNAME 2 #define WM_NOMATCH 1 #define WM_MATCH 0 #define WM_ABORT_ALL -1 #define WM_ABORT_TO_STARSTAR -2 int wildmatch(const char *pattern, const char *text, unsigned int flags); #endif libgit2-pygit2-a011e26/src/worktree.c000066400000000000000000000137121473744024100174120ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define PY_SSIZE_T_CLEAN #include #include #include "error.h" #include "utils.h" #include "types.h" #include "worktree.h" PyDoc_STRVAR(Worktree_name__doc__, "Gets name worktree\n"); PyObject * Worktree_name__get__(Worktree *self) { return to_unicode(git_worktree_name(self->worktree), NULL, NULL); } PyDoc_STRVAR(Worktree_path__doc__, "Gets path worktree\n"); PyObject * Worktree_path__get__(Worktree *self) { return to_unicode(git_worktree_path(self->worktree), NULL, NULL); } PyDoc_STRVAR(Worktree_is_prunable__doc__, "Is the worktree prunable with the given set of flags?\n"); PyObject * Worktree_is_prunable__get__(Worktree *self, PyObject *args) { if (git_worktree_is_prunable(self->worktree, 0) > 0) Py_RETURN_TRUE; Py_RETURN_FALSE; } PyDoc_STRVAR(Worktree_prune__doc__, "prune(force=False)\n" "\n" "Prune a worktree object."); PyObject * Worktree_prune(Worktree *self, PyObject *args) { int err, force = 0; git_worktree_prune_options prune_opts; if (!PyArg_ParseTuple(args, "|i", &force)) return NULL; git_worktree_prune_options_init(&prune_opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION); prune_opts.flags = force & (GIT_WORKTREE_PRUNE_VALID | GIT_WORKTREE_PRUNE_LOCKED); err = git_worktree_prune(self->worktree, &prune_opts); if (err < 0) return Error_set(err); Py_RETURN_NONE; } static void Worktree_dealloc(Worktree *self) { Py_CLEAR(self->repo); git_worktree_free(self->worktree); PyObject_Del(self); } PyMethodDef Worktree_methods[] = { METHOD(Worktree, prune, METH_VARARGS), {NULL} }; PyGetSetDef Worktree_getseters[] = { GETTER(Worktree, path), GETTER(Worktree, name), GETTER(Worktree, is_prunable), {NULL} }; PyDoc_STRVAR(Worktree__doc__, "Worktree object."); PyTypeObject WorktreeType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Worktree", /* tp_name */ sizeof(Worktree), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Worktree_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ Worktree__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Worktree_methods, /* tp_methods */ 0, /* tp_members */ Worktree_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; PyObject * wrap_worktree(Repository* repo, git_worktree* wt) { Worktree* py_wt = NULL; py_wt = PyObject_New(Worktree, &WorktreeType); if (py_wt == NULL) { PyErr_NoMemory(); return NULL; } py_wt->repo = repo; Py_INCREF(repo); py_wt->worktree = wt; return (PyObject*) py_wt; } libgit2-pygit2-a011e26/src/worktree.h000066400000000000000000000026511473744024100174170ustar00rootroot00000000000000/* * Copyright 2010-2025 The pygit2 contributors * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, * as published by the Free Software Foundation. * * In addition to the permissions in the GNU General Public License, * the authors give you unlimited permission to link the compiled * version of this file into combinations with other programs, * and to distribute those combinations without any restriction * coming from the use of this file. (The General Public License * restrictions do apply in other respects; for example, they cover * modification of the file, and distribution when not linked into * a combined executable.) * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INCLUDE_pygit2_worktree_h #define INCLUDE_pygit2_worktree_h #define PY_SSIZE_T_CLEAN #include #include #include PyObject* wrap_worktree(Repository* repo, git_worktree* wt); #endif libgit2-pygit2-a011e26/test/000077500000000000000000000000001473744024100155705ustar00rootroot00000000000000libgit2-pygit2-a011e26/test/__init__.py000066400000000000000000000027131473744024100177040ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Move the current directory to the end of the list of import paths. This way # we avoid the "ModuleNotFoundError: No module named 'pygit2._pygit2'" error # when running the test suite in Continuous Integration. import os import sys cwd = os.getcwd() sys.path.remove(cwd) sys.path.append(cwd) libgit2-pygit2-a011e26/test/conftest.py000066400000000000000000000050211473744024100177650ustar00rootroot00000000000000from pathlib import Path import platform import pytest import pygit2 from . import utils @pytest.fixture(scope='session', autouse=True) def global_git_config(): # Do not use global config for better test reproducibility. # https://github.com/libgit2/pygit2/issues/989 levels = [ pygit2.enums.ConfigLevel.GLOBAL, pygit2.enums.ConfigLevel.XDG, pygit2.enums.ConfigLevel.SYSTEM, ] for level in levels: pygit2.settings.search_path[level] = '' # Fix tests running in AppVeyor if platform.system() == 'Windows': pygit2.option(pygit2.enums.Option.SET_OWNER_VALIDATION, 0) @pytest.fixture def pygit2_empty_key(): path = Path(__file__).parent / 'keys' / 'pygit2_empty' return path, f'{path}.pub', 'empty' @pytest.fixture def barerepo(tmp_path): with utils.TemporaryRepository('barerepo.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture def barerepo_path(tmp_path): with utils.TemporaryRepository('barerepo.zip', tmp_path) as path: yield pygit2.Repository(path), path @pytest.fixture def blameflagsrepo(tmp_path): with utils.TemporaryRepository('blameflagsrepo.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture def dirtyrepo(tmp_path): with utils.TemporaryRepository('dirtyrepo.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture def emptyrepo(barerepo, tmp_path): with utils.TemporaryRepository('emptyrepo.zip', tmp_path) as path: repo = pygit2.Repository(path) repo.remotes.create('origin', barerepo.path) yield repo @pytest.fixture def encodingrepo(tmp_path): with utils.TemporaryRepository('encoding.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture def mergerepo(tmp_path): with utils.TemporaryRepository('testrepoformerging.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture def testrepo(tmp_path): with utils.TemporaryRepository('testrepo.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture def testrepo_path(tmp_path): with utils.TemporaryRepository('testrepo.zip', tmp_path) as path: yield pygit2.Repository(path), path @pytest.fixture def testrepopacked(tmp_path): with utils.TemporaryRepository('testrepopacked.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture def gpgsigned(tmp_path): with utils.TemporaryRepository('gpgsigned.zip', tmp_path) as path: yield pygit2.Repository(path) libgit2-pygit2-a011e26/test/data/000077500000000000000000000000001473744024100165015ustar00rootroot00000000000000libgit2-pygit2-a011e26/test/data/barerepo.zip000066400000000000000000000453731473744024100210400ustar00rootroot00000000000000PK y:F barerepo/UT VuTeaux PK y:Fbarerepo/objects/UT VuTeaux PK y:Fbarerepo/objects/05/UT VuTeaux PK y:FaWߚ:barerepo/objects/05/6e626e51b1fc1ee2182800e399ed8d84c8f082UT VuTeaux x[ 0)/ "^el`[~䵵yHD{Y P#*3) BP*,6 s&kJ h@+,ƘadU҂^vYI}vA(%{L3?m$GPK y:Fbarerepo/objects/10/UT VuTeaux PK y:FoT:barerepo/objects/10/2374bdb1e8efca5e66cded18fd8f30571654a5UT VuTeaux xR;N1 )((8X#&#Vp 7~_YNU_tpVbƶ kllK*7p & ,iqD!Ż R;h0AwhݜV y)g'pi1gg™08y['J`cܗhI4 |R桟o3'2PK y:Fbarerepo/objects/11/UT VuTeaux PK y:F+b:barerepo/objects/11/19926b06311143cab273f0af84eae77f5b3462UT VuTeaux x+)JMU045f040031QHd(`e>Af K&_POEk8*yv($3$\MjB~̺5:'(5A@dOť}+ \,d)APK y:Fbarerepo/objects/18/UT VuTeaux PK y:Fcc:barerepo/objects/18/e2d2e9db075f9eb43bcb2daa65a2867d29a15eUT VuTeaux x+)JMU0c040031QHd(`e>Af K*0=6'2D﴿%mfb 7szx5Z.q>%PK y:Fbarerepo/objects/19/UT VuTeaux PK y:F+:barerepo/objects/19/bf31524643d743751b09cf719456914bbd8bd5UT VuTeaux x+)JMU045f040031QHd(`e>Af K&_POEk8*yv($3$\MjB~̺5:4A@dOť}+ \,d)@APK y:Fbarerepo/objects/1a/UT VuTeaux PK y:Fikk:barerepo/objects/1a/f81f24e48f92009ca02a874edb6151c71f60deUT VuTeaux x+)JMU042`040031QHd(`e>Af K&_POEk8*yv($3$\MjB~̺h3PK C^bBbarerepo/objects/29/UT ]1Qeaux PK C^bBQuvF:barerepo/objects/29/7efb891a47de80be0cfe9c639e4b8c9b450989UT ]1Qeaux xKOR04fHOQH+I+)GNPK Q:Fbarerepo/objects/2a/UT  uTeaux PK Q:Fw3:cc:barerepo/objects/2a/d1d3456c5c4a1c9e40aeeddb9cd20b409623c8UT  uTeaux x+)JMU0c040031QHdRiӰu~$P$DQq8U!|(i3 PHfHīJu&PK Q:Fbarerepo/objects/2c/UT  uTeaux PK Q:Fñ!:barerepo/objects/2c/dae28389c059815e951d0bb9eed6533f61a46bUT  uTeaux xOIj1YA֑ }!д̂ ~~I^[PE<7E:3RMu>D 䋡 scjquV:/ ; %ĈH#V~4]K ^7=={7|B~{\rMQHz])ۧST+ ?.mEZ}s]PK Q:Fbarerepo/objects/39/UT  uTeaux PK Q:F߂:barerepo/objects/39/a3001fcc2b9541fdcf4be2d662618a5d213f47UT  uTeaux xO9N1$+:Gz|=B|G{m-s/ RJ6w"p*蝵Q4'CK^b&/NUG1B=4)1)늝g11qBn\AWx9d}[!&FgEϸg` 9p>)cj.4+]̝OW)M]=PK C^bBbarerepo/objects/3d/UT ]1Qeaux PK C^bBr|:barerepo/objects/3d/2962987c695a29f1f80b6c3aa4ec046ef44369UT ]1Qeaux x-A 0@Q9\m@$T,#ePAH%a:`{Bіl%xؚ=TOnUTvBJ6`P)mA3X]fPK C^bBbarerepo/objects/61/UT ]1Qeaux PK C^bB3..:barerepo/objects/61/4fd9a3094bf618ea938fffc00e7d1a54f89ad0UT ]1Qeaux x+)JMU0d040031QHaЬ)~aϿ9{frv PK y:Fbarerepo/objects/62/UT VuTeaux PK y:F5TT:barerepo/objects/62/cc88a53cfb046fcf603b3aaeb73b8e18215442UT VuTeaux x+)JMU0`040031Q0005MNL3, - MŘR ,MRS-V[NZV֋WPK y:Fbarerepo/objects/63/UT VuTeaux PK y:F]:barerepo/objects/63/59f8019b0954201a807b766547526173f3cc67UT VuTeaux x;0D}R "!D]:``Rbr{ 'y< a:y0md { h-{XUF2{;cBYiMg: [.8g+Ri#E 4ϗC{PK Q:Fbarerepo/objects/6a/UT  uTeaux PK Q:F:barerepo/objects/6a/270c81bc80b59591e0d2e3abd7d03450c0c395UT  uTeaux xKOR04fHTH+I+)V0HpPK Q:Fbarerepo/objects/72/UT  uTeaux PK Q:FȼRcc:barerepo/objects/72/abb8755b2cc6c4e40fd9f50f54384d973a2f22UT  uTeaux x+)JMU0c040031QHdRiӰu~$P$֏ |$\(U䬥kB2CŜ$^MV*Ǭ (PK y:Fbarerepo/objects/77/UT VuTeaux PK y:Fj_:barerepo/objects/77/88019febe4f40259a64c529a9aed561e64ddbdUT VuTeaux xKOR04aHTH+I+)V0VOLPK y:Fbarerepo/objects/78/UT VuTeaux PK y:Fb:barerepo/objects/78/4855caf26449a1914d2cf62d12b9374d76ae78UT VuTeaux x;jC1Sۇ>+ B).]Vddِ fa}[`\zZS0q#gkp6xdJ F^}Asvb)nBFK)$[H"YA26v8e~~yC7@KaFUy.OCq 1/׮pNUPK C^bBbarerepo/objects/7f/UT ]1Qeaux PK C^bB7;>:barerepo/objects/7f/129fd57e31e935c6d60a0c794efe4e6927664bUT ]1Qeaux xKOR04fHTH+I+)V0HoPK C^bBbarerepo/objects/96/UT ]1Qeaux PK C^bBΪbb:barerepo/objects/96/7fce8df97cc71722d3c2a5930ef3e6f1d27b12UT ]1Qeaux x+)JMU0c040031QHdk\<~2ӼI IP8YK~31dD9I_TȏYI%PK Q:Fbarerepo/objects/97/UT  uTeaux PK Q:Flm&P7PK y:Fbarerepo/objects/ab/UT VuTeaux PK y:FaL "":barerepo/objects/ab/533997b80705767be3dae8cbb06a0740809f79UT VuTeaux xKOR0`p,*.Q/IUUputkpPK y:Fbarerepo/objects/c2/UT VuTeaux PK y:Fkiٕ:barerepo/objects/c2/c2f6b06efdb6c1e4b1337811f0629fc0cadbd1UT VuTeaux x+)JMU046c040031Q0005MNL3, - MŘR ,MRS-V[NZV֋WBH3M5ML4I56KL2HK53JM5LNN6KMH3L0303N0gQYVǪY/W@6/PK y:Fbarerepo/objects/cc/UT VuTeaux PK y:Feβ:barerepo/objects/cc/ca47fbb26183e71a7a46d165299b84e2e6c0b3UT VuTeaux x[j0DUB#%(%[[ Iwп9ҷ> a"I֓咭06Ilb㡮b.}Iވ9ss0|YXN[}ZmSPK y:Fbarerepo/objects/d8/UT VuTeaux PK y:F=%%:barerepo/objects/d8/79714d880671ed84f8aaed8b27fca23ba01f27UT VuTeaux xKOR02dNMKQ/IUUput3mPK y:Fbarerepo/objects/f5/UT VuTeaux PK y:F\_~:barerepo/objects/f5/e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87UT VuTeaux xN0#Nkq$]Gw؟E$F 4IuۖNꆧiR?Wt6YG[81ejcމR*A34!\ ۿj%U;S;/h2HW'zA,&O;i32a0nKPK C^bBbarerepo/objects/info/UT ]1Qeaux PK C^bBBy66barerepo/objects/info/packsUT ]1Qeaux P pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.pack PK C^bBbarerepo/objects/pack/UT ]1Qeaux PKC^bBWP·Gbarerepo/objects/pack/pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.idxUT ]1Qeaux _0 F&`" f&DQ >tq B]]; >ȋm>K*J3nzP/Yό\mQ/;5p7 Ў@|'1χMmtE_O蔟Rή S8D>+n^2PK C^bB60;Hbarerepo/objects/pack/pack-822653eb59791a6df714f8aa5fbf9f1c1951478e.packUT ]1Qeaux PACK x[ 0s%I<Ʀ@@\PK y:F Abarerepo/UTVuTux PK y:FACbarerepo/objects/UTVuTux PK y:FAbarerepo/objects/05/UTVuTux PK y:FaWߚ:barerepo/objects/05/6e626e51b1fc1ee2182800e399ed8d84c8f082UTVuTux PK y:FAbarerepo/objects/10/UTVuTux PK y:FoT:8barerepo/objects/10/2374bdb1e8efca5e66cded18fd8f30571654a5UTVuTux PK y:FA}barerepo/objects/11/UTVuTux PK y:F+b:barerepo/objects/11/19926b06311143cab273f0af84eae77f5b3462UTVuTux PK y:FAbarerepo/objects/18/UTVuTux PK y:Fcc:barerepo/objects/18/e2d2e9db075f9eb43bcb2daa65a2867d29a15eUTVuTux PK y:FAbarerepo/objects/19/UTVuTux PK y:F+::barerepo/objects/19/bf31524643d743751b09cf719456914bbd8bd5UTVuTux PK y:FA6barerepo/objects/1a/UTVuTux PK y:Fikk:barerepo/objects/1a/f81f24e48f92009ca02a874edb6151c71f60deUTVuTux PK C^bBAcbarerepo/objects/29/UT]1Qux PK C^bBQuvF:barerepo/objects/29/7efb891a47de80be0cfe9c639e4b8c9b450989UT]1Qux PK Q:FAB barerepo/objects/2a/UT uTux PK Q:Fw3:cc: barerepo/objects/2a/d1d3456c5c4a1c9e40aeeddb9cd20b409623c8UT uTux PK Q:FAg barerepo/objects/2c/UT uTux PK Q:Fñ!: barerepo/objects/2c/dae28389c059815e951d0bb9eed6533f61a46bUT uTux PK Q:FA barerepo/objects/39/UT uTux PK Q:F߂:> barerepo/objects/39/a3001fcc2b9541fdcf4be2d662618a5d213f47UT uTux PK C^bBAz barerepo/objects/3d/UT]1Qux PK C^bBr|: barerepo/objects/3d/2962987c695a29f1f80b6c3aa4ec046ef44369UT]1Qux PK y:FAbarerepo/objects/55/UTVuTux PK y:F;|:barerepo/objects/55/60f04f38a674decf34d16d7c7476642fa03794UTVuTux PK C^bBAbarerepo/objects/5f/UT]1Qux PK C^bBX^:Ubarerepo/objects/5f/e808e8953c12735680c257f56600cb0de44b10UT]1Qux PK C^bBAbarerepo/objects/61/UT]1Qux PK C^bB3..:barerepo/objects/61/4fd9a3094bf618ea938fffc00e7d1a54f89ad0UT]1Qux PK y:FAbarerepo/objects/62/UTVuTux PK y:F5TT:barerepo/objects/62/cc88a53cfb046fcf603b3aaeb73b8e18215442UTVuTux PK y:FAbarerepo/objects/63/UTVuTux PK y:F]:barerepo/objects/63/59f8019b0954201a807b766547526173f3cc67UTVuTux PK Q:FAbarerepo/objects/6a/UT uTux PK Q:F:=barerepo/objects/6a/270c81bc80b59591e0d2e3abd7d03450c0c395UT uTux PK Q:FAbarerepo/objects/72/UT uTux PK Q:FȼRcc:barerepo/objects/72/abb8755b2cc6c4e40fd9f50f54384d973a2f22UT uTux PK y:FAbarerepo/objects/77/UTVuTux PK y:Fj_:Abarerepo/objects/77/88019febe4f40259a64c529a9aed561e64ddbdUTVuTux PK y:FAbarerepo/objects/78/UTVuTux PK y:Fb:!barerepo/objects/78/4855caf26449a1914d2cf62d12b9374d76ae78UTVuTux PK C^bBAEbarerepo/objects/7f/UT]1Qux PK C^bB7;>:barerepo/objects/7f/129fd57e31e935c6d60a0c794efe4e6927664bUT]1Qux PK C^bBA$barerepo/objects/96/UT]1Qux PK C^bBΪbb:rbarerepo/objects/96/7fce8df97cc71722d3c2a5930ef3e6f1d27b12UT]1Qux PK Q:FAHbarerepo/objects/97/UT uTux PK Q:F/6{sz"LyGwRU]UTIͦf)*AP-6ۏ{[h^%X7zz[=PKaA+binaryfilerepo/.git/hooks/commit-msg.sampleUT Pvcbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKaA ,binaryfilerepo/.git/hooks/post-update.sampleUT Pvcbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPKaA%[9/binaryfilerepo/.git/hooks/pre-applypatch.sampleUT Pvcbux }1O0g+i҈ $$uGNzO}})SZ{ۦdgfQJܟptV񜷄NgU@{q8ECf-ں8슗GV $ju,^)i9 G*]3cY%BT@b#_癠&ژ5uvu&QʊzB|l_6oL|ZwipW-oh~PKaApٞ+binaryfilerepo/.git/hooks/pre-commit.sampleUT Pvcbux }T]oF|6F6)YP ۅ n0CGޞx5y%+3wd8-Əٝ7RVY]T[Dr ,mim-iO}^2Umu, TMÒM:&ա&cIu߲ z]j71YAT`ƾfM:g;E`j{f 5bP!%6PID0%OEҤs,ӊ"#Ǜ38WLffE&;k1$svtLZ4#Z)##7nɖs^,˫;Y].Uy嵘_ݲTWzquJ|)GE;ۧDF؍ݦ _iFޞ%M}=x7@5(I^NЊY$!y%yw[X w=;"zkp{I>Sϗ?gy|tW59nhG ^"b2$S+Xqz\W~Z4Ewlq,v:8, zr]΢'Ӧ⦤BoZlzxSYpKN/$b[k>*tPd="|2^26p胀 PKaA,.W+binaryfilerepo/.git/hooks/pre-rebase.sampleUT Pvcbux Xks7[.?tZkd,g'm2jز$".L3{]E;3{cyl3G.u5jhh~r1 PK^Ԟj_51j!Z;,_khRm@5DI,-uV $z1&Y.)%AGU:rK-C-OM`6fٮӝuAs^n\oXB%,S>\SL+\1Mdm?$susGY𨆪zgv =0i V/&>\ ywFvd;BV5n=v[,t%ZJ{E@'t $H+TN&l+s4"u0kEt xrl$zgg}]cjee"0rRJ PI>cMtJ?%zc Ge KZܫ(9s_}ɿ[?3lXQL<=}A D/HwhcDzRhXB~|{ 7{CXBtv_`o?۪}0o"ߩȠVeAPw0uMӓt*G^h\E OaYm29MCOX.~w'gyaWg cGϺL^e/QGXȊ˷כ};UN?h!& :*py*W w"dF .H̽eϯG ~ېA^I=-ǪU\[`@.hdOBOq]OM#;FbeV4K{p%ZB!gw310t\ںqJ~.?wxA`?:>]n'O"FfsH$6ʸh84a*+;l1_һ bu5aP%rr[-RYB;13ދ ذ̯3@S%7O,JPK%o8^Ηq0ŧLYg2R%ZiAE  ;*4kٌ 5]_}E#wKA8.bhAMLVq?>y?L2. (5t/Dc&0x"'ej#@?^won^y; o`+Yl◰CREF7үtW =<t:%=qa"t`0R/ЊC_|g]]e|:VI@_W9' Ǘݢ ˯@6Vs)H)Ι Ҍ"WH uN 6 (: *o+b8ZT}@w]mxW|a]D(`Lwzxos ǫ@v7E!$٭·73Wgo{!vnx1qӌʹyt *ee#'3Bp1 8 rǖr҅ zѓwck]A޳4M mWO)goyK&^>t3ș6͖ .p_S&ZlfcI/r7ڞy^y4+}s_ m6%vcog_"W7RFVmnj 2|#&zp:*RW%6fܙO6mc=BV͠ӳ0X)Ω3i46vpq!mfQVcMp*%u 𸕕DTŕX!%B_Kqg"l!jX%H7ͧ92dt~@'rp6>.xNf-3(sCY (oP@Sz38= ;=҄‡>xMHlpk%*2'٫41NTA~!,|]|zw9i㨙,Aq8-_dt㲟Zz%)Qx:zDv~˓PKaARv'binaryfilerepo/.git/hooks/update.sampleUT Pvcbux WQo6~~U6ڰ${K ] }PlPLY\ddKk'HIwwwݠK%l8J=s4dM3?jZI&26הjA\ 9esa(1O|dcHkaRb|i"قNXe3|)hfS1"N$ž) W Q2s,uY=S2$in4SnRﺺYFSNN6i\uzrTZ/xVFM,3qáwWXH N VXNH8KysΌP2u@Os&-C0p% exV䖇u’ד ^,Q&$ {gaa2JOpt)_4FaOw˥E$SͭGKY-# P^4#jDxJΘ)}[wzP jni~Jvܼl/X5Zuv꽷-vh3v~L~w z@}a<øX n:1Ł #%*'uO*4`yl4jSpLsCUK˃Y!G!DʔCH}nfsyp ;XrdS|(Fu}Բ+F'@(su>zo)}UQ#?zЮg713vMx^FQe `wp'vL#b>:UyVrnu|ǵҵv^|kNvj COj[Qaudl^+>uB8t65};[u;Yrmu ˡF|z|JZC&/ JWb;uboϽ'=Lk;3e^·_Rx?ϪiG-|mi}C, S-C[kNǍRH|lSBEw= ֤UHS]݅PK8G~\binaryfilerepo/.git/indexUT /Vvcbux s rf```b0f@F3201^d`h\y'p==\L9ϰ;)3/2>-3'0$HI2*p1{i.vDoMg?`ypF6oSOߎ}<PK aRTbinaryfilerepo/.git/info/UT vcbvcbux PKaAw=! binaryfilerepo/.git/info/excludeUT Pvcbux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PK aRTbinaryfilerepo/.git/logs/UT vcbvcbux PK8Gq/Sbinaryfilerepo/.git/logs/HEADUT /Vvcbux λ0 # EI4uLLTS J>MPK aRTbinaryfilerepo/.git/objects/b8/UT vcbvcbux PK b8G=JEbinaryfilerepo/.git/objects/b8/35d73543244b6694f36a8c5dfdffb71b153db7UT /Vvcbux xKOR``HK,$PK aRTbinaryfilerepo/.git/objects/d4/UT vcbvcbux PK g8GIm% Ebinaryfilerepo/.git/objects/d4/c5ad8f3d1c419ec991230ae43dd967c9735cefUT /Vvcbux xK ]swkA%syX* o/\jfB]a3AO qh_[8R/q R\ y[Gح۹&M4q@m#GR.,~>$PK aA!binaryfilerepo/.git/objects/info/UT Pvcbux PK aA!binaryfilerepo/.git/objects/pack/UT Pvcbux PK aAbinaryfilerepo/.git/refs/UT Pvcbux PK aRTbinaryfilerepo/.git/refs/heads/UT vcbvcbux PK 8G)n))%binaryfilerepo/.git/refs/heads/masterUT /Vvcbux 956eb78a3f8dce2f5d2e97cfba5e2f0d16ea976c PK aAbinaryfilerepo/.git/refs/tags/UT Pvcbux PK p8Gqbinaryfilerepo/binary_fileUT /Vvcbux change PK p8GAbinaryfilerepo/UT/Vux PK aRTAIbinaryfilerepo/.git/UTvcbux PK aAAbinaryfilerepo/.git/branches/UTPux PK 8Gw"binaryfilerepo/.git/COMMIT_EDITMSGUT/Vux PKPBqG_binaryfilerepo/.git/configUTaQux PKaA7?I9binaryfilerepo/.git/descriptionUTPux PK aA+isbinaryfilerepo/.git/HEADUTPux PK aRTA:binaryfilerepo/.git/hooks/UTvcbux PKaA`X /큎binaryfilerepo/.git/hooks/applypatch-msg.sampleUTPux PKaA+binaryfilerepo/.git/hooks/commit-msg.sampleUTPux PKaA ,]binaryfilerepo/.git/hooks/post-update.sampleUTPux PKaA%[9/Mbinaryfilerepo/.git/hooks/pre-applypatch.sampleUTPux PKaApٞ+큳 binaryfilerepo/.git/hooks/pre-commit.sampleUTPux PKaA,.W+큶 binaryfilerepo/.git/hooks/pre-rebase.sampleUTPux PKaA3binaryfilerepo/.git/hooks/prepare-commit-msg.sampleUTPux PKaARv'binaryfilerepo/.git/hooks/update.sampleUTPux PK8G~\binaryfilerepo/.git/indexUT/Vux PK aRTAbinaryfilerepo/.git/info/UTvcbux PKaAw=! binaryfilerepo/.git/info/excludeUTPux PK aRTAbinaryfilerepo/.git/logs/UTvcbux PK8Gq/SR binaryfilerepo/.git/logs/HEADUT/Vux PK g8GAQ!binaryfilerepo/.git/logs/refs/UT/Vux PK g8G$A!binaryfilerepo/.git/logs/refs/heads/UT/Vux PK8Gq/S*"binaryfilerepo/.git/logs/refs/heads/masterUT/Vux PK aRTA#binaryfilerepo/.git/objects/UTvcbux PK aRTAi#binaryfilerepo/.git/objects/01/UTvcbux PK 8G0-88E$#binaryfilerepo/.git/objects/01/8f4a29a24ed6c9c0914b2941539108b79be004UT/Vux PK aRTAy$binaryfilerepo/.git/objects/07/UTvcbux PK g8Gr588E$$binaryfilerepo/.git/objects/07/0b930029d89b63b88b87ced933045aefe97a30UT/Vux PK aRTA%binaryfilerepo/.git/objects/86/UTvcbux PK 8GS)[E$%binaryfilerepo/.git/objects/86/e5c1008b5ce635d3e3fffa4434c5eccd8f00b6UT/Vux PK aRTAx&binaryfilerepo/.git/objects/95/UTvcbux PK 8G{E$&binaryfilerepo/.git/objects/95/6eb78a3f8dce2f5d2e97cfba5e2f0d16ea976cUT/Vux PK aRTA'binaryfilerepo/.git/objects/b8/UTvcbux PK b8G=JE$R(binaryfilerepo/.git/objects/b8/35d73543244b6694f36a8c5dfdffb71b153db7UT/Vux PK aRTA(binaryfilerepo/.git/objects/d4/UTvcbux PK g8GIm% E$A)binaryfilerepo/.git/objects/d4/c5ad8f3d1c419ec991230ae43dd967c9735cefUT/Vux PK aA!AF*binaryfilerepo/.git/objects/info/UTPux PK aA!A*binaryfilerepo/.git/objects/pack/UTPux PK aAA*binaryfilerepo/.git/refs/UTPux PK aRTAO+binaryfilerepo/.git/refs/heads/UTvcbux PK 8G)n))%+binaryfilerepo/.git/refs/heads/masterUT/Vux PK aAA0,binaryfilerepo/.git/refs/tags/UTPux PK p8Gq,binaryfilerepo/binary_fileUT/Vux PK,,y,libgit2-pygit2-a011e26/test/data/blameflagsrepo.zip000066400000000000000000000571441473744024100222230ustar00rootroot00000000000000PK ORblameflagsrepo/UT `cbux PK -fQBڎblameflagsrepo/.gitignoreUT  Qcbux *.swp PK OR44e))blameflagsrepo/hello.txtUT `cbux hello world hola mundo bonjour le monde PK -VRblameflagsrepo/.git/UT `cbux PK sN>blameflagsrepo/.git/logs/UT y_YMcbux PK sN>blameflagsrepo/.git/logs/refs/UT y_YMcbux PK sN>$blameflagsrepo/.git/logs/refs/heads/UT y_YMcbux PKOR;BY,*blameflagsrepo/.git/logs/refs/heads/masterUT `cbux Kn1)j B{H vyƨQwgNl@."vM*Km(qʆ j1:XEGコl$|N5çHw?;}]viނ >xx-/Zw+c]+ #3ǨP t6|DmV)iR1m0p3tzԞw1q2HY)Q&fϦ$F'2<2Cͼw 2q1U $g/t$RBeR:Dʑv#u,N|inf!{sQ[4(Q;Ө/\yns恞izy9L:`}2L,"`L%+]ь-gr>,SWsn MPK,fQBM}Y0(blameflagsrepo/.git/logs/refs/heads/i18nUT  Qcbux MN@ #"d~ U`QJBgbL6xgzm(Ib+6d(^&:QމUX[>?/|ҫv"ekP:wGKTH}.v::L2g[;"NmؠRb c?Fsklhv=lԷmqd)`^(&5M0kL \[mmGr^NJQ($iԘ*Me> <;_ndܵ}ɒ9+*g~ss~w=K9M@=C/PKOR}.7blameflagsrepo/.git/logs/HEADUT `cbux n0Sx BD_FjR.RYPaTI:0,x'6@_ E +Jr>CiE ` ;.AzG)dI%qFbMC`#y~!G8OW&s¸38,yJl'8wyʰz2'y'UWi/%4'm݂oI za5pQ+\qT`QAX:59 YjU:ry]ցŃdxdNZ V{>U/;M}6}+8 qKz.Q 0X#z#ie7-~@ gեGecG,FPYI,(H ߌ- ݸoȀzY5Cu͡'#/q3h{] eC}56< A 6`!I@JÁ;wbXb3}ttiV PUCSXqJ }t|_lGallVE؞ZqiwivͪW/Aj[G}kO, ȌiD]M$TK򁜻m۳j*))bZPKʋPBXblameflagsrepo/.git/configUT Qcbux 5 0 )GQؓզFv/N$I߮Q%S=5lIĸJD]3=!F,s`VLu`y+M0AͥoEiتlJ`> Llw{˴͂2,b}PKOR 8ƾblameflagsrepo/.git/indexUT `cbux s rf```{Gk$dj?*>=00wqPM :Sw\Xk;yHFJ/?PK sN>blameflagsrepo/.git/refs/UT y_YMcbux PK ORblameflagsrepo/.git/refs/heads/UT `cbux PK OR>))%blameflagsrepo/.git/refs/heads/masterUT `cbux eaddae1752f876d0de669f2bcfd3087f4e2256f4 PK ,fQBA=))#blameflagsrepo/.git/refs/heads/i18nUT  Qcbux 5470a671a80ac3789f1a6a8cefbcf43ce7af0563 PK sN>blameflagsrepo/.git/refs/tags/UT y_YMcbux PK ORblameflagsrepo/.git/objects/UT `cbux PK ORblameflagsrepo/.git/objects/73/UT `cbux PK OR$%XXEblameflagsrepo/.git/objects/73/da8935960429bba1fc51e252cc79ae0db6d9abUT `cbux x+)JMU07e040031QK,L/Je.۷gNIciZAUeT0,jè/#l6^oiZ'vPK sN>!blameflagsrepo/.git/objects/pack/UT y_YMcbux PKsN>V{Sblameflagsrepo/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.packUT y_YMcbux  ptf```b Bsv[g9 VMg[|CʑC3Hi(j\1dze:_苵-2a'8Yfu ~.BSH`zۊ[zNh4ͶYԲncfZVEQՇ?i))5mܞ;ɚ6W^?o95=Y~ݿe ׫>ʸ;3~bND~70|Bɫ}͵;2VY.R+>.\nOLT^"ojw{[zl{-ZR10y>O{ Mt?Vd'ٓ'L1 xro5)?vk+,_tk*99/i?`b`cfܻbi'Ok?ԉ!'tz|uSA',tRblameflagsrepo/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.idxUT y_YMcbux _@]HcDfƂYY)l80;bN,kbn.۷gNIciZi?-tN1u⹼fvڅSygWʷx*t{Zoгy:ӇkN.9]9UM>_mrҘ(gvEsy䍽=]p@9wAegU>Z=ni IXl=Z Yr⋀XLŲle,~x6C}0O>!/.ˌ[!}g%'PK ,fQBblameflagsrepo/.git/objects/54/UT  Qcbux PK ,fQB.Eblameflagsrepo/.git/objects/54/70a671a80ac3789f1a6a8cefbcf43ce7af0563UT  Qcbux x1N1#N^۷ޓPQڋ8)@{D@7ӌyf063XrJf42L0bZݧl$+)")aPi\'\%7x]KnO>p|{|0Lvw~:? 'Z4Uh~XMtPK NRblameflagsrepo/.git/objects/50/UT `cbux PK NRXo88Eblameflagsrepo/.git/objects/50/62782362b06a2b86da192977ec8e7428323154UT `cbux xKOR01`H*/IIT-KJ/-RIUKIPK ,fQBblameflagsrepo/.git/objects/97/UT  Qcbux PK ,fQBw_ooEblameflagsrepo/.git/objects/97/33b6ee7f6b3cb0a276586a413c8b281baeb8bdUT  Qcbux x+)JMU040f040031QHL+(akVJﹾ|:89TIFjNN>XRC4ZD"v*(/~󗄼3e<~0 PK ORblameflagsrepo/.git/objects/ea/UT `cbux PK ORܕEblameflagsrepo/.git/objects/ea/ddae1752f876d0de669f2bcfd3087f4e2256f4UT `cbux xM0aήbx'Ή f%Rlok4@cD@:PHO{=UG3yv{Kc; t  ۿCTvg5X.j^/ 7TPK 'fQBblameflagsrepo/.git/objects/79/UT  Qcbux PK 'fQBR5d$$Eblameflagsrepo/.git/objects/79/9baaf570f1f03a22ef0af4f2cfc36c836635f8UT  Qcbux xKOR02`HLU(/IJ,U(J-, KPK ORblameflagsrepo/.git/objects/30/UT `cbux PK OR>99Eblameflagsrepo/.git/objects/30/a280f7ef88328e68569b0fbd4b1c962aab9272UT `cbux xKOR01dHWP(/IIT-KJ/-RIUKIPK 'fQBblameflagsrepo/.git/objects/32/UT  Qcbux PK 'fQBipEblameflagsrepo/.git/objects/32/3fae03f4606ea9991df8befbb2fca795e648faUT  Qcbux xKOR0gHOJ,"zPK NRblameflagsrepo/.git/objects/1c/UT ;`cbux PK NRCl߽Eblameflagsrepo/.git/objects/1c/ef51e3f29d020891228e804baa303d87c9b49bUT ;`cbux xi!<[!8BH ftd5^ki ݑϽmjeKV)]tbMarM1p˂~hY|0x,Fɐ,(9IQm}0sCMsŸ܎$;RC=tW@w< o}4z}ՌRGPK sN>!blameflagsrepo/.git/objects/info/UT y_YMcbux PK sN>66&blameflagsrepo/.git/objects/info/packsUT y_YMcbux P pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.pack PK NRblameflagsrepo/.git/objects/b6/UT `cbux PK NRiWWEblameflagsrepo/.git/objects/b6/f333fef0028a797c484e31db17cf987b8c662bUT `cbux x+)JMU07e040031QK,L/Je.۷gNIciZAUeT0$U('mn%YD0=PK sN>blameflagsrepo/.git/hooks/UT y_YMcbux PKsN>lz,blameflagsrepo/.git/hooks/post-commit.sampleUT y_YMcbux -A b WM'?!PKeIU/^g^9IQ;Auk -PCT8JosԯNr*{ k-tq$X;Cm_b)}P PKsN> ,blameflagsrepo/.git/hooks/post-update.sampleUT y_YMcbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPKsN>%[9/blameflagsrepo/.git/hooks/pre-applypatch.sampleUT y_YMcbux }1O0g+i҈ $$uGNzO}})SZ{ۦdgfQJܟptV񜷄NgU@{q8ECf-ں8슗GV $ju,^)i9 G*]3cY%BT@b#_癠&ژ5uvu&QʊzB|l_6oL|ZwipW-oh~PKsN>i 03blameflagsrepo/.git/hooks/prepare-commit-msg.sampleUT y_YMcbux }S]s0|&3M?,[;L) ig[T$ɏ$9!2y޽S\J]nM2Ѐ?ŪW1*+{@owYeZXsłB)a #Ow 4HNP!:2Sb i&A;flK3Plota˿Q!y4R4m4ր.'.zZiһ@hC$ [ʵ @"n2r㘍.;0-eF7JVޝ0>'h^ndZaet U?DT̴Mxemzƌ; F`um̞CȊtz1E9u&Ʀgcz=8d,jiN[hJ2+Do).L1dYfӷ#G&Pƫz ,8̠́deVlXBQO&3jt4IHS zeEN'DA;V"S ~JHLpxD\?Ga׻aMGt4a_/i"X,m5Kk˔j< PKsN>+blameflagsrepo/.git/hooks/commit-msg.sampleUT y_YMcbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKsN>}.U(-blameflagsrepo/.git/hooks/post-receive.sampleUT y_YMcbux eQr0 +T6<IaPlx vv(*C7K]yW'+5ZӅ`p Az3ECV 1$\it%7}pM?[@'g0JIS$KЪ˙W,̓B' CX42׻v DujbaEy`-td>xY]U*][ׅ]ՍRR5Xni{]enm]m]TU;5A *g D b >9BHy.f+ݸL~,"\%0Vx[5|ce 9x>6zIq21s\oPKsN> \*+blameflagsrepo/.git/hooks/pre-commit.sampleUT y_YMcbux }T[k:~ Iw!I’}9Slڒ+ɛl)$[SܾYarH+M$ڮaِ+OV'b-v~Bn'tW!Ȁ[f2.:,/eqŵ_բUU0/狤R U3}$$ 61r+\TDp;r[No{ΓqoM=hӆ9[+blameflagsrepo/.git/hooks/pre-rebase.sampleUT y_YMcbux Xks7[.?tZkd,g'm2jز$".L3{]E;3{cyl3G.u5jhh~r1 PK^Ԟj_51j!Z;,_khRm@5DI,-uV $z1&Y.)%AGU:rK-C-OM`6fٮӝuAs^n\oXB%,S>\SL+\1Mdm?$susGY𨆪zgv =0i V/&>\ ywFvd;BV5n=v[,t%ZJ{E@'t $H+TN&l+s4"u0kEt xrl$zgg}]cjee"0rRJ PI>cMtJ?%zc Ge KZܫ(9s_}ɿ[?3lXQL<=}A D/HwhcDzRhXB~|{ 7{CXBtv_`o?۪}0o"ߩȠVeAPw0uMӓt*G^h\E OaYmH29MCW%x.~w'gyaWg ϻ&-ڦ3<_: p70|w2ь{CLt:U}9GN+0bU DNAp]<{ʞ_FAp1Ʒ"K D!zZUs߫ Iz۷>^]. u1 &q`GGX[Z,h_ "j.SJr}HkGr(B3L%xw ;@OE5"*efTЈl|Is'rհ3UCȩS˩okKjfQgh x7`V0&ϸoNY`<(A5B/x~f^8_6N0i|J(k&=(X3_(ӬNd3F/~t}}A`Z+.EV x861Y=Ճ ˷!:2˸d4jcĴ3ЕCPi\x)߽y,KfQ_’I >a#H]7tDFևiHP@+~!ėuu-wXa$3'}]5g*3i_v:(lګ.J>6f[y'"@:g.VJ3^!/ddA:ؔ&(`kQ߿v_YuY29D;vͷs(l>YlhnT d*ÃJ:bPX\MNIkǒm3*Q4Īm**06o[y^H"V(GO&F{YOHpwAf{t7ݗ^=u.›x2"g>M@6[c~OzHh^]R}B%ɿ"Ah{yyҬS}"o3ٔ؍/b|^LRKjX(o ExnJWO_.A|H!Dp"~ 5* c4/PKsN>`X /blameflagsrepo/.git/hooks/applypatch-msg.sampleUT y_YMcbux }=O@ gWIT$$uGY/߹k j~^ܷ;-P xGE0!]( :b 0Cf=  :F{Z:} t&NY55#$|',Z& & qG 1@=¥.K7m p}>/6{sz"LyGwRU]UTIͦf)*AP-6ۏ{[h^%X7zz[=PKsN>Rv'blameflagsrepo/.git/hooks/update.sampleUT y_YMcbux WQo6~~U6ڰ${K ] }PlPLY\ddKk'HIwwwݠK%l8J=s4dM3?jZI&26הjA\ 9esa(1O|dcHkaRb|i"قNXe3|)hfS1"N$ž) W Q2s,uY=S2$in4SnRﺺYFSNN6i\uzrTZ/xVFM,3qáwWXH N VXNH8KysΌP2u@Os&-C0p% exV䖇u’ד ^,Q&$ {gaa2JOpt)_4FaOw˥E$SͭGKY-# P^4#jDxJΘ)}[wzP jni~Jvܼl/X5Zuv꽷-vh3v~L~w z@}a<øX n:1Ł #%*'uO*4`yl4jSpLsCUK˃Y!G!DʔCH}nfsyp ;XrdS|(Fu}Բ+F'@(su>zo)}UQ#?zЮg713vMx^FQe `wp'vL#b>:UyVrnu|ǵҵv^|kNvj COj[Qaudl^+>uB8t65};[u;Yrmu ˡF|z|JZC&/ JWb;uboϽ'=Lk;3e^·_Rx?ϪiG-|mi}C, S-C[kNǍRH|lSBEw= ֤UHS]݅PK sN>blameflagsrepo/.git/info/UT y_YMcbux PKsN>Xtblameflagsrepo/.git/info/refsUT y_YMcbux U10:!xh"),\g՜Z`jhv eSuʵwz2v6 H "W}uw=! blameflagsrepo/.git/info/excludeUT y_YMcbux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PK ORN))blameflagsrepo/.git/ORIG_HEADUT `cbux 1cef51e3f29d020891228e804baa303d87c9b49b PK -fQB+isblameflagsrepo/.git/HEADUT  Qcbux ref: refs/heads/master PKsN>7?Iblameflagsrepo/.git/descriptionUT y_YMcbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK ORh/ "blameflagsrepo/.git/COMMIT_EDITMSGUT `cbux More space for hello PK ;fQBQ blameflagsrepo/bye.txtUT  Qcbux bye world PK ORAblameflagsrepo/UT`ux PK -fQBڎIblameflagsrepo/.gitignoreUT Qux PK OR44e))blameflagsrepo/hello.txtUT`ux PK -VRAblameflagsrepo/.git/UT`ux PK sN>Akblameflagsrepo/.git/logs/UTy_YMux PK sN>Ablameflagsrepo/.git/logs/refs/UTy_YMux PK sN>$Ablameflagsrepo/.git/logs/refs/heads/UTy_YMux PKOR;BY,*tblameflagsrepo/.git/logs/refs/heads/masterUT`ux PK,fQBM}Y0(kblameflagsrepo/.git/logs/refs/heads/i18nUT Qux PKOR}.7blameflagsrepo/.git/logs/HEADUT`ux PKʋPBXblameflagsrepo/.git/configUTQux PKOR 8ƾ blameflagsrepo/.git/indexUT`ux PK sN>A blameflagsrepo/.git/refs/UTy_YMux PK ORA' blameflagsrepo/.git/refs/heads/UT`ux PK OR>))% blameflagsrepo/.git/refs/heads/masterUT`ux PK ,fQBA=))# blameflagsrepo/.git/refs/heads/i18nUT Qux PK sN>A blameflagsrepo/.git/refs/tags/UTy_YMux PK ORA blameflagsrepo/.git/objects/UT`ux PK ORA< blameflagsrepo/.git/objects/73/UT`ux PK OR$%XXE$ blameflagsrepo/.git/objects/73/da8935960429bba1fc51e252cc79ae0db6d9abUT`ux PK sN>!Alblameflagsrepo/.git/objects/pack/UTy_YMux PKsN>V{S$blameflagsrepo/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.packUTy_YMux PKsN>,tR$blameflagsrepo/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.idxUTy_YMux PK ,fQBADblameflagsrepo/.git/objects/54/UT Qux PK ,fQB.E$blameflagsrepo/.git/objects/54/70a671a80ac3789f1a6a8cefbcf43ce7af0563UT Qux PK NRAblameflagsrepo/.git/objects/50/UT`ux PK NRXo88E$%blameflagsrepo/.git/objects/50/62782362b06a2b86da192977ec8e7428323154UT`ux PK ,fQBAblameflagsrepo/.git/objects/97/UT Qux PK ,fQBw_ooE$5blameflagsrepo/.git/objects/97/33b6ee7f6b3cb0a276586a413c8b281baeb8bdUT Qux PK ORA#blameflagsrepo/.git/objects/ea/UT`ux PK ORܕE$|blameflagsrepo/.git/objects/ea/ddae1752f876d0de669f2bcfd3087f4e2256f4UT`ux PK 'fQBAblameflagsrepo/.git/objects/79/UT Qux PK 'fQBR5d$$E$ blameflagsrepo/.git/objects/79/9baaf570f1f03a22ef0af4f2cfc36c836635f8UT Qux PK ORAblameflagsrepo/.git/objects/30/UT`ux PK OR>99E$blameflagsrepo/.git/objects/30/a280f7ef88328e68569b0fbd4b1c962aab9272UT`ux PK 'fQBAblameflagsrepo/.git/objects/32/UT Qux PK 'fQBipE$blameflagsrepo/.git/objects/32/3fae03f4606ea9991df8befbb2fca795e648faUT Qux PK NRAblameflagsrepo/.git/objects/1c/UT;`ux PK NRCl߽E$blameflagsrepo/.git/objects/1c/ef51e3f29d020891228e804baa303d87c9b49bUT;`ux PK sN>!A4 blameflagsrepo/.git/objects/info/UTy_YMux PK sN>66& blameflagsrepo/.git/objects/info/packsUTy_YMux PK NRA%!blameflagsrepo/.git/objects/b6/UT`ux PK NRiWWE$~!blameflagsrepo/.git/objects/b6/f333fef0028a797c484e31db17cf987b8c662bUT`ux PK sN>AT"blameflagsrepo/.git/hooks/UTy_YMux PKsN>lz,큨"blameflagsrepo/.git/hooks/post-commit.sampleUTy_YMux PKsN> ,큈#blameflagsrepo/.git/hooks/post-update.sampleUTy_YMux PKsN>%[9/x$blameflagsrepo/.git/hooks/pre-applypatch.sampleUTy_YMux PKsN>i 03%blameflagsrepo/.git/hooks/prepare-commit-msg.sampleUTy_YMux PKsN>+(blameflagsrepo/.git/hooks/commit-msg.sampleUTy_YMux PKsN>}.U(-B+blameflagsrepo/.git/hooks/post-receive.sampleUTy_YMux PKsN> \*+,blameflagsrepo/.git/hooks/pre-commit.sampleUTy_YMux PKsN>hӆ9[+큿0blameflagsrepo/.git/hooks/pre-rebase.sampleUTy_YMux PKsN>`X /9blameflagsrepo/.git/hooks/applypatch-msg.sampleUTy_YMux PKsN>Rv't:blameflagsrepo/.git/hooks/update.sampleUTy_YMux PK sN>AK?blameflagsrepo/.git/info/UTy_YMux PKsN>Xt?blameflagsrepo/.git/info/refsUTy_YMux PKsN>w=! M@blameflagsrepo/.git/info/excludeUTy_YMux PK ORN))TAblameflagsrepo/.git/ORIG_HEADUT`ux PK -fQB+isAblameflagsrepo/.git/HEADUT Qux PKsN>7?I=Bblameflagsrepo/.git/descriptionUTy_YMux PK ORh/ "Bblameflagsrepo/.git/COMMIT_EDITMSGUT`ux PK ;fQBQ FCblameflagsrepo/bye.txtUT Qux PK>>Clibgit2-pygit2-a011e26/test/data/dirtyrepo.zip000066400000000000000000000462241473744024100212560ustar00rootroot00000000000000PK Z^T dirtyrepo/UT |8bGbux PK [>/"dirtyrepo/staged_new_file_modifiedUT 'N(Tbux Other modified content PK [>/&dirtyrepo/staged_changes_file_modifiedUT w'NVbux Other modified content PK V>dirtyrepo/current_fileUT 'NVbux PK RW>dirtyrepo/new_fileUT ;'NVbux PK [>@C%dirtyrepo/staged_delete_file_modifiedUT :'NVbux Modified content PK }[>@Cdirtyrepo/staged_changesUT 'N(Tbux Modified content PK V>dirtyrepo/staged_newUT N'NVbux PK [>dirtyrepo/subdir/UT 'NGbux PK @W>dirtyrepo/subdir/current_fileUT 'NVbux PK VW>dirtyrepo/subdir/new_fileUT D'NVbux PK [>@Cdirtyrepo/subdir/modified_fileUT 'N(Tbux Modified content PK {[>@Cdirtyrepo/modified_fileUT 'N(Tbux Modified content PK [^Tdirtyrepo/.git/UT }8bGbux PK Z>dirtyrepo/.git/refs/UT 'NGbux PK Z>dirtyrepo/.git/refs/heads/UT 'NGbux PK Z>'))) dirtyrepo/.git/refs/heads/masterUT 'N(Tbux a763aa560953e7cfb87ccbc2f536d665aa4dff22 PK Z>dirtyrepo/.git/refs/tags/UT 'NGbux PK Z>dirtyrepo/.git/logs/UT 'NGbux PK Z>dirtyrepo/.git/logs/refs/UT 'NGbux PK Z>dirtyrepo/.git/logs/refs/heads/UT 'NGbux PKZ>Em{%dirtyrepo/.git/logs/refs/heads/masterUT 'NVbux I 1>*UD*#D}u*(xC< 1Qgg|ٔpy zg8Tgħ[%)Cl(-*oaqU:d.T{8 ~eEm{dirtyrepo/.git/logs/HEADUT 'NVbux I 1>*UD*#D}u*(xC< 1Qgg|ٔpy zg8Tgħ[%)Cl(-*oaqU:d.T{8 ~edirtyrepo/.git/objects/a7/UT 'NGbux PK Z>@dirtyrepo/.git/objects/a7/63aa560953e7cfb87ccbc2f536d665aa4dff22UT 'N(Tbux xK 0a9셒GU1`H;o_| ":Ĕ03ѡ F&=_GlqW_UԙFo>dxyHuHA((bA\gb~+CrCPK Z>dirtyrepo/.git/objects/e3/UT 'NGbux PK Z>@dirtyrepo/.git/objects/e3/728acced34e063a6e5830c52659f7fdb7a7858UT 'N(Tbux x+)JMU010f040031QH.-*J+OIex6M9{wk+qIOD:||JjNjIj >u)i) ,.IL*KHKO-g$Jĸ6눰WB| P(.MJ,b|R9smkWu__ PK [>dirtyrepo/.git/objects/c2/UT \'NGbux PK [>K(!!@dirtyrepo/.git/objects/c2/17c63469eca3538ca896a55f1990121a909f9eUT \'NVbux xKOR04gOLLMQH+I+jnPK Z>dirtyrepo/.git/objects/pack/UT 'NGbux PK Z>dirtyrepo/.git/objects/e6/UT 'NGbux PK Z>@dirtyrepo/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391UT 'NVbux xKOR0` PK Z>dirtyrepo/.git/objects/info/UT 'NGbux PK Z>dirtyrepo/.git/objects/b8/UT 'NGbux PK Z> NN@dirtyrepo/.git/objects/b8/5d53c9236e89aff2b62558adaa885fd1d6ff1cUT 'N(Tbux x+)JMU042d040031QH.-*J+OIex6M9{wk+qIODԜԒrS22)<9PK Tdirtyrepo/.git/objects/ea/UT MTbGbux PK T%GU@dirtyrepo/.git/objects/ea/10ec85c10797d76afeef56f8bfc26da40913bbUT MTbVbux xKOR`L/JM(!PKZ>7?Idirtyrepo/.git/descriptionUT 'NVbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK Z>aAEdirtyrepo/.git/COMMIT_EDITMSGUT 'NVbux Initial commit of test files PK Z>dirtyrepo/.git/info/UT 'NGbux PKZ>w=!dirtyrepo/.git/info/excludeUT 'N(Tbux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PK Z>dirtyrepo/.git/branches/UT 'NGbux PK Z>dirtyrepo/.git/hooks/UT 'NGbux PKZ> \*&dirtyrepo/.git/hooks/pre-commit.sampleUT 'NVbux }T[k:~ Iw!I’}9Slڒ+ɛl)$[SܾYarH+M$ڮaِ+OV'b-v~Bn'tW!Ȁ[f2.:,/eqŵ_բUU0/狤R U3}$$ 61r+\TDp;r[No{ΓqoM=.dirtyrepo/.git/hooks/prepare-commit-msg.sampleUT 'NVbux uS]s0|&3M?,[;L) ig[T\Iɏ$9! =e˥ԥ&$ Sz {a|PJzP:'Z,{)ZMb$}QZLύTJx #cQ<%ƀ1Foh#hVX̻4!H& U*G!K3F_h 8/rAbiǺ^Ω8(,Fb%`DWe$0lp u2qlupށh)4Q8Av#{J,+V!bl<-#4Gl>%6fܙO6mc=BV͠ӳ0X)Ω3i46vpq!mfQVcMp*%u 𸕕DTŕX!%B_Kqg"l!jX%H7ͧ92dt~@'rp6>.xNf-3(sCY (oP@Sz38= ;=҄‡>xMHlpk%*2'٫41NTA~!,|]|zw9i㨙,Aq8-_dt㲟Zz%)Qx:zDv~˓PKZ>`X *dirtyrepo/.git/hooks/applypatch-msg.sampleUT 'NVbux }=O@ gWIT$$uGY/߹k j~^ܷ;-P xGE0!]( :b 0Cf=  :F{Z:} t&NY55#$|',Z& & qG 1@=¥.K7m p}>/6{sz"LyGwRU]UTIͦf)*AP-6ۏ{[h^%X7zz[=PKZ>%[9*dirtyrepo/.git/hooks/pre-applypatch.sampleUT 'NVbux }1O0g+i҈ $$uGNzO}})SZ{ۦdgfQJܟptV񜷄NgU@{q8ECf-ں8슗GV $ju,^)i9 G*]3cY%BT@b#_癠&ژ5uvu&QʊzB|l_6oL|ZwipW-oh~PKZ>Rv"dirtyrepo/.git/hooks/update.sampleUT 'NVbux WQo6~~U6ڰ${K ] }PlPLY\ddKk'HIwwwݠK%l8J=s4dM3?jZI&26הjA\ 9esa(1O|dcHkaRb|i"قNXe3|)hfS1"N$ž) W Q2s,uY=S2$in4SnRﺺYFSNN6i\uzrTZ/xVFM,3qáwWXH N VXNH8KysΌP2u@Os&-C0p% exV䖇u’ד ^,Q&$ {gaa2JOpt)_4FaOw˥E$SͭGKY-# P^4#jDxJΘ)}[wzP jni~Jvܼl/X5Zuv꽷-vh3v~L~w z@}a<øX n:1Ł #%*'uO*4`yl4jSpLsCUK˃Y!G!DʔCH}nfsyp ;XrdS|(Fu}Բ+F'@(su>zo)}UQ#?zЮg713vMx^FQe `wp'vL#b>:UyVrnu|ǵҵv^|kNvj COj[Qaudl^+>uB8t65};[u;Yrmu ˡF|z|JZC&/ JWb;uboϽ'=Lk;3e^·_Rx?ϪiG-|mi}C, S-C[kNǍRH|lSBEw= ֤UHS]݅PKZ>lz'dirtyrepo/.git/hooks/post-commit.sampleUT 'NVbux -A b WM'?!PKeIU/^g^9IQ;Auk -PCT8JosԯNr*{ k-tq$X;Cm_b)}P PKZ>B+k&dirtyrepo/.git/hooks/pre-rebase.sampleUT 'NVbux Xks۸,R=l}:f&3;ı! & hc "-+N|=|7Įā8 ]nL\9'iL?WE~,Z)FIRK*:6 Zj"q:-W=,Ȃ *9HOt9K-C ϩu`6fٮuA25^Bg^R+}z4y(L,S13LkE,7x;6mhRExw1i`f^+2֕^[8&2M"Lr+H"2 D,<k|ʹC؛2 I cl"[eҖQ ,K z`f3">;eŧqW r tJS9$Io*~w豛|4ӫ[ V"*]c\9) )^C,e@tNN / D vo0rRJf5PI>cMri!L"@3q}Ud >`ߍ%+M/R JbSE,|,Xl:h<MZ܄ې-@u$8`T P1mMb>Սi L\9AP7*ן8X71;^o5(3%7i3-m 3gAVFWmؘw/tmnx'8X|K!ڸ+g{Su-;uc[uފξ U9rz4N -# I4]p33T&C7\$>#Xal(YW{߀ɫE7esYrv:G0}u|@Rן|(&hElTNA/{S^ &6dײW8z(=DRO˱;A*W޽Q tbFኈkIr)UʕHԨġ[iQamOak :ϑ^cu*ϬJ`~%Mr(坲X5~bQ„/9.~Mp)> a: *QJ*zfP 0&)U'ޠȦ _@i-, \b5!lwC"plbҒ[vpCtyqרƘ؜g@E9|!R|ǣ9)2rIx[ٗb]jX}(b~B1 [#Q㳈/?2oӲHgN2<1Tg88luXPشS]~|l̰cLEr9sTQD\}!# EGAEߊ /<̒!ر@o,Q{Oalj d×Dt2_\ ۍfjM/z͢2^},al\:rn-E}l +Y| ; CiB)ܡt(bxh4oĕ䚩'gthlM)H~SI|> \V~K&-h$DŽX9MO}.U((dirtyrepo/.git/hooks/post-receive.sampleUT 'NVbux eQr0 +T6<IaPlx vv(*C7K]yW'+5ZӅ`p Az3ECV 1$\it%7}pM?[@'g0JIS$KЪ˙W,̓B' CX42׻v DujbaEy`-td>xY]U*][ׅ]ՍRR5Xni{]enm]m]TU;5A *g D b >9BHy.f+ݸL~,"\%0Vx[5|ce 9x>6zIq21s\oPKZ>&dirtyrepo/.git/hooks/commit-msg.sampleUT 'NVbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKZ> 'dirtyrepo/.git/hooks/post-update.sampleUT 'NVbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPK Z>+isdirtyrepo/.git/HEADUT 'N Gbux ref: refs/heads/master PKTd| xdirtyrepo/.git/indexUT MTbMTbux s rf```bޤ !GTa4PLE{F<CJMA׳PN \z%yE P4G^ZdP!y_lٛ.^s\WuГ <ɥEEy%i9Pߗ#@10M%<9)9%)x͋"<̴(h ?ffqpϊiK%'IM?$1hZrFb^zj1]Րie4yLC͏%|l=_!— j~^j9ܽ/i{2OazXKb@&e0@epqiRJf>f6,2 uo*IBf"2 57(sEbf {==@ž'fOuPKTۚpdirtyrepo/.git/configUT PTbPTbux 51! N d"BY„cg _oxfUvL6[+YV-)vd"zPI[ߋwɸd:ѻ;x,T`V,g0 PKY^T= dirtyrepo/.gitignoreUT y8by8bux L/JMʄPK Tdirtyrepo/untracked_dir/UT gTbGbux PK T&dirtyrepo/untracked_dir/untracked_fileUT gTbVbux PK Z^Tdirtyrepo/ignoredUT |8b|8bux PK Z^T Adirtyrepo/UT|8bux PK [>/"Ddirtyrepo/staged_new_file_modifiedUT'Nux PK [>/&dirtyrepo/staged_changes_file_modifiedUTw'Nux PK V>.dirtyrepo/current_fileUT'Nux PK RW>~dirtyrepo/new_fileUT;'Nux PK [>@C%dirtyrepo/staged_delete_file_modifiedUT:'Nux PK }[>@C:dirtyrepo/staged_changesUT'Nux PK V>dirtyrepo/staged_newUTN'Nux PK [>Adirtyrepo/subdir/UT'Nux PK @W>6dirtyrepo/subdir/current_fileUT'Nux PK VW>dirtyrepo/subdir/new_fileUTD'Nux PK [>@Cdirtyrepo/subdir/modified_fileUT'Nux PK {[>@CIdirtyrepo/modified_fileUT'Nux PK [^TAdirtyrepo/.git/UT}8bux PK Z>Adirtyrepo/.git/refs/UT'Nux PK Z>ABdirtyrepo/.git/refs/heads/UT'Nux PK Z>'))) dirtyrepo/.git/refs/heads/masterUT'Nux PK Z>Adirtyrepo/.git/refs/tags/UT'Nux PK Z>Aldirtyrepo/.git/logs/UT'Nux PK Z>Adirtyrepo/.git/logs/refs/UT'Nux PK Z>A dirtyrepo/.git/logs/refs/heads/UT'Nux PKZ>Em{%fdirtyrepo/.git/logs/refs/heads/masterUT'Nux PKZ>Em{Ddirtyrepo/.git/logs/HEADUT'Nux PK TA dirtyrepo/.git/objects/UTMTbux PK Z>Af dirtyrepo/.git/objects/a7/UT'Nux PK Z>@$ dirtyrepo/.git/objects/a7/63aa560953e7cfb87ccbc2f536d665aa4dff22UT'Nux PK Z>A dirtyrepo/.git/objects/e3/UT'Nux PK Z>@$ dirtyrepo/.git/objects/e3/728acced34e063a6e5830c52659f7fdb7a7858UT'Nux PK [>A, dirtyrepo/.git/objects/c2/UT\'Nux PK [>K(!!@$ dirtyrepo/.git/objects/c2/17c63469eca3538ca896a55f1990121a909f9eUT\'Nux PK Z>A dirtyrepo/.git/objects/pack/UT'Nux PK Z>Aq dirtyrepo/.git/objects/e6/UT'Nux PK Z>@$ dirtyrepo/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391UT'Nux PK Z>ANdirtyrepo/.git/objects/info/UT'Nux PK Z>Adirtyrepo/.git/objects/b8/UT'Nux PK Z> NN@$dirtyrepo/.git/objects/b8/5d53c9236e89aff2b62558adaa885fd1d6ff1cUT'Nux PK TAdirtyrepo/.git/objects/ea/UTMTbux PK T%GU@$dirtyrepo/.git/objects/ea/10ec85c10797d76afeef56f8bfc26da40913bbUTMTbux PKZ>7?Idirtyrepo/.git/descriptionUT'Nux PK Z>aAE8dirtyrepo/.git/COMMIT_EDITMSGUT'Nux PK Z>Adirtyrepo/.git/info/UT'Nux PKZ>w=!dirtyrepo/.git/info/excludeUT'Nux PK Z>Adirtyrepo/.git/branches/UT'Nux PK Z>ANdirtyrepo/.git/hooks/UT'Nux PKZ> \*&큝dirtyrepo/.git/hooks/pre-commit.sampleUT'Nux PKZ>.Ydirtyrepo/.git/hooks/prepare-commit-msg.sampleUT'Nux PKZ>`X *Xdirtyrepo/.git/hooks/applypatch-msg.sampleUT'Nux PKZ>%[9*dirtyrepo/.git/hooks/pre-applypatch.sampleUT'Nux PKZ>Rv"'dirtyrepo/.git/hooks/update.sampleUT'Nux PKZ>lz'!dirtyrepo/.git/hooks/post-commit.sampleUT'Nux PKZ>B+k&"dirtyrepo/.git/hooks/pre-rebase.sampleUT'Nux PKZ>}.U((+dirtyrepo/.git/hooks/post-receive.sampleUT'Nux PKZ>&,dirtyrepo/.git/hooks/commit-msg.sampleUT'Nux PKZ> '+/dirtyrepo/.git/hooks/post-update.sampleUT'Nux PK Z>+is0dirtyrepo/.git/HEADUT'Nux PKTd| xz0dirtyrepo/.git/indexUTMTbux PKTۚpK2dirtyrepo/.git/configUTPTbux PKY^T=  3dirtyrepo/.gitignoreUTy8bux PK TAd3dirtyrepo/untracked_dir/UTgTbux PK T&3dirtyrepo/untracked_dir/untracked_fileUTgTbux PK Z^T4dirtyrepo/ignoredUT|8bux PK==a4libgit2-pygit2-a011e26/test/data/emptyrepo.zip000066400000000000000000000244101473744024100212520ustar00rootroot00000000000000PK aA emptyrepo/UT P cbux PK Temptyrepo/.git/UT >rb cbux PK aAemptyrepo/.git/hooks/UT P cbux PKaA 'emptyrepo/.git/hooks/post-update.sampleUT P cbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPKaARv"emptyrepo/.git/hooks/update.sampleUT P cbux WQo6~~U6ڰ${K ] }PlPLY\ddKk'HIwwwݠK%l8J=s4dM3?jZI&26הjA\ 9esa(1O|dcHkaRb|i"قNXe3|)hfS1"N$ž) W Q2s,uY=S2$in4SnRﺺYFSNN6i\uzrTZ/xVFM,3qáwWXH N VXNH8KysΌP2u@Os&-C0p% exV䖇u’ד ^,Q&$ {gaa2JOpt)_4FaOw˥E$SͭGKY-# P^4#jDxJΘ)}[wzP jni~Jvܼl/X5Zuv꽷-vh3v~L~w z@}a<øX n:1Ł #%*'uO*4`yl4jSpLsCUK˃Y!G!DʔCH}nfsyp ;XrdS|(Fu}Բ+F'@(su>zo)}UQ#?zЮg713vMx^FQe `wp'vL#b>:UyVrnu|ǵҵv^|kNvj COj[Qaudl^+>uB8t65};[u;Yrmu ˡF|z|JZC&/ JWb;uboϽ'=Lk;3e^·_Rx?ϪiG-|mi}C, S-C[kNǍRH|lSBEw= ֤UHS]݅PKaApٞ&emptyrepo/.git/hooks/pre-commit.sampleUT P cbux }T]oF|6F6)YP ۅ n0CGޞx5y%+3wd8-Əٝ7RVY]T[Dr ,mim-iO}^2Umu, TMÒM:&ա&cIu߲ z]j71YAT`ƾfM:g;E`j{f 5bP!%6PID0%OEҤs,ӊ"#Ǜ38WLffE&;k1$svtLZ4#Z)##7nɖs^,˫;Y].Uy嵘_ݲTWzquJ|)GE;ۧDF؍ݦ _iFޞ%M}=x7@5(I^NЊY$!y%yw[X w=;"zkp{I>Sϗ?gy|tW59nhG ^"b2$S+Xqz\W~Z4Ewlq,v:8, zr]΢'Ӧ⦤BoZlzxSYpKN/$b[k>*tPd="|2^26p胀 PKaA,.W&emptyrepo/.git/hooks/pre-rebase.sampleUT P cbux Xks7[.?tZkd,g'm2jز$".L3{]E;3{cyl3G.u5jhh~r1 PK^Ԟj_51j!Z;,_khRm@5DI,-uV $z1&Y.)%AGU:rK-C-OM`6fٮӝuAs^n\oXB%,S>\SL+\1Mdm?$susGY𨆪zgv =0i V/&>\ ywFvd;BV5n=v[,t%ZJ{E@'t $H+TN&l+s4"u0kEt xrl$zgg}]cjee"0rRJ PI>cMtJ?%zc Ge KZܫ(9s_}ɿ[?3lXQL<=}A D/HwhcDzRhXB~|{ 7{CXBtv_`o?۪}0o"ߩȠVeAPw0uMӓt*G^h\E OaYm29MCOX.~w'gyaWg cGϺL^e/QGXȊ˷כ};UN?h!& :*py*W w"dF .H̽eϯG ~ېA^I=-ǪU\[`@.hdOBOq]OM#;FbeV4K{p%ZB!gw310t\ںqJ~.?wxA`?:>]n'O"FfsH$6ʸh84a*+;l1_һ bu5aP%rr[-RYB;13ދ ذ̯3@S%7O,JPK%o8^Ηq0ŧLYg2R%ZiAE  ;*4kٌ 5]_}E#wKA8.bhAMLVq?>y?L2. (5t/Dc&0x"'ej#@?^won^y; o`+Yl◰CREF7үtW =<t:%=qa"t`0R/ЊC_|g]]e|:VI@_W9' Ǘݢ ˯@6Vs)H)Ι Ҍ"WH uN 6 (: *o+b8ZT}@w]mxW|a]D(`Lwzxos ǫ@v7E!$٭·73Wgo{!vnx1qӌʹyt *ee#'3Bp1 8 rǖr҅ zѓwck]A޳4M mWO)goyK&^>t3ș6͖ .p_S&ZlfcI/r7ڞy^y4+}s_ m6%vcog_"W7RFVmnj 2|#&zp:*RW/6{sz"LyGwRU]UTIͦf)*AP-6ۏ{[h^%X7zz[=PKaA%[9*emptyrepo/.git/hooks/pre-applypatch.sampleUT P cbux }1O0g+i҈ $$uGNzO}})SZ{ۦdgfQJܟptV񜷄NgU@{q8ECf-ں8슗GV $ju,^)i9 G*]3cY%BT@b#_癠&ژ5uvu&QʊzB|l_6oL|ZwipW-oh~PKaA&emptyrepo/.git/hooks/commit-msg.sampleUT P cbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKaA.emptyrepo/.git/hooks/prepare-commit-msg.sampleUT P cbux uS]s0|&3M?,[;L) ig[T\Iɏ$9! =e˥ԥ&$ Sz {a|PJzP:'Z,{)ZMb$}QZLύTJx #cQ<%ƀ1Foh#hVX̻4!H& U*G!K3F_h 8/rAbiǺ^Ω8(,Fb%`DWe$0lp u2qlupށh)4Q8Av#{J,+V!bl<-#4Gl>%6fܙO6mc=BV͠ӳ0X)Ω3i46vpq!mfQVcMp*%u 𸕕DTŕX!%B_Kqg"l!jX%H7ͧ92dt~@'rp6>.xNf-3(sCY (oP@Sz38= ;=҄‡>xMHlpk%*2'٫41NTA~!,|]|zw9i㨙,Aq8-_dt㲟Zz%)Qx:zDv~˓PK aAemptyrepo/.git/info/UT P cbux PKaAw=!emptyrepo/.git/info/excludeUT P cbux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PKaA7?Iemptyrepo/.git/descriptionUT P cbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK aA+isemptyrepo/.git/HEADUT P cbux ref: refs/heads/master PK aAemptyrepo/.git/objects/UT P cbux PK aAemptyrepo/.git/objects/info/UT P cbux PK aAemptyrepo/.git/objects/pack/UT P cbux PK aAemptyrepo/.git/branches/UT P cbux PK aAemptyrepo/.git/refs/UT P cbux PK aAemptyrepo/.git/refs/heads/UT P cbux PK aAemptyrepo/.git/refs/tags/UT P cbux PKT75O\emptyrepo/.git/configUT >rb>rbux 5 3L.$C_CR,iŃǗPZv<5-gAфf1dćDO1po/PK aA Aemptyrepo/UTPux PK TADemptyrepo/.git/UT>rbux PK aAAemptyrepo/.git/hooks/UTPux PKaA 'emptyrepo/.git/hooks/post-update.sampleUTPux PKaARv"emptyrepo/.git/hooks/update.sampleUTPux PKaApٞ&큙emptyrepo/.git/hooks/pre-commit.sampleUTPux PKaA,.W&큗 emptyrepo/.git/hooks/pre-rebase.sampleUTPux PKaA`X *emptyrepo/.git/hooks/applypatch-msg.sampleUTPux PKaA%[9*>emptyrepo/.git/hooks/pre-applypatch.sampleUTPux PKaA&큟emptyrepo/.git/hooks/commit-msg.sampleUTPux PKaA.emptyrepo/.git/hooks/prepare-commit-msg.sampleUTPux PK aAAemptyrepo/.git/info/UTPux PKaAw=!Cemptyrepo/.git/info/excludeUTPux PKaA7?IEemptyrepo/.git/descriptionUTPux PK aA+isemptyrepo/.git/HEADUTPux PK aAA<emptyrepo/.git/objects/UTPux PK aAAemptyrepo/.git/objects/info/UTPux PK aAAemptyrepo/.git/objects/pack/UTPux PK aAA9emptyrepo/.git/branches/UTPux PK aAAemptyrepo/.git/refs/UTPux PK aAAemptyrepo/.git/refs/heads/UTPux PK aAA-emptyrepo/.git/refs/tags/UTPux PKT75O\emptyrepo/.git/configUT>rbux PK libgit2-pygit2-a011e26/test/data/encoding.zip000066400000000000000000000414571473744024100210260ustar00rootroot00000000000000PK L encoding/UT mZcbux PK 2[Tencoding/.git/UT cbcbux PKjLJ[\vencoding/.git/configUT gmZcbux MA Dѵ"Gr!)@ng3d?;ZKxGc~-YYe.&EShR*G=JsQRx PK 2[Tencoding/.git/objects/UT cbcbux PK 2[Tencoding/.git/objects/61/UT cbcbux PK LG::?encoding/.git/objects/61/f0b8751112c206b3d000d2ba53a5aca49ad180UT nZcbux x+)JMU01b040031Q,׵05+(aPg;?(-]h-߇]PK 2[Tencoding/.git/objects/6a/UT cbcbux PK ѤL?encoding/.git/objects/6a/a416ac00edba818fc1d4b16305298fb6383b46UT *nZcbux xAn0EYs*6 ذ!%U}}}gΠ"'"PNbiQe:ym:G~.ȰMqFPVgJuV%-әޒT8 ~9@D@@Y[ZddaKO-! r^/ZvPK 2[Tencoding/.git/objects/20/UT cbcbux PK L/0&&?encoding/.git/objects/20/1e0c908e3d9f526659df3e556c3d06384ef0dfUT nZcbux xKOR02b.,.LS^ZΕb PK jLencoding/.git/objects/pack/UT gmZcbux PK 2[Tencoding/.git/objects/11/UT cbcbux PK ѤLp$l;;?encoding/.git/objects/11/eaf2be8b82195a98240a2414629eae38c2468eUT *nZcbux x+)JMU01b040031Q,׵05+(axg<yRM׭R`iLPK jLencoding/.git/objects/info/UT gmZcbux PK 2[Tencoding/.git/objects/e8/UT cbcbux PK L\""?encoding/.git/objects/e8/4e339ac7fcc823106efa65a6972d7a20016c85UT mZcbux xKOR0`.,.LS^Zx RPK Lencoding/.git/objects/40/UT nZcbux PK LW?encoding/.git/objects/40/935d8766cf7a1a8462d1f914fe48ff446f1df3UT nZcbux xKn1Eь@[UQ-! lYӉ4!VYsO/uwo}+-y$!ڌDV$qdbTs (HB,9 /,z3e]Pr_mV_pߗ2UNm=y;g&!Q^^Q-ץ &f?ObN8Z#Eù깆PK 2[Tencoding/.git/logs/UT cbcbux PKLu&Xencoding/.git/logs/HEADUT nZcbux =O0 gWxVIB, ݀GkK^_ON~=PK ѤLencoding/.git/logs/refs/UT *nZcbux PK ѤLencoding/.git/logs/refs/heads/UT *nZcbux PKLu&X$encoding/.git/logs/refs/heads/masterUT nZcbux =O0 gWxVIB, ݀GkK^_ON~=PKjL7?Iencoding/.git/descriptionUT gmZcbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK 2[Tencoding/.git/hooks/UT cbcbux PKjL%encoding/.git/hooks/commit-msg.sampleUT gmZcbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKjLz"%encoding/.git/hooks/pre-rebase.sampleUT gmZcbux XkoF,[E>۸AndıGPbMr3(jsg"-+b﹏ѓofU$zBl4G4?<~NL9, Q*G{ԯhFi-et]ﵱ Zf"cKpZI Ţ$(KKcU<ժ`%xh!)|,̦R?V,f1(y.G*՚%TBBZ358ԕZ3ih- x&Ma0,(|fۄJ,*J1wZqԘ {(ZWjmĚj㢪,<0ɮ% &(^B5xJ4(.ǰ7aBZy"S疲A U" z`Uz3섵F,+>QDO'429wNZfd3̍l0z̦X<~~yvqCwwQ@t % $H+H+b94c(G*@SK#{H7@% w6U{R)@A6/KOSq\e乫 .` 俭ww+EϿS5JfSE,|ݳ&u5h<물>[H( ͉*%!'ۚt<|  뙸sZe>r[oBclvkj+H-QfKv0;HOJFFmXw2\3Npg9=A7:vpưHwn5ҫz2 ۛĪI"$%[F(ר'0v/ ` ެ6J&؄rA,goqx^%GiCQƳStg~3c Qqzw$ۈfLbr<ݓg8wt^a|(sYp+rGpt4{Wj4r7 tN#Jr^J˷o]L#>5 BmwM?7Moί_Pv;\B'f<^ 4D cMߦcɔX(RF0.xzp?"zJoXN^AW,_EeI"|(3. J2-c6 Hdes=Kz}7F W1@ +V&S,Sң~Qg(ve@f+_ gu%q+ 6D0YBQ73'/`KVg.1Bo\ /$:RVn2Ç7=7,. 0xx%%FyXc+]zz@Y~r:UVFmY7 T/Dc&r?Roy"'e-k-@?^F^]^_z;o`+o~ņc O[`Tk{x4tJh;HE`?(%lغʒ-|:Vhwq.A_WI% ˗ݢM;f `9 S\$HyJ*F $Cql;H'Nh8ZT]@wmm8|Ra]/`L/zxgq{ @f;>$k.օ53Wco{!nZ;!aW ʹyt 1W2_!w8^yVӆ2r zQ/c6k]^γ=4!m)goyKp&? t3ș6͜1\ ?w紇Mķ3W/ )>ɇBo=p'K,Z,eKEUu3H>h_SD*{Lq{ u?6&~%~c&V5ߟPKjL%0\j%encoding/.git/hooks/pre-commit.sampleUT gmZcbux mTao6$ `َI0 l`bģE"Uo#% ox{ݻ;~3-*;Ύii_DLK@҆V;V"$ ۥSiFr +Q,hp7K­Mj_]-_d57v|C=ءY0$i^BA@m)T{Qb$"ïft4jIiEM |t7i~wzɎZhm ɸӃAz@uCR+ECdMv3.Ï>ޮ"\'yvFi[R^GgpP|XCMh4`KLZ)0x,W㲵ol6DkȢ;xdi #FQnapB7\>cZŗ6z7"<ϑV\>%2O0 o9J[F 8UVmK(OPRpvs'+aε{@zhngPKjLO )encoding/.git/hooks/applypatch-msg.sampleUT gmZcbux U]N1 s *HHp.nS-l[^o^ܭ7>ɩZ{B!"0([J8˽V&ZfTA=;H9/-Ov`&F3j.[K?@#rvDO BYq+7Sy:Qfm(}DJ 9CSQ ^&4VB09eٝh}6ЃV` Br)}??M PKjLI &encoding/.git/hooks/pre-receive.sampleUT gmZcbux uPN0<_1}@[ʑ*HV"!UnM;B;vʣqzg3:ZZ; :*Ҙ lVbCZQm2<+MʷWP& Y:Q;앓hJd텄^ի%i$㒟jBk=hq/BK/E3UM2R;TG֡I/y:M4[,R +~EkâveKL q(h_uԌMkS6g9o5^q{~㱯a~"M%qpQ,kbbPKjL-encoding/.git/hooks/prepare-commit-msg.sampleUT gmZcbux uS]s0|&3M?,[;L) ig[T\Iɏ$9! =e˥ԥ&$ Sz {a|PJzP:'Z,{)ZMb$}QZLύTJx #cQ<%ƀ1Foh#hVX̻4!H& U*G!K3F_h 8/rAbiǺ^Ω8(,Fb%`DWe$0lp u2qlupށh)4Q8Av#{J,+V!bl<-#4Gl>%6fܙO6mc=BV͠ӳ0X)Ω3i46vpq!mfQVcMp*%u 𸕕DTŕX!%B_Kqg"l!jX%H7ͧ92dt~@'rp6>.xNf-3(sCY (oP@Sz38= ;=҄‡>xMHlpk%*2'٫41NTA~!,|]|zw9i㨙,Aq8-_dt㲟Zz%)Qx:zDv~˓PKjL &encoding/.git/hooks/post-update.sampleUT gmZcbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPKjLL )encoding/.git/hooks/pre-applypatch.sampleUT gmZcbux U[N1 EK:@VVB=DL&Q>nq,o#S!pWcHƞjO8/ 霷.Eh_p*^L|huV#iت7 Q E-$_%'-\29LH52 {a֗a2Jpt)П4FaOw~˵E$S͝GKY쮤-# P^5m4#zBxJΘ9}[7wfP \jni}J\kWG:IqL{mk).Z| =mn_mF>0.pJp{q`GI*pݓ !X/u;Կcv?15EVY"eʇ!H}nfs}p-;Xqds|(Fu}Բ+&'@(s5 ϟ =wbmU,LA:fW5 :aNpC;]qc@bS*7USݝpt]+!&_󚓝ZBѓVGkXټ9Ww]gDoeg ƚح,b @vJtՎP#n>=>n%-|@Ux|` :m7Pz9YqnU4k˛3k@jFyFG2LU mmFP%;p$끕^ ؛2/[/vM gaCŴ'QJ۩A!Qޭ5' +G6 $R _>)!JÉl\k,)ޮy?PK L[encoding/.git/COMMIT_EDITMSGUT nZcbux Modified iso-8859-1.txt PK 2[Tencoding/.git/refs/UT cbcbux PK 2[Tencoding/.git/refs/heads/UT cbcbux PK LX;))encoding/.git/refs/heads/masterUT nZcbux 40935d8766cf7a1a8462d1f914fe48ff446f1df3 PK jLencoding/.git/refs/tags/UT gmZcbux PKL3Izencoding/.git/indexUT nZcbux s rf```bƨybkgь l#2@τ>AiBsl,>g,׵05+() ruR \v b|Үk̺ؠO~zs +.{oPK Lh&Wencoding/iso-8859-1.txtUT nZcbux Kristian Hgsberg foo PK L Aencoding/UTmZux PK 2[TACencoding/.git/UTcbux PKjLJ[\vencoding/.git/configUTgmZux PK 2[TA5encoding/.git/objects/UTcbux PK 2[TAencoding/.git/objects/61/UTcbux PK LG::?$encoding/.git/objects/61/f0b8751112c206b3d000d2ba53a5aca49ad180UTnZux PK 2[TAencoding/.git/objects/6a/UTcbux PK ѤL?$encoding/.git/objects/6a/a416ac00edba818fc1d4b16305298fb6383b46UT*nZux PK 2[TAencoding/.git/objects/20/UTcbux PK L/0&&?$nencoding/.git/objects/20/1e0c908e3d9f526659df3e556c3d06384ef0dfUTnZux PK jLA encoding/.git/objects/pack/UTgmZux PK 2[TAbencoding/.git/objects/11/UTcbux PK ѤLp$l;;?$encoding/.git/objects/11/eaf2be8b82195a98240a2414629eae38c2468eUT*nZux PK jLAiencoding/.git/objects/info/UTgmZux PK 2[TAencoding/.git/objects/e8/UTcbux PK L\""?$encoding/.git/objects/e8/4e339ac7fcc823106efa65a6972d7a20016c85UTmZux PK LAencoding/.git/objects/40/UTnZux PK LW?$encoding/.git/objects/40/935d8766cf7a1a8462d1f914fe48ff446f1df3UTnZux PK jL+is+ encoding/.git/HEADUTgmZux PK 2[TA encoding/.git/info/UTcbux PKjLw=! encoding/.git/info/excludeUTgmZux PK 2[TA encoding/.git/logs/UTcbux PKLu&X) encoding/.git/logs/HEADUTnZux PK ѤLAf encoding/.git/logs/refs/UT*nZux PK ѤLA encoding/.git/logs/refs/heads/UT*nZux PKLu&X$ encoding/.git/logs/refs/heads/masterUTnZux PKjL7?IZencoding/.git/descriptionUTgmZux PK 2[TAencoding/.git/hooks/UTcbux PKjL%:encoding/.git/hooks/commit-msg.sampleUTgmZux PKjLz"%큐encoding/.git/hooks/pre-rebase.sampleUTgmZux PKjL%0\j%encoding/.git/hooks/pre-commit.sampleUTgmZux PKjLO )encoding/.git/hooks/applypatch-msg.sampleUTgmZux PKjLI &Jencoding/.git/hooks/pre-receive.sampleUTgmZux PKjL- encoding/.git/hooks/prepare-commit-msg.sampleUTgmZux PKjL &#encoding/.git/hooks/post-update.sampleUTgmZux PKjLL )$encoding/.git/hooks/pre-applypatch.sampleUTgmZux PKjL؏D#G&encoding/.git/hooks/pre-push.sampleUTgmZux PKjL!D%u!A)encoding/.git/hooks/update.sampleUTgmZux PK L[.encoding/.git/COMMIT_EDITMSGUTnZux PK 2[TA.encoding/.git/refs/UTcbux PK 2[TA.encoding/.git/refs/heads/UTcbux PK LX;))/encoding/.git/refs/heads/masterUTnZux PK jLA/encoding/.git/refs/tags/UTgmZux PKL3Iz/encoding/.git/indexUTnZux PK Lh&W0encoding/iso-8859-1.txtUTnZux PK---1libgit2-pygit2-a011e26/test/data/gpgsigned.zip000066400000000000000000016254441473744024100212140ustar00rootroot00000000000000PK CT gpgsigned/UT sbsbux PK CT+isgpgsigned/HEADUT sbsbux ref: refs/heads/master PK CTgpgsigned/logs/UT sbsbux PK CTgpgsigned/logs/refs/UT sbsbux PK CTgpgsigned/logs/refs/heads/UT sbsbux PKCTq8Ղr+gpgsigned/logs/refs/heads/testrepo-worktreeUT sbsbux Q 0 o=EmZQTSY26G|?&d/>N쎖bTO8;5Ze z]u_ %aqUp{UiڃPKCTZgpgsigned/configUT sbsbux E0 )X'!tdXv&EE{hmn$o1n$Ix @DwMLWРr,,i93ƲMAQ͡kcZ*m7'm/e8BN1]Q.OX0LMƽPK CTgpgsigned/refs/UT sbsbux PK CTgpgsigned/refs/tags/UT sbsbux PK CT. b)))!gpgsigned/refs/tags/point_to_blobUT sbsbux 1385f264afb75a56a5bec74243be9b367ba4ca08 PK CTz ))gpgsigned/refs/tags/testUT sbsbux b25fa35b38051e4ae45d4222e795f9df2e43f1d1 PK CTgpgsigned/refs/tags/foo/UT sbsbux PK CTz ))gpgsigned/refs/tags/foo/barUT sbsbux b25fa35b38051e4ae45d4222e795f9df2e43f1d1 PK CTgpgsigned/refs/tags/foo/foo/UT sbsbux PK CTz ))gpgsigned/refs/tags/foo/foo/barUT sbsbux b25fa35b38051e4ae45d4222e795f9df2e43f1d1 PK CTTn))gpgsigned/refs/tags/e90810bUT sbsbux 7b4384978d2493e851f9cca7858815fac9b10980 PK CTgpgsigned/refs/heads/UT sbsbux PKCTwC() gpgsigned/refs/heads/packed-testUT sbsbux A ?j2mz8 %[;d-ѥ~ijPK CT[ϖ))gpgsigned/refs/heads/masterUT sbsbux 099fabac3a9ea935598528c27f866e34089c2eff PK CTu))gpgsigned/refs/heads/subtreesUT sbsbux 763d71aadf09a7951596c9746c024e7eece7c7af PK CTyH))gpgsigned/refs/heads/br2UT sbsbux a4a7dce85cf63874e984719f4fdd239f5145052f PKCTr()gpgsigned/refs/heads/identUT sbsbux G iMY#xR֍/ђ8ւs \PK CTȟ1#))#gpgsigned/refs/heads/long-file-nameUT sbsbux 6b377958d8c6a4906e8573b53672a1a23a4e8ce6 PK CT[ϖ))&gpgsigned/refs/heads/testrepo-worktreeUT sbsbux 099fabac3a9ea935598528c27f866e34089c2eff PKCTz;()gpgsigned/refs/heads/dirUT sbsbux  ՜&Xg6(ݶ'<ϓ5PK CT]:))gpgsigned/refs/heads/testUT sbsbux e90810b8df3e80c413d903f631643c716887138d PKCTx()gpgsigned/refs/heads/executableUT sbsbux A mC$ ^I433X=VX[PK CTX))#gpgsigned/refs/heads/merge-conflictUT sbsbux a38d028f71eaa590febb7d716b1ca32350cf70da PK CT+isgpgsigned/refs/symrefUT sbsbux ref: refs/heads/master PKCTՑϖgpgsigned/packed-refsUT sbsbux eA E9u:3F{BCz袛{sp/ͼo}CŴ竏I9$H rBȃgDdIJ+I- U`NDFbչkx $-9EV{]TWPKCT \ͻ'gpgsigned/indexUT sbsbux y\LO,TfΜu.D*\٦ipev-d颲\\RYM!L#͜7zޟ罝3nhы!v:V 6L0~o\·bjjSS?ƮƎ;Y9`Rϑ=x?u䪅s5D#cKo&lIGpG޶Ib^+3o06)y5y>.G6T%Js]_d=fyl\;L`Ɇ24D"42&V7oǾe-K[x<8Č2LJ8k*wJu;9VVwx<󀱜O+qXrvq8}U7)nJ$'F?մ 0a:]gl5z R , dž@J>]>uugafmMsCq9>d O~n^wOD})Je b/nh)ovm,OXv~V޼oz"#"0:K⩯*#MT S:t"yӣ/]Z[5xzGgxm嵘@=Zsܽɋw۫3c9ӉoNl/D X+y4#S|"*J6ݴ>}"ŕ. rC~=ɉÿ/:>3t}bée %w'ĝ;^ԝ(!oO#RaKJjek fo6z '! ޷s⫯5ylh˼/3<8VِϤ,ʔSYKJً+oH,(vL7Bw&2)3fmV|]'9^ȨX&b?sP[\bV|sQ`4yueiq<uxzx~keO<ЫsAr%_*\*L>/rM<߃OX{tN^gh?3MmWZ*a_X2)yR9$|{Z;ɳ=9ɪlCNZzOCɋIu&rDt^Q~oZgMj<)&ϼwA'm2j-/ꊒ̋'dqN=lg۽"EGO)Bb;$i1>'67P˸s+=/_~Uy΍ay':ppKn"Dv;ɉ|yiw~FlLgfJ^(QuNqdiyRZmrAjvu;GO`לXT1a'襾R9g[G`gXiɟľ͇r_Vu1o9}宖 { 8SGӋNU%imt˶5yA/m&ΑCG}S]æZCm \wت$"vDwV8^Û9cgﮫ :s]Ȝګƅ>@(ٍ'7N>.*nM`4k|5E=<:w~K4^Wyʫ(Jm`;ٟsԖh/~ʲYӗ4ymXXmA\ԫαǮSt v4d@>D4Iv 8e䟦r{RSk_b +JŰ]zYX\UƩS3폙9sEU'&S}grvoyigWʗx?Py79F~6z`ѭH,xΌK8`ӜurK8x OI~v@`Gau;;Fsa$5|eדE;o˱/U!``b>[!ockFe'm9 VM'6Ǐk >vX@?.0zx~c&.t`Z l\ScF> [6*B(|v֗Gvu.w8~.8˻Մm'Wďa$\sl9iݲWk2[jjH/z?0`ɅɵJq weQŬVpQW$ti=N$zצM[ZLi.c+4F {ԔU/XV/sUu 1Y%ūG: 6I(F_hr8=  ;.5v:Rr\D=?H1aa|R𢡊:"e&H/dn?<ғT}bsGzO'WAicw`|nŴHg ;qSB&wz>47g֏n߹9z3!1RZSN|mt 5ק,bn ,J,7U.Q*a\gTWi]hRvW3`ֿ4 :Gރuޣh雭s$d_ sN)y8| ?ե5}qR0sSQY+!HD(LHH !@H` P$`_rM{"m)J9'^>e^ &H>.` b ! ABEp"DaBhL~5G?>M֌/9GH "qH`PE8E-, p7TlUU'0" 8k%1b8NSbH HJ($r_=%e>Ɖsz..#-$$H$%0$DaB IZ$}u݅FO8ϦVs v}dE~QSbbYH )P֧_=ߛ\7Gdv_v} Υia)i Ba@(HTHoO1 5yoq1<%@ 2'in`1 ?=7LKHּ;?9 ЇbTH(#0 ,(1>۟ub{dVM= hK4 .(bYCl(3 [_k"-+]d!Q> JB83b@дKFHO3Gߡ^5ʹ*GS:ݥ:1('s`^~)b-"6AR^7;}\I}^Fnҡ DbGИQbD$& 3P>/K&}(eUA?u_IF!@@H CDR#<%o0޲+nd6ѮO  )Ld>gOA@l.?ۀo3zőYm_hvd3@L3"1IhFT'qR$&4NH 7xSލl,S iח ADQakb& F1bm8WswaD/87DQmXh}BI!16@Fp8%"C$l>#ȈE:_pGbЧB1b$P,DA a AbKöP}/3v|ֈ}w99~~xmQƧ1MAmK0lkaFb6AqwRz|eP(j, B|wZTQoh{=9y S?n>RiejKׯs>>o-0q)^\Xv/s eVn$<ݎ[2`H_h 9# :łsϲ;+ *LR} edQLWg[r{|i_'| "Ŋ- \ǐmyޞPfENVinL\'eN5!Fȹj y]w3o6s?˹λژo J^!|ζuWڜWW\uTFdd00(8-5g9evʃMc7DZPK CTgpgsigned/objects/UT sbsbux PK CTgpgsigned/objects/94/UT sbsbux PK CTV{ww;gpgsigned/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162UT sbsbux x+)JMU044b040031QrutueXlmmAṃJ}G;UTWRQ`6Kǥ^/-*|W3Py`%E\&g|0{Ӎ1XPK CTgpgsigned/objects/09/UT sbsbux PK CT>%~;gpgsigned/objects/09/9fabac3a9ea935598528c27f866e34089c2effUT sbsbux xQ P9^@B!1F'J?#7KJhMVE,.3uVsH-;U,MPIɉ&Ĕ׍סKO.2µո$8Nݗr!lCTklUgf0sÓG(PK CTgpgsigned/objects/76/UT sbsbux PK CT~V;gpgsigned/objects/76/3d71aadf09a7951596c9746c024e7eece7c7afUT sbsbux xAj!?009o}H6}jUPPZ&Y AԛpFdpz[fYPqLJ.,Z`Ů.`v q $5+9Ot>/DE/龡W*eVdf1>覭ěʙFThk.i^0?PR,PK CTgpgsigned/objects/d5/UT sbsbux PK CT]EH7;gpgsigned/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83UT sbsbux x+)JMU041`040031QrutueXlmmAṃJ}G;fb ~޽>[TLU뒡32sRJ*J\w63nqgΞ9To*K-+Y.v29s;97 ̺:żW9гVlq=(1/9#>-3'Uug>6wٞOpҼr_ʧ+O\\iy|tI=&PK CTgpgsigned/objects/5b/UT sbsbux PK CT4;gpgsigned/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644UT sbsbux x 1ENi@k2 "X$YW0YcÅszMD08!s Xgd::@X0Pw"F/RUzmZZV}|/o5I!1z:vUim}/> F-PK CTgpgsigned/objects/32/UT sbsbux PK CT<222;gpgsigned/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54UT sbsbux x+)JMU06f040031Q0+(aOܶbK?TK ePK CTgpgsigned/objects/a3/UT sbsbux PK CT:T;gpgsigned/objects/a3/8d028f71eaa590febb7d716b1ca32350cf70daUT sbsbux xQ 0 Pw 4iGZvGBK! rOPK CTgpgsigned/objects/e6/UT sbsbux PK CT;gpgsigned/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391UT sbsbux xKOR0` PK CTgpgsigned/objects/a8/UT sbsbux PK CT/;gpgsigned/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6UT sbsbux xKOR04`HT(H-J6PK CTgpgsigned/objects/84/UT sbsbux PK CT<-~~;gpgsigned/objects/84/96071c1b46c854b31185ea97743be6a8774479UT sbsbux x 1Nۀn` V.`.pa Ô{StlH,_9$$cf&hXQ*g)cV~p~"Q@JnZuӶL 6PK CTgpgsigned/objects/7b/UT sbsbux PK CT~eƑ;gpgsigned/objects/7b/4384978d2493e851f9cca7858815fac9b10980UT sbsbux x-A0={7!] ,X@`5ds?0OhBR&*4]Mo ȡNɾ09(J|1$pD82$"GgvdjG]j7x22]s0PK CT3Ӵ1;gpgsigned/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785aUT sbsbux x+)JMU016c040031QrutueXlmmAṃJ}G;UTWRQ`6Kǥ^/-*|W3F@_ZVz ڙ=\JITmX.?GhOýP%OLeU?gj{kO-PK CTgpgsigned/objects/e7/UT sbsbux PK CT-U;gpgsigned/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0UT sbsbux xKOR0c0+(PK CTgpgsigned/objects/62/UT sbsbux PK CTdH22;gpgsigned/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dccUT sbsbux x+)JMU06f040031QH+(aH)b>ÿj7{PK CTgpgsigned/objects/9a/UT sbsbux PK CT )22;gpgsigned/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4UT sbsbux x+)JMU06f040031Q0+(aP[v L PK CTgpgsigned/objects/4e/UT sbsbux PK CT22;gpgsigned/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63UT sbsbux x+)JMU06f040075UH+(aH)b>ÿj7PK CT,\ 88;gpgsigned/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10UT sbsbux xKOR01b(,VHIUy) y %E y) IEy\PK CTgpgsigned/objects/66/UT sbsbux PK CT ;gpgsigned/objects/66/3adb09143767984f7be83a91effa47e128c735UT sbsbux xKOR0aT/PK CTgpgsigned/objects/c3/UT sbsbux PK CT,bC;gpgsigned/objects/c3/6d8ea75da8cb510fcb0c408c1d7e53f9a99dbeUT sbsbux x+)JMU026d040031QrutueXlmmAṃJ}G;UTWRQ`6Kǥ^/-*|W3P)y%`U3+nYsN0OFVe[(deǗ祖vfF9'=j5dh/p/bPK CTgpgsigned/objects/99/UT sbsbux PK CTd%;gpgsigned/objects/99/1f1b12603e1d78411c1b4042719f964efa7adfUT sbsbux x[msFԯ$U)QnR-٦lْq "A6 ж}lf-b0ӸW觻qgwK,-4'$Qe>>ErO+53U$jUaZlr'/"*$fbQ|*O_'I\X]ӱzLCRi}ˆKrLՍ>%>uc’,LœI!UdӒpKb݂pJfyQC"dyH+R4{/+b M"4;vfYWi&8cZT ,"^lBL4+4$c4򅠶Z#U,$Wr9!4xLUyOM!SX!?PrA=a6cJzէQTgIn)XE!Cɧdyr+ 4f ٳ| c:DռlHVNÓ{"6)L̠pS,9yxlZg:d8:{Px9|qq9/ 5$v)gyIR鼴*6X|f1H t"Z3 |ds(_E{7wO7q.^>;ϦO_},`M 챯~q.+-VS(< yYY_=?Qw~<<꫗'$i6&_VI2={CSO,.guZ'Wp,QIV7ђ#V7K(IOͫe\zjOu9᧟z= *()!j =e ( D &3ک* #I/h-]sP5mۋz[[eO4CJgq~Nٓ7oyN_!y罍p0xt1r5 1*Hpmok{L3ON /.ΣL/-ӉfIy.$Z!M󇴖{[bpBloo8vfDrP 9hW ;&4GU:__O7O' 7+Mu!=unC-↮^cV|Uω%k#$!_ `<#S$y\ <2l ^]'}+~j5&EJ-E=gk\vVuX]5HE2- uoR D͒{  4=R> F?9R@G͆<uDnKx-1h=; Mzd0Er]-$hӄ גHLحl\-ri_TW;_H/@0%Nz1^te7\nj鼣;EeJ2tUuߩcx`M/].KD);2o J2Mal=Qi;b_0uҥ> 󶚱.qknܞ|Up3t F=LTA Gq/:xO6c*}tYV 融(}~RݳY G>2D̩(b9؃X /=Q>z<[FmޔƤIE@eI \Zx="OV=I7khOn6BKXT$ \D˼l gJ(L k۪8_8= Dm AA7~ K N{ bLhc"n 04 E Yz̀դľ%4N d} T=3g/xm}>0 >#E&:0KfBW31s @c S5v?J: nئn_N/^?J*p'6w~w VQw!T,8sx@<)̏ 35ҥpj+&tސ  yo8­Zky@((168꺞F~լt7IYQ1{wӲKBx(P35Rk ]Yt{N=\mݲXG`rlk `6ca&ϊ~M!9D+Ul$d$l`P"U;<)r-fg0D:XcÄ}縭7?|tv;TZ\Z&!ݱ ɪϾwTsǛVݞ+bZ| $;NJSJm8Q3::KL=K_UeDZ픸qkCx篑;}./"@_.J]@)6oRG_zK5"tn7ƴ6a.5e[o֒Bp^e}|'pv`rLh规dT -ekׅ~PE\ˑגFs`\ʙI4iNɏd[A.܋|RdR/NaRP6}}Ok ~ b۰ss8^뒝 ZaO]F buG-!$hε U ɔN7lE"ȶnī3-zB&nՈn5KtC3(il,,k4b/5B WmlgjxI*~I%xsw>;';LWo;/xmq߈µքbIDmltw4@>HOQ@ ߛˀNlݨy/jbj %+T;_7yOP—#Ȅڠdk.h]\'6tO9!59&™^j/rN]`f~}b%]t*Y"yz˜[s<{{},Tx ֋[Ba^sC1Vhj̪RCva"3~&O|!E*1Gڕ*>'jCݴ$ද(?NmPir7]B16C3z[Mleh0]{5\D304-1O)>7}%~3_~B6 4ja9A}k¢|* Mw>PL$l^;ѵmj}3ytA I6-)ahT`)ƸZ+ſ~`s MqS\M"աy-e](ۿ(8{ "_wJ\j4\fVuMWqa{YۯriI]\-$ݛZg:r tC'JhLdf9fX0D>yo4?7}n'v-}wv?Xccr=qyWRM9,oD)n8c룅C*ab끐kb-[ۗ8e GݨULj_ӹ ;__>F}Ƹ%M%URs睴Y .wLnȍהDD''|^l A<pHwo-;9De>+muƣfl.l~jx6D.Ǽ@3qTabxwٻ$Kaf %ZdC@d>DpԍYbqV|e,J,9W M_7V77W>$MlcP81P Nﶖ'㡶G3YZ tMz5G̚\-X@^~쩇vbo_il_#`aSU]=)N9AǷZ/Y ¡g'O.aL._b.wMto+ѧ]FA17dߵ:TvD7PK CTgpgsigned/objects/6f/UT sbsbux PK CTb2;gpgsigned/objects/6f/d5c7dd2ab27b48c493023f794be09861e9045fUT sbsbux xA!D}G Mr\m[F11gȢ(GRr3Co"v^hq<7AY{"&$DSg([B!ΡwƳYgl$%Eֲ'\d_w-[k'1hZt B&;:A"m%VPK CTgpgsigned/objects/b2/UT sbsbux PK CT=;gpgsigned/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1UT sbsbux xA 0a9I p'1Ѷv\x{cVpvWgǎ0x[ ]"g#{rD Cot N U $?9-p+1^Qx9O\C m'D {mV(+l,PK CTgpgsigned/objects/14/UT sbsbux PK CT;gpgsigned/objects/14/4344043ba4d4a405da03de3844aa829ae8be0eUT sbsbux xA 0=e4DD 6ɮ-VjO6 L^j8m uYU:EGCJ;vͫ *j-1h^{,YS6.+dG[yKpN\StKBD}-3'Uug>6wٞOpҼr_ʧ+O\\iy|tI/PK CTgpgsigned/objects/13/UT sbsbux PK CT;gpgsigned/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08UT sbsbux xKOR0aHSDPK CTgpgsigned/objects/a6/UT sbsbux PK CT=;gpgsigned/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750UT sbsbux xQ !@sBQ" ٱ r{PK CTgpgsigned/objects/45/UT sbsbux PK CT#Va;gpgsigned/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6UT sbsbux x+)JMU045e040031QrutueXlmmAṃJ}G;UTWRQ`6Kǥ^/-*|W3F@_ZVz ڙ=\JITmX.?GhOýfCPK CT;gpgsigned/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057UT sbsbux xKOR0fPK CTgpgsigned/objects/1d/UT sbsbux PK CTYoII;gpgsigned/objects/1d/d0968be3ff95fcaecb6fa4245662db9fdc4568UT sbsbux xKOR03`H,PHJKW(H,P(W(MU+I-*(J\\! i9@bԊҒĤT."PK CTgpgsigned/objects/cf/UT sbsbux PK CT/6f;gpgsigned/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9UT sbsbux x 0ymcׇX<#<螈Fir[y0:3(9u4JTHZGAx뀈ѕ̞r;ѩPM$*6+5=$.>?[ݨO?Բm*91om*OLS|װ̑I(&,rq5t/!؈#½:h%1%{jv7X]5B=1/|=`Ubt6[Хmw[E`P>^>Rgfg~۽^f1 %'D_6]ܒ7 6>-kLʫpm:"(N ǐF[ڃXc,i/D "{+jWte˪I9GA6ˏ/柞v69xW]u~#%7uP>ՊSp u ᜛P G̑76;8k>Eg0s2Lb^]#Z ĭd_~?)߬wj} 3J_m 'Y?0ۍ:)*IT {DJ  Z?/;GW8CLu&a)Ƴ=[:[mSwhֱ/%Ss%5% 5Ql36}WwҴ/e'-{Ưt*w^$~YĪ<2OS[7Jm3ͨU1Oy~Y4Ͷm? M(uƽ.EJ,\I .H77s"Ci!NBV 3XV1ҵpKʐA,Z)S x緞 u*D 1p `zOLrR"[%GRGdq"r%4T,HB%|}LDk8\4нT癒x4VIia@< iG7})9]+Gp`NN>-d&Y,(p՞HaojVVb4qsHĿ8,P ߈wὂ?0TԀ?[hhC6ffyOn'4!(KUN'@SXZN Rx Yީc݁PΉ+Y.a[ 66\<8I6i>X'Woޞz}XKnPH3-FQP6~?\!xcʀjauE$qGM>/cŖ[PyKWfpJ~p~ມK0Q<s4PmD[.> ’D=Ƅ7kM;ӊva.U>N$Tal:W>44PY}.C)>c-Uj"6n :' 4oes4hWhaPJN5VߙL#!YQ(ݾjZ1tǏ[i1{|i=Ia+G+L|mteZ沠2/2{wOoJF ;#i8)j#6/ZK{yˉ\T[mSsM˪Rk]w%AlYUWT.QueRBS6{yX^dyϕ<ĬҚR]u ne_Na >S9W8j*TE"%PڀɃ6X۹;,8yCƃlgVJugHޑIt@F1OSXcR'=Iɗ>0cC`5%y#/Ҵ}+ՒN`=5 Js !F]ST3EV_Ն!$RfDd[UlWkY[7lJmPN0ԧa @ն(m[*UJ,7B:r&7rnЭүV: {lZT)uҔRpq}U,Uvzd#`2ኙ v@.|G :0’~UkJ(pz|aׄ61NRKx  b,h7|`ɓF:{sȵmm oBхwLbF6ZY Vj .F:kƒ 4w1ѓ IjD0Ӑ,ڣYQsc*^-,De-5tS̵*Th"We"$ݮՏ{%@Wk=EI )-! (%AZMpdPB5r h+s=.+5ؽO݄lZ1Znx^{ 0BNn6vVa K2̌18Dk '|3Sڑ g\u¬^nROoߒ`t3xձwB;ވD6cd+LoJ7\:!szwgo ?h }"JI$Y3bdhV ڗ&I!snUUR>~0R. ;fk3l .S J$JFԂ ~yJ"r m.mUU]õNu`n@zve(|]n<ۀ](FpXnKgi!Gvv|gll_ʇ_hN2 ɓz_WzѤ>==8F#qY`a+ d)dӘW$`t+C&ZZ&Gsm-Q)ֻzl)H& =p\֤_CK0B ea&hnVE]ocĮ6f{Zc~LAl++ aGޞlnU2;ט'DEm3WWܓz< $9gMeWnu̺q5oC6Hy&|{j&Y\ Ǡ:߷'Fn}H08@x Y']bieW{( i4Rct7EEIUvV҅v7߀|5 v!gS7N ~AU'lTf]6YXSMHSk@3'Sgq{f_9x±= A}L`c]5BVy{ߨ'4pyLz ݈1s5,,ԭǷA%K_h6EJoz}l"KyA0XтWXcїwn\tKmQǶ4 "n+6;=OEou+/{ H0zF5cId#:A;v:qZ2T2?ӡwEE(@̥Pj9iB6E(&aANȉ=CzPfINgaVQbZѴAa.GU#5c&Wzw)v{Th Z*8BKIJTISt:gJ^p?r 6vڷCq{<,LCO–tGKs"/ĿKI EStAl nĕkQV7P*轆c}d3N{\jhz ]v; !޼wWU% Q:R!J}J>Iљf8 Yr+%.wXR2)碥qQjW6i8=Uւ!Q"˗R̝y&is4$~|>Y̻汋F{&kfɥefI[1nV50ҡBӯt4Hs~`d \8ї{|*6}Amg <L;s5$\qz&ۭ0 WtRk?M4#s@3``f KIQ#X9v&iܐ}đS>Q6-kጲx1>饿_ TYџ^W6ԛc%l/+ ̈">[!-Ji5BniCN'-ꢨ~$B 8|Uv&&8 >~E"ޒ7k˿AQprHk3MZd}AY7Gv<#u6MBrer* Q6ՠE{3B &vP ]Z-*ZE]X%N7ZQCVhJsNmL@z>NYm?U!po\/Uyoc10_(j_7B/{9A3"l@\`lȰ; 8Lp2hއ l.q//A$2 Lo0ҽkSǍS}9`X̞ZyZ%rK!;>[WXO9lO(9{mU !^?^ uz]T0tK<5>֕;!wLnjw(^¾1l1;.O }H?܈u!u+h+~Սm'"K ZDm t vKE-riZȬ6ep}@c?ϻ77$M)=סmMs v5Yp @^L!(e[B?:8pt`FrK0T@YƈgzG"@~FOmk1}ć-oyo"P )LFkF=Fiji8B:9T$u>Gˆ/_a3c"i MiլEC#O 1ՁLb-\E)FMq&񼈠"]:$ p[ՊZ{ E-K-7}U˷+®!]tl0os(\!ߚiSO>QdZY9*vȌ>qFtzI"! Jh}#/z?y9zԄ(fjƥ4;Tfà Ѧ[)T<"b], XoM-fBb"=H R1 6ުTrıY^̟ UuqpL''d"v ]%|7KQ>QXemU͘z%g˘ >6A;Y}i 8om0ϥ Ę+5x.lyaW.7} /)<%UނR 7h2q|~cn”8;8 ?ͩQP2_BZ !:o(@c5q%8܋?0sOb O~tZF^i$ͤ(k{І "`-82;+E;^Q)bi},6:՚{۪rw㞶lLHrVqt!|K1:njZ7^Wx9sE}ݓP#A^^]@X79ZOQW@yb'&#par8h19dC9': M^AMhF+q]Kʾ( CGW4V,C Ιdb6O&_En7-ɡL{+$k$ zkR9Hڷ:s&c 1'wc7>% 5)ƣ'F}wObݟ)"t~O 1m 36BI"l<@NR7!K@| [Nn10ATĪt]e95"]%ޤalb Q5#Y;岚ŵD)jf*Jp"{s8a G6rSFn)][]l1<޼9V f1Í"=ѓX8###۪22Sj(AMݱs!Lfp }Y/Y.M;<ٙ697NST[#3i:n2!_5{3Ȱ5RiGО}v4M8nbcJIkZǏA3]xsb8eYO?2}"CÔR2 blD?I+8',c﨧,{paWZZ-c{aЯ}.ĥoƗG PuIɊ?`=N%md LImߤ+Lz a?022<df׻]/sS&#R&%uո[ugME}c9f\ D}ݒJ(,v!J.Mj1g: Ͱ#@T;O? 7&WAR\u7Tb{87z;p:k 0ƶx¹D,f KA8zЖrٮrZCm:a|nU] 4ėzLxK/+鑝2 o N?ùxڨ/(ТTe@9~*)If3]2ȖjwGf`yFafٻj% ͅgs,Y(nz56~e_b~bjdsXoa-ӵ^k׫ ւ&1[.6@[#4,AAe92P:2QS%KC`a!&|b0'Α̺*cJ1# (r5e2QFI‹/ ~#/R-rn眆ꑓ R,nH*adP=B}/8>VΎg)؄JfĶbMS mDM|)[tw[Id[td͛ GO: ݎ/`zFs[C@HXsSބa[ 0LԮ) ~1ǐ1$g7+oȽ(kdb2r(N$K-"ϤŦm- utD lǂDhHa37MF*d2& -X')+;ߨ {{g?zhh2՟H9gpENJtkӰ7]+@Ὄl좬gн"&IbI[a@ꃅ :D v/HeH(4SU%kc+J,!bـRQ-2st!.C[A3ZΊ{I{J2([sG ;~Z-nc&K{;9D?FG?YOjFZC׶KGnR 2l:'1X3Wj{bɂdzʲ܁CYG/kMWwI""'톩uM73b1_ùْ'E la8}zyn؊ ͮXآzPar#VYN_߀(_G! $?Q%we,'v? dkg4"fsJ5V٭B; qHi-^^ChFP:ԢZRɄxXʒ\V pJD\C-ɣlpI3!eHɨ*3ttyF%^P3 *߷[rO]fdsKrgVCp|& 8S-.Ȁ`|2|602q S /'(P1'G\.TɁQ;2դ޿ 1 P;h\t(R}+wXiLGmorg\KAPYvwϻ|~cfo- %X=NR޲)B2b/ԆyMRviT})c&[H<$]vY UȽfz RΒJ4'f86 D޼ciJTשZ+/ZVTD񜇧x3 Ig#.{>鰅3g9GiːB sOweC,;$Y.Xe.:mxcs7XuTfg++g#22A$L ]/]O_}{ya3I`oz[}N@&H'F]10df8{$Oc ǰ&=wB.X%!o"PZ^g-;dK-')=ț؄ Ga#z2,#q9s~xZ*Tv"1Є$-s)Rϊ_EĆ#Xب` 5+;ަPRE7OlhJж}tJ+v|BZ`RR+dp#=K yNa8"0:EBL5kj[mN}RO*WyKGvZ %Ia{TUuIdT!Q[ƺFr&ppw&#"+N/1k֗.٪Kل'iPM"DNv ieDI&!?HESӻPQNw@emNgA]V<us{`3N-]߬K|1)5I݅;f-6O_zq! 9h.;j.Wg53tr'TF#'6v<#9Mb͓YL-˝=?m2*ҠraNuͬu) 6LMirth16-Y Om0ifAr þUp(i#yD5כi,&(u",e,ĸ +Z֏~54\#'$#f;$^tnжQ(P9 BQ&Ke}E0Mc;G@N\>^dj3գxbr>r{tuױFl ,s6V,qVem0%XP0R^N.s֏'m ,6J\u%.G9Z+$KO*~uիX ڎҦ=鷲1b0^D{s =ya]N[|n/nKNåH|xq L1OGvl#0^n ^oJ*{ǩ??#|m[aP$)'/iiR7ۿ_qiS{e"X&BkTiуo]=oWdu`f饲 f)Kpv1f./S]Јy@l^F47:(꺩{Lr*JalV`䆂GϱD&%2 FV#䄵x6AM gG1iewU@Al\Ft"ak1Vt>QZ~c>d~ي2-C1*[SxMldcKccY gJY ((s v @: / -E}A9!+_ɷ@.[AJ(i< lvɼY6Q\'iȷ{  Ro=W[2޲:@^J)C-'1'fG 1ʦ(Ȗ*-t@JHDӾ訢A5ł$JBJ%q3)O1Ɉ1( T#7<%av2*l2C)SgDYԷWzwG69YK89TF^PHv[7m Vk&Dޣ6Lnk.7;_"jǪ/Wnx>?&]VM₡m.Eps`. `n*ǘ!Ō'>σ5Ƹ}`Óy!?x "=)"yY%:j 0T#y,/eoc*{ƧX\"]D+ճ:ƙI*6n2⽕ҌalF+j.ZJRL7XʦJK 瓃j^hgӵ,q&ȢǴ&ʩ=R3nbHܞߌvG#SV)œ!tdz~il7P :p{bK&} #ˍ| zdds8^(~aq11%'Ww_GEb/r{2Nq,{Qu2yxM߯^{f=舼фpQz3*&,_>YONzi0fwJdr9 'nUC^c< }begras10%vn68b4Ma*ɷx`;"~I,_yXRTnZ>qar$ح 5r`ik]Ɖδ 4OXqD6ΰ` ]%yS>E:Q ˰fPy0Zx0!jaxHMO껄߲{Cg{2S33Acdgn p~O6CkgfF)2ބHvy^8=,s 0XѵlȭE.8QҲ\?o}P!&8.u0xQ-Y2wS2\4ZrG+3`SmwV;PMtVHSe)5[:JtbۨRеf &W0]N<ϐj5v4+G6tfk]6CEt$; ala^4BN ݉+GSLG wc l EmFxQz;&dO=c8; `t[WȌh?6smzh>m\V,AɂZ.]b$]LpM4 HU_eBݥK}>Ee,hWX`7ЁȒ ,w(׬ \>%a '0:nVqK]IJP=kKA7{ $9Ke$*\ٵWyq8H@ gS=L&GOy#1(\J!p7% ,.9 79}m7-Ѳm BECG)TX$Gû@Xy C)msƉ1= c1AuBWT$%`5W욻CmĭfmVTg4ڽs0v)dlܳ lmB\OQׇ"f+r۠5^7<#9|G0l):.`n_xϚ>x mVmYCp !s4Z3bpȥghB庭A a/%B#˻G 2T!Fj8dE׃Pfr-s*S#ӆ=dl9n4P񱞷k]ފuX6ڦO3Saz@x' s]M7_V+K{r{d ŧTQ?֪T5:fů4)?I!5}?\!'zMz_J@Ȩ^9z$| L#~K$0$~]!v8,KB VlTԬYGRGWـS*K w0W:3;O#K׾2] nh ؋\q1<)[H*_Z+Xϊ(<i xbo\ѵCS%ܡ]-4Wx2eu.q>i {\8i">/G#!pPP"#Dm;TPgz3Zkh)cnX jMձ5`7u\G ZuV&(M׋k]25#rQkL {:RÍ ז:ZB]b+;LנF IXg% \b\ޖƗñ| (C9+=^Yf&aadu/țt!4  _1w]7*\ M8{%h#8[ʠH5}NZag9'#EcoX51J'`9G E5cQJ*u7JԼP^[HX'μ%kS&w0DZa<ŝ519N2;kZ)~N]xr֤Áojdr\y*{O MJU1z< N82,1)T.yŰG5[n8hxL"u`%NwMk,c¾9{x{w;hx{_[jB];]P߬q^#u;@**Z䙭kM52ON[lZӄkQɡG:LR]#D2itz#5װ qM0b==:uZԱE&AV[AdYLQ' o!L;}r_%@ȹЗZz5 7Oo:AoulC-b0#ʅ5 >]#a"N04(M**@퓢) kaխs@TJ݂K.: `z{q-xUV1p̃2%O]bwZJ/Om-23 ¢Vܳ8 ,^Ц}p:ae9ʿC t㌜?g瑷(ژ? P@(*.~0&#,1fBԤQܘNuZXuiӑ)\bRh~e+Ugy1stO=fTG^DC`u)VَfM=`)pKE5H@zn:GG6|`!?a $}PYc(%UĖ=G]Xgi^7I*F;Jaʊ.K/G(KڒLɶ씒Z[<{(15/˲uRZ6.TLkSIJj7 BXtiɌD"ˎVKTqd)2vZ,~s 79Di`hM,*\]0:NNHIm߾Pb"ZMcPTr5֪N09W*+qQ/8="Qn`* 1pI܁rm+*7[Q4ѫ߲9RW +,.w?G_#.)'ighV)fAT+D'!#Lrdn&*_9($vkOfd"B,mW1iaBmIҥS1I,RTQUYHjKj)-VNjANzOYhD7/kh7mxJ-5O\駚bD\W s3CT;.T]LIA7_{bH0x|bVKQ?t RJ vc\o\iAx73SmP5ȍ1]Q~#1-F~Ӗf0蠋(Ju~A;ArZ) +$U]*gv h/kn<ǚBF:u#u7+20 z?2 y[LoXFZ0kqպ;4q+iSԒN}ܔ:& {Vf lz̥ uxIA+_Ҝ˶g?QW6clF0(%2*{m.{^Ɵ3tt'U;0@8PB۟)$-M7Sjs8ة&E3eHB.v#"t/ )BfP{B1wEa=Mmhޙ=j&VH-bb~}+Um*\Ĩ&[Agٻg@W}|H*";lz U"?H29/-c){31lrY BJ弩StI2 BahyJiSz>:֘zcrzJю~NpXkLc=h̦wf?F n3@Ťoeٜ:CU(3߂-AZEm0Yz Xw~0ocbG>|ybwLe_ QDCZA HdVnPPP$>I677\Z Ԧ 9NIA N) J-KYj{4)8i^ևVWtrA_# =qA;-;A-;՗HR0̇`҈p-Ufry?75&q~zC;v%,{̀$<{ۍq,1I9DN:*[cvrmoAqr7B}k@~o4RRHGfq-rќ:]pK%"@}2vZCt e WOk89z٨S&|cޑ%XnmXw}PQ;xbM2"ݩ2Fa/S4oMGxkڋpK&o#~rX͸ #@Ao׹^/u,ңdK> ^"/e^ª6*\XԻ})PtU3:6hDZ".+=.u em8ȱD.:?6 GҭDL~&}56-no$]S~o{`ScT g!pAzׅ3d~z.pOw"-H͢@DH;yx\F~^0'JN]ܒ.֮5iЖ{:"cҲQLc*vtr9mB$iN$+ $w*ݔ>E:0O.ܱAV~EpAݕIfyl(~ o1j5[u;apR ޑk^kbN'r=Gm St] u!U*A쥛~$A?:m*kWWKyi6RQZ{<21t朕jG{nbjJC-Df0&SǬBADU*mҤ6TӘr3吻~ =CF+VqL{I:͠.nKdH]\ WU[-Ư ݝQ. <3?1\%%p̅K\\9A@3䦬Dd:q)H\g0`#%ω$KBoů;XE^Ɇb>;n(3$*tzFES'8vTox)B&6ŲނVǬ}6釱;b gX6`M}B}&@uRf!A^h_<^u?⟹,`\bq$lU F&BFy+VFm ם:k2 j*֩I!BNcXeʹuY5,# N)n=bƴk1KbJ)/A`1-w;-<bҕ[m-ac̆g>Nތ>7Dٔ+Spv-C8GY+.tf}6Sgk{р*CUQ&m_;orG $ WZjA߇lxLFgW+B!]׾y:wStboZ'A : b]2FyK+eU󙐣V]9Y\*DؕnW0^PBj,0lzN+qA!n$<"rzm X0ԧ$#-e0ؐ$ zg~NOۿU;w/LCV&ο[OLP(%E@˄nɾBĂ cK& {[պTa|nҋ.mVz BLc[yZpujUn:\ G /fH曥xEJZWay l#[)2WCr]K=6xc&L.a!ȐzϠ2!#!v_@Li]01OiG@&陃 ~1Cwk8Tto3ӪV}5R,~L>,Pz'uI -r`~V&.8͑viMW5E0J,I+AR8J])f!h+:05iͶU \7yȭan*QʹvvKMvd<:l(%L3sD4TsAiE\^ (%}y@׎\Vm-eaAp)"\f8}m v[ SU %ȌIF[&^>.^gP#3 Νz7KZhcx tG޼խdy0pXp@7(L1I9k0cӢ" MR lpa͖aߞe"%Ϻ,0?XǙF`cv5T k˘&&|f$")[!d^lt(ⷢ>!ke[dB &[QQU,m,{k,3ߤLjYZ1JQ!s4%cD U3GB{Q72:Rb R \Uz*&chul([Us̵RLe8jVRUUk\7ưX{`i΢C~tӼhv[$5|^;:k :'8U 8"k='b2M67x)v}e0yrͰ$]&F"=q@"<BXZ? U_3C?)pd&-]|6H~=U;z#㧍q?#ȮkX fLA"O^(:ۦfasK\+!/bE_ˎ@ tHF:o'G-eRzvcYr +Z֡)}5uԔO]Fr)ݽ]]QtsSYEѢƄ(wb>Yôq 0zi('12)Ӎtdvw>.ŷfgGfMhSyq`o*)$ LjLuϟf{g] vٵnZN6ǬVDf9NL:g8ňۑm_eSzcwY%P"x[|p=7OQ} -jKaw IZMBEdV2 }x,GE rQz| p+q@c?+% ĹT6-`{N~>3)J.\)ރR(yGw2L(Pt $d/jBOTi*q&ēZ13#1Ns]lSbG)P>A:q~C.ZLKa{JҔu)ZP8ԇ@lLٿs/o Z.G@2*T<ĕ9աT-)렞zt~ſ|u#'wFK~տ)0=( $+SZ, 7gJo0*)c 7cc}5w=+~릣X̑ /6*w|7^jXFQ|I6LatMD:zfku1 dRG3eLʆd4'XFXk5J_ϟ O ^a߶ɒ])ì=C/\O4L !'u~"lxR_9n6򿤿`I!:wN^ot?lcKa=5dn5WWj4IX0"Y`כDNɯRO7A!,1t1'/>J9%C}߭/L'+w fbb\SI@" Bۑ*r &oyv\/긙;ȬˡhH*!~D"9iaAB\SF;Ch{=W˴ND ;NP oV~E$sYb$%[%4_@Ac>jQfE?_  uv3!Y*]UZLnϠ;eVHe} ҁQlE37,mr#LXæOmA.\?յ}" =+w"9jK^!c.s(Иc;ɴ56DsÈ(@DZ_@XDI(P|s ډmr#l-pTQJ:;j!G`{fnQu.s]x`n:gy;w?`"7{`X-g\dIBhPgԡtֱ궻,E2J9[آ.*wLRg LPw8bjGTYG1t7yp--@D9$5sܩ\Errh8M1ѿ;!u|83.v!eIxk|S&\(߭$BOvTc_A.@d8G ħwNKxqtDmdEgi+B3JljخJV3 qNc\Bjvhϥ2nP:0]n8+@r3L.b[|Kxpd7RrJ9K C= dsxicg.Ƭ "nNZ*162+@\=D @ Ro8a-@̝ CU7cGKTENDY1j' Ys"#6҆)9 *$2[|xLc,&Meצ 2ň}&_*qw_bڻ܄ EBEܗP" q6Ïי7t_qZBk-hIMwZT(a5ƴ+|{+Ajք-].(g `C6B!|~A GgJ(>1D}Q l_$5տe}#4A6-L!=[&>%&Z* (>M8@ Fj}دq1l)+E1VZ݉j}fbΠ K7?'JQ͊wqZ& ;ֹoC73qb;Kw8="=bϭ-+X|}#DZvs_6~s1KM?x~p# n+b2#iK%Pf~q˃qx"(>!nתʦchm6g&ZR\~3=V"%x&I[c3"Aqm ˒&`|2$r2k5 wb醠 z{3΁~d*ۧ:S nbxK+m7u_} 3nHZq (C}&8 >4P3h+i"CT!5ƳtT7:T(ճdmનdT^$^L S]ιL-RmbZ"g`>2)&Q tۖ0quz(2jfv w8cO@E09bӏhica/3?b{L^3~^0=‘HO O]<ŵGydSϑ5;ٶA݉1 gAIZRb֮LSr=qVo5cB;>x9ǯoi[dime9oᓆ$K[J' ۛ7/uS_  Qn4rߠ ~ vqMAZ FeߊY{_ Ut YE-iܦT9V9۶־vm2)p&w]8n%_UPw/+ܽ4ntSk,n3`=+de gd O+Y948 YZ 0-1#OK!KP7 e)XG^`9X4JV/hYCO+!} $Nz$$t\B-mj47)+`!Hyi,U띉4X be޼|aFQPiHTlf>W{|.!bVqٗ!ES#DD9ϡΑ v&ZWx>ǃ`,!W4ơ~8`iU%Uw?~4"/s5NyzvPv~ږyTb,썄äIdKx}{ӆLkEuiS`ްbG_vb>( D#>qF/U?y600x{q" 7-[hm$կ_xʚL+A/`oڔ.K]dF-RF"p[IUInQEVtcgE|xgeuV8Vvrꄒ9XVdc1\C[ˡ֟Pu- "#8Asr\|-&^RW,ډFӷӐq87Ű2aR_]W2C*/flsq͖9odӮo+nE=6PLH;KL]t&@1pX+WO곩 {dwLgc?[ah%&mIVNOqVW'8kO<\/?>IY:~ܐ%p+fD&!hyՓ4fk޶lk\^Dz3@zxftCժCѩ4Z1չQL Pէ!kBuʨ[oEjY:CnP3EOlCY45ElHS/E\RG?_p$W)Ad+lR{daM@!DM!`6Z^0LwNy놀eZ 7LO'X!#f26SZ+8a_bP[2nP[{UIeM nx݂0"N xtBC'}tcCh5iPUp9^ٻƍaջQk*@''pxI=pOR9X)f=\3PZv{X!WmcaG\1}-T?_eY+9 n;Gqw|^G/ވt\ͧo sqtteL_`LCp_"a.=yej-,eG9?ɣg7gc9)v9㣟#vS[;wcXe-_Y'Fabal:-rȌQ*_M_p"E|&}-WMFU+s]T;R`;ɳѴV5T4^vpT|XR ;\քz8]2* /L%Kt:L3K~VSuDG>Bȃ6ńrqALdsrQ%ɟ„\^\'~L'չq&D2aFiGuMdEr8 QnʪBi/x3NmW *De;Q(T+^{H]5{V,"ƍ ?v m45UiW=LMPyhYVη$X0p yvO||0.{`~f1C:>ҕ#%5-Phߨd䝸;Kw- _DEsw!7B[pOaɁZ){xVKbE_Ɏ&=֬42 L@\/,نSCVh/,[lӽ8ˁ²ԤG#b!:m@>)@+*;z03,c)uH—󯠓%?#O9:LQ_ގ.;i4L~jQ]+ų3 jL, AIx-#{'>FAmq a+1ܫzZZۻ?WZ]*ج;DS|G͌^ mC@^ E&5{"aG/y'ó[Lq"x} HLA&qKg7LT=%0gGH(2lWeHR) e?Vs0Eo?63Rk>gZՔ8];J.s /U,rbwvVج6q3PoBFВx6wRG mOr#N^y|-ge#]Z谱ssv2?A16Zc۶m6:m۶mcN:mvt;>wϙG\JXijL%^ # /xG?4ϟf;׀5fkn]1?$o&Ȣ^'mۣ9 >UKPXJP ghm:n?ٝ[G7zKt~Aa 4}\ǮPRBM?=i&5\'RT]Asy9$(UIegmC3d|E}\?BT,3͸NTۻ䕷JOnۋ7 jNVB! av -i(KCO{Nס=z/!ӛޏI`D_8CXXT3,QSF]LJ!`yaO0:$K=5ŠzRDx%&`Ƽ T$oq/[uSb58kSf"s?}kOZ#XW$5)E.2 2Qvr)Go?x쌶=Z% cݙW?`ɽ1<$ o+zYLlҿ:ژ\!&^rLasOimRہ ?γ&~& ¬נ5|# \S楰neg{ё}GV+L{Ot&NGj4uB0L:k}ҭu,X/wbQB/C\z4BG". oZ\/?qm_7x( Q=A' 9 e" p| ho$_-<<(Žҏ=?ԟ%Goݸ>X8[ʶWu64ac?u 'q[fPx;pfZQS;~T_?}"ΟGlpyf֢ſD>Da=vf6^kV2r+Zi&sYU{MͶDEѨ8>&:µcuư"E.%,blP{bHIAGJge;Rw=O'k ^[L*7}Ơ1Pfz@l׵wrUs7_U@qμi?bCՙe$t/ғF<h[oa \7sΛ|t.U=OE@3.?Q7Я BAFAts;f61Y-z9#&n7-3BZQb<1[׌<}1ٝ+mϔ 0jW/j(ر pOF'-)a}3…%=Y}vmkݷkJ!YvD+͑!pf47J͉{6  }mE^ڦ3cm NaF-c v=rW+2[i(L`Q^ԜhoF$H+WktɫzZ w3wu$n%`.( =j :hao`׵]sYխK`ys!8iOl!@msp?5Fs\ǹ \XCyE([N?ߔN-Kogt${`:q6 &.b4DZ|>._ \i|qj|g]XLrlnSW8j }G {#ш']5tlnAz/єlO(AǼ1WыY&Y jG@Yy` 7@]}#jrR; HmC[k91U2Nګiզ(;O#[TjDT䖜]T WTY:)9J_cbM#eOu|t;l[B^<N:P%< 0#<2^ZLc ;F힂uۂӻиR b1 cVTro4XMh8w1 t4Ս(mSCZ#Y@5I1d$ |&:?v@;?}%C rǺLwzmwG2m ׻%Brf0{B+~qҢC"bbҟNO8]^1~ziHܯdP~nLdzq3iAo'po}݁/bX%g~zHm_)T>9ʡa}(ыh/ڍWi~uw'8U1fR$5.H$GLE< #乱EJֻͭZzgw|)ݽ?p_ =^J 1y-ɌSJdGo} gySpu7LmdH&;4epe]J7g:?\&p񻕕ZF/0xAy=)};;q T.LRRq1*7SB U(MHY pyR9=dÚb8f2<[ bM\k|jWϊ2_ZcIՇkSGG5/fDqp7#‰Ea|R\}aM9<8,650yT?6\dC҅+S9'ݗiM5ɲqZh-_`#kp Abȋw;Q6BkaEc_#rĝvnr^A) ~ԖmP^x >tbj4Xt\uv!17-foO2պU#H;J TGU74E4iLd3Zt4KvePe;¡?r:gKh`wq#^(ve#X\ht/}Hw. öMHҋVruzq f0JEBtAxs#{ZnŧDo?YpE5Ḹ,F60wBo줗zpxcԶ\F ؗ@,+s 2O EŒqw`X|o 'O+r![pAA!UC\41_L!.TXLY }B%׭Z/5?h^lSncV˚jʇ͉'1Kx9a {UQvP`gLLg(tSTg:EsB$+f>{xWёAaAY<1$rG`?bxA&W@U&h=$Ł mQXZ }tNAl2j(c̕=^eׂnMT˷"Ӈ[%\ya羆^WYf ,FxOh)ǒ9xrִ L~x8W$JQ0[,Wz3(թvaַWNOfrT" Մ\CPh"Qa;.ۓLV1CKzFf>Fͅ.(ǘyJ©g8&0Աe}5uU-UQfYN#VsrP_`+;{q|{ѳ~ Ykr_06%i6fNsfd=3魖9вN1 wrn+RqD *3 s۷ۧ=~-*?ͺpˎ!_SմgayÓh=2=]grKj!3~pl"%DFJC9oP1=N Np);ܟm}&:\Jz5oG%\7gּBs6Pw(|~]&3e'$,,\Ռ-ZJ5f;@co Ov𦂇A8Щ&y U)@0RGfO4K(-;GR5!R0+jv<['l{q+n…u+QLĚ;d:ciTfvqME&@:V(ή'*ڙlU0?&Ő |֕YDVbc/[W$mƳ ,\P#w|M1Gk令2]|ccl |g5͸,z]2bM?nNZP&}jyMeE 9i7] oBs~VIIJ}r9rV X4VbSr3~.LյΞ $"Q ݙ8%Cӷ& \g8\_sy=4M ۫ƫW}/,vh/Azс+K)KQ߂j|3i',k on7!H *.ixJK0->s\shЬp:kRqP(@B4PtqS{kv$!+Io3[uܘQ*yq/~zqh $MG%Q&ђr`p6|er<xqp&SҖlCl"NX^rZǒnR2BHئu}~w_%;ƿz?Vh" Eq>VCl#;)\XP]1@73pKGsOysa&^}eE пk79& 0X[W}z_, O_?JgW +\HDg@Q g$z6YLDqv?Bh1t]n0AA3ȝ2@oVmn0&#e ťMWfl"wsh|(hW9\Mm:ĈMa镱n"D+/;L%NEq# t[(tc=^_zuI'qvpdWqy6lP>te,3k0UFp u-pvR_t$/^#[迍􅆂om-NKߠO?a{OX&tuy!ԍu(`41wZOZ*B l[ƖN$_l&Ej;MFrV*Ʒ/U4AN LRty۞.\-i2tMsYԨ.02ωQ}{\+8<fSM$cG)8TiS YuѪk`&qi0_g?.=EXgƐջܪ X-oXKd,VoBctG7EAI11ôX˹DeeEipǨ%BƊWie_myEEUՌ -EeݡhՅg@ KL;@.^V<_D3ϦOKU%4iE|LϒII׍R!D $(G.c5s 0}BB^oir}/N\õ[ "۔O :DtZ CeMSAmY/3 Nx3d]!SQaSބ\!9??]|K/}|(W;|?KQX~7( HF)MfN-X«%ע˵>'ˋ̦\>î=4"߼UVFՒQUX'cb…z8 Shvt9QI}ЬR]~~o mx=H% y{2kMÅ'lEh K3#uT;/"e%ȒSW:Rr6ҸǦ H跏:osI~Y9E(_i :,LIնIG&hk\Gk{ ٥&騘qQUgr`rgQ_YԶ}dˀݿ?%D;GVi KbEs]3W ե4e?2ZY5 !%ڦ}qbpc;DiMC'@v'5ۭ@4p%YŻǻoiHa3?Yt<"(,,~wf g@,(':TSSDX☩nlvAy$ W!H20%,Lo~qe8 ̋x9W}N'@um*]}Nrʶ+]KxV{i>]4NVڻշ*?A]䋪_E.j:9\w +n#͂{ld\7` "mm ;&kL>?{-=2Ԇܵ(-ZRgDT۫LnID>^TN=y'JD/-:j"5D4V)Bdw`Ъd3PJ. C=ii5%/va[By}Asj$CdOlRviv4Fqq8OcѶkr*Wx #SAyR}ݕ #ĂHo/ͳ3 B¡`\ۡ'tLد,5!ҜUׇˍ撉o뭥O xf:H BWӍ;ÌZ}}w=eL,mIree$ ypZ2P.t}h#@׉p$0g`dCF8(T(#= +6מ'u4S1Ik $QI?d ꒚'%kv>TPz҆ 7]>Bj01ɐUI 77r? ȭW[ bp 0z-~#Dd<z o>5 R#hʷ(\JGٌA)Шr2eL Z8lA˶ˮ㠺A?Gl)!rf~A(aZ^zl![:Vr{ ŽN,&>Nr}pa6(Z\j({&kE}RN;Wϝۺ}KPf- n;GEշ( 6)b#Y<2G>v2#%G/N%uFꇍ#QaM՞d,&, Yۗ&n moP9^q XV kӼiI79+ntȔwiOȾ>*WQ`1Lf8`EPV+v QI.ạ;Ou>KyU[~WiEkT\(ů  sbm o,GC#tdx9rB*-']j+5 +7j&RL"ҲjDلEUU7<45U&Y'6V79SkC_{nsJ2Hi/Yv=ec宖,JX|/6O _5kG8^Eo  a#U5pF}bִ17k(FZߞ(Aݯ eW+1&@,J=!g ,3@x;3!#҄#D~f9 N;uA C$-jBAx 3G9L+Up5}8ktmK9tb\:xuy1Π{R`E^d/"9^6?) -u\Vsf$jQp88X?t1ϔk1.xrr,K"qfO<`E^B ڂ%z|z;L$CjQ"=V-6(]-M GhHQcvdy$ӯpx8|#_n1p62ЧEClqfUk^ ^8fHH3lqǕ"dL#뜳*wS}UsX|נFȻ[7tY? ?V?@98+?d\[؟ !_\A޼RDS,7 cg@L+5MȊR቉N#^Y])Nc"6sf#Zm|̉k: _3HT/Hmqdh. R#A<98U.H@fӿT\@h#T@5Gep|~vc{f3Ӷ[?RUj1wERi5*͖lڀ/z|E4Ba@jsCk.EUWp Օ mwf0Hl**+3kdϥJP)eo_qEG/(UJ ;szHkH^d#xb8_5!$r (i .k.%PI# g0ߙ_bk1%!٢2]9ˠ/s6m|9z5r"a\ Ȉ:b\<~G#Fx%IbaCD:[M1擞 %h?}84 'jR mtԱZP+͇'u1;v|}EQ/&Q̨m7`/ݭH"kO]'&rP0#֒ '_r`@]+DɏE@l.&N-/kd7lj3.+*܀n7l:"0]__QAI2\Y+ x3 x0{5GHFD A~0'ؠŹPQ{x4=^$ l^LikdSl>C.^G|i,Փ8tI=um楒|Xn#I q;6kLw׻='4n \$1TVwqƢ7jrZCR`]B<4r0xRמ:՘.[;>8!p:"Yi,۫ Ԇ40&^!4uK`fhȰxQ;mܞ#[WUB y\.:y-Bv ۆPE k<`ȞK܋o}TE:t׆ExGJN_@kniff[Ŀ/eXϤ+6vik-vNr~U8h˴ϖQ!w31ژ72cR7렬|.s3ش VUxȧ2mWve5+(<'Gۨկ__Y(3OQF@p t`W,* FVV NalEJJ+ *vPZdH\h$i|&g Mk鵟vcTm2id.cM+Hmטso95RZN(hux~K٢Y <]_~^>I`6 RNO3S*|J8:nqm.<5e'K4qoP;`Р2axG_TK<w{;n2&Œw}EPb_W QviQO_b\P;UXe'np8h=GH̫vZeRh˴'n:{0Jzw3ad ƞ C9ZpVDxJmz UCsn5U ̈j軟x # Υ1 $[D7~ 7X 1Q`ޤ5U#] VGvB~X 7;3`+ܻql1s|hm[x#: l ,nh+YICZڤB1@0<= %1v4X [007$)Y{r׍Y%OH.d6!@ݍ9B %2kv8xMy߿(^ܬ"lWx!Md;tg,;^ۄBɎC2UB\jDNJ%JL9ΫN*B T'd\ڦQH3IA/gGQ՛M]t 6< t\'6X\8GнBEPgBUA1 F0`mC e]*Z7 q)FՒC{v6A^FZClcX\f.AvmӇk+Zck4f gD=yESfr'"P*d*5SR@yRpU()^oX5͔MfJU dDϹ=#8z$ɧBSû@FW~]ӮKj,qdb7Uh.NcO9ܿ2?\ׅatφ`0( 4&w{cDt mtPE;}9cɞt}hDal p ⌯'jUS8ي*%rTA*ZCԊq h->D`?㗣qI$ԧf͐f=gʫ/l}R :=8hRx7̭x- aW.Fkxէg/&-TҸ;ٮ"ȊZ W` zh<4Z'`U<6q l%?(yv ֐񜔂hSf8z.lrg"h;RmJ%,=5 xZ~15=n+9B a隺G IT+?ZCisߖlTZj9+88fJS)-D Wh*Tⵝq,RJﱹcʟUDB#G>aٴE1ћhE=&~hʅUksUVsZKZu+#`zD1Z9zOgr coDjP29Q=\z:lc, {eO*9| 0DsQJZB!/ )=-Ё_oQ>A&Ķ] 7:N<$'99%^䂧 }: 6xU:vϚ~˄A;#A>9O Tqg?"kXPh@@ݛ9zVUۯR~bP3Fp40[8/Fw9D3N%Fky|MQY20(lN`t!HVœ>35QdU9 !񓜂JY1]#p`SYlO [0ζ+./p&(qN@q|̭ QjvOѫV_'rI^Ƨdn-d-4Ҽg%Mv%j/hN}/oj>9^!OԂ[VW=wNHSg,9p E'G1%K #y)w2k&[Ql`l$]鄽Y\Gv)Z{C)=^Tx)J>zz.T{Am'15(8Vw/.[0$td.UN!n[4e'owںKp+V]Hsv@ƁkȍTc %!)H$I JG>_5OHZ5.ښs?pIRLCۮ%N;WO. ƯiY|IBY*hYIQ/@YznqVgGKIu"X/9\4oR0nU*qR6 ;ub[n-\/nsAu} pªQ9wfr*[m6aħ tfVԪZ9bh/bEHsEʤms&m)JW!/ !˷rsn#POPhNpSC4ھ|TVrq; Д?ȳg}ta!3[?UUHpI)mbm)1uGsB#=ALҦnͦ(P)m( ʰjM?Xai}+V}>wG͠ti189[lڽaov9kPu5˖'/(ۂ-Ծ\a?m}:)?;Ex &P?,m?@R^_2<$hWPsPj *w]H,O"s5F`]e- ufe2P4<Ommc VrJfzWiPBD95r?z8~ˁcچi 673$OG7PG0p~zxTO/9T6 SzȘY1Op\MtaP9r/F0SgQ[vy΋&ѲYBY-e9HֲO@ ʿ DpfcnuL>nO\&`둸b*Gqh(!x@w,ReLk3ip˔QIo/;s+ǜ=>~i.ȵ"1iUP-4!רyis03O-vč7_VY&@?^qM<)w̉F휝/Kͥ748TD2z=9h2a@c 8':'{Bm g٤8AfkEJȴºB(8BȂ#rյ2 y` Q< h8ƔPHPP|8=~ꡉ֠+)J{6m_ow~P~.O5I)?nmeA ZrUo> t( 䆼"3jEgTĒŵE'8jT=I=Q!I`=K9EKMr^7#E6f#,n-^P| Kh.6n qx !|Lr0z9֫kVB\J>|\05;A!/fC 1C S\{GB='u X{Kl+z {}:U%=n|J@q! ,_?ˬJR舮3*)Ulh4{rD9kg@Z4@i6GC8قw\Jt/6^e3y;~}7\X9ki9 tS?C(iHf ARX! #O݋-Qd;jUH1N.|´PfDeD`m\. w4LBw- >W"pvS9F%boÂ_%0XWO{g{k{۷PM:8Cz.{fD=4GB-Åw < hZn@PrR0/<6,%y Քo@-j >Uum*ҡuњVw`Mz=a80\7<-Y02NچWҳUdfC~ʝD[ &;1nhlR^V~SZJ ·- dtPnZAa^^"n&QT8VO%`=A&(Iںb#ۉ^&Qt ]RUcρm1i_~VK,>w56YpR-mVaQPN]ݖ^.f輠--MFe m[@R?\r8sEMmEBU&7lc+߭t82B3{wYv2F 7`WPqW4}goP2}gOeGPR`U$֥d $2TL[]9Di]ӌo E:;hx.W{`^{& uʘlpWOv7@7jY(įc>쇑0Xkؘp4dl@'7H+2o0sXk=]m|]۟kIC\qq U>ƄG˂.Z%Bvޙᴌp1;#4˨;xB4Hܿx,kS?EZ0HZ=u'YG %/YC{d4B{%"k܀tsU%'QU[yѳ a7uk샱֪Qvjes,_b8^m E`%{J E_ŁҎ:f2ҿ7@ `*4}O{ hNħ,SBDp(xNC45獤ȂY 93ͯBB|} Q2#TчM`IbfC⿈h89ʶnJE̒ 05/!PLH q{ȝ ;u'3}PFǜZ6+C8X wwR=XV5":@.t+0BRWSCФ@<$uGN$.BMqEBwPuvP`VJj_?TN5 r*^Z33mvXjKkmW8G=b^Ӏh=Wp\s~ihC -ݼ37$A5|_ξI- ձ e~#XOng*g*cie ?wH) T$}jw8pfЏ?f,-ĤЏ(`IExX_SdpdM:[g}ӭ"Fhf^ക2Sۚ8^_[@XJ&_.X{%VktJ<1+T+-3;6Ԕv#xy-N7Gih!{O5Iٸ.?5̈́mTiUT̫I]G[Ai|識> z&!\kAHX [찊N:`vZlB]ˡXE/]hU˪Aa:F%3nnJ|w5%^(]{ٔ0l D&zɱ!"vrWnFtbS=(S?iDLG)#g"<*_#8I?;FT-{#X_w8!uJ{9!Ku`|Hxcq$%ˡ *2`i[-eqX;N*8J⫒ CU$-5"V3B"|~lto;qa٪`@uаȄпE6AB>Ap@Υ՘΁¢OWD.4e }r"iZBdDLe) Y!RȾ h[IZTZ%smp9"5 ˃$`0k"/iNfV'^ը42S{<[ݕQ&=ㅮa`7 lNXd;G@bV 9a,[! FJ!l]:S] b27ǵ $R#ƨ9gVz'4l쟫f y #.IѠNH`J^,dc'_z@\ E$A֮`)uN LL+'DUҔTȌտ>JA4LȳXѳB_&&eIAJWge K@a(nLE4 N:D9NHxMV>Scs钷k$>IHV9Y{+ϛ]ocYj7jBl{ & { UKzj?!򿲇"D3%@wOTHQ#aP=MB4ǹ8YjlO1b +*OB:QM[}+^cXcϦ ]J4Rq9Ěw*3:&/3Ȯ0Ѷ0#[5I*NqD,bQ[18 ?d:e46̷k }Yg]2 5͗6ͺ'~)c>LGDo̼2aI*k|˦q ,S!<i :K,L{,CBgv-|,y@ڙ5s'(]!|ƪ@jB>F0býZжvqv'۰+߮q?9qZwwD?9άeE3(Kq8(A[[Gv8AG[An2!գXLb1lGn! J&PP T^)%6}s?tWn4_^J@V˳K gAOJV/\m 4ܺDZ`Ԛ?A0p,.&3;!v3+fX?|7@Iibw kl2 +-Q2A~7K3a8jօ[->}[ZBr7 KD+Lf2p#B*F%~ ]$9B`{|oc? i!Z F<}GSkIMsS]MsEq(K?%KJl9Z,;K֊:-*[-l%Jc*p]U㬴'g2_".n[)*~#7W4jmV%R<,O|t'`}z͠@Yg#ǜkĹ: UaAYW]:#⑏^4@TXԘcQV@4=-Z@-b08~-`HAn8a~vuȼ.UH/dWA=yDdԿ9 b?fJxjʉg/꺪Uj2",i g`y D@tɧ5@1-)=x&&dPlqbds+S4iYQ6'ѻ-vSdA^RGLa`Čvd4U*n#"-t/z Ck++*+;f4,K9l:*km<.:d-ϣh#B'7`f^Bw F'N8oo!JKi"k(! ,F"beQW07ag˗ZEWb`2 z((Zh^xfМ2NCFux^_U4I. K7+ xYNI!H᪆!{rR(a!hUCa+>0OM3#te>"fU.~y{N,`BvxC$.`5ۜR 7 ;*q PGbq*8(۫V/R8q U'{Kmݛ\X,3Y'@r-}_w0W7?ר˳*`MbC{%y`f-C((ǹl5MstC7 wղalf :4j6 x Edko=3g>Wwܵ8|H#8ϨpbR$[dVԮ*ܙlWT« 4* LF]H1I2u!F%,Bv2T&4<|fdB~k>r1Y>Dd`(\ӗ]O-ˠykcCdƟ2bN 04o{˹; =z1=HUkJ̌<'oыn0rՄzwN !ψKAQZ6[I/nWbS#՝ZIoF e~-Ma3LJ!4E[)z?weFwmd%x8kCqث~/_3$su/ qUNRFS{5ʒ^p?:F;-:! *, XrjoMΞV\A|g/q/ C 'l(d\6=F97XKW?k} :Stq E: XDnZa o>2!ݪL4.)W Yyyt))K`Je0d_]dfm=`ς$!)A50`4U]a A i׊THL#6ۑdMo%DR k~ r21J鞁WT!>Sg4uŠۧ=ZlL8ԊA).@RhB==Ku/Hc-ya< 4R { INi\/V:y?(+=Ā}>5=yز $AfL2D H7],"a5|QzPH^9(S½OfĦ3H H^DRF1Me]_˅ "jz㻘 ‹l܇EA?=yh]̆X`0Bzc;<Mdx'MaUc|b_'n͕]ƩBU{=6)el 1$IL;:~,ӧbA#B}0F-s2ChiW'}q^>9T4gD{ŭu;k~>7vQnިeA$|#wG yHng%?|{u/Ng LɔH@e3,S0JTVU '0"'L,JfUik_  ljÿv! sK2QTEqEa.x#!@\;;bJJ=ಬ0uR?v:  nV,mKfO\ഽPMQь%Z$W'M_?:(D|IT׻L,DJ”*Hi1I :z6hl_ոw)]=$A)Kbq܄ acGCKЃ*VTX53+Lw 9.Iczu2)Y͡xqg'{ .\ qe~{0/fJ?$nG\<pOonr?)b60LD?=v~:kvpkBP<[>!w d;@^:Ik.>5\ag8ڭ =m,JH~*N sMts}ʖ9#b{SNˎy;ݧs{;bl-#?ʟgPb 41v,i XZ1)mѧ{% W`7ËT)RRIMk\J({58Sg7m 9Á9*2gCE.DC\F)Rü㝂+w I;ċt`鈴wfи$6~Lv?0EKv`Go " .8 Q-?o䚈q=%9ȁ ae8`-P>k ؖ٘D2F=MzpI[uJmɢ#RwOqoJ2u}:& ;ЊH)6׫ϑC=- , 1sJjRs IiY_HZW^%,;w7 --_Xo)AfE4ݨr暙:J>чfɈ(ӊ4`a+j 1V+qha%ЈhuMo0V3oC.t2:mML<#Dg+Է9,-̈́4m80U%e@́ 儃dU5Kњf8Γ O]W aX*&t T5].;>I3byXr螦|{ .'^}94gʬ8t#+)XۅjLɷcm .9V2f|S^ s܁z)3[h2FEsk޻K*U7o(N+t~ ]J쫲j5yId7 jN]S)uTP'HT! BX:>XTS!jə$I&f<Gl X]ɳ/L'`Ġ/gAfl-X*%avIyE:l %rqbFumx:9r\xvkz@`RYDY$ g:!ȴ@w|H w4T%_3ġ09iYy͕{wn̯T&'DF^1(!_B(dXHclŭC6^X27}2x!-TRUءe*v02.+Ñͽmc IG%U?ȌuaV[eMRʕ@[bθ5$X!އ`.&]9WBfU}14(\8qt<vD俠ez]FONİ^4㾠IrQ؁:(̊ͨ8EPIt3U)Ahc3!f_5ϐ Q?(vgΑo.pt˥H#Y=KizLC̃z+N7xE;Zy. M0?ߏ}&A5忼N.f2wl!yyKSއCı]}1L9|"UzRp>+GlM(uĬyu"B*2O@렾"[y;D˪`MRRNB9x$= 'KZg)L[3E)3AsYzߏ'.5K\{d'ʑ {mW,~[Zb 5U+m9ъqx_ RqѸdz%{ߣk).:MTb'D4*J o:^fZvq4їOz%o3p/|Ÿ ϨuRX5ʼnlJCGcFE-H3ϖGl:x2][~.Qyodԝpi"t{ 7țr.Nhr#l }SϽE21_@΁ѳX,0%"okrG@̰ekUvf+e(SsD G8zcgy gW0p\Suv!q;Ur*BèQν> s}_71!qtˢ`"~4%+Ti = ^ %zh\#XA7ԝ|"Lyc8 5x%amDt|=k VENF X qa˚α`!s5xJDZ|VC / ųC&q찻\oG|? g{WSx3X45VJX=OTBo.SffT4JmKi.kgͺaM1\d.ܤtR-n[+αeQ5~9 J ܍h140sH 2 Ε)TGBlي;ӕz"MkZ:_OƯ EO;Zߣ?!HptfkЏt㳩R9;m5w}Yv0De&wKC`RNS߀>O8m&=58(IiNbըl^7 JÓG8N$Y]qÕ7*$)0<}QG",ϨB篺e `7G}P~`ہtAFI;y}-ai:8)#T1RYA2a {^9qR*Og/mW%x_ }B]#`lE6 IG 4An, P8S[go_70N 06{l*M-J6K A"pޛ!NK hWm/yb!%R(We[#  ږ'1u5:vY-B Mu?]R52e)T]I[0g"DL_4Y>2msaʳvhD/@to d3nЂʥOd4$NloBGlLI,M3p{BAnz: vvT Bu!/HC$ zBQZOlPFv [c4E!"V:ē(6K6l paw$3Wh`06u稛=pvnyڥoF6=lSo}X0ĀcXuhi m%K "ͳǯ$ N>mo8u=FG. 82y92KE\pY5[Wf׾aaaRyFw)r\#tУ_[̀2'X̞3`eF~Im(2FQ@N&$qJo1N.1ym=[_51=g3aI{T\猦{?$S5:YR$ 'gw ?P+= ^kvy1,*讒c1=20T"{O܏- "WPV VwJjM K&~Z,nt&nH`i"T&UEMd#{?DXqԔTɹ'm i R}n0J,01NB7Dr)_;n7d  6 &r_YJvCuɍ'WU37_vt+&~:Q ozZ]VRq.cG+mtM Ld}ɀ3`AKo? .\Rfa`/6b`598.)Y]n,J!=$2QLl_|(Qul J?T}C vKڷờE,iç~ omt"C8]7%Ep-l,aV%u22ut ]cnnQ UEI!\<;+,ݢOf򗾃ļط4p$5(Gt#|Q|yk vr1"F>ENrZ ÷{z^ l&<:ӭŬ 4LEw% ,=/%9?jd6IU.a7EEe$h2.I5Y@h oѺ1ټQ+Bg8H7&es\z32ȧKOM\ l^.V giE Un@z j͵@ jp<hK gvNqVaaڳu5׵` Zj6u6զStyPl7ʩfGuh[uOLuCN1MX)~1yHSUUΩbP//]=Le:ӕZ,s*꟎iy;(@!>A.&mX#ψGX5Y(ZovF5W"Vetn9w.V?~TwrξAI?f2{`tI8}aD'^?<u*t''u9O5Ctz.Y%uu } T0v 㶝Ȍl̆js}$vNzyĺn) h冫3?Mc&IKާZnh.}?9hXyU-ꦢ"I'rw)ʝl*ffutPKԻ YS*Xu9oO`W4N!(|AIS' &q{s0R*#|!`aVI*cXt\Eet_41N)$ rޞ!b?3h`vO^%6j@޲pQgj4;]b|KY,tQe> v;6L(h7?jHQ|knM4B(۱ЅD)UEQ5tw3 S MrlcEE~a]pJJSdrh~5"޶z]w*vM[?c8q2QG]uw/ڜ 7 #KLV(A9(GGEEY!'r eК܀nvEraҀ(pdel#C#|"@0p稏5hȳ$YZzPy%ŧȖ8a'xNpxBcqƑ]g_8%td^6o~i/u`͈2nm3%MysŘ&: `$aK,! b} I%0XȀ۵ҧmjY2 Kt|,/j=YI/$mv֤_ٚiN=@!XM/./Q2WBD ]eI  HLV[o%5Ptqz1x,"+,5#+i3StΚj&U=Jk#E7W (7u#DW={* r .H%YdYdyVs=Nlz^P8C г΢17j%spObPp|KVbآtdͭ҅Pc!nw[At+`N7IOilR[%R?QD3KK<v8 \nKcuP3a*#T+p6j̤!(C+ )G}'t[([@O>ohE,>)b{ oʬ=+[?i8FvË_|,\kblI:(?]r8} 瀾ALuihZL ,S(KUIW^(-7G& {eLY^>me46^qPl,\fhndP89TYV;=n!"R.A/3@(fZb3zx~7۶U6wU6v>/ -fakNF:g$q~"BӐKx|RBby!jOuTHyV,I\Ѭ$`}MW7(o^.{U4^ZڤfJf+B5SËXH P]YSjQSB>Y?ACJÀ7Ҵ^< qB|r)$"C] (eƒds+$5$#!OecNIdNbG:dNcU):a\У a;$xb*H;˜R61<5ՎٹI䅷m@dœk6!gl8T?gzm# 3b #+uqv)Z$6 EB;rGe0&JHJLB +UVB%f5;x` 8,4Tjr2NXjۜ4e K$i t7F?Lhcz$g@"~ϴc&9j?cV;ÌF('cߏ?׽䮷?)|o\&r{hDq.^L?YWIjlG-Fјp!Ϧn֌~-Zs]'O9ڿ;}tF~|3xҶc Bh{QWeӡ).D")u%[ɹt8,_vv` *ﶮϘt(qD"MWw 5/}Q/3fҷ}M*$)'JljHƘ|&`_@独 Ґp$Δzg!P~̯_kEq.]ODQ3(Zm'F2GxC8Gwz*P*Aj܈KhqA%1.ޖH溬Gv=TNcTJy*ٚ|\*&Y;Sw׋dN_!?`CGeɲE"Tyzx8: KS Sz]ejh~lhd;kC:Xmo۸.,faP!HW'{m+# [1" 00_.,Lf d(K+4gȫNdymj5- -uY] Am5 Ih/2 a[vrۏqb^]NuOfM9ianX~T՟u'1se;=u BֆE6UW3`% x3rQ D ۠yauna`Q5" :>}sAU_%/vGãvC\ L"v Y?,UX݋my#%(_ռZd)_2ȇ^oH Pn|Тr_"]]Y4uߧח0U$ދ7!̚jgBZٰDї 9(`!_-\ 1 p#ȰM"K" ,\n,JSe#r6``v7(xIv djVV7-\y80 J4HǁWGyX/XM&v[ 0 sqJ) ”usS>֤.UٿLJBߟPF|U;,7UVP X+-w-tJ+tSeC2830^ž#H}\'vM3NKDuy7 xjqp/tq@pk`xޒE RȞF4FDRɱrm s1h^bE1rKod9+0܇=7I|^J]]~t}aǪO:opl89㙜Hme ]s\ hPIB y4 `oM^]L lL}j=#33 lzt_K]PEm0Z#|ˤ$}I0JԷx#q7 b3Նb"RyM+e2Wq]<IP6."5U䴺i4СH%WcM!#I3Ì"c> %AF>sj*Ύxz*6kv(a%3At:fj6>o?Bס0Q>ݛ%X,Oj)W/Xxt)VҠO[BcNR;=NJ&hkJ[݄)cU6qҋa:b26#> jRaJLT5i{@:7.1w83\xW"q ւ OIà  L.egf t<2f0 E3%}>Tѯm bqjcF ԉ/2`|L+̗5Oў C Bat֎ݢM1&v|%*}F44WH®1x62)FmMm{8UZCb80 W2Cڦrn)'4݃QW2 ,yW,k6-?h@K-.Wj|nB8x25li(')0-llC}ٴ xăk5{-GΉ2M=]#|ma+D+^Bo-iBfG)I_uw* $&?cJ y&/a=t񇄣K$LI88iij<݈Gu 8OX&iʆ Utɟg tDA,Jڈ1e8, Ҩ &u.J덐BsJZʛ$e ,;$m=ZL$5ŢY(_Z-8x㜍,1|fQwgV~€ekd/ W~b!!~>sB&D\sف9B;mWqQre♎=@bqoC_ {Ft ꤤ!~>!ǵs. c^Q3/wlLNy XC͵L*~ə7ä)\( ً>%uMyD^|R>GLˣV ,s&ާyTa> "݈U,\"-B^7Ȝ7ϊŖn^?EnbTwLXHhwS.tf;7MJ跀}tjޤ4JHc2{Pأfolܸ<E4qNGne[27k^;ARaŞ@" uC&khq0;e!`V3~]V)64ehݿNzI'077 GNv[Ls<#1xF/;"0t""Hʞ( 1E"g6I`.gu'#'qa%&ܼ#JVӽ,ҕ'w kS\a{\"Vh[)ǖRƶd]VD.dn*Ԯ*@ Y0 0/p=Z79.'Bpü5L(єoA88\ SW9!Jf~siBCeks}!sG`dvcݥ/r-ӣ!iwq}U#u^6};џԼ'z&At}`@K\в9)`@(KtL4Ŗ0޻"NM+3nJ xTgjLƪsiRDuȴz8%rG#.g 0ڷ2߷_ \YD8lO@qØ[9}֨^,Q}6иXgRA3Wte3?^Gob0wqĮ4{IAG1+ Ph5g,psT:VD@YQ+6&.լGzsRSi B<7 S6F)o]/x10uc$69VMJ4[΍M(#Kd _uRpf[? MdB3=C\+3w{{8Skt3 \7! `uA @K\ yiSi`fQ"7/50gLv X_<&'$=:S6V+E+^T(_.*FXkAk,o!=%|)%Z " ,+;T@?gLrmwYs ܏ ڔa?ruUƬQA!BAG`>k$CFQ~la!]}0/n_En/]/i_-2`|quB%CNpA)9_l9vե+ޓ=e^-*2}G!4Wz+y Dׁ3揶SO9g!to: a~1 0#4Tmp6h3;҃cpS$;N: ¸h{~zܽ$hpV)~<0$c)~St(ssG)v4d3O>pj <ܢ\^ >Qc s.ot"3tYr\^$HIp5(8Nzè=7 h]b (H'RX!V\Pvu .Hmݼh1{Xgq]~*\ ے0)D6l;q"C"R-G& !EpcնlfQ, MֱB<~1gXk=k"q%p +VXcjU!s 2Q>3@ F4H84'O)X4ŗU% %{m՛޸-K0S0|QHë1+]1GnMcd_3!9~mo^/H xu0F|>o~)^ gqM?BN҄ϹI JRs4Ex kj ]71IxRB'_x̜Hvvdej$6/܋yFWrSfgq đj.!Εe m̝b{ ݭ g2R >iLeI Ji<12%ha'0=PR jݙrq\S(yu }LGZ{6Mb8m[-0rgp_{?v8@ $g6l0%`}J9'GH/>U `D{ 5{lPz~ sz ea.[NOd+$P5jiq.== D~a~΋oU+bT5bH٬O{GoO3=Tx:?5o6 Q z,󵭝7';G9̬@ [R6ƫEeSEIcxb QBIBf+RY T!"v.r3="] fMW?rtL 7ᒐSW#~VBn)6;J0O"{ 35ntPwѮ Jwiݡ.@B.#]:[Y.{9dg%Ok'DZxp| YN׃VD?*̧Ӯ0yӸ;WpX,X8.d0=<,Mw|s,-8r]Su㮰!54NJz7W 5-%XSIlR2$B ;on X2(дI+@TU@t`$t;^I]|xwtg2/J/yHDa 2ڇBK3!  Q^cx8 1%GI2"$7Ը%4,HL6I`1vd;ypN}+)P:dz@~K"5L,Oӛwwo L¾ s|sfEO,*w}2kV/GN]MO!:ѵ^^q_% `O ] -<lQpTjwb.YWVqU@ FKMDl 4J&ȲJBJe_E;wS1w-n9%\][36_*R6%!KJPr2?,;4zt.Yf@r}G q@h ni$tR,!b`A" _;xPz0SoE/P}$9ͩ*G*,V4D}Iw- `XhDS&DhLd/AtBYzUo5wѯ̩ЎcG;PDM!* 9kR|3Q2)XW-Ѕ"I8-zgucZwLJQA6?ymSOyqny0oߣ5)8l74yFӋ#?ˬ ZvP[nj"BVGl JMU;.^*RzSP= {^8@-4I뮢DCodMP($h0+@`]rcr#O P˜?>(HlSP0o ý悩f Z漜.ՏƖDp kg~ |r^)c%^D4W :Әhl5G<.arm0A|>5k'kO|wؗ z-&@ ] Q^ TAp. :SJC2ڪٸL2zDnѲ|%jEV\%+`} M֑cpquUkI'"ˊlX=PG +ȡC0$>eI;P)Aɒ YH;,/}QL'JKJ*yXyBK8D@j3P"N+J׫T6H%awƗ %hb KX)G5۪ EDQEV 9*RcƔ?ZIb+HQAiHRBpβ PjӃ݆vzz_7h Abc@2]Au0 t+N@1*Y9^X`zq8[To9cИyxprrɆ.5D f} S$ao* Hjb9mpXb}kخ0ݶt~RAWԦRЫI5 `0o zcV;JNwǝZQ9HB{sYvM Nq*0Lq#HG FG|iВm.=Et;n>Y  @VH4E m)&dT+yXF 3i=o+_nyCނ ˂nzXf HXL%1;-w/ 1I=$=Ͷhc$=v418MŸ<0@/mecG0:fHv(c <9:phP.,# SNDwlCʭ3W%ĞI]|k5UǭըoNu^e$^zKuL% =.z?c.ߌxEKĐ.013OHN)B(DB7]`׿ff,¹*TL({cKHEg4զ"+g[;n٧H:Ur[pCyG[Kׅژ Wb;bRfX.eCgʩ^K ? 7?=Wԝ~jRv0>&q /p^KI@v&Z;g#6nr&Ya;R̞%;v?Se_6P?B[z>lb!(/n8g4Ƕ#3#:]>FAasM_è3QSk--sV!tHyJ2ieu^4̅<(d aS cJku9Œy-Q4$| u8YJ4yܴ457I-M(&+|+ v'3g{,Ux),NӬXr#YwԫGF(.%\G4 `ѝڪAJ0t_7c~T6ʖ̫252U\\AT233]לVЋy)n$۽ܺ#C$̀?@St.m&Xr5' j_ˑ崥~m7 qFo޵ $.ޘ/ x<I@Oa8Aj=a':߬g|#׿D4Xwic0cuj 7Vގ4(~qfԒU$"X7#hv*XVQuRpWȭ~ -~@Ezуfox ] CG<6譩ґľ:3.!jkrQCUC#ml'Wi*P4]^"㎻!6Q:!yՊqy^12ZwY ](*Et]{U3zM;D 9"Geo(7.aW*х'P?jJZ"T.95ʖpe?kx_uI<$qޭIR09.i#9-') ~Qtt>"qaO)G yضm8r*wU(qlʁKGm/s3}Yhk(Vsugs䣼. uKCsk~%FZ9J6R:S-.axλA #c\}XVV9W=#!i%@= `7NTp Y}wh<f\k<ۍ `\]~g&IyA:\8qvj%^޼9RdLx>G!K!VQ3ɡ"C|H#Լ6#_67 V l<-}O Bv-WyqCKTgI'#EђC6~~ Slho2ևi<LmHGPM3IWװ`+HZrK nkڳ] VbvkKCG0>`nZjY̰;ԉ9i-Ch0Ɂ-YPg8ϙ3ldV稼39ewq5"OOE;ZojA~S3-1P `VwX 0B(_<7O)&I /8!\me&1A0393SS.0TRLPJ|E/rąɒ,W>K^8 (e Quhp ?sT(G&tn^ ,0Lȭ/.z!FgL}=@M!5Gźw~r"jf f*_*8eUTʹ5tuLZZvc3 Kȯ-~HdwIi/ diPb J~ )e z($^"gOR g'9_Կr|T*5*)0V]L@ՓP\8(9՜@iG9}@(e",tPHQx/%-2! ^opguAiRf$^MS!OVg C|P6T}rgѿYW%!aFzeo;"%wNz n#"格87Z1}"m07&$w(Ϩg=M\͇g (jko1Sw ~\oj;u#xAG$ҮLچiS W?drb~䑗Cwbfa``fT{wqv5iXW:}:띏KW#PETSW 8vЭF[Wk(q5bb}~j~hq>D. 0:O] =aO`L vtiOzE:g9Lf#iPEdc]%4ӳ5բ#u/" ?:v]&޲4bAR劮cS <Ռt_qUsܥ&p5VRjw~M@  }4V`4^rM~*',$ʞ{hڛ$6w>z$Yt]k^ elɆ_ImCQ)`-!x1٘BJy6Z)0)z.τxx.&&NJɪ5%mr!jli;Am7 C vlʨ%l4R`n (KO\jLly]<U2. <$ *-bilc7eNt a KLsU3LL` 9Uٺg;FX5dQ |Y*D!N̗҅8?iy$^Y"8x`r1}k}wt ـ訴$A%@iC&_8l| n5cJ-ok3sVu('*aHQ +zc-ցifLcP)%+</_73'Gu9e  :; txم@7'G's7OL3Q%8qh#UH| ?ataeķ'VXo7K4SV,sl; GL'>)0Ɲy r#p ?nYYy鶒,}feoo{aK#}OiNM%jt_,`,9KiwN7ףypʽ /^W#Orҋd̰&`R|pKSIu>?)J9ܕ3ޛыG5TFCpi.V2Gu2*20v|Iy4i|tA_̾]g=&A %IfKQ|$yB)MА[jhY\2eٴγ`. rmcWx)aԤHRg$Bϒ J5&#1C1(#S(Iy6)(iH0I+6|Qp =r|lbhx(ݤpfjYj3L ^bh{ Kn(@( vUU?{ޟai7rqjB&R9<1!H1Itij]v7u3ՉXY\#!͸B] Դ xq[ MH"xɛ!+!_uc*rCe@9n؇#W { (|Ps!M_A^5H}VJqFY/L/%"?Ex LHO1}b$@,BWFb]eP;rXpPřRҭ&P%4Z4 SR-eN\|"XPޢ+'6:7JscdbuJGG`uGKG#WK&d֜F99Go;xbg:kp~-/nMeCSiaho%.YU=;g"oz5? ⃴Pó:.nw._K-%j=5I; `IaYvAP5 p1MaN1 ާML^@)$sQrΗLߏ/Ẃ r:VݤdI2?n&aa# N0x|8Qc >6O^Kjs(%E~`ӄ6jh欅8 a/syf;Z~@\a~ ?l191Y ?LcPBe?An}{>wzzz ?Å5 >hxR `bP`REiLv@ PAבy늅gH;5*Z^'tҷt!j}g>]!:+hJ})C^zn7/Fz%,Xss4x#h+F j1:5䢸2AO39' a!BJi(qpxMM H9EC (V⚣YHIJos3XQ9a@h?r#DgL"R'{v Q KʌWtkL,@±.;hZ "t02`pjjRZ`(ط.1q <+r՛bYH|ň'I/4hWX0Q1NQS;Oh׃-<܎PZ,!lvCNNw-RUAE`BH)[Pm"ueh1lMG 1ȲM=77`,_Q ~Z1jP %QAFxYlqvzhf -N!~ ɲº 8>K.O}, O},l t@(Ni /Y;槹ḣ.HIϿba˃G@,oHQYirS4@*"Y@HMP]W.i=J۸W<  \iO+8͕O{ /b4ߓhKSXZ*{(l&(9|Ӈ.Ȧk̓DK _b$)2]!JH/rl^ECqLJφϻ&4zrvXv4Sg7)P} L('_`؄=.$2ԚRNq ڢnpxkB ;*̎>&B=T9JEGz/QHVd=:]LnLuQt-I 'ddR t+sFhJJƭE@([rjʻKûח~!)֋8fFbz~k ǐ^Les8Aj!=7_Јj7ݭǯ,b|z%'X@ȌCҐ]+8;wlܭ^kt(` `"Dl+GSvw-_U4} *{pS#kV/FL(w\|m3'aaԘJ9v6~ʀ?]if|6{o=O;ʻ}zO$5ϝl?aL]-iL3/`.}!Ǝ\F(}")T?c/ !ov k5]Tgw׶Vj1ty$/ zל@Awy":xJgl Wjm?'azXƁHk2"&V!mvMd+;RwcNi\%Zr26 *s@9O3;;3mȂ4ށ4 Tfsk?sMM,I:,6"`6)o/ޡתq9E'H1=.)~S~'&٦޷Ҭ7 HgI7aaznDѤd|3FW a&4iɲ?uת|7 '2Z{FLQOk.Z\kJ퍛@GH[, CSF=ȱn!dpXVJ[xT=rtGI@ݟ.Ӣ+Lr[}y.nƌs'DپQCO"83s_z:4r̝Gы)xNp"RϊMorw, iٸt'iRQqc {|9hzXyڂȯX_3h8(Sǽd(rwrrnC{#(\4w42G1Vw7q+hɾGy=u/o@3a#UX xc=|Ltz{/? s43=aiELY.T#u*B*`nm=޶ڭcE]n2FԐM=lҪyLlzUͲ?Գ =>\kݳ\zQ1ˈn8μ9ۇ]q[27~C(@9WF Du,tXIˣdBXe> M,ڲ0"d864H|2~}%eӨ (46# ef> ]hG&kΙ q~).{d-R?[-ބ"x; g+\_4rږˣ_Q!HX2@$P41J*Y8!Th욅K`y鿹 t=}9lmc`7bשěrwDêˣU-HgbKQ::eHA<ޘ>i%Q~H,SQ u":F*42L]sIXiVֲOpprhUOK.H(SP= P SY$pJ)6"?zU|)g@[{TFc8cLF>DAwTfuy0c_Z7̙9{ # ?HRd}FMɹQ[HlZV]lMy2E_{? Db08Bh<9)PdNSfHҵT/gOI*4{ǀ'6)V#'N 2Ytl:I,ƙr/-³Gh͢([\;YEҠ@iHϛxIџbZhՆPJw_d.;;#|4Ǜ+jrY*Ӑ6d;詣@Ddas$S3[ ltPY;'E7J!pmux\0 ],} %4=u0@r2ٌ?]KKj! @\Cy.{J 27U3Y$qxFjet.⟒ߌλggch]Y)pzՇJ9u-N-sݬfdɏ! 5cM!Q~iͻ^; ".թ('&_Vy`xX=;j,O;wT,$NE7DȗSǘDrDny6ޣHA1,Z$myκGQۆ!DP6f]QPuíc&Aq=/[Tof, R WS#;xZ;XS6Gr!RmN>Rw6T2y}iuU1^:GXv ⚾W #j} JTYU2M ?=]zG9Y23@KҀ~3ISZ;lgOݾESnX(@P94zA=f{{<(N $n%cw@}CɫFXUPlV2XTh݃A~Ir[k!u V1nYI̥ΰ[D2;h McDsTOPG!KUje0Ʊy+׷JJ_gbEgO h.iboU#]]fGwJ3\;N=ժ}FDj.ǧ o #?æSxKvWf7 ,|m345z@ܕ5?ų Mؗu{*S%)I[zmmBJx9auF6H ^PůUQ uQ4|@Ppȡp ?rrU5~nNMі̾2ku'FYyxaN;*I3*Fj0huz_= ` Q B 6WØ q,1S&6QK0ў ˫8]P={6lYzX@m =9Fp6*1޴x8UFww=H%01[vg=^xf'~<\tK9QO=Z\+K Co3ArˆAg8QLB"GlL3|g?. ^Apzz+\z"u/98`al`v֌;\UxFfzxBb$2DӃ41 *j X$iJI5#ll 'ٞ=bH]Ti74?v7HajRpW#リl<<_)iMƞ=Brޚ1Go񞉹"e'E'&%*Cdh(KB(¬ª$wpj`s42r,S0l0*!Գ*A_ ؒ1i:9̕- 2R3!Vf)7G1 ܆'4?0 a%%f痛O/*fb'4*8| r|% ]}Ijg!P1E8H~^һguz^^kTs0?6|Rjz8EItARgyu: N^5ny9@_;@Ynn:2h@F2mFXQF)4fF_ FAD_DmAFGxH *FP1 {yNiEF+ERJ,@-˭?.!j?$dP|G8PrWDYK#RTJ{6fSYCsY`T:`] a !J"Nb"!b" z ! Q#D6}*0Ko; a]Ht䆶7=)>&I)<1I1GҴcK\%)QSje Vz ힵXov>Ҧ%]E>}TרJA+';M 8lo*D:BNds7161%prvt1rN@I AҨ Hu[ "ѐ^J A=Fi Ye$iJVZ ɽ }SD"O瓼iB*Gk7,wkONer 5x [%|Y*ЫtJU\%EcLy32\Ha_bnU!s M0h =$! osaϸ1Oi ? STz57EnV:ӲhM,1# ]jsHfJEӝc/kqF4< vCbA B> Č?I4 H$~{l\j,[cљ7(i@(sB&?C][ 7mX~DAΩ@>C IX7o%1\q6gҧT_j4WL#0C~ZzQWum)`ޟz+t>|:6138 3#hd&]Fl ȮWK 4(T{cwO7B7o#ߤMD*?͖Qu0Ɖ"``؉PLtJoit1xr22PfFVzdƆ|vFV܉~~ U 4x FoRSmO|#QKDGO#NL&N%}K'0%H EOt"HE&&Ndd jXGB N8zw*,6>E`2VAݳidN)hf%fefi%Ngf'eV'eN&D¨hDSK&}1~חgb#f1*DhBᴟ[da٣mXMfHJZ¶+q:Njty0^FK+o/KѺ֯.ثx'y7Ucj'(pthF^7b(VgvwM^}i_a_`ocNޓS@B".aPWK<ܳ@g1T pEN^zb^Bu&Pq-缙6=EMSGPeϕA(Yߚ֞JASdu8=st)ͷaveU[0Ǥ|``ZSRQ!0GbDVQq d؅jQZrOcd>jS|gqAn}X5BvMƟjwܬVkʅ S#!O:N2Ʃ@){GɆh aF X2-zq(Etן2 L0aN&{pD} u7 r (v= .8vz'`/K^l梵y>r}Dc^qTY1z]CB,1x?c"Bi\ւW3̨U\I?˂_^pFk{;?Dk8ZYdݨx;cu][ŝPHi7lvK`tFa4("18lq]@=je!D=/P{[ʤ? 3N4D1nJpWR;`)KoD$Q%|P"[dХENgum ZxHKpLŽ$|&; *K)KwW^nvcG xm}ba)L~ R! b|t;vѱݩ];~/7ք:x(Gg'Wf6kY;z]:H- =Z[iWw wƐ%qj}2D%Y3*I:3U}Ʃ兞:쏮R$|kH/c'oD k5_/B^ڨmt!UV%\MA*V:TcG? dMZ+;!~\J4%ixEW[|gYhy@|[IIܡpTE}Ʊ&%GSZ-Kg#)͒t\iJ͹X1{]KUq sDlUm!D^W76!g^|E›F3M2&%S]rκ[6 Nkdc1mŹ 8`Z _~3/ë"~ؓR~br+P=8(Ԩ ^YL^^ K\>1E O颂|]s \zM=JMC%308/Xz$zG$-]< (U NTϞ{Q}h+WFj M7W >'Z= EP"3,&U]"C ]PWNRfMu󬌓$qwq+}#J.8 ɒ7` o< +\\wm'C?QFZ92j41'-%lNUErEǠ/@{:ٓJYΫ4#.(1@U'xQ9/:Hصfj{n>){H);>1g񒋼$@<$} ~)a oĝS]0 |1YJm#Ǫݝ>Ӷ ݖ Ddr*IS4fx&ΰ1x n;PU`ZqRqFsM= LXdvf1IS\8`@OF' Z$#` ^rw{ңy_ U9zgz4 ˢMtNS힙䍾z@ȥY]LUN:ҌJ9[|wmrl8 ҝ [jU6SV7\Gd}4S U䪹qB>r=j kF00)Ɔc Wz*~OdEFC9Qfj* nTnrx L| H 6 8yoFy=scS=oE anlp ?ˆ̬Ә`k3XH'vU6Uh8'$]hplq9SGl 4Bڄ=g}z1p;3qCUHc!'JZ0Aty.5CI| PWK !JaUCƖk\R h&$vgխsx"вoYq<{CnEוt SÍ ?<gR"F=v9kb69mbljAoH="T=&@‹16S%<=98Y_I9!9hB' r#4Ylc6Fjɀ; my.kSN^0]UxD *S(VFfh$:}~^=nϤG-Ǧ3I#|I=/`tSrZC' p!H 4UЉM4>%4aK@ 4]X s6tzج"?K8ת.v?η;>AZ"S8> 6ݡ9]/?I)h_hfHAoe oȸ]%@g'8쐎S֮R睊ZXa' 08+2!H[T$M`ZCCA2i*|T7>%"p+v$Z!y .zfJÌ2[ϔ{ eϚQ:ۺQ}āk&M#77iLv k6\FWdVl7>"@Se3cb|U*8\J8 PGt-;KZtw+fU~D0O(tmnb";Ij?!WT%}!uUVDIVXBUgܽ5 Fmns2f,+zv¯;vMDǾ>aZa)3=Z s~hZB >b硆rc/L AHdz$B Z}noThH  4a8΍gZ7žq%j_ B[#F PIu&<"uUFG&l?嚭|yg5&äS5(LO/s<2,$%FWSUbw^8kR+D\u4NԢuC F'|7MI[,tHŦwmmSTRbUYDl6*ޢlo5t9@+g1*ޙ.#:s^$A'`)ZrZBW kǥzThq56V7'E^P΍9Wbᢿb-EYOmm-T'e88b߫4o4#U([KN΢QP`0s $IHtF*JK'}]r@UfN'Xg{z }Ev!ME-?lA}g@Ȍh8^$ ̺!C k]G yޫnD ٛZe#L2kHLe)<ڤ]_Fi / F[lQHs`gu{%]u}EB&pxkHj2ʮ5唦ϝa ;E{l_(]dه|߻it~)d LpDfkD@ TM pKHu)޵Ja<"'x0$;`Bf3R;5Y(cAZ1*#KHXҗ^& #A4~̣at Ua^)[uLDDg%+'բ`+meD*AdpJ d|ҧ$J'JB%sâ#.P'8*o7bîBQ߁$%jk0κMe(ʍޞ*pE@-EDm h [(=GǠ!.V \BQbL^Vvc/U]Dәl{xI,\U{.b3&-aI#}G^[ዴ39C;R i鱱]ʻ6 ,˷v':jz\NWGAb~$WL`%^3ja*X;*`xR+=_aә %Thҗ{r?REb [WGeX5 #/[cUJU*]@&Xqs^/X9PwhOMH!?kvN|;[:dzꡊ~.GR? _{OTV dlIҸ$OFs.9i9; zaxPalݚ 'aY I]`{ bfۏ IĺhyV}D JYj8xxQ<1ĜOB >I`xcdBQN]$n"ڥ05mnP xo []NY Z1?l38܈KWJm7Ǎ2Y1v1c .0Tvb_cf+74+`y8 P$il̃WzT<}4fs1qy+l ̀@̓c'1ql#+U2-f*zVm={@ s1|+a OlV\4{n/AC3k%/e(<'ft&T WI'B'n~u?>3(9AYBC>DZt At*Il!2e{ ;xu$0j~i@Pj~dW{~u񹒕{k~[TЎ1_?}H[U(nj8xpطHL'>ײHrd\ste$TruI^a[+9>a)^4Bw 6g pZ ,-GǠgHێ 5lv%bp`y<8d1gy苳v?vCKXd8֞ev/=Dnc6kYTraߞö)9o>X|'#&J_7*8)[NnDW{V|[Wb>PY#oлݥ 7qyCC, PtKK4Ęsܽ K5hU9xG-G2'"Bw;!>2mۗޡm3kqBEL~.8&isG4hs!Y5Ţ!*#wATl::!=H|K{;SJCAx '!CKׂ>$oIi&σmZ]xnToU8L-wQƗ\;83)鬛LGSk scN˳_x(C:w⹆p_gnR&8>jr8Jxv>rߊs\&sI|iN8& N ?zt~%J*A|IxԈ2LXoAo1:U\EAEjӸ1p}{LV3סz.ge\e)sUxPdyFA-nHY:nVllԲ wdzԇC_EbN|`+7/LVdAu_/~jIC&%O[|X!*W=we}AIjkJ]"{DiZ` (DB Q#2L {zVqPBwa'eښO.> "=Xy <9W)?sqHփtorQrG--!h<ƀLwAGe?!"!x8nsiv?'vQq@X }׷{=vf~; $J7 V -_}w&8woD\X@ݑ'bfзBw#if$%!Y E&ь27D+$iA9$g$7 .$n$9QH`Hk!77D|rxa@1 M'4n-ăI0DGobq :Zؗ< >uY tv8`#ש)$<'s^*֤i w]2jm<"fpC5 ù 3Y߳#}S#`V-E@ϊgR.-VXE6:y ,㯛m&L! k9?[;qat:V]_\w`M꟰ZmԾuBo: i76aoS'-LIe$]dX _e*$uTN)bF"ԃkc };+pwH 3*kYNFU8f_L$K1=:XEGw%P/[/"r6-Zy7QDGuM^r 6aiC1X$(CJ=PА}w\<5\UJtxT<m\rr\v=+/Rd4"{ќ+]+6$,BXτɚU%jWn8Z3al!ۯUm"X2(!?X}_Gkـbq/K%M+mu Ю$ 3[ف` Jb8#_+W#~Uɖ!x_s.ׁ"OmP2_2WgbLk;b if}iP`T#Pb8Nfw.ZݡLu 8bCLCZ J [rh vn]lؔ酴W7/烦li_ݠvg*ONZ믶]j_p}s/9 Cs67jOZ.qt&]$,~ Ҷb&Օ?ʄ|, Dy1I\xIw,}8PckٷAgk)MUW`DP(`-= .1~/'Qb1/|pjRmyq^@"ȅGB^ ߀WvY`JY?9 1" `qX懣sўcY2B0Nv25/V[yq{ l7*^%(^D )|nLJ ⣧  ,L$^pjd&ao/َ<|ƮN2@Goܡ͛dͳpubV ۦ0}=d&gr*w )Q=oJa-0x? ceNψ3Gzr|,t]%)OG/\ieR# $k:C-_":Ū]X`gk:e͆gXO?6c;ox@E"MwO$X=L#RIrS˿>'i-1s2=Qp% ^fMkP6;)bTSD &"Uپ,%kuBmcU l߲飮pAUFlm`_WA7š-/]XM>AN0Z  y_"qs܃ A" ʸٙSY'd&)}K3 `50{0VғzP^h[IUW*2ICޭHb8f׏ ?x,jj,j~L־^- kr\0t~mX h9F(뮲Cy+u&VYK<JE:䘸2TrIHY9-^r%ٗ$LtnƟ€ zlOQwmLמN-`,2L]<&ov`Dӹ5=Gq&@4o:uFt tMK5@5ԼD:h6`TkAb o[X';@=' n΢J{Qa]T[|BӴF~B4]tKIe21m*98)T6@& ya`+cDFE fO4E/"V!4v)zyP6Ų GMW:+|t TI|"1$%E 4$0nd3v -jkwEyy"}aX~-Oun@yrv}7KW]u5pS_^I̐K&)־md3ͺRAܣIʾ: /HXߦxcUt%9ٝ%!ʦ>~=v)挥T׎qڣW\`<) M6-Տ[n·_gjj_+l70Ibh}jY 0#83@Ww&fSfVI2ڬΑÛ5f=°:kJ4|cեs >O932 Tii 闗E2ggq8 `+IP\4»g@#խBM`ёA9#Kw[yDVt'4;`w­W8K-s7TS-efU`4 X%GeVCWmd0#bZ/Dv[DK9 n|泝q.${:CBDCd֧uhۉzc2[XB^[&azzP0_mܵ0*hQQl18mi3X~R fElۼSz⊩J8Eu%;]Wdj('u i&x ?Ƭ0S*d/2Hazn=~\?p0_EIh)^3_R7*8d9Tw2(o_~)R C߷},epD"(>3 I D 2*T*;47xvHi"Vnf.ԼOYo3tc 72Fn{e|IW,CeX~:X ,4သ|2NOãZWfcRb@ԏ'ԅGޯ*jH Ŕ~{V .&uI+|gjn:Zb%OxRue|ef! K/Ss"j1J Yyjv`Zf[kEqR@?YMm^) .c7%xr ˭# W]Kڛ2{c$ KR`i[GwU#RfG* LT$QtaŪ(H}7#|dj M_Omm׮j€JɏW++}E,/inP63>LY!)B1N@1+β,ez(0S4楶2 0rwW-Qf1E Äߝ896v[HPQ26SK;Vi6,uH6OTRc]={s'둑:1<\M3s d4TSFE(SԦ&G_դx#v̘M&P߹"m҄!e!sN(:oa#2hg4"m^~'q;4.O P40NC+0j{_-[3\ PONΝN;IWow[Om*;ϸ\ߩ]35"$mS;G9"~>Iv\KqF{@6^̤=FsXRuҐ]h7О_+W5F Y9BjAHȠ@h Y k^ҨPu|'w f@njc^TMSJ@5]u̐!k,Poؓ)`6`ئ -4MXJEf Q;_! ◕ةYиR73džh8B.zeˀ7f-,H {-9rf 3>8ya25JscZd*Uo-rPX]ώ}Ӡ|]e!@bDi xAsE:-~((H^^B 6AQYE%JaGG,%M]YfQKaL!ͱ (HYW~Lj`e9A$eRyB " vԀ!wF/RM=֧ hVډz%fϚ:n ;P`NKJpV{p6=k2J،Lj ߃Ţbo]'Yt&nbU`6JWs0`k_etsXd&U#@7ܣLvI0i/:)_Z\e C++ M/a1V1lB؞4g-U \gkvCņsiqlWZ]xM%2Ŀ) <%&iS6bGhmE"l=i\N=ҁQ58ܶUp\wѴo 3آ{_S3[!>mj$,̵Yc'v漡d[_d  &g]`'&B*^j?x SW#n"ʼn&h BA.t=㪤a1r5`kߴWܜ9եh&rN!I%J]-XGg'^U0 p/*'v"4ǛOsCU>j'#qmTU5V9jyq8LLpin Viax(t6E z:/tu1ԡͳ3;_1컌oQ_s):om砠ݱgܳ gv~[d7:)0vD9::-n[s.Kh (꼇KPw0튀ڭ58+|uQ' B&>DO~z|@N}[(v7uuSKd3J❍(()a ER4+$<Ŕ[a?x%PZmv(Qj:{`7  ?p(ִ~O]ժ(AA-4k!/<ŪAuHl6/޸jf_z$rؤѽ8>Ҡ0tOo畯ŹNYLg 3؋6qn cqttTusfypүw(͑y5u7 }=M;TJn# ng |\c!勺ωwsF m K׭_[b};ʀ(bO )vU=K 2xfP,%C[9 y̿K9a[iǽ0/%)%["($'cAԀUʹyx/2J-7/C< %M {BmO4K VV,B}NbX6/2Mo5.-+s׿ P3:+䩛 YJ[=Aʵ&RR.?nT^"*b-BΔ e,b"~=8UĞڪڡe \_i}gNàMGo; Wk9&!Xü+Y[X;.AYP?`7&?/>+,_D;yx>;a'B^dG -y9դ u)ɀCXHV (X>XS)YӲ&>\c$kw~h,mz}]ҏ}pssPiClrq(Hq*o W1SsG| X:؞"/ivX Pz;|AoS1@}9bms6kn1e#s&ˬSDC(=wT p>.fho;*M͖lx1';q슽9,BDU*8Jܑ_\X{>Mk}dK7& P{HCRQxxwY^\Y^nDw)Ԩ4@ TK{vϬcȰ=rs&2p8Id(~iGV̝7?_<4׮t֧/*j=p0#$9 e|H0S?LG & Tʥ!Q8rekHbd=t&qvDp˩S8cn>[K XgbWŷaZQaX ѐBg]\:W=bH9[X v=3ahDY `Q o3~\9_uEKW]2oEp"9fKҸic~2PXDdCL% hn kdgGf{|#‘m%Skn>fQ_`MܿɘџYDQf\{SWE .yۏ֢-Vg]xLkSKed Ve`acl uٜUUUoU9"ۉ&.s,{Y<" P{LmZ]qR0l(]"ajh˗O߄]Эk9W ¸بJdz"e1e&eSS!m E]l~<<V~~CitENiPdv0Q5HZ.kr^pBΝ+si-o焟@ 4,>PW!}He\*`y@cW3}up^8ughj)mI")IYn4}!W"FW8 b\H &^K~0wo4Tw<;db޼nMlÜg0YLm3j.FL۫ٞ߈B !ZkId17f>8~(VÿHP",;r7EAO㈵g$f; ̕񒎮$] >QHi_`C :6 yhg$hš9,;*7qsG"%@Mʼncc3SRŦ_Vu @n"eV_8 ` ܦ490+s}ĵKʱ+.=Z BEE +@P25YwNo[RhX¥-s Ό[ŎXc>!ɒዥ!svŎ5MMf*6Im3x {^rxz=^;B>L[;1 |,;>>.-`ҏE 3 )ր^ R~ TJC0a ,]Dc l?aSybdʙrjo\;i-ShØ1k߳o}lQ̘__ ^lm}NJ~eI mx*?1w[}߰=Q2Bϗo]ZJpr)(ɜ!] xozCӷ 3k|*M k8B'7lS|M0A}L^BQ@,z*sxa6gvq@|g;=!ϿCB vfHm޶ӹYy%HuH7 `i#~uk0bKl`q70ðx3thPBUZP9C_K*ep&)`ו>,8(2}Xajs#YrT^lLJجȀG MXصV/NPXTCͪ"pgPL4 VL $hĖ6bpbL2|_e,CT"A,EDú>+C/qphQÜ SSKXagzh E8,XNVglK2{-ߵk%7O]''|<ƽaH5:=`Mb;h_b/ZQG|I|@}jIfa6pE7~Ԑ)jxH}B- -&!ڎAU’w&<8+M?\щ0,uJ}J gؤ8#ؒچޡ$}fv*=)dEIp:B 򗿡Œ϶.ۿiu"Ek+!ʕzY'4-xF~UdHf"8G8F)FRDȕZՅzZahM4Bͫ#}3옱/>s Cn!w(\CGD+2Іm3;\ʑF䭎'{!~z X9NN8~ <%zs8&_,$ {âEǷ)^#)rVĤ8=,DiLyN2F ;+)8nsN,iXB0̵%f*~+ȸ!!q)fJ/9sPBíPU*U94K'2ᢧ (?\aUк!Zz!ٟ?k6B'(#(ڎL9hPwYn*`λ7:UAvc=6ZrRv\ESaQA2œڄON>*jp3y-ZN)ՔkgPZ#AK$609(lDbDG+cvUcKB<Ż sGNNTlaLBݏ^sU Y 5'eDن]r 4h牷{ԤQMA+Yް|YQ/2rHQǦRB <), ɪa]/9$H~t!;+ʈ1cb'aQ_Iq%[ YpcmD莃)^ 'U͋*įNX yĕx*k~"&l1 ,(Ȑ@dvS+kG'y^gz\Zb&$Q,!5ܻ[wN\p`ŢɣnC["OK&%"523suJ b,"yq u7TcK@ hP1PLf[ҫuei[{5͑RV`O  t+̾J̵U&ZꩯyM@?HHeӂ0A)NoytEf'M`rNVv7%q byMzG\fk!t"q z8o|iO0R\{m&?rtTw"҂HKB+y–A)5NŌҔ-4sMumR*uCW)NO9B,  3(H%d"yF(yv1MبF*ded"44F'}sc::gجНݨ"?l\l /|.p;F\q_,BqY'۵T p:Xv/#x)Z_CoS>i]gX0>!n〱--!%* vK ~!- PX҅ԖMf2t5b ̌9M92W(;nn{ۀs"`==]7Z/s`"9<[?6sϝ@TWwQp J2YR D !q !@=kDy<'틻1wh9rCrSr6ʹ]1/cĺ 0sΒsbmLQjQ;czhS`b8B=/[9Rx5MY\!$>vС /Z-$)OC;)3j4H24mD-piUM9FR))ӗ=f"ivPa80C1[&AV#) jgH ! SPkAŁO ZhHCT'Dut;;TS# ̲Đ,ܻ>wוiB jM뀙]pk"'>r ;=W;=ZM#dG= ׻gD-1~lbvIW'MxWдRyaѩY=HC~PR 1=ZG?K@IT`]q[.c sCk?$9>V,ܳkTl7|ԤC!-+S(YP:M׭Gi2}}lW9668B䄧OЋ{fAjr<1`>Q%H۟zʜoV$w:eu]܃T y~_6BW?{ :sIc *"UiRȳA;,7)dzYraq 3*Nk.󩫵n? 8du(X֚5g UlTi(DȤ-T};UoXVGNQUO}Y-"8Zk?!m],t'_O5#bjDŽ3;Wa{ό(0'":vl:<(ܭ<K%&";@_aA_.)jٽ"sCH9PdXZqiUֵz)R%v.+Xݹ@Y#tP}БHL`:֞yQM!C ymwcS{O$dDv8B n6JQIl-@v$RǏ5XҠ#z2r3ȤDDfBQVF9*Hhۋ.j̖$F_)?Nb^tw "?O21h-2ϡ* fNЦ9\i _A2_zgv٨as !31hGԷc26< SΔv E{F,*az| 4kat+kS׃%jH#Bh3$#Sۘ[jn~[HS[ 2cCu'YRÞ6{yxY-sxD՘i%/Z V+#`AUHBzi["i!HXܡ9&8^I$ <%(2K 蠋A2HsOzf'*=vQEy `r 1K5+\̶3{wWN3K&=%45IuȽVx^TDAyw+WKu@$%e6\ >:nqgBCPD2~)#dU4 G&F/c @&} }{tWBpgCmmjlE)`ϰ*U>2yrE$]xIkLf X ;c(DM {+ϼEK*3L_v}NaS%thٟYq O"$i_T:5{p2 75ma7B9Pgcw1gEEģEٜ-j=8#͈kgXc_RIDIifGNeGwAǪ? <}~* @ As0aN aݖ˭O1e̔/! FՈ6  Qxp%Qxۘqc:*-[XՉsA\Vڢ0Z|MYY\حpe3MTXfo߽L ʐ[մ^eC$1D]G/?o7|frl_@GԟN(QAj%$/MAC[ȀzB%o' _23.7+)Ÿ;=Xi2DPAܷ8I|ܱ'D.gE3;Qb5B6ݸW$̀…;)UX~U*CimbeDKIpv&_aXRyC1kGl03s>6P˷(ْYIb S.ȻZGFcV$X'ʣެmT; ߿vq vʟUέk>{\!{D22.-Asp] An.~l%M>C(Ŕk[Rs.q /lH  * 7OYi4W8/U+3Ȯ[OZJ!_xe 9،9ЊbK"R[U65DRu4X1q"1Zɨ1J hOB yi&q.Q޵AnXNzCCy0ǫY+iEzՕdpn' ;Sn 2NBkr Pi(̫p%EiI@Ev4h `v |A]_YmB~^P$>}96h>q?.}o@̭|t9Pkb%Wdz۳+4\_(/C=xJ0E`xX./[m4ACdʘD>Aw*# Bit$c$,"nnZ ٻ]Wpe3C]f&эIkz1MRΘ.dnE8<0;(aX?dE!nØ *Pv}xi.>PAy+As 4O.k4pOCrQA>|j&ENeK}ZhY6:=F]ܼ@HZ%X>j e :ӥљj%%wh^ldo_ƟZ9Z9XKC޾+ {8XJeՆ$ʿm*T5-JT5'ʨڢ[ ~c®*ve!M ! 8,:sq#M)LEie+JOiQ"䜥)3EsFrplPOsQ)عzQÎxI$a;A̠8yNs{qQ q( d(5Au_{q)DӶ*0.sެtSl%ȸ˝+M^PPnf^PG̷mvU*i\ 2(#+c2I)}O8B>]sw'{h҂(cgL٫AR0OTO~Mi8/2n$ i,^7[<VX# =\[82 l8vxC-:3ʨGr˚l|i1<{KY*FD(œ h9wse0LPi27 3fE7?I JzZzJyJxkVMͶ _6giY(~K@-Sc%V4N@#!Rʅh ?w\葅\:SnޙHU+anrr7'5MHm)5&#)w0k .$O_?}<JDtCyfOM#Z~w)ɠFȾ?%BUU|t&7UL>}RKM؟y\@wBPϸEHڶM0 `} PxcCFc<6R|o% 85*鳧N]gusfNy_TTI"+f7W|X%$Ƒ6Vj jfl-]]ŢOV'`k=IyVۄrDUT Nqd5UlBYMk%BW7 !=D,e ^+__q;K,nńA#c O(n ²$"̈8V٦t%Q|ZspiJ-gv!U{L:`@jY^o]Lc;ZͰ|!ˉ[~ܠN]̉Z臥̵4przzb|0/]P(VdZbʭ-CF#+&]&fԹ"CH]@Pn܏&殃uc.`+N)4 JIc)%©c)x@Dz6Hi(O|+F݌No}\t~E8i^jzYSʯu.e(še=^AFk2zUUzᆃoNso&g] #f#$^B/bJt𾥇4v3/=|F:>ܺLV. xmyd .rG)1}!Ifܴ#'}(":Ռ䊧_f6`pvY v3eǍ[eâ~8j.q'\L27+,Zە8+p]ZY[Н}{@Lst[n;p϶R* i"m⋢Z;BjOĨI9ox +[ZrJ#-Gݪݙ8[WL:nF|tu)jPklJT! YYe`c('Z]ipŧZQYKLb_IinhL4)*iٻ'Ut9zW1P|,6nRPY˰DyiLR|ϛwhԢ0j$A?b-ґ~.}{ѫ*H"zEt^Oo<]%hQVn>No}]k|oxA}zjoQv M(WXЪdu)pL5y8K a*f4C~ D6Yߚd<~0C}onX(!fRԒ荿6j9Œeoky遇JbnVlw&#ga G>C4;%9j6 /dz/.<~Cxtkuєӵ8 hMJӰT)U}[ŊrZ::.v']p#,tP8\9`!u=w.ZQ_~IԸrq+<}}c PA؁7FeK?[2Of4 K%٘Kϱ|wyJ!@ UR`=ZdF <:Rcdg_aJ=pGp`(Ǻ' sBhTJz1 z=XK-rP:$Y[W,L%Cl5ٮmҘ$\R?֏H6ܲE9!`]S]ltwcFՐ3 heyU~P\zuf~*yХ_zAJo`K$9d*lIll}&Z6(Kn9aLs /RZAӳ'X`4|! ecBAzK;ܟf<$7atz5<q(0T"rfEn#Ȑ'f}(mqo*wA.!灶ɏhꝿD O1Yty׾~g<Ȥr3?\?qkOH-{Uèteܭx}1w/-!Ђ2ivԢMÚ +x=ry4|'a)ʽ{ )#r斔lv37@+A)Xt;(,L鏳~<鷤v Jm8"/ [٣&ξ;l<$Md=es.!J28y3,+Ƨ%Ū)?>;RYrÜ[VaTr@TH%k[/~Á0PLJPqDWO5IPSNհ4iˉu|- əZMg/iNJnzbhB~fPgCo]a}Cb <2[:>d^vT#>FA5LO :<{?`Q-_1=F{PQsBeM]~2ƣ$j+Hj{.x,RR%YE {SP`D51 EU9*|6+-5ϕTVd- fs5l&T" L:8ǜZٮI BNoc J )jO*fYVNP׬idX/*fA@ ExӲ|&-pDBx]Dif8]1M*&9xTLYqmڛ6qCfU|r.2/PMGY7.=nIdtX"`6 JK*Y*!_62;-ApFm.?#0װ:Rl4R9;ވsr!uxg8z$ɲeQХHբH73Їޣ ]yIpXژ&Hp:4 eڈu&2񴗭2@py2~F{O h"1v#'00҆s "7Qyٽ$Xmr' r!Bb.B?!V5[8G]ɈHɭ|!E&GU'L!E(-ñBDy^"KY1rsT v}'Ӟ%8#5穢qk͠󈍖x9Vu*iuO TDH#D}=ћtJ'e٠-XTp}=PZr&39Fr4O Jx#Kd?\,P!TYU]xQJ5B3LPFg3*ݫJZg! 03j: '#m(eiǫZ}6~߭8yClǵ<뮸ڼuk 1C ^_#v["BQD@*g*f6x]]~|0 HB쾚&7d]." v׳L~FC J!J8~ V81)O^ AlKе c5]@R.|a{a'Ŝ,98X5?}pF7c \+¾:%IlBpDD7Y"fp<9ٓ 3wVgCex5AA׆1g\ۿS! 2y2Z !E >TPK-_x:\rɪ-p)tVҞ@i_l%+O], Ѱ]PD.ZG_mª,k?\6.&kV:=5G-f̈)Շ20r۠I ]SD~ W|Aˣ7275ֺWvӸ n[}3zҞ1`WF"DEҊX8&n~r_^lĐ9 ,h,C\Ҍxyj-Yޅ0^\UUbD?R!|h @u|I{,Fx1``~R5ϡ疅T5.5 4bpKy75-29ju i,&މF@uuOil1VZ"t/Dl _ޞi}_PnI)E_bNEIlY4$"ZbިaK7_lb?Ele\^.69TE~]#Uf3@޾No&@rJ(B;HunH#W;nz59|6̽P7iLN4բgS+&'*V%qt3qv%AIc.ؿ)YZqQp9#0ص mA +1s#sHm/B gbX(,Ɖz?2KˍȈ?b0őٓ;k"`oQ,7?B,s枽Ezj?$CKr|<<]\|2m28=8 JCw<ݭ 6G^? @O1'.6]V1A:EA5!"ZJoACޙe D ?2_R 2xR$v 'p%~u$]؋Q񮮦nXѡ5)C0_6UӾ+EuxANH5#v/ 0eD9ZkrιFQ8S}|l]<&Y:=oNe2j" q eu"$CE.ǰ.]g^8CoE,|Q9`0nPUq%PW#yJ(B6KLySN?K3Z/aR&}v yhk b:3ɷl~w6'NEy˳V>Ly1H33X j 9lF3q"fL!PK9SWʿݙZH)2nS/Nf(Y8'/޼ObX^++bAVjˑ lS~rDdAH `aAT@A Ujf}k8]a+P$M|lĀ/zg3=4ck!5-1dK,2"36÷zʄ&M4bؖx#\c޾'ن<@ȝ}΄I:_94uƨYj&0jo{EŽ\/[&K7vEyC${[(i8Z5)Fz&&6DUU$2b/ړBG/, q뛱~'!sُ4I=+ h*DU4% Mew2wC7v}HD$+MyTX'e):+Ӊ!rlz ҦMFAMӓ؅@*ײһ:I B^lXmSR 2Dܵ ө2%ho bhԋ'DXm9Xnϱbֽ䊶6 jzXE'^wB(3l_C MU1. *֜w{ux!}Ѩ0-0Ryo35hi.057?eTe #*ձUw"i7 6}cw$v`dp(,~*@9&7$Pl}VDʥA8WYYVOY6W"OERѪJܤRg}u$*5HGޤդzRq{a1j]n<9T†gnBqA4JgH"%4̓[>T]ΚUSMc[sePR&gRTEZnoں:E6%t]r"\>TN'+(.8q7,G8_h”lVEa_)I?I'9oC &V]wj+J,X.-5.#6`CIkl!UdX[5ppѠۆ,nݪJM#"ۭhz%w~Lq8JHQ! RN#|]8(&72 $$ڹ|Hi$ˆć=8 E0lc`0'ؙupD"G-'!cDX8=طA"h6M0DPÁ 6mFlpN`=bz-k.@/W@'X!zcqϏy8pyf!ю.# ~9v?" zwmpܜ"S˶UIs-=^B"ͷs^N! 8puoUn~sE]Z1O*)\HF㏘* =aaӡ?Z6bY| ~0 ob## [AksuͰzQµ◳wl`*YnJu-`ʝB; ]ɒ6ܞ5`7mUJ骁xQ>#,u.mE m13|/Tt׌'V`{iһ\+vnf;rocct饥O glݝ9&活:gެab[ Rctmsپ'$ReC͏O2UyxY1Ȋ'd{+B#d)`1mÜjTKP4/7Mo5s5=2_6Ľ\˟UqRdu+J5Ë5o!\Mxrr.y4vhS0Iܘv pnbSck8| aK,sH=1;Q;Zܢs@y=.7Z?2 ${<ɥTu߷vD]&ܖYک;GJk t| ܿ>pCp' W>Ltv"0a{gxDC,Db-8n\J>n =[ҏ.↥+t@8'lu\W!SŎ޶mxȼW& +q8Ay|) `'Q8xTq }ջ̭(njcec6ۜȌK7m)+mE>WOQ7J>`}o>~Z͘[uZO;{ D>[tJߎTtq2=d|g@Wq[qmuͥ ۝O-%:Wz|=Y435&υ+Izq]]v{  spkT -#h-4FY~~p?n"L3r((p>q1! YAӽY&g$emFD@eBƔ'QLaғ -qeq$ `đ>eB+d#h4Kt2 2͑{C O6掹9ɠcfY/ЋUH=}?Te"V]`'""p6 P}>䂉\~."!eا zV2ՂIRJ[5k)ǹ^gxjkG5ar Q;L\ be85 HXݹ zW!D>#Zk C"@X 1 (u6K %;̔l=9sE#-Qz61ٛǥځ򊬬%B;oNV71Qf.ANĿ"Q UBy$V؂ՠׯCxCV!#DyB9}çGf9͍RJ,,+tU-< l{~bU3o1ں(8&qL2QaҴYPv,9Wq}_mG۾ݠD bq&lSl6e]O+8psG^J<[l/Ot6ܥ7 83w:zvE|3,\;ŢY'oҊsc|9;<:0˷/*CE2$Jy\{mvQx uοձxXAS>/B5sx }ILpUY%2X~ nI`gvOK Vw/6a^U1-֦eZB)lYg ֵɚӹ+~cқv^Y3IUgF9'SƵ`i(XՋ,`?=U^eD*rUZ+r@{!ÁLЧ:K:blYuel.A1+.7B_|G HjE8;DwPyCԾ(hFhɫv˼SbfeG< ⴣAy! Q lZ78cw4CH"t {w98<bNr4&w,3?LM_V؇lz ?a!Al$$ UE- f~rIˈqV?Գ%|JKa~ {08 =tGu|E%>l4zLZu^u(ƟS tx6KC'_&~-e%.gZ#U"K:h"tP; X Z9Z~Ax/qϖ' `]ʧNҋw 4o ^QiQ4#7F?_n@Vhv4 涮:wj6pU(K<=~EG4 ;/\u?I*Plv%jf.\؅Oq^,v$ո4U?lRp#Aaժ>ChehN4&CC07>=y2tB4]"eoQ&LѠ^sFx9f}9 rqnDŽη_3lj1,pk%p*z1VI~)Dc[w'%VpYr~J{R"bZ.k)-FsZjɤ9a`̩rg)nQbo=?8Va XV!̧'? D+btBUɔ!69հk-5(S8x չj? V]{Vw:.C99݁Ubfk^?fۗ}i^DCRf$p`vO2 u՛emU|^CJ"sW(J&B-s\RpCokPɑ^IߠM 6e~)05}܋۰.1Q6q0b*i{cWOHzBlؒ$F !f*/ҋba^gv!|[ѱTFo8XlKM׮#=9:Xz&2nrJu-*vfWgT5Yygd_Yԅ,Oh]x7Y[59?77?O/CK93/94%UA)?% U`6iF;fm f|?T.??GAKS!= >1''?YȎUJI-P&(J-N-KL.W(QPZ,"u@Ńܔ9 Œ̔T B8%/iE(LW`A3xuj@q`hBĩ{Lri(v*' } y{tvU6f53}ϠU`sϕ(iPT0gbR AkEjA'ӱF ^ fQS3 0q]L` OHTAWLf7KX+pWgњ) //ڸ*FMGw(Wq}N6^qn\ aD۵t( _s"8 rW0I4*ܛ\Wvhx7?v./.x ^rh\UK7\76}5ĵ*|UtȵqwD3zf~6=: ?qf}xdJ>]\N3I9tkuf /$Ijl sҮx"RkqVi2Z8:usu>r7,N& Wʝ t{'PJCC3yu~/aYNxT]0|WzMuAmr$ J'Y,`ű4W~|m(䎗ٱɈ<mg)&)S"&;0Pa%pr*ľ 嗚r9k۟PMҺ.ɑh9M'B}s7ʹƌ4#2- NhNTk8m:Tz uN[z MA-E{B*  ww'MaCFvE[߲i86^+[{Iw}xD_>QK"u]"~`[cr̘2xkY]Dnd E:y>p27ryVF懽[Gk^[8w \r )JDh?LQat!bdU&kc;i6e xXX,\adq/P'9Wf+dmQM 9Q!O|.\<5OwqY5˄G<Ґeߜ{͉78ZK7_+NW]i97WnV Ehuf[X3N+Yx9(֗OM azxTmoH|QrD* $@-rUxU{]'!m{g@h/Bg^yfN[p \Zdܓ0x []C"LuFƵ#7Bg@Sm9j#84p SThDu\f2Ae h λCXB ׳MI#H 4l=r\ R.v66}۵*d)SR}Dr}#+lI4 TFo(mӹJ9TuoiO Oǥ+M8Q@ QUŖk g/!(Go[JNs r]ߡ9~}QγI3U]dgLj'bJ}%+ݏ93'IĞ.@S8_`>b1 WnPdgr[wG G,X=1 Vx|C.Vz6\zq?_%^)xfKM*O YؽH+R`$ԓUmgB@*vz3)d<.<j5}Ÿz3M=%Z󑶎}?  ..Cn:] fG颗BQEz!iHPug$D}{Uuvꉽ=wmJƼ+_nC Q-2T`hG=8b]@O>Z|-`0l0YKnP 䈌~7XE g0du4-ɸ-&3`]ލ3NC~X!CZC}B(F!D>SHCCVy @Us3^NoGkLytYrR(yog'Qkt+ b!W4 w|wօ=u_yY.n) <Л큏sz=Zxӯzӛ UX:ݭ:mZ[%챿 %N~[o"ӧzu,~7CYin7UxԅBwT!873a Pvv)V-^<80f.LF]I3˦ʼ,3X XRWjp2]\M&P肯'|"eZl:+p>@K_K}Ǥ{2.qϳ02hwj|1mO;N ޹uĂ/\/6>ƖH z&t"F{`^ Xi׸}{ᭀEX tGh!Nb rL{y5j<7H PFg.fxCxXC;HZWn(=jz,&Bns8HIcPz`Aj&{f- qV䖓a΋ot!شKS/dx>~hRW}Kx#vqJ8LٕK%=bʻ{ʜ $׸oR;;{y}Dw_{߭7߭Eϩ,BnZ^ׯHxR«;y`7Sx$M+xU]E&j9ٺvVA$Ct!f6m%,+D7GH(3MA ??GdDf^&Ys&7rHˁ%EEũ%`\\\e)qByá"-Nz2 w(VkWXTqI5= %@!S+2Kրj*( l t+ןSM”3=!^(怙0K"K_qnS°KC`}: P#>mGf,xSSpX  !B0!œ``I?~,+csÂd N~k,怼T&|0S>Uv>*'s".OJLΆ$ԜT.$9CKNTK(AN8z Đk0#  Ѧ1)𬀴 "5Yf2莄Pg Ȧ -QPCDqyFfN4q:u% y$1guseJ4vusw^pe̠@ԧiFuq/ZsN )Uu$xN ;O1^4Q=oI4ttS|)@㰌Qv4Kont54RjA`h /ø#O0~ =AqWfg2nbz *W)纇D-,X K_rS*m-9,xyJ}+)gdźq _=et.*YV\m LOOMwOw>,3sΊl^ކ9ﱻlfarE7Jѳ,g,w6isV.9+y*X6WOy&&gMD"ApS.`'7iH1q$NҊYZ"Ġ!8S8% vK✭lBpFڒ1Sl} 'p= I9b,2@ ‚E '5EA{Z'?́{Aψ\}ܽCipvSf%ID"AHaMmlR"eFɅ%m$d1$pzhԿ|io\0aZ?`t_ 'b8j]4]ػѻd?\!"k$#^qR(XuIĖ!gg4"a>g$$!#czv/x:,mvi]v[c I!ͬFl񠞞H, _0'O犐YSyl*L,̰۞E 93%-Ü /M_t<|3;a;밽!Ᏻ^od4^,| ~ R>-% NW~{o2M:-;~^c"T.h<^^}9*FGg!6StQg5(c=$>/Pv{M2x?GA~Tj ^+W^aAĿُqُMُ =e'F{xǞoa~V^-w̺B$KDϒ0Lc`XWX>؇wf3󼣎x$ڽf X<r% d;v<5 gl ^8#Hy?C[!%8Fj/✯ ɜƒqzB:Gт=6T24$' OiSO ǟ:\]+t`K|U@UQ ;qf>L@21Nrv6r09 ;9ao޴wZOi 3] Dkm `܂lW0*.᩠< S' ڬ-=$Ay[7 {<ْF@ ea(_!ݡ݁y~ /E)m |[R#t_3Yüne :0w{,f/ю n;21/>Ɵh:VO$aQN%pP=DRNbp-3Z91;Um9m"|dH7Y}Q 9(5V`4?U?أ!iQ3Юu\G>])qIF(rNȐzA> I(Dc!:]ҶI)"&ZNN{wP?5HS*HtrBQǶԍ麖:`ȍ5BqێDPՑTF vE14(ǭu7P9(zMjv>f%9Ï"GLh} Ðm=]Qs%/ WRw3O4.g"GSqm]jH"4Xˁ0X;Ph볡^̇ .'?Q'.<O ،XG5g&^VWOËI,Grz֬]%Hېe*REe)UNb vi7ueZ_(Cn_)v3[B~$Xe/232ATds/H4~48>&~}6Lީf/z=\8欒1,ś<ړmpp\} S~;`(A_pTT<`L=;wnm>;>\$F@b`mPih۩ .,}[Tl2|bɫ !'Ѹav"kt~vr9zp >r.E[ֵվmc?R{jݍF(~QbuM!B(֬xH%MMK:B[u&p/H>VX9Ŕ2`TFpD$ 4 WFZ0a =YIKQT9d&WחTFOT}rWy 03PR⮀Od@;"y\q*v*)^D1Ҥ#o0!㫯}s##۪rl8lʯLHQp0? #x42ԕ|SwOݍQn`&оLfn2ClL~ B B JzlUÃI![Ly|6;|esP?C\" }&uehںqUJcdS9 iT'D8)4t@߻7qƯia)qWT)9p_Ђg~̮mt5o]! @vݗ˨}kowg4jj>U('<|s*R+ ^ܢjnݩپXmC*Z^fZ.b'p;_j۪8z*j^9*@yVX}+x}G4 I0f [.j`zm1T+)*AJB0qTU)+%B]OXCp=!@7YnD))N_sDr꟯z<9..08>nGK՚75(شQZ: kXf%$*jeHx,m-c͑Y3MBǭ5H?[d}Qﻭrp-q<3g$/{=^s/`=/GTlC1'PXy)m DjZf PT")~{rY@nFPJ< ї!XٓUnnFQ0UjAYIa5C\eΰج0mut `۫ino N}ƥǬPҧm gM@#I'?{ O3uXȹ izl T`%Knʂ% [leF!W}!(̳L(?m-^vmWnMԹt -*b=o,rFE\aS':$UaIH:05v.y2K"Xj4R)+h|ۛ-oa^.߷2,jx -@ FWg&M]Ce5CZC9%7m(u㧹oPwQ+9lEĥ>o\vV!.Kxx|?s'\KnJ( .Pj/:xuA @ Esl z)hw2TH&"ގA7{ $VWĐqFJE4w6VjxȋiD&p%dnqeώ IF3s ]I-0(%_ MǼkV >xN@yIHhbzӃi6.7+̶hEۻˡ'i0R$ =^g4^qŘekI&xDl)$"=6\j!9.iQaUAV۲+7 .ג&!s {ܻ:Ie\`82pÜ8هT !P^L4aL&{Lnߢ3LmzI `EHq2%D 7X)C}@!"4㠃O,=d*tE8*6;0QpjO2zF?-$!1lACLH5nbb\Ӈ)^ueYr6%v97ᡧ|  ޜ4ªʲ=e=XDu>L_B<%6&v1Q8\o!;A;;7ڸ7*Z&ISsxr\}_tU㊯u׋W~zS;ãvexWO8tlQ˶P[P?qU&NcQqP*g߼7vڶ0yg$Ss;fuX[@\ORa ld t KbP>0/!\2b;tp:a"aU8Rn0" H|d[ħMl)1rl4Jw &8ةeϘ.;B*πEŽpdj.! 5r Ǩ ss|CilɲrH&aeJ/th1)'CM-u*JM a 0WE[9*;9VOܧݫ8ZLJTZdbI=A,ٗ)g+j9h-4Fz8F7zFwp[ϵe3X GdljPf S_ڝPV{mкAnAbifػ7Q>c6$]ʆeAj[l.66gMjϙ6 tWdW$emw/r#{09u\c,\=: }an uo 0w-ܱL ~o\8A2>ksSj/&O nB.$|J vgw#8+ IL @`Vp/S=8p8qC`}"`Y~Q(FGEtR]Ф( ( ( VKKVJwզief01/(j8Ǜ}m|zUy ղHl )En!S%0u眻eTP92ޗ SJMZJ [;s4S/c,k41)q#f*\fzpJ1T u0{iw% ^j VB -U*_f9~Ō h3fenҫgЀ58/k~q7W*9v4zsGL"[˳2[{/A myYKPgW?B,"M-INW}[ PԘ[[f lxzOrDsGp}B}ҙy5tql:KC,D?/ |;ĺKpR_ ǽ`2y9K! hQZM8ϯAgN<˘Ztx340031Q(H4Kfx%v-.̫%ŗy7?"`(Z(p%O-X\q]^ڿm+ǚƿ)Oht.ΏO:Hh p<:: Ex}먹(\U5^7?%QtMJ okM^&XYi/"hFDrdC|v),vNˋĝ1p:XK6I Zճ nܘgTw_oQ%a:rZƒMMSgZg 4#CCQ/&I8t2R~ks?7}L+9ruxh6Xw cG b\;IUw|&WMtzkp!8Rh~stk[C{8F2"xe_K0şOqa/]!t *EJfm ͭ0d9i{~79'3/wW P.e%8kt^TCM lխ5JyP7D(LPb 5 fNkeBPpS돰7HrIM{2*G(xiYu=>GH}ȎHl$z}yHQp錓A> W2s% y{]s.^gS %Ύӣ3tɜj8ZoRg*x[ϲeRF`xE'dTQE (hYPr.߳xYioH,Ɉ6,: 1rrI^SÊ! `5 KUuիG]q$q^bTI! &bf&]rE2]m2=_w8<7ſexL+|/rAǾSoD(X's=A3~ E_ܧ1(re= UBe3c⹰cސ]ftD.ULk{{׮TY^edS 9̘B&seT7)f^nD~ t!?E( ޗ0S<5@KD_"KjYM3]l^Hc'-y˔/ZdQ$Hel](U0I(x9T9&H15% 8PI!Zz_ˍkC1RT+aHx9XsTX+*/8S *&J3S ԤU%FTZP k9{cF,.$y̶LH7ƛخI'}Sgq]S?,$NVT/^ |OOxrlfܛ!"̨e?z4K=j$?3깍 k]c5TS>E:RKja-FLOL{C &v#t8 j~yn}{ź<Ƒpa'NƒUhJK6NF!㌇]h8nr^4aɰ:L[xDL{֩;qB:#c^"^t4O[gȉ˸8̜Ii̓q 2!eUSeՃS̪OJ. QW fiY~rI͂W'[4ׄ&+aw;LĬ,pܣv !saVާTty3`.%Hr tSؕ!;M<p*;>x{ D\j.hV .T1xDHEvg Bc21;˔_#dwH Tj)hF (0W˲9Iligvpb(5pGOT}Eq!&BLP) tɸ&h+J-rs=rvBr\q߇b=@KmMzru9OeHAYPU~Kcv ⌘P+\3/Q JJZ~:7˫<^==XE(6֏=x3M[#0M O{@C-ԑG&2@V|6  R%جeu4sH+e*TnjBc")+ZsIO4CȾXƖ>9C\쫺3oC}IIAUiG˥^i]lFPfl;+^<~k!rz3~a?i!fփ}S J_d}Tkl\KT&T/\{ 66sl WnB<.=ˏ ۦ_1 &8j,YRvԃ|!lYy5 @*S|-ƶISUgQP!f7ByJXZh.9*lq#d;KdC ϕ'ᵇ ThL/aҌM7 ~ lnΊ!|+8a u+o@Y \> ^VH>Ot7t*I=Pu(b@|=2m4ooBdkܯZ} o6-`߈}pp[t!KؼMzyRԞFYAH@_U7oQn,.., wGUa_1g@c 9!"-KO"xBe@ۓS^LWGPSoؘS;;FU=T{q3gz8 ^?Ei8Pg.]۰<q~ y5$Y Qu=ټ~rP8}ozɡEW!>3(EYi[["J57\ԇ@ E 7IWD9b$Nco^ M6p cny7V}0{ aVMSŪijO]dvh/Q ~s&bQ%\M8M w_𯱮WsxwS! XLD2M1ko75! ж~}M; N wQEf&[uҞtdW=-- hwA( %5۸ZxTN0=ۯhxܰeXƅ)if@bRB7f:-VTB__ߟ>|r84 k)YH̳ I5} |wDtjw3\c2L4pn&n| O 9I{[6%{x$x]:_ ⒮F%lGkW_ ž(STc[\X+ݦR_ 7DQaSv ͌0pRs _cLU8*V$znH2lmB,_0s5GV<&;.Kke J+4+ ܼ'L7דlHu Ċ(jxmAk0 %5ea0(lXIեRpn0g.'Z|T;:gq'(wM(XUSm1LMH_fKs-"*J(ln?]~벻YA$GRQh61z4}hG%J!p:u˜Vtg-_e9`ړ!{jk0?BxQo0ǟOqҔLeXTJAj@hOVK]B5[|wet^6,7Ue8c>3qB%)P߽e#Fb~|7tգViaZb#,|ak~D'82'~vo;2wpBIƦ .Qz P:Ý!4dJ̬-: )CjwT$M5j7Gխr[(mHvFpaVz,ilR:{C?vf~ۿ,H&JqKQ"b?]4z ]43|A鮪F30Y"6n;M֚XZų_qYr:㐯XOc !wf~>4I&Ms^x=h5Dgu]/UxQo@ ǟO "P({VlFRiK.85Eww@IV!>ǀY1"[a'*3oygc(E/]&~^@/=yΠr]| W׋ϗPg\C|ǔ X=.W"SWe?Y'D:!B7 ow Qp2i ̫ܵgقL"K-`yJWKr´?lZxVYoF~64lIm9E^ƀH 6t uX+kcr]ZV,QG I7A a`.֜Qsd`rcaanQҐXWe`N qu3+.f a""ĵJk|0)!\9!ᙔx`0hrd3Xr&FG .6 K;HE(k"YD*͐%O}>?=a6SgQ^a8W[2a7e?-Y)$񞔈qm@ ֪~BmwW{:32"g͵C\ ʩzx3gJ%С[aEoFwp3yށC˙+dmR FMW$|F1O;1H܈A~%ZFռnWp oMyjuZ>t>tFkW>K2lDiNMtP2D٪]* q"kȮ2j4 nLǃAeQibA]vI%˾~ ڥ(V ꐃ(\&+tIAƵV:dmu~4xK]4_aUrlsj7"x6=k,2Z0 ̟!W_G~ыP+[/*rй|ε_}\䡲vh1rpmN\ġoq-%)Yq2VpY:"Xpܔ$\VYʭ^mM4Exv7|h@:~ourr,h ^V%u[qtL3oWmpt܋  w]aDT?٢Q7kw'Zjl:v\@ v ­ч`<9 5ulɋE?xmON0 =_aa|@v9w5*N$!N^xe{zlc- e,<.Ⴎ#n5@qp: 8jBY+h`*bYsKRS.tL,O+>wbQγL5WicЅ9״?*G%'䋩7N] ',u7ge/ \ \x340031QM,KfX"kěWϹkMyMa?xSMO@Xv%ouxSYo@~ńHa 8$>]Pwfw vJ(}31yH0\,o7_lg"ZQlZڕ1uTEC>aPG߳%L}ؙ |ϓI_j&Rb9 r?7{Z v8;p鸕dQ![W>g&iޥG4T= Ad ?9a8TR=xCJQ)3$t3q }![M grZ^XJ_Q"X--K l<m%0 vL JSQ_VeL < 4p P"T(0:S{D#Xmv9Ҁ?%d+3)x· ^+כ8TEgÕJh..@5tS=dQ6Qa3L`c6[#of-]|E]F!<°-!W04Bhfީ8}'aU4+x?/ۄu&zkZܩZz. {rfT7u8 &F]Vn:hFuضPwt6UX9+հexU]o0}&J<gti[$ %$X 6V:qVs8i\fXs|Odvm %mQBZdW6ߨ}Mbv$h9Jdi21+#\k,kJE>aOě2I| s;ul}Wj7ə]>z (O.9kȅ5P]rL.8XC7dÅCPE@}sVl8 g^C`q(exTk0lR;MfRK7ʺ~(K^\l]ߝ~$l}0H>{wtx&$q|3j%ͯ!|8t(ˮW*Q"!~Bř/t7T'(_g90J(6}Od ' CJVS9p40џ[\3ÓC@DmPݓ.]]IU%2mMIv;%ru//ЩW64D ʃP:>" ,BI6s*akѨB{󀲎iqX%] .EYKM=;=ymOhqs}g!?K3 ČUj>kc4*UFLq%Oyid$[b(̶{&/^RζVj: {60~Ľh>t '=Z:3Ѝ\9B@]s:^=bG>SyhjAb&Ny!S}A,1>rb S?[O?ml܇ I(n*>Vө( J H?I#r(J7ٝ1e XKǴ < 2'ӧIo)+_h|vҭ=_:45)և[cb:i20eO!>ITqc:9c,=iК: sMd%Tz[DVٱ*6U \vs>ȃ].*Kq0qw 5. T/W+me])+e ]ˡ*R.W\,>\v]CLZӲ\9ˊvWb$A*+rH֠ʞv*79d"5).Ó1Qx340031QK,L/JedSgZ9glAC*Դ̜TUu]v^>4 ?G_O?w+E4 xh7a}:\P%EEyz e v3EU6Ϛ'%U_:(+׬zs_d$.BRi[\RTZT<؋%^fpu5Z݊F)E`CN=;.[z|mp@wf$g.8w2EݞKNL*O\μ!5In~RVIeA*P}btN-;d} )5)Z,,pwʢ_w•"8'?d2O>oʉ-1ݏ}2 I9(HS`uC^[4b N?z(.u#A9~?3rXp@e=f]ۢm&H9\{  lMR |q5)Vk馤$f>Dߜ 7Ѫ+[X7@oT $8Smi&bTg b-)s,7`*~ @Ĝly~9h_QuU)$&<[zreeP5O'Cf]<9OTȜ~f߿h n\S*iˤ1Rk}Yf@7h B_ĉ=S(<Ӯh:#x=LB4@}3/;sQ~ȁ?FIXկd `dCᬯaRK3Rs R@hgĖΚpǟTf0pJ[(<fhdS_2YeNf3" 'M6x+OxVmoHQ486t4TC'# I{װxmRUofm"[˼>Vcׂ:U1[LxC$(t}+/ǁx<(>:0!\ oVydW2܁༌d \š/?*TpzNƁzJ2\!`'`!x6$6XXf~0ĤXۥw,$*ij̸ D"O->|Z) |4%\u>D6I09Ex2cAӐDތd"tsgBP" % qDUZc올8G`*Rc*6#OWy#;k0r$XnIkyvC,8Y.J^"}$snj?l|SNJR{aLKJDc;wGrMֱsNOXeCHy6I-hDzw=u*0v $<(~>M=1Muv~:!lzጌÑ6~. l~g{_zX6GjtvDų@˜িmL{vKs X6zhӼZ⸷A~uaGTz^aowӲҫ"Ek°-0eˣ>xZw1YFdJEH8n Vd3S!W&!dN,_:H;?XrVKfԬ2i#gzi`9vml:P۞G\sRmZ*jta9el#ψUH-oVpRljlG;!T-gq"lwP_CV=(ȉPM_CKUۨ qmYRo5/ڷZg:wٻ}g UʨE[/r9D=pݭFp gE8LXxs eٵ" Lr4g&|@a`6 dBwqf~-C1ܨ/"JJ0~X\%$i‚  -.@i__o5UY/'U*w_icjx8 -Wextra74\afdef OPENSSL_SHA1]+H)xeRAn0 |@bl r!h[,"ÿ/]o 9ÙHI@&^JL5sbD! Ǭ9Q6 |i Ă"y؂ZW}mʆDTm?Awt/:ה}ĻAB575딫QodzĦ>c"xcl/1I˅>[p:-ms2բQ(ײc-5aK읅eh<$BUe1XFEms#PЖj&_©;Ɍp4#L(;D%fd}ɪДR3)m 0F<kM\-#ϳG/ix340031QH,"̼ e5TfsdΩ' ! <~S妹ﮛBqbnANn~J[ꔹvg}t0v?i,IAx <]oٗ])=iG c0YlYH"hOi_PI)KQ) Im}y{=s,wZAKg A!TF;9 xfoݬŽQheA9kHOh7NvPL˵fuݺl<$$ 'jYp[F٪4f?G/; 0 /ջݧs|D?[:'"O&=+gwm'D隞_ɳ08_|kHuI{뻃f}^̓4ٹN GChcFPfBI`>I|w"|A!DŽV}.rL ']^-Rw42Z/:9S]w_?V,EZs).k1Q;=v{"gx[]b_%ݯE9E|ӋKL-|1bgo9yS.ISoyɫ剩:)04 Rleʻ9+]0ڰ(h5.g(>sݔM: GuMBV.Y_ "g*~s]w՗ȵx64`Dzus>9S v9%EV_}еjk՛f\Lo296<#zʻM(9;p39.ǴۜB>faۡe#ڳ1=go凸I띿6_6S~Lɞ7鞴^ߗ/Ύ_f<~a mnrb9Mzm/هٮKDt׳ު\LŞrr;fϠ^;)VXj}W`.rbϹrfanhϬ';io&hg&vv +x:1q;a:%;c#wUyoc6}{h|Q*-.rbgl.NeT69S@vd~JL?;V<"g*vV.gQtϋϠ!Po ]:s3;; z?0(D+^:9S'f0Ne17\L%8؁_{Vˈ6sUB9SS.Skl~qѧHʻ?M4o\WBLd,as+f7b?̦Ej<}OɞRjb.=ET 0.J6ψ2N2btK\L?,H$Mts ;șd1n ўȏ}!TiY)>6?Ow~&&ib?̧dgW{.sjS(F޾=^u. M)5 X͒sdcQ}y)Y0?lHߧZhmx[C^9Sr$x/kxU=uDM{3qo̴]C=5s 9|s. !;?7;G#vr7LRc篧{4Cl0o~a#A:79?m}wRfcX<އ5̏[gKP/$]ח;{a84 ulaIWQfb;+|󉛿~?MA3:#q x 0W06\dAl!%v>DӨ .y0" qӣWz[JDzHhy`TWaC_;f޴4t(9GXϴ~ʨi=@+TCv@ihHaOع2=wiQ֧,8ZfsFly_GD֏@oݼlI[Ҿ .HbΟU/I8%ж٪Og[L5P#|29h%hV1-6$ Q?b=VUc2(&Tm2t7 ~0[QPU?ysnzxV}bSqXV׳ɧtc/VL*s5r_qRҰMޢRBu<~sl?C/r ʾEE>ۏ/Ir}lo ȧ}p\u[ .9xrӇna9?s.grbtUȃd<_\}2?l yJh@Zzӓئ寎\Xb ͂Sp # ^;󫹯"tf.z!m9 'Bkv9JNdK^Ζ" S ~q19\k%wD zHެ ?48)04 Do˘lctE26n;1'{sO3`OgJnVIR/0<Ƒa4)+GY\hV%D{s39 lC/Zӯ,B9em39I)#pC}?H8$c텦Qp8bg^ߥc'lŲ8UYL;W*[|Vn{LjQ(pcu@Ҟ -C'>HC0oO[uaUR%<}DN0ʜiЕSwDw K&3Ms'`8&gog[W9MG1m=F}Y v[xNs,Fso @"rN`=3mfa͜uޞ\qK8BSHzE*/X|1TzӐ7v~mx6|xka< ȿ\l'|nmo),4k>2fl:)Y'#u`Aϼ|3~6]j}@vvq+JWWq ӧ(K4C;ٮgNߖ)Q&(~84]N/`ȝmTgSE6}/#rH4FJ9Qri*)zgqR #8r MawJ#Q\> r@:E1` yQ.ps)q%zrE浹Y[l91q;,4ӔtyB5odC:8FMx88:,R_WфǹӜG?@-Vfj2%u]x4#G9L߸Ts9? ~Wm丏<ӌ\T2 Nt9Ϸ"/7ng>?Fa˞ԺkaX1ELM9'өv;_hh2CLت xvBʃHSjޑk\;w'zI?#ğjO͒<6Ͻvڷyfߟ '2o./[iu½?4OOܛG0_٤Qc|[tuOrD* FQz3ϣ͇A$?B pK/}&jJ& M9kʜdvѸ_UvH4qSF^^Xp[X~Ë_+j&l#| ce@CKQ@US8)MlViRV< NvOza"}>'7}]!K0~`D_*JȜFE]SAxuJ O+EUL)ytiYd?7Ci_u8ޖo mm5:[y~'1F(X&iN0| `fOzqt[n9ID)l?$qy]rg?bwxFhtw%X × q|{+I`i}0Oq6vU9x NP jvOڲyZj'T3'ϯ %DZ4 Jg9W]S *?Op|9i.^:&[BГU?Đ"OlqR5]L.y sĹɲۥ2v\ZN f0uj:uϴFzn9'N~6"aLd"}G"s{J^9谯@wbUtL/+J [2 ڋ|"s~Y_*}JC2/؆Ľ7/ HiWE}dAZgGzĄo/̗rd.pr7`*_ҏ24;6\}#|   wsOӅNyT<9ō9uᇷ 2l&qK)_Б3:}pUIRrZˎ^R58V:>u ܣEZ޶v^'UEs7 }KumSq{*[M] 8m G A1G*5N)8,rB3k<`ɢrNݷg<%0HUk'=9)x~|Fȕ#oƛk~x\ę`CgҶ=_5,0;<\o͙>09USK0b:~gr^Uq4P WM=ߞ#'`g;JQ/ ?5.jnT('{g_zbse&^}e;ݨ)RpT{?Cc˓Ow**yNzOra)?g{G#,cS8TǹiZb_ӗ R}9=0<(ָ>nvgs,gf9;_Sm˕?_$l7u]&L>ۺٛ5)YP(5[7Ga_,y𲄌~!Kf_B9s: g50Ηm pwkr<)b(ȵVW|'|y퇆r?7C-> 3AlS-_t^w|5,ׅ.t#Z͡|{q|i7sEZJ:E}N~<&gZa^dmbU ݀fGY;0rq C-JoQEg8. ce ^[cW?yR+!+ܥiTѾFLd.0}I۾ ڗM;2]e zs`  hw]F ԋ+дE cDsj7Cx0^ _lGd Wggz'6ea@6?W8T[Ӻ:8?f+*_;q]~y67 9hj YuqTZ̖'u|6够 6'ik\lcAnγֱs(eb gK,=af9C{s>8r^,@[c1~W0 6~ Y"೮&woG{O?$ CtLe}.Gv7jjE1֨O_z{`*l`W%Cb:4t>qg`U3N=eN-2n8HT*fn+;k{<n%[9$RT ܀,&Jhmڧs+Fq+6GZ j];8`е9bK3ESv ~ |h? )zZ-zxbtSqb&qb3;Yi?V./ا>0-|;Y+tBAecC~f?_]qgpĩ+1;mڿ eGv,ǛR:?"2xt^+?ʫyߐ3ODqٗoj5-pYUQ9;h sȇ},prl#s>}A/D`莆f6{(?=YE By="x,e1 s,1YnӖ'{i_ taI)t2 "yOO_` Oc^%ydz,C9ѩ덻%W!9CEPu@ƜQqd s$1dJyTXc,MhVޞMG+)9o" pGѣ{#-3MZ>2%>NZ&%sBhHI&ƅb)O~! ]tK1e\5 1y ' 9_\^_ 7<PZ-Q{~~QC7z?Jf&'/gZdA~Mq ߬]~8\?r7YlqahlzGx?r/(YyXGcX9D@ΐS_ָz,̾,68/a]FI%.?/&UFsҷH0F`x>؇V{Bmzq51e#c2 Xw„OSg~ r; _`;g.޽(@IJPu&ZyFi̲ ~xTO*pqV<9󮵬6v={VQLP$kݷ MQ0|:@Kz%p1ZJPO3ȉ)z f._P\BE<Pv9S)M_%xkgm"˹Cax,&p3Qa\(u̒cٵ OQr%CƣW#G!0MuTϧz֝+Ťty`.{z\t≬UUouMwL0D9z|/$}= 歀c0r"xŽ|wu_> +JP#]1lr|`EeӑC ;sE ^WqM_40^OdW;m(xIէT G;뺫fλ Mdۀ,ǟ FN vn8?_#̞1}KfZ ϟ1J`ߠb}[ ,ޗ]!ᚉ,Eb߂w\փ=3>W;|^.HYE57@>^je|GLW:8z7Mi}egZ'/*Ia r`D@ws.~p3L 4I](1X,տvX>[:se3sO# Ne0?K8vӑ{nFZ{rvy[/TIׂ 5wT|??c72<{Хַ;3^Ω:睼܏`\X<^b:54-q 䅎=(߯˛xa'>lUQO.\#ŒK.5&e'Glh$6 ۈpS8wcKʮk1:+*^Xeh<2D#a^~IcwՂ_Ij_Sֿ+p~`aSg!x_uZQ"o_r`EU[jh2<.~|BH|9;O#A7tK߹O\Ep'VPI-ϻÏÕW@0rZծ]Q.QaY|2;A{Vfi[Nw tH/{p{=/s߯@12J j S':S-Gn*ftLDQ>+x2AlD;Wp2q^ȨhU,t;VLwE.FChkѶ6mګBx96-1THI?G91 AZ'%O^ bӡI?8@}E5tO]fe0'\ T]a2"%Svy+sOãtٲt7,^8˨N7Km+55dd`=8 )` PNўC#uQ]޽Zu2|)|1?w#j,y@h5,?xWbQYesۋ& ̆'xW'n z| |-$&#UW<^=囪~ sp|`3q;@]¥^\c\u}|V%EhPfҼrbMIJ:}cq@[b* /?Tަ2mxUGy`UA@Ϳ |+-I}y(qT.}E_[݅OC\VuQ,"RRh/5owAG@JIUE/_/l,ȶ94Ϫ& J drTz~yHEG&yz(\$[n"m}[jY:Z^+--Zňt".y*<^ra'GH~QRM&UDO@߷JXX<-eSY~0V~||Qm}k +8ԟFLJ~9CXD\f&ϿcA/ի_0MfO!vVMY8=.sˎ&&r#a Df^2tҕ:BS|ﺏ _'UKDJ _Bú9M[l>xQC0p'| [k(lJoھ+aN*X.PgE'z4G$6r졥!\|bފ!\~XռY? "TLѼ:D,W䫾2;rw;^݁ 7쌞x-X}B]{H{v>7-ɤiAspSۣCj[YgU.?ilndՋsSV9lbjZv̧YbV\ެE wrf |^Ӣ*~.;eyW@.N6O->r~Mʗa.HKadT%P`ۖGݦRV[m~TG3Pw@ܥY|. '5.mUu\ P~wښB@KrEC,{^-]Fw}͟BU<)uxl̴ ]i^] :Ng!q M*&ŏ6ԝf,Nj>H5ȎڙԆ|o T+_j>VvۿѢaj/ϾNX<5R]}@!ʉf#xʞOf,r;OV.X*>'g5³ca@]A> R :Vo2o<:omI5G*_Wh:ed5j2o<xww4Ԫ#F^ *# |0ϻB^r+[ãi_NTsDQ3pm#WNj"\x׏l=MjܖU[>:޻~>Gxǯh5ֶ$ś \c9 Kyώ{zN6_7~۞.[Xm٫+ۺi q;ZmDtPY6?~c=k}3b~f {d3/ziޡ[3=Eǰ7#pnAﶸ1ᙧ[Xj +ho?[nTvA#+.U+@{{|wY&4.|nCӎ&e| Q{ޛ58Sw .fF^'p+ө]8kjkLXRk?x_Րŗezrj׾,iXze{W]KnC^-y&G;ϧ|y"$]D_uMzS3]1̱:dIgM~uh{y~a66ߣ6U9ư09|yN%rҫ -?0;i.;62x'?cϵh-kp+}> ͆9ǫ|?}sxv\O@B+w<gz,N8FXVf_j{.T o3c[f>bBbX Jz޳I ?+A\L+ wG6?"x,Mo>zjF,l+זF_.[፠`8N/zh97^N?tQmy(Reax;u%QW_΃MCu_09zqћ3{P[ݿ`ўꝘgmyz3 ޝ?o,]KԡR,97Rgy~U9$n!n tk;۫9ޑ8~Aq̹g0ä q,)n\y% mOg-Bj\It_nFo%V=_ڬݑ( q='ޅK94k4Vzg㘇&8p~:kD5b 9s|iݿ:Ÿod ЗH3Qyyþ%,S#~~ǜp*<v8#.,)UdCI#"g&8`g( GZ8o9 ~Ac<F~PH.A&+swy̤պ2a1IG.1\<4+/Pd:$8Bo\2D= !fnI{x)pƿ:ۜ_nYL/~/l6q"~!TsDrH聰~)29IdN Kf^7a@@F"@_* {uPm7#/ M02QgCg+Uopּ>cҺD]Q1:q͹ 3J8ew_XŽ3򽢣 'xb~:>kdy7]x$-Ʒ 4?o NnľgƢG FR@n¾6\;$Tvjk <>#)x"n1us]'VWsx?Pì?y8Fb8*`׿-yތt;ue f07X]\pȇi& G# P/_jٙJ>)fɳu'I'N 2p0Lk,`i<[w~"0uPW Pkw?5q cΧ\ہ:'|MWex|H(h(.F8LJ坈.* ,v13 @sE5փNֈ:i[ aȬ:ۊGʝg}ǐҞָTf/2p}[μ2?<:wcU<>.oO4}7r) pU mS/ QrvANj0rv><=ʧuI"|yn4kϴ D.o 1Tv]?F5?ϬjǵB,9D‚NS ymW>b͂As.} ́Of~hz9O$+hX9w|Zx~qB?2XY h$9#ED6R=*M/'$՚~ l#d'gtpe!yMƍ4oD.Um`/R^p8gY@Vd,;>!yv.<,dIM5[#/?g)sZi ?tvƍ۲^Va-Uwdn%u驪Hß;-h=c)s 4 ݏU*''x[ͭϊė\cʼn+c2~I f @Un9qOnϿ_{@*#kΔn])ﶷ>,yyoһ8b+CPVq Bra*YlUi} ?eu\R<(2]%${r/}yM ]>g]D=m>^{Q֝'7Cބ49'sG`Gn]e>v[by'ѵ8#C=;Ar8!, wTS gcJB_󧛮/3̫Y8 X""%0P%g2{=KHqCp6";_$K}yBrՕL,:߸(R㓂qRy5ǛuYeJ"q#; ?\>ՕI9ȧ>3:HzjbP[O7\t+[taN%[XrCx?fO5 1|V&y0p07@\)JjYQ%g`1鰂_7/{=ux=/!^kY2+c_ ⸼S/vrkrj]Mnˊ0r` bn^湧u~Jd/_ٿYq͸=8zc{;;R|*7 o, O(gDvҀ;o6}c^0WG~ u +V??-*<Nh9oM ^_;IJ[rd0..'rukf Vy&>d{ ~sہ!wXN@T׬AoU_\;ۭzc8X6NڱٟY~5g)>B|۷Z9u<c>2OX|PNVV`|U<:R})8X>>ok[OcW 4]13xYqbO1tfhp08>nSYJE,:nJl?I#|P ,o3D%(mJ9Y~.]<`?vάF$}x痪,B5C8A7?wed1h`iX|r7c!J,is}'ָNpG45Jsn6^_Q[ k\9?JY%CiOWA_U+/ >x1]z XuC*c݆07GO\P?hR  Su~Bc1ϐܱ"&c|Y?0_4r]׽&)ɆCgXv kQE"Bj9 7]n-o1x$-־(#AvL= ["E"Q.-k +䩛?pL%~+e:P/^DOR%YMu%ܞU-W|ewdu?N?'4fPcĢْ>*'OW ?<;>^US G7_ L.5HÄY3 >a?_yQLǫ=훿J%OR,.={gTz/[i> I˒/Qn1هOrgKeJU+= `I0s~.5[Vd&d% S^} R0<5cJ]ơ-![\ JYy8 C>9wؿN.ߛ:a {sPY! J,6? 鉎KA#<"iD{]fƹ3AZ_;fTɤ;[ U ]Z9e׽v/KdT9kj;n4ÕX;(\wY~Rp0<.Du]A[V;IWK,3j#)@5<{QfxF7^!VGA (to)"4Z䛟\ _@=c>5jw3骻*zhˏ0ƒbQwX8 1?@Bybc83Xotn $cLHu/*Rfb܇ ZySJh̒Uz?⯷kPa1.|+PftbꕟE<8;:䄶)ֽVGx4m~rzè꽍fbڏ(: JVgU-&oGl_pNP}w>ɐ_i8AP:y)ezVryn8ݧ I pxܼ%&effk\;A1@R?뒋q-|7A qӑ;-Mgl^X ~o1>>r,y}d8O 88[| ߖyG.!$!.0Pw\O'wݼoϟ6SFefTƱs'QPFIsZŸYд_omVzƝ&T./'t $7eha@b76;nr 2;w.Po6$K؝[gW{[q!H;Q0 ;"K^r `Y64ռ*F# !iawUmCy?p32|WuH-ht1E.%r]9?;G"z]g} i=7rb(qe\@}EӤ}U\DTɟ5[.a(1L ޥ{P&>Эt0y RPOyհ|ND|7$7Y,rjY'8O_Ɲ-qj=]\ď&U ip೾|7"1Y [;C0^=I?!%I͇uMhX)j[yWl }j?=`05byRrP&ǁ9Mߑ^x/59|An{gx!Dpv9#Sq():޹`냡aFEp:'|Es\vs$,ϿFI\;qٹƿT 2輼vw_\ݙ¼w%\@hh<]љs#bI>#5QΠPֵ:%$nA$?q`N@vtQDLM^eFn&n[wfUmۄ}{ڽݏG8ŏ?~-?':=h|vwNGWewe8(|Sw_WXH\O-|➜2#FŴ%˽D^V`ZD;;s}f;eVk^h_=SɸIq ^òu uפyΗOzl{q!oS'pooTV.ѫ߿ij+0fϛF@w3sxIO>s{ʹ3.(\v\Z ]n&]6ϴ7oH<< Fy:廥}kXHu;Z d?kAƄgRP #ߏpOeDy$/ڇx5FN=G"Ki1n@⒎IxXJqV58^zsݙ}"*Csp|EDz<Q9Tc [ 5ЁSd$ \~2G *DC^&Ͻܷ!lD!ea W>cCEk^؄%\)÷?byC%E}G6VE&o<%aPpVŢoуJV!-Ih .R\88Gi>H\ [c*fճp|>'1~{or| gQ^g,cq獬vZw }\Hgg||yk|؛K­L7\ q|ˣB8{øuyuY֍94˞Rr9ߌbUnL&9I\/~slʓlސ;لc;֗q2+QdcƮ*š%k sLy$AWbݿ@Tu'moij anMO+?>OTuG_b<>5wQ:w%W鴙qb9wU:w%mo‰X!h&>;cQ-^"_*S+J=l r˄WRC]/Юzky얩XZGKY?|~|]j(i]xZOs.)5 5hO}7>)yg[u?H9sݻ"sL!ƻÏd<'g!5'YZ=s7v* diwAJ6lQ3vĊ\pt8ރB|q.!$ׄ[Bmm'45~OgkEF7D_w(:asNt^G[q\ 4=n!/Xm͒N5& F8@l# bzkJ(k|}aoL=k}7gL?\f ['v2&4#4M̬aEfH/}}I9|YOR\fvw[Q/zR:v BPȤ'i5ɎT?7aeu/x-ky Y;:td7c봽!fbpgdp~m>\e^)M3NΚt*p^M]`a]HӬzs϶Z=X릛ƩI|I*[~ש/lkv IxBJA}AظDvԦlKi\bO?%>ׄ}Ϸjb{rMTT CsSפZ4o!1BCx5=azJ0f;\wb&I828P[/R*Җ%qt|xǮ8Pa)26xԾo9:.m ;<) _a}ܰx;`¯sEHjCƕZ1? ,<`g9Uvσ_<:( k |f> ma\wskpCLvvuJ}p>񦲽at.tѕK?@\^<\nߢ ˚áC-E77ahf8X~Unhj; ̣Gxdbp@yyl[,"n&GJ?Mdqx`>yyJM7R}KMrع/G^. p~&a^K8v|YS|YgׅY$!-[覌Yˡ) ,89 9CŶe|zT$!7YǑo5L7″LP7|DMa]Wj9BotK,Iui\B\}'p.®}(#Nui16[.;ϷAķ":L ' o[Ey+'nxKC魼 }>&` se-'k0< W 7>xf:ʛhһ27fh/ ^%.9aPRܻqʁ^ނӴ Z ?:${ ȐJ_gf#-?3n5Ї2pG ;1Uq4v'g@s~ޒ:q?W9o'ŅbXdܷM=óy2[Ly@>t$OGc.-]],+zހRI_п@ܐ[+s*nyBSWs;M@Ӟztv-~E VW5ZnwQ__OSZ:I>ϻ>~ <ߙr %ܽ/7>dGm6'Џ W Yuϥ vCXPk? jI~T! ͟UmX]#@UHǪ#;F5Wz|_R@{kP~tLL7v>0zSxtnY?Y5kX f'1弬kQ|0зu"o8Oc応<ρ2< /̆7vȼCKTs|6!HH0I.dME=V,dkX<\r Âq:jrז/oFnVߟ>>CWIj4t4AnJ?bWǒ8XUYPHߛuqu&gx@jٙvl],, dVHUuSiJPpS?6_Q SM㔧DV?p8HLɣ~(]@x s]BA՚zOh3^YYAɣ:>|0:Åz+"VGQ7c #| h?co.2-rh#;:ȅ!?-/ٷ0"eeW8d(@~2eFy.ClDǗ+m?>T6=[@ 62Eyߋ~eG卝{s,z<RjH Xp' &EcI/_}Z`q|<ć X|oѸ7FuLW#ۖ5 ~XEE[gLd~m`C,fv`RL< MEx/,z 4[tL_tѸ G8vގ_?i XߦUrԲ\G Y3ַhu㟮 jVy4 7(?L*~'-U|1t6vMg3:BBDh_|c}4 J;]SZKvk1*Vt~ B;9y:S`JS'΋+6\/x:o{qZDaS/ǜiZvY#ߙn;ګvx<,f_:GRr41e/*%T#F( oV pұ,qEu5~2WGfp8lZ?ݪkaqZ qs1rpQ894qA!^72qmrHC;QHJa WNm~c1<_nh ˺gT)$笣PrQ<#:ބsC/ɖ#t*$A`8qd×+IpD\ oGLX dXW6v;7o#1|B@>u=Qy]QS-perX ~>g.#K~E|vaK"/?_&7 T*ނ Nh#hc^k[R}W .L08OQG_Ryc|vڏ}ZS<_ X)jj-U^fG^5\e{TTM,ir.b;cƞ޹'Pr]2>W#}Rxǡg#s?3Tf=oSFE[pA"!tuԹǀvJۡk}Ъ[dž'w3#80a71E>gcWf2WA$`]ZX#%|F3c(ۗT;hdJ.WzФ\w(DzFz\:SYzK[٘Y&L彾= _qA'y/Iᾧ7o_/Y8z?W(qN)>X %a:qI^ kɑ񠈷/Bup i8;ӽ:[$js8(Eҗ;~(hTJ>)q-Jm׋ b\j#{Ex>!ōh  9K!ڱ"~>qv!DN.݋o?Ʀ9:Ey|h9%ȹH9| <Q1 `pS\UtDDH`V61E%~v?Kg 7V9փ)+({pʞ!/} AXʧil80y8Ex4<` W3T`sS@FK=2[]ZN0KĈLq`f;:9\J>mjzŹ̽ŴC+| O@WRۿ]W\5T9-RG(3RG-g/R)F?ПS^"}[TG,^{t$|yJ>1 ":[=ڛ/|^x>֋γ[rhXt웍oQtw>xE*q8{8SmK:9Aj%]#"O3x>%cRvpMGц "іm_'_>Aĭ9V+_rYp_)S0|kOm廓cf&abd$CgKW??¿3F.ˠ* oe!)%|&5A)Iikd[E ;5Gs/lu~a/''B"Є.o%0>yf .q5aS\(*` vtJ^BDu &Eae^f֯W}r+1~ Œ*K#p}G8,-spa=ƞĶ6;51&mۜN'_;;[N}{׾Z&Kxe\0@]W2 W 7|H};9kZN9lqlP~1w>1_24to>(9He>kO{#k@[w!8,W wl[ݲI^\ 6NtSoDxAW*EZ'0 ݫ o?']]^S9tQJS*T[#5EF){G_CљUq{ \M7e4ޞRbHu įEy5#ި&,39@ӿ<4[ګ-1uځHڸ!o"'T0;hwXkZ85.ѵsSux#JyyȳvIj˚T;~;Ma 6%FG{GrΓ$er0a)[WgOMc41 :qsFطv}ܷөqf $U-?`>Rl->x씦 v<:Ԗ<#XEtUT'rOl}w W "GSXe4˽ħ ]C`lM4a=}{]seK Q 1%C82:Z3ϫ2^b/3/$g+"zX~T `hæN2w$yFb* 4A ,s7u#@8]jݾ1̧ÿ~z0Ov+*[@I. 9Y~*\E2$= mu.0rdfڳ_ B4T91L}IHf)E e']rىlD`t|~YWfLg '1pEGR<+ |ͪr3bUFt`~-v==)/ثϷ]q d)-}_Lp5Eо),enG*~姓Q;JSǪEL?'&VV !w &SJ;h}mno 8I#O5.ֿU> rhnGn5.ZkS#_ PIrȯ["2 QkV~^i2:mKr*[ȡI4{V#uV+u*/k-S 4iP㌫C>qQ6$ީ;- qͱvp+^:pa Cԯ xCi^: iP&;HHH" ۀNnUEc k/jL& 0nyd섍ܗ r+n^sIB!:orS&@BBX3s#%/i"cn*/[ԟzefϽ3}^ KMA<І*,xNQ4.jv QHZb!#&ԃx.+W Ty6K;muT1mȍ*սBY DEv)ξA3B؞n| Ň)M4*joZ?Ŏ~j =W~WhakCTԤ'y}J2!o8F! v2̃SMZYuki6Ġh~,مNlr0[galzlx]*$H[Jc?M=2%#vlZ,H*s$f{*(p!/!ma pZGcݶѐ*Yd=()Xe` AĀrm34,24зZ[ <}L]]y2Ȑ%jZ/gh=)c1Iz:.U~s~_HpX _.ALUZYD}`]G:TUM޷}^b6ᝍ/2q`AE.YDkc^8KFajTys2 tbXunꂤ ~?%;<|H\ԸԾCnf跒?H?,#ers)Ȇ# 3 sK;C90eo$cn6/4FtERSOf)%| |) n&ُT}mt?|F޷8Z|Hg6W#H{%&yV7+WK1V%i>oJ:kRBWퟹHc{_px_ HE>H$~~+F FVoayky\]mB_ @1GqG灊4uI y\7=, ݐ)O_'g[D$l2͌!zNKM>)\th4i*x|ǣxe2y`- (>cj *F2s!IxhҌk{{D6y WO\rK  SURH>baOiMr.uU}i9X^$kkIvƒn6{,~6 )2\ *~9N_#[z}^ `ert-4|8^)ߋ<:6vB^T:WSE-FJNB MA US=kđ¨Pg !!Sr" 'jViڮb)wCӀy ^2bf޷gY5?./_-ʢ3 0g|vȘ)]O%#~6b%<@ERd.iSվ^GؗrL)7x^RRJ]yAi%|Ia)/~UOrA l{$P\N+wXA=O&rM(p>yʻҿ|fyBԨ{b<>| A+~X`"ɡIVI#HÆyOcQyߗB5\^ ۲-0sauK~N,2BYR SCTNZ>:Y.8&({>|AI4 2zpܒ=)V}ډ_үz}o֧dZqIU>uJMRǕ@[UDφ/:zd;t-ۤ}kiVj9q< H 95^AX.τ7aU#oSxX ?pu=Drky['aߍuՑ=.B:{'DXM(>Hxz32cZ|Q6 cU39К=L6(\g-]j!GۓdA0m|V:$v1FNYYy.ζ(p`2 3!N C-Eq\@ s?10)LԈ.\79(uYgc)8|'KE0bDѕ`X3̙mіl!s;ٶǯ٨ʟ&b*"YQEy# Ab3ȁ R۠z.}4QBBAz5wP%,<Ѫ"dlܓg)d)0"}jܲLsZVy>YV5 ܛoG 8al9-5޴+ɰi%=]=>mNDG\]iV9ͧz}XS;VVvQG"=*`liG^sE#yz>.7=o>:$Ls)-ze9Æ[%6pZIDPg@}؁-Fyȇfsenak9m[[&7 d_ (i x@>m;Gqes?Dlw'D$Z L;8kwU\*LcI /j#f9ZRouo;;?rE|K:rIזSͺ59-[m 2c p )0>-i1ƚw߁p%HH[@dzv 8[ 'RnMYY>r>'ZNIJhLPs SQuSQ=MCF@HBR˵DV{nzZsJJoIÚbrVzxv8LVo:?))bMFGq`MdӷĘ)W9n_zL[ڀڎۊ㮂 @qqyy=:L;& =[% SgoƝxpl߄l/ ,6X?Ǎ`a^ݼITT-V3`4y%X LM} !a>3 ΩA`|ؿq/pە(y*[p,: @2O ($&{C-T]@1\Ec1H!gBx%nmf2b U5 a/9fžϑ4KﺊN2XDKu\M%j@P6 HtN8vLJw\CH$w*L}(,S$"'QW½<+_j/7yi/BS,Vm}u9(}1 ֜jw6&m̨CprQYm+(ZrxI1MPAik- rc+˵Jv J >Le}Wqc<4NF\=g-oo^%ڭ⿀gWm/[ $2v8o`:~~a^؇oCmj~>xҨ޵na.6eu;̤5E:?~7,t9i/ZG.h*)5maя х$^pڍoCmk=֊Jk֏U*P}as-'yIPI"AGvz ϛr] Gow/H{SߙWt_e';Ӂ C+1AT;9ȱqm}/а'h8o;Z4J^Q$(kSe+XWG[x(}0nec|75@Cpz\ڡ̮ukc:x.7/7^*ZxF٤MViFnIhqMd`*vn字reBh51t.otǃZNLsE i &]sk`LsOAg@ o`uqjώᄆ*(t,b{bvQ~Jpޗ0^hMse 譐8`=>L>rm:_" ]4,+ZX@ 6~o '8%gfH>bU``*-G)>YJv3Eܖ}©~!0"ݾK2bc*XQ#MKBs[R}Fwߔu<~FLq@ 6\g)$%I _e.vHKvW>L gw8ߑcCQKS]Hͦ?b}j.4Jᮽ]Af,LoSWhgWMJCD0m+aI0 }ז\4bOáJN ~kB\ZA/a$j}W +a9Vz5sZH dT˽ ^cy3 O(j-wїKCK]YC'L gkFڊʤGu qA& Ju^?twΫvv,}#WËo S\)#yq2SEĜMRq|!$>|Dӵo1'[Uzodv26@ ͅP`Ry\ĤҢeه`XX_;@9&6]ְ*v&;a# Iiwn^ڍW @oSFy 5πv?W搓Po} 'yƀi0 *oMn* } eMvZ466+} @ѵgAQ3xc]e ~ګD%Re'ņkq}<Mij\Y'^>"iTvs2;i?>aË(ۀW.2Nؗ,=[ac\o~d9pb,Dn}(ޠT(X۬S-Otjv?R:<}Ʉ,U5/wjEz]_X{Ɯg}as6@ 9fKĝ7w'Eqj{;A_T/#O&h;m]G5bE H0WpԞ] dp`*f[~6^z =*$~9eS7}r.Ye0Ugf%J-J!-ō'wz u|u&vմJ^J -{BUb zBA >Vk܀lF(, W6g<'?M2Vh'H4+I}ǵ(no,΄ԯ38'JKrn4%Mt( cRcрaX_WYwXmqdw}!?WQ8l ,myoUa1)SE+.F>C4ϫ#56|7 Y74ؿXvC}۩~IJ SD?`/ʲ  vuN6ޣѓQs>a,:!X| ;ؿE<\\=z5Ŀ)mc:]j)b@L-%R `7WdDe=cZ RDcn*od='`^]F1~SzO(a BCngH pj?j; OpR:2xIdGp(81rJyS7^}p%1%wEejOz>](O Ըjø .lJhj"Rp{~.E~JqVF[H icO@gJTJ{;f27(K%ͫ(RtG"DQث윰P>yQ*IΛ8̗~ .9"jM:nrŮ!emb[0H&Z~[ChB飊Ke:B,!wpųлvd{z!#ǧfGU:=^OwzWTnnl{3@ ۻZ3FON \ orAk*ZPA"}2J] `y|KcmnC(XK0Y$`36OcF1aRQ&$6)4u`~|Pz=::M Tc;6izDD3x.bό2 .l:/lP\мʚJj;+QK~Ȍ/RʪWU;TߕX|X,QA'E ͎G9k}vk8&9c{RO( A=@e%4bF/:n(n&-N^Tb1yƨPĥrL7n+\dzD<0ˋj>#{nMRC69AbP4Fٶ'(L5| s>1&N'<WC~gV̧/!}Nf"Ftxg?G^Nsh4knFn䎥%G@F !hr5{-ܬ2WE1a7n;qj1z$DCL=iA8Y0Oc !?9,l}O 5 LS\Fn>C_>s:K,^-}:M,'n1ȹɎQJekH/سey%@ AI?ckx@W]SwجG'9(va6Mi$Bps@{mp7s8*s(Og Nu >$CB%t2eRςۆW`p4wIefZa:]0tw.E}mmٳpqW6ۓ04 1\i pQZ9}֥2kOGDڷ4 k]AmaZnG.쩶 2;)➆&_fW Bc~@+m&xq퇍klRg@'%qIrӧJ`,#Ѕ# 壮(žNqxU$_jG&bG/"] Ѣ]lm z:_mo`"4e<4\kmmLD4Nlj\E@V=N|)!wX)t2=7OS >m iY\+47?='snJ}?}Ö)sSb>Wrc}b&|UpJ|pC}L/ r"PFQNp\8~ %C%9Dcj&<:PݟjJ#(cx^S(q^B S%ܥTn$+*;Iϗvm|ӳqG _S P"#Ȧѧ*t 8NjAM<lO cM;`W RIȷC挻5yehNp Tm]Cv#8ЫU ~0*ߞL -1L %O]+KnWW,yy^*-WK\!(r]؉\/Οx) ˥{N@iԪW=J҆#}BnzrB=}$u;-}PLkjeʸs?Sê };?aym{Շ{9پ*%$XRWlyėv~cW 5,Hkܪp:cDׇw'^" "Vb"R%Ӝ糬o^sgR11lOQ'3^ir^M@{v&Yү.,oμt^w~FB:sGnGkat04l K3(U4n( &dt] ]B<}AZ݁ѷD~WC]2(LNذ"7S!^oMnjGȤ;O]|yb<9D{-RP#';ĤzAhz(B0+\޾ذ4Dl|MEӀϡHT[6\8w !}Bc$E}R ]P:nຩ'-71VRp/VKhhܳOyx}\,{ݗ2ɾ%3Y(qG4mn|VȋydCt7 ܈/n,̅#5]R{~Qte\A٨nav Ne6aNs,fBlT<*a.*m aqқm,}4h"tV 3mp7Wde!TCGtL F܍]vW@/mc奈m>q3:y7 sWgޗ47>]!2 kZr3![[;8+=zj:DlTNi|Z/h#y (4wwTE~vγKܢ Pþ쏥 Rvi4:YDoI*`Ex$ V:yb.=b(To~E5/I Sz)7ij,wz֭rsCU/\ hf4/\aJbb??ixLn9O@k=Yp3UPK)Զ}5&iKOD5]Aq}?mo},w,Yȼk%i_`  bR᫝NnQxWxAMP2년c"'eUaUjW+,੯J)pLˡs Rpm8}ϟ]yEVKjb&C2vSj(.>>KR0 ?R  4Y,ɨe9E50ީ:1v੬sPפU틾7kg+Ƹ ='7kk5gtuaCH%M1jB O , ę~[wYyЍ0}fŽANn['SdCռ?$ O,Px3Z8jX=U~IUͰy ӣ|҈pu>˞vr0=3aazF9w[QS3rcr>X-"+vp{(WE˛s>˯qRAؔM6o3pjC\-6s1e>%6$bX޵u:JIDz8[?L1zݼsuG943 ?)w {>>sSr|+K>VlK[o%Wb! i}R(5my%}+@OA=?^t&l'Eҕi%/+i\MTS!'b\:+Uw-3g_9< !prXܢ:r  fW\kr4Q,L+gO|<Ӿ;9k72@z=)X?27pVrȂK,(NuDlӺ$jd3]"u˪]s݁Dc{I+vz}>R]rcG NҮDU\U|>Ǟ˞uY0e{Amt5wB GɱeUL39VӲ'TZ xA q%UPPb#q6| DUZ6v)pUqw ?y 9xΦ0;JV\bCMP}W$D|o.|2;[-#˖t.=•䑹%9@J^,A(_(?rw( 5PfuP Xnd;׿Eu']i9d6-$%1-;,EaUbvP_a[8Zqwޏ7P~xT$Z~/$ [O7_2cXp6T㻌o_8@ϔ0xT/PѦh'/hˆI[n gF\W'52GU*V-j1vKocG`+o4n6eHpWP0aobGz=EVAl2Ur-]hfN?s>^V#8OqGhQ]LN=ms0^?{ԄVWI텃h%}T%r4jID=ZS?aWX+Lk|T^'ەϳO/!F *07{y:di JDOZ[b~qW>gv,4)P iYRSOD@to}-(l0Tl >*uфΚ S͒ⴀf^/Pjgn5+̖I$&<\?QNvUm'Ve%nՎ |<#?@w= t@< (JFg2ZH<w3PZ3A.+bZdRSA՗hƆ9JR:;S0WWw~|op j >7$oX=';}-o<9S+%WW6rnABȅbĊ`OP?V9uI_n…nHkz qlkkZь+LM=Ͳ?4tI`r 'tg]V#qOXQ̌ݡ)' Fb!fEv87_a w>;LHP / )G3pPEECӶOwY@Ң"uϡcI‹; ;xy4vMr΋O Qcm9="k'j.ȱ{!7%y2c6P_v}H΀3tVyX'{N}9-aۑ5ocۖOD-ݞZOU/;osUHΉ(H0幌I^@BSdw6 g=ѥEC&-zv 3):.T@ƟO );r[%ٙSݕb#l>W!^{Rd_oa]QB 4ɨ0 8*`#Q Wb?JHm Jp6AT3C'nELADGd9Ws"Z}tGjQQhR*_|@d'_Dг__>D9H=A"ZH4c8pӦnt.Y K32h[SqXKৄV$6Ҕv>;%r:_϶|_ػM@cL糷8>u:%z.4񓘳\2O}WOLK9J[){ c?_'uR$앀d$fQ9=d׵j);OnAbXAE z^at;JtyR~uK"ьckF\51V/$<9}p};!v yؠeىC!MHf%.S ӿUf,wYEz,n T]J 5lRɰJ>o2׍+z3X^KRT}X4Hv4i΃aKqo+~#ĨJQٻ?,BAƠ8`H\ brdDM . HC0Ց›ħQQ@%N0S?kU|ʨHHy}Žlt*OR1wy$5aH @BOq'.K@{0CX0^Dv6<̽ Xio.i& I?b--_T9 M<$!v xx!ӷ~&5Zt92b"A 7:` v!(|_+y&}#K߀gOV5k"[AVܶb( xJwpsE#-bTۓvY-sB^ &^}WfA'=L 1XhJ7$HxRzqRׇ|Se nkm.m?|Vw䥉9k-;5?xJ#xqЃ&.#:XQdp!+iCM "ȜvWO \t!=<۳$Yh*mLc.Ic]ЩNF&)zr0Ԩ$5y ͕jѴvDT 0V^Zfc4 HݜI1 πt21O`6%җX݂PfΏyu oPk)PY`ɨU"ݓnX/ ZKYq6Usj1F*ІMҿs!ϲ`h1c3Nا>"&x:j9}1l3k_͕|Er$`-9HI0U>84))V8[]~+~w=p^qF]vޱ]y1q=^!;IWE`3h$ #ع%B}3MADz:."o1&pSMU/Lmk؝Hh<7 {d a2.i/dpmnrdwAt!Fld^Wwح32+̫SI|yDqi1SF ?L Ǵ/g4 X)h$xNUu@#2*)KhjLzS:Y \9S_%A8cFLuܭ|D)GmQM:gn76ώLczpc12*:5qpS.9BhI9w G3MgM¿u4w.}z%§+Q " =ۡ|q殎ڻD2u:qslzy]t-R71'>"4m2"O'"Ige_N#FCT SmZ/TLl4b]"15ܿ!{w|^k Ig/g9r1)JSuO6=9 )]!s=½l@uc*ӽIj+ ?̙2EOa"BZ-}lݾ#_듊WQ'a7K~hLpLM =ydx;?NILn.~omCB:̐6520he׆mGc~śR2SɀC:uL:bVk!b\5OP vnsR׳z= ~_#U'ZQ$|\0{ pd4ʹkw K'k0\--)9y1n(h1W7n1P&|FQi X OԠ؁lxEbCG-3ќ"u@JM(BBg,J?{-QsȩL.5ϠLo}R~NWi}YcV;)OL FR!y4C=)ۃ [I8R7bGA?W (Ȅ$ 4ә%#CFrc?Ud Y1rew0yx;\p3ĕ6#,"fƁz:k?ztRʼn;$ OI7zxPt.믖Dm ~k}TJ'zҝRMWݽ?a UJb86lZkq,ùzLAҋv<].?3$OC-ڐToc>Te<7 ζ.0 ok>>{pӞ[9Z*D]vqdXYT#[[^7n׻lLq3L*{7=m u̻h}c!|Croº"b\okn5W)*Q5(أP^=R-ٞ/t]|F _GQ\|wb'7crZx1#S{IZގ-gjx?tBͰ'.WYBӿ(+>d7VЅHqq> FpBp@ϊo֎ ;"k6X`lρ1H=$ ;jxƞH)db-8~ôpK2@ȩ'KΔ@>'ð_XߖyRIo_DfJ˨wY$L.b=Ql'{BxCFKջ2eKcYr/CNk0˻[=OUG=# yӡtsuTulQuƕ{~^O ZŲ[%}dG޹n6/~ME1b8[wWHRb}h졌QF#Kz@ōͥjM{/>oW}+ǿAj5xZ#u盃|8Nk⌕"_Y=<^8 +Z_䖆E ?xZfN%)QAÒE,k#u6Ewpu/v|1>Ѭ.O*b+Pz83e~i}kx0DZ4@-Q7?OH/0E`xRI> 6:{O 1_[l=Wlc @1H=T+<8I}E@ſQ} yMpQeRI_vFKMn{gت5aQm2Llά|g{ d˟-$ģG -t}fZ7Uw2,5e2{m'NOģƾp-Nif,∛_܉*>uK'8mIC,hr8)#`st6I @ՅJ x2rznp;\'emTPoyG (_ЋOf52*\w,E7(t b<gfnafH0XwXFӲWÉ:Qc'&xN&-Iq ?3!] +HmX̗YIUDZC%j{S <2=@9Wfuma5{V!.@1ґgLnN;cv|k ?oqos }A{ʵ*4kE{I 6?*^&g)- l,OQj:8UL탫XicsCDltyη(T霩 fR-apQ> M\JDXvA:=ŬWe;OUXߩ NJo!}*ImNGK㈈-3zkIZp'7GGe:"AoSg.SLbY]e8k5\@ͣ(y]W9]*xFU{z22!?'luPIBuk܊ -}LҸi0?}7\O~ @$w5O*ߒ^ ;/7*FBP-P 2OѧWCQH@Wz)qm0w78[}eMaMoQy}RAd%/g KISY&2[095y32^Z۹{uRuёiCɥSm'DHyTVDo.X0eChRXq^ˎ崿s!Swb>cavq2Jw$xVXFGL4UPxd}QvuIc"Tsg6S,%w]^,CLБgs8ce*ԃ쉗S!¬2s)hs8q+`q]rpԮ(Q6!,{eneto|^nSKj۷uS`ϽڽwSE'&sP[v 3rf2{kώ9.lJߺrϷ ~߲}:m|EG0>wݓG1|> p]e7׬ޖ_~:,/3ISyCRs+:<.َ^dwl\J󢳋 $k]֜|# jOT)*~º 'p+=z14MržX,kRPp6"ZL˵~w=]P0~2rytd.]-N-U֖fL;[g{ v2_x,#\1ֻIzJhM;hr8 O ۻWu`X= d>-NЦad=ݜ*o{^n{q6_G V;1brblKWml`d|C6I񏊱TJ]nM\vnkru tLu9LWwit0j!rրqkH`~D*[8L8!< ˙>]G-n̩ 2!rСDG^󌉆qx?j%>]9m_*#GU&#]{=hԔ9+1|V8E-O$` tL5:ײI,2U{]Mj"!a,۫-k 7;:5.'=\L/O~&rMmȖclE‰C"ڥ,fKu,h5ӭ} 7!N@5C.Ej:O99juo8v[" 4 8_phE\L~oJk[;u.A.]u v21[v=Nr{Y`PBB>/Um/s/-4zZ.JQfب*z T^aؾ@D~1(˿8xcm /flpJByZG7OOD{o-O b_`5\W]|8 ^r&!!NJcvY3^m}^~R6IZK:T(85aT]_uhi0w1#f(qjvN2W熧M *= ;_._ ))#QT@&C*@I,!ɶ@@0{>Qkl f UD0 QD@S&7KG.{WW8|Jck4@# xYYL TOX\ԫ<-ωޝgz 1Gy7aW{]{^)/] zw$eo>-B=DL PmJ)p2HH'W:]֐׏+MbN)݋UqmHA"CY2}?eRBXJHUqjO/A6NKa*\q8Qiik81_xꖽٿeXq:vJxI 6Dm9HdXP,d!B8C$|Eh ]?aOwÜbN%#RE/b9,5д}+4o;@*ٱAF\|/2`NCT8ddZp@@@۟9͏8e@Wmmb)QYT S(K9Vs6`ϰOP.!giߦwAZT.j1.PoxX2p!L&l Z42+U$ ~sJWzw`\5F~1M@1~*╧}s̴8 7׊; :T3-.l (CʄC ʵoA¹7@[麕o*Lk`Ɛ`ȣadM=rL#`B@#{~~(Qm,;Wk,Z}Xb'  ˂d܆OY /#:pwTc]?Z ॉGؤH* a~ڔ@&VY!wHtg'P%uU5.فQ225!ӄ571#ErH` /Bqʏ>ӌwq8 d.BE%F i]#[HL>ܸ%WDuRQ# ; |Zn|n,$q3=:w^SB8DD31i=Ǯ٨G^|vFJrcIF, * k>G/M4m l'MYk-O~¼vֻ=l9q~ 6PxqS\~bN9TO7.52N\T?&*+Z+^ 1).u9 Q^loLu Paa&};0v<8c͎#Ք=G/W3/b?P&}KOcGiHۯvR# Zf8GV~cD9I[i #z`n5uT+ӱ9k~2i "qϲW Q@>=8 rK OZR߰mٴ}8-G4FҞa:Q/ @l: <)o*?sA.eS]gE[ԴkLtj)w&NGp1)<&KELrgy`ǾK!ۓzꟴs}}ԱB>zUB9-:Cg+{5z t'<[ ǷQi583E+HerA;_-qίĨ0Nr:^Mt7n:,6^.V ftr)5kB%%H C[v|seQ`gccVPUfecUmgbWQ5qו;Y'9۴>}ۯ_l %lȪ0r% YGd(?X8XY98e8XYXٛ998XYX:Y )/ߐr弇dɍQQ쐪b&jArh:Z87lg~w=j/RP`j} 7N9V.: 'xT8`r5 F A G;X8=: /ˍVM鵭_md,@xF=A-9-5 -7%=\SgN{gc, puKGWki9ɐ!ul5czgBŕɎ&]Ȣ(U5E.ZifyEYE6n9Ezi9.%Y6NRmK[姑Orag$Xgl`Wҧc`#+ ,, ",+ ?-K_2u5g5tް`pjR+uٱ?YPAӴ\SŤPJ+:{$w,H# ΰ֦˺v<* pu湀qz(tU&HX ms<3)Dc=c%k;VWX5owN}εR/$K4v2rS\Mk0d98`#K 61E> ~>}7T~nK 戄 ;pCJ,EǬHɵ(W5YM>a.z{B[pā,GY97Iw2JV/|n/#ꖶS|!GIƓlF©$!-!)*-Ϊ.{ֺj2sXzsyTP@j+s쬧) Hr2Vf^ !Fvj>W; s"՜]PҹDz%=>=kKO}6\kGTW[+"%.Z+->, \\23F8%xctO'U`P_T~NFVb3ax 7~ jߊ@Fɯ`g"FfN6.F;rs1\#$#ޯ-43γMNذVEh0Y,,?=?=scvāF7LFL-W gUkg\l^r x))M̾- vmӚLh:d7>EBE{*X)po?B5*l,$V2 u,_z%NZS(pN{ 8U6ph r`J%)B\>7R!TuS{< /~‹[Fx8t.*JS{DMj᱇T(4a7Ӓb8sW(^ɀ.kryy(j ݊s/ukNZo(EY>QoJ2M, ,2M,܍2r2l,&h\@dL HT}j B . K ([AafmMPhb7CoYMEFx "#EGkr1Od~"mQ}J6\#BVsM2rog%:Ty QsUC㘄꾓](BԝWo5ߖu'Faĥꮣ~2+UހjNPڝ\Δk;55xr4q}__N1 H SssߒTpl"ObHԽѕ!~8|%d'k[%@!_Sf`UKҸ8ؤ徸VU]^S^ZKSQF$^CAN]i!n$PՑ̒ L{yPc q'4!"`;"p*~M,+X+r*rp)ɳJsȰ+AҙjfvcAO ltLM8)H0]F̃tA?tW4,z8[gdw (^(}BGj Ⱦ6o~gv. 4jj#'[UХ6]v3é@"DS?A]L-9RIL'Zb1aӡU)YVՀ{]9w V>>d&sHAjfi@$(:c~E]CVE XdX89X6FyYcs]ބ"T8 Vn3/;dQWlT$_ǀEÖ1C&hz!͖$2~']r<,j > J}I-^rv#IKe\P?1cLqʄpL߀=$lF^^^^MUK^^^[ANK냳7 ʥ z6o(̢Nvg}X0:h,-,l22-\J Mu,\-Ҭ'CM|oH^37GwSCoO1%Yp|t oy(R˳ppp5s˳6}S][[[BDSW\["ζ;K:m{.-7rXBswZEY9vZvnN!S gWZnvS5{s$w8]C"eH01L7 gk4D!8f,&nx0@wd4K J\r2 _k W4; 뗞|mҬ_umg[/3Jszn=WήF ̌ME6Wɳ@ޱ*xdN G@L L WR6N)< l==!3s5.[KR|Z\wG" 1ˁRVb~pY{xelFpCUx- 'arXx 76gix]k r Xl]ˆ2UPk0AGkB/*gzⶪUW]%2m>gwW(`!&iY.fFFF-}P- 7Z6E؟ͦ5 =bQy 6UrWZQ bsSE B-NZRui0tғ(wMo],ĺI@*]۞K- /Svv'$;gIvg|k@Y/gM9o%[Q=4o'Ÿd4a*Yy09{=[<1%@ijktGݝ܌ jwqܼwKOJfISh $΄ `o %KS5(Wx]N֤LSNb؋m!ʤ4s޵Quyr)2T*تmi/AS1XooywA\gkǂ.?tsYodExܬpLȑt[|b9]n "W;*v4B"~ßOl_ֲik:Vd5R]WݯD=;L\_*Y.Dly_ub)\ժ6)^[YH'Q{K=hKӅէz Y^柢?iz{JW CB Zʟ#12pIX + #KuzRnVAF]N)8&?78`5KoS'$v&` Ӫ;9r- '#ox^o$l,l٢2L$q Dv|2ZFy\XPp|"\ UsnRE0CT7b (~/IuAG%W 0 .#cfsO2ᘟzٹ8T4f"|>w8׬kjK yǷ]5v3W]&s 66i;}ĺ C&ٻ*IPSS*?2A{mҏJQ'PSW7,t ??GSH u,g JJ4JE/b>*=+7v[)NؤB2 Շ°Ґ!BnkOE%x>spDZI)ݟѩ.j< X\ooi#xCPk,|d޹ՙ =agzjBqB-W< '/MK-Mu!Fy3RA !(4l;+ܒO՚HV: <4m׹P?l@\ubF-++c!Ȉgz|]Jc߼'mx{_JOû0;8ѱLODTԖҶU7cU۫ KJݮ}]5Ә9Sp YiiA'Re-IeګNCT6aM%'ǙT<ܐ:{i+޿®0r DwgeJw ꒟coc{7%wzWfYthZ"1In_p{Enr?GPKI Rʼn,vƶf\㰲LGySqYn9FxFuzu + 16+-7MMYptu5y>B*l@:f'3.TUd9( 3H G\m!+h(+eK5u " QרUȏ{!B⅂xXHxxhs6 ;8'r&+}ָ67Pvʚ :8dR+(Kg|1ARau K ߇hLl^A&Hk- MrkIȜ :\*lVU".5s$n'v%ذ޼tt5!ffzJǹ/&1}ɡ75C6|u?.9$nUye8#hs}3[|%zk.)/{{ׅZt;qnq5 +{=ZFnS'+¢rXD,PP%NMSFYjƵ/ׇ^h'T^=%,6u+:?e˄w"t^|MkE_c\@\>L\)!oT,_7чهK""NjALK_L3_'1$~V坣jTOEc̠Mr[]8UBs:dn \(} AmF!Bj{Bů2D`I'˼=>+?IEB,^*Ȁz56gyusĀJ?W>=V8*v^`D=u%,0oOgj#OjGj֏mxw݋#Sf/:-FZoFT_[%-xVil1.\:杦ijwe [|D~)#4BW8̵J#̮ e89|x˔ЬrqTKe}PjjA7`i e ;YzYhܲ@L-6ÉkTBG46ɃJ6cBF@OL*;5˨+I%Fml Ϲ18$\-1C?A9x9Oz;ߍzO^PBQ#Ȱ@/%YcM$LBi)VU,$(`Bp(h={>8hfs|`\v/D)/EMV9(87.mWTǦː.okO.|@^?#~]΅ oq{l;p<_?|>Oι1z^!mZ6pP Y8կ/ft*, pN֎ͽ8`b pډ ]8 SR{J1r|F*RJ̷=eӵ2!UMt ӵL)d0oBKȂI?m 9 ÅZP3er>׷&,1Dn\l?̋ kA!w">ɏ徯IkR XI@ 48DrR0]tքl]<{Dӌ^ {g2G '!Q 'U8skrņ!&6Ww?mr0-w 3\47`$L o Y\DV (ZoA#IXzwI xW寃gHvpi@E,2]$}R;iJg}0Bf\m6Yi(]mD5C?E84s?xn# JsInWRzj }fS YMA A[gSfrn'k[?5iW oZ?eYi't8fbHĄ[L{( ?iӚbJ/QU~8Dł+*y;y>]Uv͂/K6D#şY:ڸ~y^t˗.QƲZVq@+{fhpR=NnY)pl+RR(Q.:VCOEZQcMuZ".G[%M>l0rn9wݻeLBL @U|['*L fWW8Rcqs(|[2*88oW<#QQ=OX k*|1殪0!A۞nkwnsn<~dU\v;(XVonͲ|/l= =]do\^mvoj.Nf]Lln(=utmݰo³ͩ*3e2*Rb:كK5RхW>ԼӉ,kt+ Л|`qt3C Đj5bnk"46\UBRLl~XSZ\'7K+ ':ApS:vA+,J6TҎȩ j[fz.~^q>SP_zjDV=E1}Ι!E B(ȵh;5UP #uQ/8g Bg&\y J>hұ:)|oެ UpR>fals8}dSd̸Pk3}'D0>^5~O+x`l Ñʹx_sl)..'QΧ{KjR.p 0͹O1>$yŦ_VY7լB6c8~>}ّn'Oy"{њIj^M%iy%X '[-{c>G=XLp6Fn4:3^]o%&M7~ gܨ~'LO6rVH[\w$HD3TFHP b)B9?lE~Q\mD&k8Q\睴vASl]Ie촣B.? =|Ri&a5x'kvJV;d@M3-_pAJvW6p)a1L5Ѹ=K,>dғ3QIbσ%ر` rm#LW &g8nO@,Eh6LX 9~b =NHbB8H8A!NdWY2Ã8o^roTK##uP|C]0sU!jl0v;^Bi1uHڬpnULfo}aDt\n5d{)4\Txt\_\|oUhi] ;vU~zm`3U7^Kl^3_]FohlW <ѼT;O襮EuAȒKU\0vu-H}}(SV.W ]ݮKCv-- ̫bSs8Yy9tyry>QLU2͂uAY|BbUZQNɈ^62zvkUU^=.Qebu(9aNb- ?a6`+H u2INX wB I7'>YD^mf g!+ gჱIYהp"\8=#! 0!k)3И$l&wI$*Z@ Cd8"۩'IG \>ׯGhTVpRSJ0lQGֿubM)ARM2a񱽇I^p]Ah$s VNb.̕R6ga]{_x#Ϗ\.K.Dgrz3&mЫ2ȅK-䑲hEpo\ #AE咐aBwsPFngL\XX;l4\x~RUz]e vgS]ܤ]$*@":&۴PFkF%!vqk䔦1> i+4(@~/V;g[doH^ !~dئ@7W6m@G^:P%LG}rMFݞ]uW?0鐵!$krd;hzN#l1ꕈOD@xLn\-B!z.e/ n#[~9_#ZRhNn_X%z XϔJqA/.2|AQB4wGXQN7c,icȮ\ D'E@@Bͼ :jħ n.Yn #?qxb @+Mœjp{pI`U n l@epүq)2Hu>l NSP0"4.i-̎D5"͐~Bh| T-L_3?F99!!{Ay 7ҷVv̳4sV܊SN)T=L@bv'yx+| #PE|h15f-^JbXī5Pv`/yg+a iH,,)&Y|"U.pyQ# HC)B4 d_Ā<=/C uN)UvOdWM2418EuKU:_K2G U U8 H@T;78jeG|GPjGXRM9&Ŏz|hPϡK0k#[x3(y28 8gD(qo֡Jurʘr=&ẗ\" -ߋ̼ǩK!FY8ThlO߁i.ttOh `,Zl#c[9Joj$f\D=K`!}S rP#7 ]+4K/;BF`h}/ߥq@bl$zb'},oFj~΢zи[]p5˴,m"΢6ft;XhZĀVKӈm/YWɺ<ҪԯkA*IBDD;x@Lv\2tr"uve`b |ڳUЌRg.%sHwNDKoFgsԷ`JD-ĨWk1iF!: elOҍ\w5G Z! ?\eY}5.t {b~Jv3B*y6y5[ޗ.鳚 0`飜SDqKeAB@͌>vQѼgյ -0,}e s1}zʞ&q]a_"wVFlғw VdpW~kĨAe υw).X#W'eˇ0(}b,MMn?a1 5I8jXLS!=z9[c/WӪ-a'pCsw^^w~lva8eBX^*Nj11/]+_MԫW-Fc&2-Gʸ$[^T͞~w׍5k.?ώ|},n05^$Fy~V=?b)unCȧb켠^v+]~xLoje#_00P̀= ^b['sYގ` )k_is t",i\X4Q{qw 88}b:3?bZ')meE9qcORiW ":R'NGQPI_5ҩ1:M˦i;p{Vvzg BH0#@^3+` `!y;8ۂtM|P7PXھJ:nSuom ln9m!8|)ov}52Ot_!5_Ng-d cg iZ[E065? euk `pSz{ Gq9"B߸F-[įҗ&Tew;ZiE;zntETzga qz04!CM1jr|Z^aM"}ZHዘ,BLޜZ<hӠI h")$;cV4!"cGE|#Ў;e/F#ZhAD39) ! Vr^䉾c Et|n"R'O!9&T546`b~~\ɨNz_mȹ.|$x٘)yA24TvR%jjҥ oy݃BR'f<^Ye*M{׎=ۡ! Z2<QiI./;1U|\׭ xFtҵȩgh&毓C{)38Ch1Wt"b|KRF@ Ep׶M%CYȇU &a]A yGbXqzP3̈́[ yHe%4/^8$–o 5WzT+ BCwyDLVЅEnzh$ yy;WﻢjGS!%]̸(~#.~ZA5])?T+)۶Z:(h|jnף~jvuASQ7L?nY:2h *<#&AnE+'GI >"c?,&&&*.-νU|0Rԑ ,-%9ʷSΗ8LTS:N7G1gSQ2͸"$]< >'fhPzf BI` % Xz/vq6E{wп|$8q攃ơd;~우І3v]6IP :_93cMy$4gsj y/Q 39oeM(r0"$E&H]hFH|e0,d岳qi=VxPu0}ZX9)G͙؉"1< y T(:Fg)SNz$ԫ$&kjQ 4aP3*;,z&Xqf}l-'QI p7kDqXor֣y Vœ.LikHnB@M.sfNkշJ'"UyebK0)I+ûڼ'Vh)B0 FWPDӖIE$?_ ;*{rt>UvRۊ~Y/4aTRҹ hmF|ooAQZܕtBLY .F( u 79D5ޞXa֢GBa|K L.0F0: NtbD _l)DkoV)K--XT9U XF}E2;̊d$< !TfS7)c~F.ZWG_֑DMvBV "O/Rrn;O7-Rs Q獓?MOE$q Nl";jozZN̫6 fԨK[6EL"*IKIROK:_!+\Dن.׫)XQ}KP!DN&Oaز 0ipUy&)ȑX֕`Q'uS({)*c!-Kd2{m\.|:{֡%ڝv5:QELfK# E/֦ $y:ݱXrFaӿʧB+})STeKibՅ 8ɒIVuhme_{Z/J9L8BTsr*-jQi̙iG;R>⠛ؼ k}"М1 H7ـQ(@(V硈%0ۓܩӖФokG8ٶx-vǂm2=H+Eu"K|{^s]l-TM3ri^ ue $$Mzsi"5[`xYtG%GI6wyz `H \|nD$h*[ĥ+kÄ}yhvlѣyƅ̯͑>t- mGg{+^f}{8i4`wrihwU 3wcDI+eoݛVy抣u"S)Lj\yrc? iCklv&/fE l J=Q :5爫lrfO5=(2a/g鿎lj' f5awIkR~4ڔmtbe)@LDΆ͎F֊/nLmJөff>: 6v?>T]FG-z*婪;2:a-:B0W7<̏WS33_sLy Kp^h3lb$Y r#3OT+#2a tA)m%? Y{Yo06.nstx[4F4 ,9ol|GaGZpD˖ͱ/(*BUr&^Iψ]&ʹZʷJOW4$΄BE/Noa4˴ {PDtsk bp"ryqӦs9OY=K<0=K,)O) P7Н}4-@&v9aYujJ79n3W!+vɳmv\[mI#Lk;+;M[ҊGYm `Fy5"ítT?e?O^^rGP id{fI>ٲg8tgЛqraT@eO&a[3ϹkI1r-mw~63}RX֞G;5I+ 4 j)b3%Owe݂*/|KK27谍S2.䝚e C}Z{ tE ]{6etX$顝].Pe^lO}nw9 Fp uЌ/X:Z(8Z2> RJ* \$jpE3 A$oLEd/=KN6')p8 _Ċ~z)ϨQ6eD"3=1.Ad!R^EPL>f `R߮|*o,2 xϪh_X Jl@U7 QlK}:m)yd1# +c,B-hcpenv1*P m}o(#a' .=AqsvA)v>3N!Ѐ*SĠ6N܀ge~i2l?H$xGN% 5]d%%(dWPaR`ٕcRnםlqx$ "$Hdw:O %jX(6< ԛEJ-⾒0FArƋf q'qzv YZ>~ UYA &D'coӶ2R AJ-D\+_JW-2NlY/sytq3D+OTǖcZVð_7+>B|1B_וLGZg@[@LˣYN3S /G[q:,d;{ӓd 'NNdz.l9CvЋȐߕxXW=sٚ1T"]UOTg‰պYg]O6iו**[J0[oNt $lپO>(E]+.2ۮ!/l_- h{FhWgs ԹvPِX9?}ԫnh8d&YeX{ȕ0C= LZ$:e*in)mj5=7(↱kOjufi^i{N^mD f8?V`/r܄2wJ#s:+lS?i4I]ƀ6qS΍ux}{ }}يiϭk uqխSl oNa_DΪ8*~ >+@Jm-=kY~@K&!󦥛 Oa7; ?Ȳ+PWewZ9srL#K'H"99N5.B$9==1MX5x2dLwDO!kN?:!9yJpkҤXF54JRJT-%7Ll(#sow F:@vr|HdUL` kB1TJ`WW$AY3 e$hπ-:"L~j `2s30݉)ơ!d~`M5=$rsIPK,p^!bd6w-:v (!zkCIOFF2gtȂmFk*W}`!%2&]<-8Lx&&QyT|F-fJSc/$Y/|oJF|>A -ՠJ}S+c3țdKk̴JU7v1I|'| !܁2s9ud|iӝWz`i/WHFQ38͖G*Gz#rybeVw',߻H$Y/ 8KU>I)G$fCѴV'C| lې* ctms}TCesPꭷus@pT; 0˃>'(gKhAUQmC0FQ!v Ҁy>yy4*!HkKUdUO .T/k.,[BiScإ)Gѯ}Ik?ml!XV+j8~?{}]>}-<&|bYP4Sʣkʵ'ղQFyo:db,0YH&Gikttaض-,C| b zA>4);/݈ "TY/oŀ{XF0AƲ^"\«la @iZp[O#ը(;2nEooo0;yA:iA{TaQ3r;̐Y:$ŠJx+ 8:G"7/.AiQ4LD*$8VzZ*W=fyD']BL2bE [MtF?ڍ'ă)5k,>B-7 鼂:H%s+=ǝjuF^Pң@_0]ǭEAz&wZ[jn} b@3a’[ܧީXGkxԁXPM@QAAl a38ImYzsƽݥ -7\"v݅,mfT:(03_ogvZB2J1yy{"~?x(o[a>5ʑ?v25fؔ+4׃3sō%`!|ar TuWn* Bz5 ^~B:kpW O,/Y?>X<^j~2~p> d HB:͖jTg=ּLO^A ū*5c{(]S47 L=~yRs &-DC>:DE_֏$l3|NO %2kT`r7 pAfa̘z yq=&ywqL8Z7[ l5iٙ8 /-#-P`UGԹ)v("H\(څ^N'5|e0Rb^d[xe)Σc`Th$rܜRP \n 7ҐTڷoqZgqbya܂BӂS%l܂HDlV _H}ll[&ey)7~hraQceQ8mn`wH ʂt Y\9Dnu]2n@-ڻqoЃ8 3S3~t|-^_~S&nL%vvLmtVZgQcz]$A,Y+@&ÑcB|O7dՠoOF > 3! 5ݔ!Ik]_j =I6fx&tEk]`wgɜNx-IF3ޏXn2j5oWn[3ei #{q?CwS(#^1CPAhE񯞊2گq{+֡CNhQKMkpv9˩pUU]|>a Q$MeLզVS*w& =)y3&c>JW;z$#_ `peȒIDVj,oo3֗itn& Lb ab[OOFiB XL #iXm#x '<,R 5Cυs.dq'wvqW&q7rw6.@jA[˽=U_X ]#s{  W o p:Lڑ=;<' =IeXp~ 8a!(ٯߵj3qb ǀ!gmZSXf&}xG}Z+ 慹PL UʉYԏZSLͫܰTm!dz3GS4H^rHwGg"pfd~dQXjAVlĻ l?)>M=Z{T4.rjR]N7QvgZ8ήCZ"nݍ Y lxNNJPI r7H; 5֞ē҃RJ2'[w!&S5V^ _V1;@c|r$w piO) lW^3kbo$?x(.}<4;ܣW'9SsvE J[$x%-iCȈm89t&x#+ -TQvtKJ@y KJF 8Z+3b(oEޗ}m{|u2wMSS#F)"'aEC hڠX:Ix\~Sc0jDh;j3wsWN<6JXTb~:4 /:hF1sbc? u,G8f^ jGvZn!&lK X"jLV]` /e`y=WHRwȢ2ڑ ~i=1,) '! dnR='ͪnge߲54V/&)0~Tt4k$l O?E gr0GynfH9{-HI}#64HY5/c&cT ކ*$t׹ݪ,~&]"xsP.d* 3[h+1U^u8qE&Ejza?|hEM򎁺YO wyMV KCA*CFX)u2^{eE(͋12dJq[ج㗭wK\N©Ftht l؎f.XCk`jB[Wq@mύ:O݄Z2pdg+۰2<6CS(:P҆Ҭ1䊭o$k|,^bBV7S ڛîS*))sj ueUsoAB_Ig\YcJg)5/(3P Ou@Jo5Me+5#XLu,󫁱*YY& 9U p "bZ?*Ĵ"Uħ>l u""P-LeN޶ > łQ Q 1>8W,aDR@g1r@͓aUch߷ރ˒ܢH(󁈙Y3]YV*$?3E?%iX4)N5Lsd7FFHPS4R]NJceˋQ1u'Yde5KJVebqV_9s/yx5sőŎd;qV&)6Ũ]ȅփgD9BKկCfaiR::~edTU58f۟aΔ_Qv5EVӒ+q2 +uN@cä}r8G?D6ۇ⎳LlZfS[Lfŏ 3ΑDץN@]ns^4"<'"9YqsA6%J9 f]#ut.u!cLQ]FTϕǟv<9>*R!4ldvɃVB!W A+@ML0HW 6f9=aY#vPMs '*v6fP̮O) l|n痏w9S 9nmϣ`IFVu^[”=Mq5~=fn v``a_s R4>mPB;ƛóUqfݕ(XBQ3ˍh_ 39oԛh/DiZ\ g\>9sUQCj]MPhoasd˾=N^ρ=:a̲9(x"MlaZn3@Akem&/rzEN/vpéQqP2P["%tu΀8+hmZμb$b Mp{ #KݤۛBDJq-K#)W#**Q?GCμFjȨK0r`uϡpB+-364B>xA<Տ ONŕ4}M?i٪Dy`Tv,Rt%IH ARY諒!K/vx{UH/ V@&C͓e9‰)[Y5R&_$ɻ$Mm\V n:Pjp{5P'h'L䨄j2j!ƌ&as?r*W;IfZ0&78uilGE''Φ.+4Vom줲wPCS04{g'2 HÔJT kEKJ%%_@ Eɣwf~{{|u.;k?T{LEm?iVo[ȞKϥd<*rf_]EZ.W/QRY.`Dp4R#*!!d%}T eBȁJKaHֶn BhJwHԚ+mǞދM uBt2 m[5UC<U)}apJI+B $GYQd%PO2 jaAӆߩx4`@OEqY?mLKtMs 5hIt>BMFz9"8S9)"Uk([f3mEzet/dN8!XzF~e kL!+ Kjs6B̧gHTa1Y%'lz75z>zsqg܁(i_}mts[ٳ],ق:Ϳ뾿V>߁ <'&w^ʔ q]쿶^;'|Eie"kmKfُhEfԼ%cD)g'˩2|t: O=x>U*a{%Hryǭ !q !k5Z̨]-M0{',JQEA2N-HQ *A} >1oFX6 bGc)NɊgQzܗ uDsbĥ/eL4h0C>(M(ebxT. ?,}MK:f[ ,ssU̎ݪt^8 ^x ,')CW%^UvJq*]PR݃A/ ûCO!tߚ-K>+\-ibe nj;N<0!CFԉ>> T-z 4zW,i?q \6$_~F*ْ:S A"^,YLafzÛ]1 :TH9,j`+#V$rTŧx0KOW;oK|:Y7Ig,gk{'[m%CCٜ+"*}y90;;g@,G"J@.O9A"-i,fL1GxaSQ)MթYm3ڨa벙K;E~Ǜ|S6wȘ8l]BZgZJb!@!Z<,l}ʝV̚~ A 5Ɖs7uU ZQ}2);#%x)7?B1ߟgA0z`pvbҽ?]BH\ă^)?bzWРC$mm v2+=xhHqU1 "vwi0Qpm=BY5͜غUzE[\yc9+,/Fh[ԦXϊcFa|5\:ow{,pm"gf@ऍ@i8|=$gyZjL?Ebzxizvy͕4;3 ¤̿!1mKNfAes~{.3wk3J*_}aɊ"pU Θ O 8`=k?تI}5@lo,80UDpFG@)|[? Č;6?aa쒰 {SCzq?׸C<<~nn|z:"7P^u>1)X+#mjV1Aiu5S}BS,E~n7tQ4*X4 0r ͖"񛔴ߡ=Fg߭HE@vKI'pSŲt#s8%J)'";,LTM4j120XәHaڡ\d?4.͕ % Be?}.p;\m/lWB!8]@̦tN;bXc 9*3Q|Fq6srmMәpKYҨ=dS:9~"4H<,,NPA —͌`> @hKY V`< + `hFy lkLmB֮O[&=Ȇu pϲB5<2YFlN[!񡫡G-u%HʀRђ3vRk ?ڴ L [ΘJ҇YJZ:) @[J{YHXJZFse$JSӌJLKя]PY,*tL=u-Ou?^ cd\u7MI+Tfܨ8hLTYj]][h} GO%TPRpɟqtC2`Rդ⢁arUQ}WC Q/YK]|Ĺ8ব'C+:+whhUE qi))5 pl $Ť f2JZ> ( +HedAfrtxoPdž"P$0Өd54M+}^ ah7B#+M%^YFz[R }hxR?9Rn3Ӏ!!xI(JPEIPEZ#nWh(䂺n#usO{;y8#r$xh=t{8$3ՙ'{)z=XޯsΫXtM mq ~r)i͛[168IrՐ\;7d mKȵ̥"[,qz08 /90wAYB_s+8b|l[$0n/JBTJ >UXx' 6~7謿2x)Z<tx*1*IJ`0l?d [hha%b{~@;\K7 Ks4`fڅn8H` U 0$_FRAGa@;AYWՄfu. ;afdH+,a#:@.bH$Щ-:O/zuM4'qyjYڒL;Jm1]M肭~ގ&D^gٺ&Gb~לEO)]) GӇ;o`)Þ Xk׀z ]`YZV D!} :@{H r"=$X4ք+WRيkJUUuMxA| mx"㣃X)8L- (P qy0ScAKֲiQX&B6*aͧ?D柴FHOE%NhM )L2ݢ*1P:n&vh3mUH4|agҦ' Y3ƴԃQ+ ΨCИHby0PF$LldwjK n aR5 B>Rb0:OMnA%W?Lpua*,A>IjGJ64Q-Әb*%<+" (ps]M*LI>XƢ\'E+ 99O hE9_Xta{|=Dmf>v;* Jf@.wzp<'s8Yd╨C /&fiw; PRW;kQoכ}S\磚xU)poR2_.5ip/_'u̪}e˻~|ccv+8}SO{yYrQ%Ş|И-%g{E ?G%<:6@ Zp?h\bCa`Kv͠ialg#jjɪfpxh{".'l@H,$ÍATWn#4@dT`4ѯaoYؙ=Z3Lcx a0%@Y, ̌ cSRʽ?Vf2: '=pcҰK -g/X+o루j(QcO/ ŇςLIEH5zn%{G? &_07Ai,֮DIr23iۂbb8u  AVun`i@yN^yzZFVqLNI|rBH@R䶩$p0p hg(QQ) ΐr9A3hOL4ʃeR=ݩ b?9#Ei2jIyw \ p?si'T, K@xB eҗr'ΤxdALYZ،vB">:"ĺ덆 VI'rrT% mwf:4NKOkCC?[zNW'w9 \MW-D%9[+[$\P$Su~{[j:ɼH+Q,7gz, fT:5Ds T2RR }K`+"AZ:Ig/13KM@A'A3.c][ɩHln8 )h-V"yF_eIS/6 2C-DvQvhbz'CC1Pr  ZIqIITf=gu q#^P Uq1[M=Z%a~z`3p&v`AGM~)wi\(mV,NܰX]L`R:֏њ Kwd xW21jLn9ҙ~{|Կxn.J+^icX '$b䇽}ů_R[3hPz_TB۪P9*V=K1Nr"v*6r{9!XF\xn3uv -#L(%aZ"-h1En§2(Yc^qW@(9(wY#=T mghF̊￘drdRh }hGg.bTۡG|U_AfGuf>Łm?tM`oEQm'z[2dU#(W'RsEaWOb5?Luï9aQ׾6@ƭ rռ|󩻌-&Iʧ(w,Il[b^?b~n"W&nfgi YdZTB]B|rN+$Qٚ1 VAqy`@R4ss!.1C%vsʲ9}Nyy]H 2r׵ '7>eՅU=pk)M^цgt_0vܹJnjmIK=~^,̽f q ^f'tznXW5a%\udks[ęA?|ºsu!4D|mMO*`J{j*~`Őp"NIÑ*혬l˪HPQfg\n8)@Kφ3,o E߀mG~AǸ]9 BËJZ`PWt j;SMl>|ů(fLJޤw6d?K y 洠u. 7U0a% |"έ 6^d/) NJQ7V0yQ"@Ю?3:ld2ݓ%#=pl60 y4(#U=3(l# G5–5ktg O 썵/wƺF]G|xoCI"tuB'er5}]Lu5a ʑm$[:kSt[N֞q S`GĺtVX|o[Y7W;bDc]N/jcj't&ӭYwa22AbVb󧈙.!Njby/\<'8fiGd-^c޷8oެ2MyĔЄl&^ W;Trn_+w4z1ܳ^.łȹv?uuI5yoh)3(o)oˇ(L:nVgxOa)gGEBDK'Ϥ,5,9==!Xt=r,W|?4U3 1z82”' Ga2`%7 W 1+jl }3M NHӮ yl@АY~قg A\L NA,.'g}cn(je0nѢ qhHqDCaIMkEɧZӏ, Ĵ[o^*Ҷ;  &~ @ bcc;T0S(FX~^6B򽾬l0%n萆[xX:FQ!Mc +AZz,+P5_HDvqRH5M6'o266hBje/.dTbTbd.)onіF(%}Ƽ">m~)>.CfŹhNo*#0*6I@M׺d'uԺRCߌ٣!Vh+)o9]a*)0o(u"^_w8\V੥/˜-;2..-r(/M5VVz&%* y]خ3(~+bJ6;N_Ha%> bR F YT_m9~f<퇘Z Ԍ櫘3H3 fgpd(q[-~F ]/kΞMW'Uɇ!Eu+OБdY x#{Ⰽ 5ৎʬVHtH hq YJȟ.sH'LEP#|b]:B/ :54r7iag5lE"T5m[ֻJ'bĕH-r{w-g.Mp֣] _8t6(۰K-)q;Ϻ@;&ω ^Zh1f(g&WͤQMl컾놾;2(](vU[&͡xh Uǽ]F㜍vdSu=y^r1t.^NY,ERܨ qhS= I #%3pfޥǵpG /}:бUJ6v_0Psm%mfV2+LIM{;}ڒ V9Ҁc)rضMKYEzgEdu'&j/bŅ݇PwԈ!H'UW{[Ҏ۷h[Ռ@)fcs6˧or't!{!C `2e \g}CcϠ©W0nYWQ~c!=:d["8Omќ8{}7 $C(g&yoT9jx(jHkyz+։ɗ%(CB=) QUrˆlM:@hs1sm?F*~ƤOy0#jɺXT5jYLl^y 6&ZbHji8k$f2c4d0 w190&o0"F~ʊ!,Kؘ&C"%H3\:oKmLN[~Lvp昵X4S-(ypW"]r2!xTyCY98g^ǂYWUr8h=jA}XvXPf4uKNZsZh[Џ/&W8MV*#' b{Q%-Uv˪!2yɽ𮩱d|fchRVEXHth&~t4H6ɠ?Ƞ J1+Ż/T{]0[|wGňZn=VYC#,%;r ˾ WXY2ĞAPR5gj GbUf﬎f(2@|eMT-/b[n@ z щO%yu,Fg&. HDI7|_S"<ƞ 5lp7)I/_VpI,&(a7/'A7oP*&A\U@ ]YX!,SuvjwlGu!}TG&zχm BpWw˲ؖtS'ʶ$-geT4)sdѬp'tMg k[oWxKki/^1xQy^[]Y"H?= ^$^O GN $ʝ\ ]euB5vS ܓN2^R,\svȸP\RV5_ Bb(W˛}#V+7Fp :nA,%ěsa3WVA:Ttr C9N&<`!j ='Q_M@| reH(d}Zg@% U ?]nBH;mjqC>G`E—VP 5W+a~p#LXE ۗev=^flJLP6+"I&T?RQOP]&O d x }7V6r"e ҠVݾkw*|H;~oV^6B/G6\v]ȜCH_~/ZO ϠęL},iNf[бTcR˰i͎nU.@chlrA!Nc۔-"E0\.o)u৆7Yà)8$ߐj^䥫AhWe:Y~ηNcZӟI_]TAm1BԟfVbwg+qzbeTɉ(FڲwJ.FAl+lEdŤA~Q='fV[X){@lMJDa N@" >b}nYc)*VFcEYܒUw7Lj{thhAv-K!}LaڳTg؍3sT̓:q;PcE5{ dCiWrG;^.LsIO`)dLDk'٢& s`<*up0Xc'McM|SRkZ3bZJ(=_eg-!~wVZnF Y~/(۔ئw#TTalW(ucʔ^ /|*Lɠq PbOKkVR7|6 EqSn:R Bl)N3汥d_$`'gZщnoA^x_I F'>sJ8;Iѵvw&X7 zC\.^hG/S @L{QYr'\q NTubRt^AD,;͊*D>fp (L6Gc;K%}e=_]+//N6A 3gBR(gJ (fd,8[Şpt8Qf(إaz@2wdzKWC\4!%藚{tw?}EnV}/Dl$׾XJb]p"4i ۑ(a_ϦWƾvޱuL66S^cr3wQ2% Ưdqvd8ko"v0h'hJs!_Mֈ}89Aeo;$&4aYSgy?5F%cf[5qGmQi0{Ҹ/45rUaB`OIGnkp:bhj2* 4CBZ/RŵN#M2}!RTĬW2+>p;mHdwzN89MѪt@ wn,?B:ls1hbqQc5O55(ä.M~BthsG?vqz`QYHDȇc;CMWX2iLgk[yLCMzf9~BG/}CRCS* Eqnxֿ~DQvnJ0ʩ֞}bZ+29,mwf@ʛ #pVyB(p-Q$4RUR[_M:{l5>31Q4Mn|!'Ʊ3]x-ɅDHF8hD>N|8 <({Ke6PKʸ;B^"Ԍx;v9I^Y'ع)oĭ/sZ EB\ hM mJЀ3:@c}HH7z|.-L8bs++.Sgן2H_x,0h*#̗*H,X$b5+ I:jhC풕|ބ;mʥ|53PLMj[G3Q%8ߜ7i|L f(B=g ly ۭ+슀V/ <_qtZHZB7J-.cTt}rX“ӵWkmp{-#;_݆huyJ^LXS כшEbZ Ag$W]XTy48?u[4Q=Xն*LJe}=D(R兹cxI-sWV2+'eDe 1qF\sHGˬFRw>2i{e0h" vA>(%I``돺*w֞ʄY5%S T1{Zd`2ć5]A6Dg2ܽ!Y mr S/WomY;so$㓈Oj6dd^|EV7z-m1vz*(KDbJA;ޮP9:Z*6Pg'Đ݈`dS%D)[;7iSx}ȸ~ Xw.wQD.(ZDL-RYIy=#-/%Kv>Kt@=C"^K0#W|/uV? |iI.}K[,㎿ig};{:Y nG  7ltr z W(.)g_| 0j8o![6tCS-}ߧl,>#ʅ[Pgl=Xѻ3AARSA|\u.6 ˈ͊SSotO,-1䤩鯲BzGáwSuoI Z X:YUlI^W"1}Hy҂k[^b:jGɪZ0pV*iVZIv4+mjNΕoElnr8ީ@51Ĉ% z[:j12BE@11yL$xj yhZ3]N î3']+v\@Z fRn`¼az Vv!2|U$ !vׁBKE.>!lC&n?(uPTڱ]ΝP1\n8@$TRg8_?!VS!0,8kK T7OgL˜%cr`P/"9P`颔MomPI C f>da"lNz+ xM{OP:v,m?e{IvhL3 )Gs#iM3}{&1 ZxJpPS'@$sK}^I =oyr["7ˢiXz*kV}{ݶb3:LbN tm'Mp`TIN@b*} ż4 zΠ| GTYus2,hCB5Zu8zY`_S9cgjh`PAT ~@qUg1봨/H y/7lW 0g x#MDV̜:K?#vyy\D_bPi!/>{Ų4r.{\wh{ʟQOPʥRIZeQ2䑽{OZ(S{ˢH;@fQjAk6*pnpȝeGwN>_LIkcBc!RL_We3!XA& M,3GŔT3X Td݂U?`1n1Hx#'gگ=zxcYg`c(y~rB`ξ[6`}a,kl-/XMgԵ`iƉh^G➫{:k C cai~cgSfן.y eO3o* ~vyDoȣ]YXxrF.kiyӮf DN;4 YqDqXFwc40ͫd(`<)(3^DDkdu(oӍ} 6akXΉhvH,N"VVu(xI\ȼqlys6Yb ](`U%jWШvP|bvyy ]ϩZ=qWsWX9nqz<:U4|EwQ=/j!5ƞ _<:=Ub*9}f,39UF|29֑6Q{fz7̈/B cp1h:wX:Z2Y 5 +LF"~ 72"q y&ѿAcMNf܂QMA 3oAɜ&c~#ӓ0&03zK|sH 0?ӜyЫ'vz=3 H–Lff[a 'Ý O(>|ܡ,,;:(P?Y&R/t' cJɝ_8"T%,?ِu80vcI9עdMM<CV,W+͸Ǭ1Klleaץ&bAeqŏ͑xނbMPT[ ֽc5xxQ_y򃁨r0 #p5M6D/ - (ױQǃ65Վ0muW9k|((lO:zS{ lfu.&`GHAR[('g'efU,K;T?,$uO[GWJjK볥^iD*"1U閁`}bLZ .:9ni" dAKr%˲܏c?eb5`P'kN˲J9+éijz2 0֚^^Gm[A2^iyͯeZLbPީD/1!УCBqnxm]Nſ+b>Fr4퉖O;6qקz- ?."]"zLm6%o2¹?&B:^L O pH6q B@̓" ̏n+ _S7 푍 3Gle& 5,!(čm'HAKR Y#Q9;K?p(lg-N; mK1vH^* nN0=H.OFډ.H-bƊ˞O&D>:Z|A0^%Ǭ<'j[%E 8MF왭:.7"+-%S㖉S-6yMkYB+Deo"q$c]$Ȩ`%'!Y%!7bgZό8Sb1IznP6D-8^Et6( N1Bp:17̈q&wA,/g>(b[oGu[Չz~M0.qG6tٺ0ͳHE!;OODQ߁|)xܟwafV׉-QO@QcLO3o7% {}:$#)33c}(, T~emc кEu00a7cxP  Q%i[T;nԂA>vav\Qi7n%_W@Jv _G4eXoʫ7,/L-#_gA@O/pm $5yAD/UPZʟ_3\*=d\8Wm"q-`3.$@Mq8*:Ί̞+eL؎U rz4*D` e~ ѓ̺!=,MgU^gv7C\myVǜZ`\T짒)Kz=z,,v֏)˩Hմ`XH׸*tQtƕ=Cb? #A4ӞB>E2.0O/e+Oؽ5=?w4.|N}J} q}HH,J:|zfdg()_01ALkȌMK)2)\/khq*r2SR37ju>" i: eY cQ`s_İEC01H m{9I)t+Cb2eңmU4׋SkhxK҅[@aORa!Wcgӗ0ؖE"csʃ 2c60R{ӞpPsYMfcX,%됁_@mŨ ֡"'OʼÖ#P0f-kn0W^ioy Au;2cW_{0V߆.3zܦ݈\|g'LRjuzPlH&h_Iѭ#ڟs/CSmyI='@63eCpwrq):g:ͻksՎ@#բ=ӿkkB:|z 5tBn2/8ibp}﫽FoYi:0pǗUw kiBAAx d_Y܎Cӻ5ϟ-24糤vScTb MέnHbɔs!t&"AD@> Sлu8ʴ4I׳?t{Vn1 dwXG]R>fmiDp\Ow 7T5=tܞpJ4[!!Cնi+X8Xeik-/sMNi L/W&R&'8ׄUUcGi:Cª=fQi=Aپ“;[$i}Gf; W£԰OW7d% h=JP4jk1#QN]X#k E`fem]~`ū{_ 93~:(}L%z-bœ?ꭽ3GI1y{E'sޑjI>K"ߺ~40:I^1[mQ^n >v4h.i\&eɏ pp=m^(!"^\z|yad3zҐѦ ]BC5l k|u͉ W/Uvy*^ar0s J셉ɮR{S}nq[h!t>/Y8SA=mFbchHG=.[%r,JӸ E軬[--*Ԛ:?nJ$ Kd]80}+_ev>{]Fk&C f-._J1*#:0ՋQ?NsIM)<)ԻfGv7iDVF,ub$WqgM9/FA?k^Mab N.=jz3Gv| Z#mdGcyG1]ZwV=!9QHH \y4,tˮNW \ھ9U2#/E}cqT)"3ŧ a IxkU_ޡM4,]e ɾLR0A=@C"ǯL%Ibh-|IXܯŵN^F;{=}} v3~ MCB@udk/OK̀BBi7м ~Aʫ{- c7ֵiAN,f&@e㓀Ҙxs/KwY1v")UUC٫\j%9"_цM_V|?S6`eLͽQ>Y\,ħUr{hBi lgu]p#M@ x&3_Gq~o}ĥw#M#2 ˛PtL ٱ\617S M[XDbJG2 d; 3+3N;SqJ|w ٍ?便`m^W7/O̮Z.QɻdrYHO~*Lu/e6erCA'a}؏q7-7_>\|w1R T CBݺXd/`W΀4@PT3a<~RQrGXէ)oh+VR19Xa_{6e{NuokYd8+Uh#u Bj?G1 Hww+G'[*Kdgwnd#\ J-[9ʔD@0HC&()0֧w/r,T=}Q@0xֲx,ڝ Ƭϟ)/or%08GՖpopLZS }w 6K]*q CYRnCܤϿvH6nP: Ow}ܲĮ煇'1/5PujHcmXv^I NƮsS~n7j!X5A&/:)wGO'uMte; PD&ۤ-"L/JmUd)1p)f|R,Q#Pg ۅڊh| ,)P1Ur uۘJ_: X*QUm!2?n4pZAe<]xǓeϱ]+EsfT!W$jZȄÇK$g>o_ho%Ysk(w[BF-!듙hp?+yD$~R4+Ȱ/t-DggkUh]*}v-A4ۖ瀃4!5['2Q6jWVL}{7˃@Km]>B5ƒo8u.bS 8L,X!Ы;xGG1N%$B u|I1m 4;y{l 0YAAv0De;5FR& UL^BQ7Ud4s M"'q*q_GꍋuukZYAkmak1vXA1(O0&`wȁ7P/!nfShA)RKO`N*ŶŰQeE†YE\B=R! |PYN 0 ?S2'k{-B93ˢ!*ARZ+BCCC=7oVӕLKxFPxԶ.C"u钯E>Q0K !_ؾ]a" *UhA[Ξoi9 - 8HCaC-3̢춙OYG.qV/R|Mq >GV<㜺$٩jxi| -7G< ?ay2,; l&7+~Xg` |Ձ+Lܫ=`ֽtC/Sdkav&%?/Ewg=l #ٴTBotTFg[4c-w)λxI_@=aIY'nEe,TOi* KNY\ykwA&A[HNҩָuEOz@l *b}q@/ WZFeУ@?TtC2MN`c^B US3jބ H*Ѫ K`#5<}( 7 8Cǀޭ!4UɸWi8(}]ju+{@@@޵ۄkTE7Zoea!(JϝY >jA27_چ_>wմZdk2z 1<7G`וq17eEzu;~ہ P(|a$=rF]%|E㇢]ɦ/E7s߹l~,_;J ~Y2`n`hҒ!R;vkyόc)UsA>:ߖ΀yaP8i3.9\ԏ4nz0'_O /87>ޖYLtt;WңQbcˍOTVdqz hL h~Hb{"̎A =[_/ w[)[jYkB@5,zȯׁUau+ |~[lLԥϿkxNo)_Sfˁ.`A)a\"v˳h**nfa鹖՚γ, -VvMπJ/c+Cn9ny5b.2tY_}PgvϬh>fۛ` imr4vWd7qvfļC;@~;||Uѩ׽i,ba9Y%FUCvr~ㆉHI팫x׬3d}.t3f h[zCxq(6Dޣͯc;+iТ uLt,Tz me U.Tv2k`ĔDI%)ij)$I~wdamZ>'KXDW* ZlD >/I²TMe;s'G'ihBc4{IrQ8 0q#tQU ̍2آaw HllrIQ'SvSJt\9GhYoKp9!^:lEMa-$Cۮuiq1pRivK3.Yg y:ZV9_;}(.;~{/J혔]̚U`Tid4V b)#|jXω&߳1Jaۮ YZ'ip Q{מm}Au;.>׿ \  oG-bynm뇍R |ԯmZۤ'6NcB9E G]`PIVZt[dHXԱ7s}Bca*Լav%My%lF,AeuD\ƧKnnթp|£flƷY sJku7 UΊ79_UPmm;_*O.d~~m2`=8#\Iw QX }NR5=mlZ֗q pփQAa!|>/͉ٻoƣ=V :K;lH ~!h^3cxt5I'\{Z6W %cυaI}}XgMu+!H"Kǖ`wiIe=O[YN|w2d>O%g{̻ K#ޣ@B:0ѿu4&r2S!`!C?@Aݠ3=dHM@G[ft>`%9-H嘆`cq'WEHbxW0RW~f Lj`{y꩎Jݦ*uKk¢ҌBʵG'.̘G+J7uDEA771\Oy`};ߠ9kMl4zىj#ɋZts3=HV. X2<bLV÷ kx|Ԕb /r~ ߴ7@1t`3IZ_NQ7sđ6{{!<:77BT8i ],˒d]3b]]$Ԃ켬ԝ<]e}ÓLXY }ͣ"4Z&%ݑ#›O A\П0=E!%M^꼟_[El& MH`LUMO8d}!O7gaA-a+ Ss1]ž{eQ{<2>1ߏl(L=,?`MM@wfmpmmFj1l&65JKHpqau=y&ǺŴLw;QI2DMLoO2Q"S Ɨ݆'jabQz nhf `] F}2F8Lu}~s=0_ۧE;x(5R܄eTMCF`zr^ŁC6pS*6{֍9OGDhġ =4dU|Q3wh}+k8R^@)NfEtVw1mB܂jRoXb=Hla} x} ܛѪ12T8/{-^PJVN'oxhrUDag`6\9*kV9O8 QwK{LIDI8KyFߗCU:"ä ـ E.DK(n XU }oquѱ0xaNww "B@A31D%b♨oonF)*9ɏ6 unZZ2(xB&U:^X+L`Xe:Bշ1t V]R">Qׁ=34+yOdZOjhK,⧾gvs_LJDRU$xȕX1' $0E+_й`p=bB_cBE)HT߬fGY[^ten|߂Y=b[C7Ke,JbٴörG4#xm (JzpUey7?P5^Tq:cMMRr49amxP9/=ְ&+2ׄWkI/hg^'M`sKR {ڼV*v鐾 iKb%AcVfО/wd?yNP~gު.8`3Fp3mOLRQ0;%yp98>*N4 R-2ipi TNցH6:_9M>iX"fr\Ṓ1sz1) >2cr= 2A%qݪCMmNYLeZFlVfe/%<)^y k&zX]›;Y̰Yt2?l5(Y0q4'&e?vN9E4]6ֹlEd/EUm ګ? 3].NDjc|F.o֫P3o K\N~rWgҨο@ZNQ" 8="OKm 빂͡`_p}àp"lʧYJwͱ-J^-2%~GTZiuuS:FՉ/,hҕ}ml"J8*hhPȴFJLL;,x*ײ;stQFPA,8Yu]5ySx:'sd<spOdD6(oUUzS,AL?9@HW(þLg's )kaDhx 19_\?4WEI`뗔5>]' +i)Cr34AݪovSS6 )ǐIb3؟OS:2nCٚP N(nyzqs{o3pK 0>8kDTD.UygrkZzj{ q(Q@C_|/(ԃBנ?o3_Щ$MXI(mv%߀UW'!  a'vYC.[]3220)Hu+5Tj}jyqS>ɰVj:O9ϙ)u xϱKgZs{]f~yL/7ۣx]N# BJQ1ߒ!Wg-;fZS_ MuPQQ)CQBP^$^ioRD3e, ZAJ:L we~L,V#Մ$c/3HaԷ/]3|sdJ$y PX=^~om{s`+}W ރgA=}4b*T]In&APx:U/1fCP%@36>0yœ{CY,ίiƂ}2`j7 㥞]/aB=@ teϯR - 8Xz^bϕ1}%V$mgKUlZ3hNgHO-tC)eq4'x GMmHq٠R£@@طBL^{1Q%3sN/ 6"¡PK#[WȠ+Q؍etm*O?'Pl\e`7A6WM_"CJiyp egwaLC_L3ieR/;;]$3>3tfddK]B5ߺL`nߴؑ4yYl]e[ťqa z lX 7'  LK0!9ۄa8ZlЙ`G60 >| aUeұby;C/lr }Lvy'?\-6d7e(]=m՗Vp#=ǭCq7^ SG&KV,9ϝa 1>?éSg _^Fۚ'u]يkQK k!V64QylB}s? 'pEOetXԵm${" P?YǙ*qJ AũpngzWJ:A!ƘբGw]wcSmgcݦ#dž =/a b f0WF B_Ltee]TEs4tffJ }W.}-%a7p"` i 2'C!Q3AGI~1pɺIoKxQp"~FOd(ÖcM?ankQ.lP~[˜Lfr0+5s5.CFfppLr"vs͵}wa$L} N$ߩQ2Fy=~2:+XX8,d%`S$@Ep.\^V*0c%ks:Ȥӈ0` O wc:i>|`U1 DMi2$ Vdk 0 BvpBb,TwOplխZqXl Iq}cL85n >.6/p="$0oA;MchoWu]h$y_Rjh:rO+c'9K4q)zZt&}V#&d-@-u{o tcNSֹmEDgR~)gO5NɎZIHG&|&ZsIT W0|2WZTB$B`>ҏ$B:̸%V羈;7,;We=U c &* M?vȎ^f )ֳE0у Z`r&K}zU&qcA+o| :t+b|bn2s+w643 ߽JCD'OgBB(`:V[2^hرx`]^Dl͆]XNgicdY/ {^~s/<(ڏ;åuC;[ݯ:k!JV rxwwxԧ*k* ƋV ~ Hפ&/^:!\//ۛ@" ~G&Zx+A6(:3Н#2UrT /1~fYcN'UH#/ŢQKZ`o5VgHx%^X egbuo9"IwKhH/%( or/Ί#;=M{.p鬽%s,-DCя7ƥ fhW[/Iأҍ Ɂ*8FFMgnE礤5da6 ̠?EɤH `yȥmo:WC.H1A| ͢r{&rGOZ!(էwemۭlƊB#b ?\[u|y$[y@^|$6mMq꧛m>u=+J~0u;χݔQj]U|;'o.މ?⯻,r9ff@u!LxD?!7k Dښ `IIeYZJ p 2y-5?l3Ytߎ>HF" s(ĴMB}V9~ Ů_m_SeҋxF=Jtuy^L7v Ce ٳw,Swt}C]]>O)4H*ֽR+]zV+27F[-l{e:[xE /OHɎF}VӖDu3@Etw+=AKh,>wXy^zq|<{ݘfi*.,|k:kf|K XsOdc@=0/<8eڡ%SCSȭZrS=_\ ;V}$"*և9FlTRVb)-Y?7x;.UX!z{!po|-ש;yf,N}jFCU=HH;~ø*1憢x@:ЎhNwWɂ4 0d o;*)"rȥœwlw\o8+}FmtҏTia9v`(6Z(0CGTv~]/:_4럑WΦ"WS.)cn8+0\*ZE31eƒ%+Z LdvcG?wrb2BfY|Sq&QZ̮B#d~P}$BW)QvsYU=r$,Z+o1b@2pk\'DQdRdڝfa<-[-3=1$'3hvG`6Ř"cS e҇G`ToGg-m/rd?BzlJλ;K3nR SqBTQ Ͼѷ?,Nl 07D"@1-M(An*g' ⍱ltc%6VYm&Y*j76Uir]}%?ݲ`CFkai yuB-!FX <|M Ⱦн~qNѫ#SPM4]0zI/XD(Gz.Y1xװAзprBO P\(`6|u9|*A ɝ||Ig{p(2g Lfvz){dd;(cn^ە8!VP R•*ˌ&C]8}GC` ]V ;FG|޳ j M5\~dzVS: uy((MY֪"uӳ}9 9|JnzAJ60dל`J:*GWB GK$)X_kJ-A&MSec,X2>u K }w+ 4cam~٤a9 R'ֽ+mWgY`=2 ]CJ2e/kl,p-DbD,wob=.RS ĉ ^p{pNIr ^Bjeݔ[9+ЭЮ&'xd#Sv($]I3$JZtVθq~@!RB2z~' \(N=Q]p[M-{ȍDO;#ۧ[K:~Ne%5Ť|MƆ`[0Q)#op}!!nn 1uP>E6FC>3-3첛Kna#kE}DY܎]]M3 W- q #`W-󛰊zE}_@!„UfӑO Z{B6'vpb࿫,נͮČDj ! EԼ&;=! -hc`74HRF\/HȜ 5^Sv6,TjYl cSJ;jTї,0F0[ǣQ=̖I-vTDK'5֔zP[ x'ʺ 0r͡0 S}aTr[idBD$*?('c8hzd񚐩x̳ad4r`bDUuO4͔> Kw ֮䕮 !搩#iNV3IO ;JG&Yex_JaOޞvB+T;hh<@R( pţ|ڍu S~ruŷ 6q_x-69{'9tR}N[)4Xy^Ft7AO2OB|TOPr]*A>!0l1?>JWħ&iMm3\*$_n΢#MP$獎j(+dlQJSb빝3琴«տq**.8ڝԇy x4v]Hm{l!`%3N.s$.ZH+ ?Gko79/o%s6_ M_RŰ,Ŝ|ict]EBU.wكQQV^vBE:Kg`k#M^E~3Ζ)m09V+&`ֵSMf_.Q|<:>oS~+*g ~afS} g8uRVjTXX'mƺ9.C5?w">:"yo'zjǟ=9-ye5.6Ukӏh|w@4|4|EI--A#7j hY?] tHz!YQt%j  "o|&-#feEЫ^d >J`1baeno@1 @8Q!#9MSYB])H|^8ʿm\ *n3 gVh&fJs4 ;L␘{EyHU%!nʂ;qqzR/I eN#.fBx-#eK05leҮ5Zg_ET Wc k^W蟚ijhZG6~]|I!ZwU `p'Uc T+/%vD4OUbksIзL!R8Hˉ;?"֜1xҊz=Kqz[HiRL43*?5* (WA%{LlUvxѐx'^DTCb !f/+< Wq!wv*I(Ր5XlCgF12.#ax#IS"77?B}ڸ<!p@A+lO5QwZVfɳ jP מ(8󞆒{c(ZM %R0My"7 %~-lԦ/H^ bsqb!3 .He^\ݸԓ 0K%5VT5 sTɾ!<ݟquQJ" $̚POe׳OŇMQ]|T1xǰ4`?lKz~WRΌ%}f!} }=͘{^A0G޶;NTcfQf_vy[?7-bT0:OVLLVhre.m|96w76ԁj,!J (nei4)ږwm=#o0ڮJ^]w`)WG>KRE.a7NyǶT H 4D0gv\l/ 'C_;`3}^,l¾J0Rև Wq8._ʙVgnr'sЕ0-b$n#~*OJ١TJFU_p pxEDK0rDlLB+LH9C~M" Yn.:^z] M#K w"GmG=b ޥF @W1SmWmJ9(p")bBa9sk7@@h+M6@NU -I6< Tm[O>}psh}TꡍDIl5LlrDWOM@SVQ;[;:C"-gڞ[0w=Omf>Mre9Lhȗ!#  r2) H2 c P҉/OЪzUtXQEzi+p "a{bC~l!dKQdFKS"O\1~f6 ]VDds#Yz}iLSن-+q!U6FKdqV~@5Klos2ie¢0d%9N|7 kZZ=*utϔO2%9;Hv18»I41lk ] bDQ`CY_zF!o( o'GUfG5`5l1 `k/gK%dI4$ ڌ{8L ?;F D~Y*ŞMRRr4S&GNk}Mao8RAA jijArћ54b=!^&P@ٱN`.)DpBʹdG${Ig֣[H }_wFkwk/$V. DKt!NuZqpEa\JfFHn5@.Θ 愓!\.=};Z²p#;;?#d<]mDc-]*ՃJ^x TN3 ?;\)9bԏW3԰xKEFi&. {0դ/+$!Ŵ!,:۟M!-6T8Bލta[A9w@oݶ)Si-grq w'B "OgXW8Dq3Pҭ>D p ]qQ@1bхcQa6qpLcl\Db[FM6/JR/y~7]v27z1r_9RruN~~.c_*fʟ_y,ӢV|1 xN#;C?̊-D;CApZ!`F)c=CDO)UӲB®<]ڊvԧA@4SDTf>7Z(VU|NؙRgps+g!@-Vlܙ8RӔĄ}l[%77e <<+xhK>2}]/,FK37LN w6jQnUJ$pjr5G\ݝZ@7>Q|>$5/n.&q~&׽,wL]0yvn{2ePpoѓҩ͛r 9) 춲@:/i_MpTuYMw^GNM1^N"y.q|bh5t8=]^'LqD;ƴ .ZrC3!Tm8C\w7w 30%J}&=,g_DUaVI#}djf(b!0Cd0#Åi)3.n?pCgL)lvܑ~nM%kRdUiK(rZb~)y,BRV2PAhTao.-$ \"0YeꨰfdVs ?! * ħo(E!l,'GOUjwáUDTe#\fq= .]׎\|Hh9P 怦Յ z$#A7Kz 49a&*#]@^u5WT &2Pwh;&L>@[5\YCmAO2JL mGW5/C`X:AR8s 5/NH!@V0H^ c5RM!jL?LE> %[Aag"%R,jS^ YXF%K$(XVǝɿUZ`N~ojYL18 *C_^rm̸wQ#as ]X} ;:_DE;Ke[qi'b*ӄ"sSHynq!}j/ҘP3Bu #a,ЎajAOqX(@Qt8Y=ǯ|;BN&KhC|OVе$z3Y Ner;׎xOAvmm ^anT&5CLIMsnd(;2)3IVÅ<9ѹ厚X=tS$G >I{IՖG0Y,cOOJzgվlqx??ww3ß2NZ" f.rE1o]yκ8q89Mxϯw&x~B9̭SpCӿXIM՜&ox.n pAZ= tLO|?oHqoվ/ ~"3\!ps4W?$r7fySPd jBwa kڄ}#*>4 &m;`iCI-hbq;r3v)0@pu.Hi.?f{![ނF}m$GOwrVL.Z^C͏wz3/B!!$׆T?MW<_.N#I#rF:! պu/{wRt7}_:\[]o_5Fy{㠛ʯ&d{IWkSz+3KF1 hn+s-'M>ƺG8| ߲ݵ.md Q.Z~qgåc7Yc]gyk\NHd ȦVrb MM쟉l?gZVtKAI1j|WB5j\Kh0/x k`_@QqeBo`hm>ܱz>|$}m` 7eU EaKHC&vL`xB{5Eumb.G{Gs&fkuﷷDbr2ɼ*oueQ]%I#lc6M D_a T_B3+?dH%#Dr ȯ5Iqj9Ǝz [ochC(IpD76Q`ȜӐ;zqbhv\Z0+U^8^'Nn u@rw_\v]3rURgARpr1|0T1S9s Ony1B֋̶a\93ۜǣc^xd͙'$_& p, d_{9[ӗS;&JK)* 5h{ֵ6ꗭrYL0|hQ>J P&qMkMs P￉"v-q{;st,M:*$t#HҮSD /-Ð B wg)pCA? ۛD#}m&pJ2Ln> ?\KXJy@umc^S*OS BزlWhH,@B xs(yT:?sj0Yxt)pPno#Adj_@a ee08[vIrTq/Gz/o*W"i/~q6 %vqlj{)?AExޕŸXWa*ZPW9 z \ 7`௱7կYJ95{L٘LVf>`sWH6H&qm?ѽiJۅmz;`lj+.tW|+.]#`ۤ~Fxd IĆ>:^"`:@ `ÿȗl;Y|(;7v怕wM[WBHZtM&n~\b:|T ]T$'׵$ŋ4%Ɉ{`A5 0Wsf|U}bySϷY^X Bas%b2U\x+??*z>^U/mZ.Neꩥ V;e K L9FZ1ZU%6MHQ˜}Ipb7׶*YpD,NZeaisM9wܶɦ:) k D,l݅XG.P-uzcȸKˠj*:@bՅh ]t5!|x2e1xZʢ[CEb𼩞xȸT 6{ܸDX^?m;R gy'H4@}~Dh%cŁ)Fke*߹ :4 A@H_/ϷI)c\z]1?$E~/[ũŪ/cf?kn( 4sl@C\Ґ6Idt;5m6xvQcs e-fK "oDe1.E(1%ÕN8'ZZiIUUoφT}Ray`0;;PVQ&BYOSeڼ:nI."ntf[pР[Megg\QZ75h%91o9".բ ko.{ g'7REZqsAlg<=FV ^fuy=euDR[Q3WKTt) 8cGPTdii[+ҘdgO#3ia.C2+-ߺ9) A:43hL@f'0Pל>Ma6qthq Z [+ ߬3@D߾Tyth$!vwD"ǪgQlfsU q3:QЎ:Sǃǜd^lg0})&Mŷ">X<ȱ>00Nx44<)J4ּm4i/,+A}Ш"R2L|c[7߄?~m6𣧇hU V:%ax/|~ { <9<8Z =)' Nr[chcnbdPd~2R7ՂXRЙ_;kכ$~Cu:`C05*OT$êiEJeCw17ތudEIߌv+ * >Gڋ3֍Oby|*vJ+E%i4AmrE0Ǖ]gjdʮo9l5sjq0I>x bhcS(*s,݀ u-(H(9 T8?n4J?M%Ι*L u0=#P/ո/?#ٳd"OM$2`W|Pnܓצkn?|Lh9$ɱ1Fݙ1ca|%sm9yFta[%ѽƼ,_*bk^OwW)>Ͽ0=7lbmV U1l5Vk[ӌ?bXOwb|0^P3[I3b]Պ9M'Ȝjy6.+`{ [d\.F۲i۽?V|yS" ]Q䉐Jn~3Nl=5tfj:Ax+R>gzP]j/>1ǗHP Jt+RvM\#ʜ~EOZ{88;/ >e ܱ#ߞcO+~=tJx9 )ZdWMv*hn`$UZ{MXIɌuJԫEɎ&VAit~ܟy4e!z1&%PJ94EP5(&='hE0.F{BKtTޑMIFѫLU IPO2#ee>JO7; 6%, GEvt洰g2*R|eCXCHIOUb SkZp8%N?2?}>썛 6ZMdVJeËqk,ۏ$XDSU:ţxŀ*仴SÙw.h$mjzd,H)y3\galJ`/LkFEP"J>-'6"JÛu+g4(0ob.Zㆨ oOpaGޏX~!e-=W);l13 +H͇)<_iq=[ڲL)?0w^ٴ (MZ1y2^KIkwsyJ;7(*֏6r(ӾL0zZƂQ7Uؕ3/s|^~4J䛵L%))N}L.!*E9c}"q*b2{VQa7)5btƈnCsJJ{AgģB'~SEh ibnegoX/E5ґ0:ylkV.yӒqCx0}2DFg> ݘaQ[+'T\ r5 rEe,sQ19e ? OM@7xYkdb+q"sݿG1͖~4=kKðӨPQn!Ld܂F kE!UUo7n[B ! irPQ1ϼb<>DDn$>oTӉ5Ob#(U)q1;9FSaWU,'}(TE<q8!;Ǝ\`=[R3U-5,X˘*|vKE,HfP1UOv]Un FJ|yg!CHgO H.SՉ{w gTsBjcffړϗƟhH+;ؑ*61o{ex{dΦ+P}L|Aw:S:-cqN(b"MOIW>nqzºGpfKF]#2.Hj+xLgj* H8 ӹ$b4C\1DŽzlw *qN 6$qHR$)K& :IYI#U_ulB/z ]t=;@Q;On3=~ FR6=+!QjU#bٝ""ZQ}1!i ߋS-aOmIu\\­IOԖ?01] ZM](rD@CcuT:8#!-tkc$M9Jfi08z_VzMa$60~I-D"bgi]>WVZp͡i2u!G1`1> l q.`v[Lbݪڪ!F{]";/ %ciđYm3a,&*A[i}c@i, 3ڗʣ K_5K@ AF%(wL)"*da ?=(/I\$`!\|/U/=80~%PYwVsQi2cw7Ǔ2{ǘfmYGr!b~4Ƕl<=)ђ6mU/t5L:VI)& (Qnn8/mt.±7y6hkP̍ѵ T_p0?r!r8&|Ncҳ̌C9jnpIDƬIb2r!6r'.汷J>X(1[>o~<ܭobANѷ ̖λ9Ʒy""F||O~?bK$ 2= nERc﷮n*;itp͏5)bu cʾiNf|^2u݇'Lp!Z` TFSSOLt۫& OuU}`qlgIA5I*YK#)[i<5$u:fy~i";:n`6Nw&G XZV׾Z1:y0"`2'oWX3 #D/R鰦0IK8qy⢒܇xX p#&6}AI$nXCnPF*0,j]I)J~ AL]/7v:FK1d =e/]`bk4Mۏn!J!gА4r,.|9u[s)68(_B41 %_RԢ&drq`J<#VD_0T0\L9D/g-zONûwp9>HcKA_vOj+FV }I8o'K !EG'(];1LH0Eox4\J/9M1C% 잚e;xW"ET"{jqmp9zvaU򂬢eO.W:eKP'n gm]:;Эmt(?q5;"I;CEO0{n?t<6(gfh~j00x,5)Rk7m`ە\9R"\YZZh8Qxf 6S44#2:=<)jye"W wQAՃLSDC9)#?9B@D*ԧeTJj侻:,h=ڀpbi_kA$N$DW}K(=UghinbxmSR#0-2"Ll`"a &+vU׼Q&j3QLO&${hI<`#d@Fmep8¨M3hCn870P%TkB,faf+8rP|_ Cz7(JY7TjBwCGRj0 َv2,!{q&<͎ZX^i\{(-:DÎT9Q$[v125I)_&aR(t&x,LCx1 z1Quv'3h_K}zQMȬCI{%t{Քɥ GSO# T"Uۈ;ph;|X. {" 畽ͰᰶN#[xk;ʔޕz-H(!=%J?sHp@G&MXd^Zd@ܑ y<EEFs|CKΠ*l_H;hM򌓫:NS,vz0RkmɃJBo/C{p)Y3@(Am;=Y``4Dl \̺霎Lm-iA-??6y<)0WK Ngzs±e+jZQ%Lbh[e{」Cn#]Y]ckN nx?RA8nw9=gabD}Tb2Dd#+0PK<s7%*mT Ǩ j\N.f|1_6ToA`#L2&RQܵeG). 3=Ц)CX8[B|MYT0HTFLjl6H]S2T;^ aC:R9;mu(N .fw XNf4{Lb6\9\V7BGc$*#.fYP/UI*֫y+ZcAWf?_ĊO o n}Ô,2?î]`]1SDKs(IM4j`^t$󿤌wù))Aclثl}S lDkEkd;DB$d8=3…p%'{>n7|z~RsXaG{2v]IY)))Sj7Ă.AJRtіze(`峳tR9ԯFd hڈlU.5]L/n>t[Dht3]MXVC;GkWĖgn:g)_X| YP*UZMzEѪ:Tͭg&-us@ᴑԈbGZAMc>lcFJ`2il6 $ XKc~!fHBv'qTQF q~YUNa篅Ϊ`ߵȚbwKZƟvת$&I!'7!GpǒWӣJ @#~ Yq}f?1<J\ fc]y/YKVNow.̻D:3TUQCiG HYm ? mn1\Bf9 G3^pչLQ,wE )N'a'狛zŞo$wM0Uv ̔94#OܓN@3h,N\'AQ`_BF?$j3]n(rTҩ1`m@۩Mb\cd0P("7=V*Wuvŭ--3wH3 $OdL0ME#\"T񏉎 v.WM$  qrNp vd ɲ{Pr=RDXf>e.w[\^7ޒfgvf:;c`X&kPBEPjnfݰM"Z/: {q(SqJfRP؟r\~=Rz7F1@0!t!SQ4XazhQntXs>6_3Bj TG\[җsZq8OrLT' oi7Ѽ)A5(ɤLEg^¿u1%7ۓ%*?s4ۂ@ʩddma^u՟z]ey/ƮSHK܁T!T!oč$p| ?ZqDRU)' gvşN/ix.t^ hlؾ'?*L7Xww5|U$RzMtq"W.҃[ ڷcTa+X|skgqOb7[:٢?z?ua>8$l^kA3ZT匸{AI6Z}"]V`q#qv9s2zzBzœMD(30UNR%H.|RQs\* ZiB ^ZZ,=ȹXcoLǟcIO{W*ʷ&ޚV|V~eCusi=0 {AKֶ,\*! f}es@lm{Ĉ\moGD}>:)v'oVRyG<\yTj%zf^}&l穀j[S/H`+q hp\tK1&^}vE`}97j/M q%MEDX8E7]Htx ]}d>(:4q<qn"bdѳd3Z@=$BJ"3"~/{`8 lʺ%j{Zʿ \5`' UPe>aAqG>~HY]MPQcx=#XɘM!Jo"-,^D׌́@C?4@kv҂p>c>%b2Vb/+>!Nw==+@I$\؛VQY&t;0}@muىހ)`U+Q+t S(0j΁EDuBP>̎[H?3Z c:@ i/CI|jcqMd u DTIOlGL[%]"bZ/(.xUyv*uATR"4d1sn4D kDf(M.P`5s!c$!~^OYSw(a#(.֚8C/=Ņ}N@Lg9Vk< zq@gS ؜^EڛF?CtD:T>m,1&u h {Vc}xxrf4E~U0":9'O )*$ BJ m#cG.&3yJ3;7m6sީ s Oh SͪןQ_>L|5Q%/I)|@* B /p=hfjfNdeJbmfʓ & IYsOA Sx_^+:Ud}H~LjT _f)Bhǯӧ eWl6:y7 Bװ?'=;{$Sʇ%<tTژ7>1Ϸ'RѕgyN=4GU¶L+!IݿPf;MU /U,4o|xj \-;Kd&puFfVF}Q:cIJNt>88*H,KG,8QM[+Ѐ12>pi_ %&(oJ~jaḤh>sE7`<0a.} -2Ng^G  ׀f ܣ3N)8Ǡ-?!{u B,-nN`]Z:{\3`Ix ށ|S 1~CiUk ֙6g{nbK ƀ!Sj{䎪V^ʄglx&PHwa1׏5I'!?BO 2ac'L}!(s(8#$ oljAΝiej?0x96dA1?7yO3"M0GJ`2ˬ 9huWRQ'4#2'Ȱyy'HWr`t'a2lD~:~QᲷi{W;T XW#OOsG@p<6Kl!; d0./);2"V=\XCpնQAéys I>:ccʱU3llfq&~ˆ|žX2 ^*Hd"X h;nw+Lx_]p%8!ˠ~\b`%v}{A٢I$-sBvPaw~~]:G>N˫196Symw-?64g 8V;[}{~\_] o)Pu+,NBcS/=AǼ$@˳G$s+j?}ޯ,yGC\ ?><&TIӧ}(6%Ɂt+%ZI EQdŢN0g+Љhox ԤYYg~5x,TC$}Fp2NAd}9}!&`ڷ#GjZa%8t *?S2:vkD2ܽ l儉pD*M>9S hN)a:=_J$_8d7SkېB|Oԏ]_}3Yw.tN?M*Nh~s{P>A@T&4 Wh (.,lNtDӤͱRK~]a^6O:l#@ u8m|F{Dw>ܖO[ԸU? _#߼LmrSϪ-+t,?CM`dwh@c57sWagPߵ.06u!\٧j;Fni:FN{ndA4X}@WW+Ӻ]Pҡ5uW X}qW=4~p5 /3uBJo*}2\,\j=嬌 dTZSdխݥDMXjJR4Њ (J՚J0C+)L1j4hTdieeԖG.N{TeddڴTT6ԁRiu_jҰY^S/".2Bub2z˫}ؔExsڥ(j+ Aks3@ױOau"Q㳄/%N:#F+R 1ѳ0 L2Q)`Po*ˏ t\ޚ3Ŧ4iLL_۠Y|% F[{0Q2)VɅ5f;=im 1R TGR+hCFH-Qͬ4alaDdth4CZk _p w }ʰnvȲ7xN&'+ 2X{y8H%G,M*)0̽z PhE=Pz(#ow"mmk/ &g!t׎V6ϧ> @b:rŢ@<#,0(<Ċ HKij g涕yaqþpxA*~tV 2vG{m?X+5~^ $zI'Ґ^X`"_cBRRQUz}7v͋$v-KP[2B ?x=Ǎ+ Aݱ Jr,%ħ)pٟϙr1-RsS_ e1r?1gw cW/ s<Q}k*ǠywwziL>}RQDbS@ a3m]&,e.(?.ahpA[6]UEGN8w59CٟjsKRkDCEWzt@.v 3֋@U(}î+ 6pQ',W3.q\ - "B=eIƾT,h>-(Y'_iGp@J'&bu"7 ;S< , M'qӫ ߐ&a;l 8:a^OSgg/_ڬx> g\X?6O+-d:oܣO>af(n둻yb=c wu'yY%qH4t}}l 6)/M̌/P IծKr*01sӕOnE Z|6_ 7A ɹDhvgWrm7=!m- 5@''ۿI\i? k37%E[GPHNj3CLt9ן~ uR,@@Av.J=B('.'>I+Da`c3œRA[Yd"HV'Pp%g ӢNvƅPOG$|&[Qei)SN&(~JNW læI[M}yF9 B TTi kݸ2-Ä _+Tp}婰s0+@uH< X0?R)A<1&;Q|{&Σڧn-æ@rT%U3glMqV/ucI"`I ,Mok0 Dq#7'rq6a|{Rd0{21zQu _m=A@ָA$L<3=QmR(sm)_2ϊ92}( =[| *q&~dL3i!Q:S|"p)m0Q"(m{ڒvw +`qEܸ^Sļ7!¯PS=Bw`,Rt_>``e'W=/z2h;fy7vH[YA3B k\~h$,4HG!#o/o -:cAp8p%v|%\^l,o%],dVooo&9 4,VEf5 @]j3LZK~G/A}`x.Wm(5I1E+X"agHS`?7x6вͤ}L}"R o=έdp ;Lb S+p=gii=zjfnwrO_ďρwYN8_ Q*9NՆOt˒ہ2bob*f ;dvy{">{m!g{&xYcu K~MB2ԳLz_43}9YK^US9W\S\VWE.%d@|@ Qوu6ZR/J؎P^^ Z/rvf#{LBWۙ%^$#gf]CWAC .x i E]dY 8Ous)It\0QkfUIK b*꤯>\m%v9Y0;4eP4p]cH*LybJon󨺜 9&$G ZBA^:3|,&A7[dl Jtr">s|Yrijr[>G< ,jJKp wxh\;)e4)PөDK$LaˌddS(_w-x^E! W)E %\u,]1׉,H\I3<5򣯘Een?EK3 _~-9ձ]c|rkr1?wPϸ wGAWyR"m@ 7Fc|G}7Hz ,5e$!5O˾lޟ5##Kʗ[o_GDC!-z%qӸ85t(7Ps5p`rt38O/}39ywc#V9ʻQX|.:~zT7 ƻΖ>5%ij߶\9mG>n_pz/[xyg5O*4 xPwFݵ/>}Rko*Q!3`ױt/i).L ]}s{dsC9dC9 ;Zr5{1k\y>{YL~J =;rK޲oI9UrAȾ _:y9~;[pN#x]MzFePl ~d$>.ٿE#-:$13 R88frW#KexQ7a/׫]mYU38+R:H@dP03ŵxk]Pe2+7+< 'S$,Vys;8%kz!Zd2pdOMa)BD!Af4pԖ0I9W鞹/SđvU,\XFʡ.ۊV GZ_- 9|&';t : u-lQ|M:A xQ#; c'P .`:ļ}|'2p%h8"EP#x7pшBy7??c ]z[IC1u? []F0_Y(VoL NqxV=!lٵ?sLG7B>f i Ї/u`O pdrүrIX\ RF$Nj#(GG Mj')ڡ ;103ԨnUXEF.{k]C6v%e"}^*rIlҵB>+:T JD6C7@/tPB>!/u;x7Ah<97 I4 | 1oDyjTᒓgk`ۘTQ,.eRCqte {^*_ #,[3X7"ZP_YZ~J.xq\=-̩8# jC+FrO^؍'IZ W&UA_+Dr-dMnO)g&NH{ p@V~^ xtܱ1$E0jxQGF< }xjNBFU:(: [(N4wYp= ]ḝcCƔbfZ& ܓ #A}g~HJg$ZՀz(F r޻hݭVwZ2U5*υ8Vu՘ސf$Z~d&D7jzFD_â(kϥ_`>ǐF ٹAԴQ{%SJO1X6]m9Tr<^ WvN"^PlYD ^BwWjMV͐Tv\5IR`U>,w}LLpїH9IW9lؘ}1a2kF,'%3{A ǙNOzo\rX"|>T; |^q؎vДlp8~I)Cf~-NF?9Ol$G41 $i^s,OT T",CQQ)0ӼBI^{ZP12Xi+J˩!Ci^Y^Ueeܲ:Jj. A1ELഀmrAC7[8Y|: U`gy01d[J4Ie Y:b{Sro*miSh AGgOgK)ODc -qLL.E"Oq F'< z{QRd{WlGu$Ke;P񍮗UָԠ@]aA5#ư͝ K &ab qOC<-WU|\ⷀmN?^[Vۑ&l^EsŖ" v* 3K9"{zS%_I~io;gncͲPQ$ZA௮KOSa {PPF皜փ-E9{as )0߇M6ٸ'79=>]jR_g]P ^[Rt}갮xDJU6)={fzg3#S[3@ I T,\om l}D˵ I13W#Hn3O7Dd;LWO) ۿFT F+3-1Yk:l~YjF,N"JNhEq|Rs_p)do?c7Yw66[o| ^<wgt׾<9݊<;7ىw*þDz<::N|}ʏ<>ɹ@]!OҐ5Zgۋ@,qK{I3eI!m*C+gFÉcD,,BQ^[>* n YO}?%SsDõ(NۣW4!az;8Xځ)bysw8p8*4] 0nw!k~p/emا\sNLj7K/!N"V(Ƞ YjkyJ%3<Ech|'}-JN71/?XeDꈽa\b 8 AլJBIr5ABwUfNrcazdY X $p͆49r!c@m uLCU1Wevң!MaЍohƇºowR e}WlhN<NCܝ4VM旅C;K;*h9V"B" hZ l>ʤxjң J cXY荍.yپI߂z.,Tz)_(~bИx+0qeWN oO秧wy]=ɝ8b"%"eMCrܴH5´L0W+)jVgK~dF#.]]Um[&=iYx=))I{@e.Y\P E0 MN Z;cU;c+ms#طr1bV)Oc>NtL)2'EܧGB ۝&^5 ׎/8{ƶb湛U%,j0..n&O}Yi%^DA Io߭;Ο!ۏ~Rh5Złb,#OS!ݜ8Ӳ6&oA+NN֟x:.R4$}vO=E){5CJIbiWǺbpg6naH0J AJ/I~nc|`ʄ6]b n枪+V]DԮ;OeR*1$a•vYcYKK(Q~ kl7HN&\GF~nJG 9Vji^SWkٖWY{l^~EϟW ԀmUg'J5hFSZ%J{TW 9hD9 d9l*UVc(-g#mF|)ژܿt~^'vOr ^ o =hȟ^@N:R̞:J+fWlx~ 8kן/o; xN@X>RAN4sJ^p\ ?eBPؙY4rR,*Ȯ.*iӕG=a021=ybڷ MKɌgdAFy|'y\/k#u7s`G 5y+Lئ5N58GV{(Qp4WߘXgA~7ŷ wÂN;Khib{nrZTJ`h +71D;W o ɻ?[&PK# -E60a⡊1^(u?ƞ`">YEm]DA\gK xCH *"GMi-2Iv:X^o\ z1"R[תBo8dsuF݁3`~Y>Eu!/)mc~ \#^νp59Tc\($ jGXmnJ2(~9-#e_ Ce˨*׵aIt(twtwwtwt-!-- - Rk1g]qakR[v*ܹbTyjh[ z; `lRB)LQ9@7\enx.}.ؤa*+Léj$/9mNY< y6MXA]$b9dK d5KaWljZi58Ma+ ]ˎJvb6dy24T]*DȫH]x{оdM"pz*+{Msԩ"0U48Y(9s n5UPN?@NVG0icl/_5ĺoď֩J3 E͡93;@MewPeԣ7'J ©s@UlAwΩTÃ?>|2^ `9$r`])_y&^*';Q7S! Scx]E,FnfHL(kV`Uӟ^.Z9!&M8C`+e]QP K1pad 9^L5,G\')آzzLi`f/ÄrtӣCC2鸌K >7'%MB䒮rxyd75KiUbG{Y4b#z uj a^P}ɶFyfɣܕI{a9:_h2~+ GyL|^SS>}-6Dт,5{bI䢷Pa`pK[eNNL#F+QK>>WbO6E~SND^58`_ 3|r!)V@5^Dy).@قmV*^ٴUL˃zӆ +NfT|&-_$uW|bEw1)QH)Jx}h%]/Ô dgifΠ,K(~ }:J<"Qŕ}'FQBu>zC@ܳ g2+_/wRs5r{/{IjOjKS_^8>\{ф+ (rmo€ RqM^"ը=S̚*=Ӛ],:I4Y"uѴ}ʞSCYѳQZ.e܂ř/&kf Y[jJ0ۗj6·x/{.\% P4"كLҪ6d؍\u4:aAz4ܔSJ%s-m2$Ui+}S^ 8N/Iqpe'Q5QeZȺP A$>Υn,D[I˚H^&q(bGr"ΊeT5eW~cձNTQUJ`6B =.a31{E_Q;2wo*K$Gf<Cl8*406ւ LZ,#'H"[ݭZBpܪ -ؓQ?֞}ɎL[h(д9UgeAN Zxy7ޭ"@]YS,mL65>(D)b\Gև:E"M˔/#(=C&ٯI.?D}/21&b,O5b DSbGFijܨIZ 36Z&By?5>{0xXxw&VhGWԈ0x VA'4 2"w._3vWr_Bg;e Ԗi^Ccس]VqfGHm *@%74 u'* eaQrR)vd+r22l囃TO־F3d9Sc .?% cZ&ˡdYmۦ L8=ɷ}d}M/q-Iޛf'B1ZO 5R2߱<;3M;S!c:9[1%%ވn2OGv-ۅ4.K*HLlw:zp϶E7v6 PnGC)֙^3DmVUܑ>H׳{_),շ 󷺊10QEF{.x|w,L!K} @+bl6_z0sٽن&;Y L@E24lE Ǟp:_B i?ʼn(ݵn>I@Cq9;}-LCہ=eգ;#l,V٧g^(O_(I=ֹ`)Бy˼'?tC51cZF0|E<ޙ GB8;DŽ87o놤8B٧*&(0UnӍID1+U*7 ;m8Ӄ[ܓP-^ďOOkM;z;sNFAc(ˈ#}h8L[j'b|é((v]^f+)Դ^ $֟YdT|n~w8'Q$Y'X1"jِJBǤ龁nb@ 07tĻ`Nh轨G|Dlf`nVXsA9VuYE.)4%1'v\,lG&fo xn"qJ PoVְQ̀fv!>(kѣۃ1<NR6[@{%Xζ񇓤D٠U@x$hQk#gvx{4u |@c)18M PA^8ᗰBWvUE 5|L^T6fV l]&$]ss͐x$~O'42.4)|b3]QD`xt., gy׺ܿ;.r6;\AR"+ FOKa_/_]cf_ 8Y("A!0wK#0IFx-g۝(]a8sp1 ^D; $ː%xy$I9<<n˰g2h{"D-| Fr95팘@zUpI׸"J;%mף Ond *4GӜDc8lN?#ns_.p9;K?4C)Y^9(O0YK`(W" 86/C9f|I37Tlr BV|}eT۵lj~zLUP<[|=:?jhѭ)\/FljMXN:0ђV\| ٵR (6h×*xS@.\_zD;xmDT8W"n _`7S5춹.=]zk{cgN:s7BG~F~^ϢK׭1#*̿A~ܡNEtsz4i'QRנ|BDD,GC{#@oCiZ`q<OOoWM!lAkSչ@o DvNY|sQY_.J#p"8y <@>I8V\ +l8 xbx} FfSWs0&P#1Jle9?; q > _$zIǖj0oL*~P^-=Š_!O^x7v?QE7F L2SԀ>3rOn¯76{_^4ACgOT< 4LU%dDECve t(o10ǣ|,7?շeUR5"~ci3g(r[y;lLD?HR}1ݿeɀG:ŷ9q_Hb)?ġcnfjP`hBz!D*aO3V# 42PFnCn 0zz0m;QXDF_B 2v8c*{>2K<Qԥ|`<խX.7 ,_ftH ?cPzBW87 eS,hNt +&r돖l`YI$ sǧrQ7XYpH,9k΍p*G"~ǫB`q x9i[0[M^,↘FJn~srSF0pC8&JXƜ;l,"0%ߛ ȊOfYzԍЪ[5pR]`B~3U:rxΫH8@U: .ݸj(x{Gqp7AVVmeBCMǶi%7|jr62sjcƒ$)p +OhRhս F}X9aV[7I*]hO3TN\XrQЬMhVoeo &x -&deaՏ ބ䅤hԢ_c`VӅKɏ"~:Aw8S2z2ʸ_QP/KCUoz,E3nԦ0^&d1?sD]xQszQw>*`p5Ί2G sIj(#g-(OMw/[kK%s:;G`"7dįv}̔|-dSLt e6;Iʥ #?gz!憣l+6qڠf$lP>Uf#C|(:褂ޞD%=Hܰ~dչg 6Z U8=' 5x4.V`&|`p ̨JVԚl?(S%$czՋrKΐ1%Ϯ|B(O ènv%K2R`]yy"L7haаe$*,O٧'jIJVF{l=gs@__y ^Īd1zl51W_fF,Q 3n%7E98*b\ђ?`qT.PF{nQӴK;? H5(?? ?~6eB#]4>=Oϣ1ȗocg\7 #X#О3kzۙcL*fC k`NO#?J0Cu?re{4vm+QU ~»2&t5 s L ~pZܾɦS̲"JH)lCň4;R?M¼E- mMdt'8Qm0*(b)nBOzlBxv_*"2d|{ 9 _)]zs]*µP8> -6 TP[}zټ_+l JXC15E oH{ㅁX!I (ŻdefaC9ᰞ*zg٤\0'%?iB?Dt_s.|q((RRXO/XdL'bYGDs5d?YL8(!_+$2asea+Eez%8q 7%-{h-\IRI 8dY/S!٩ }N?5a5}γ3p_mlLw [F ܶ6M$_n-^K{q.I5_^(qlRlP|jXc̴d rfO\Niņ%%UMd ;UH9))_QzTӔ{b[-wqͥq9չ'df9.Fx)꾣Vl!.gR|HɦC/XRnKyaD>KT}Ka0|x]6 %epP 9ܝpWDAhhJT͘{G56=|3<<%lఀiU<Yە߶'lf{ǒդ_Wg ˩–9,N|-n^B In^WMUP|M~Ή;-Do^号̩[6-eXYv>Z5[4ѻR}*=+::{< \>.{->ҁW?h6?J+x3,/F7 X2/ A K[5$PcMnOJ.ْ5h:&HԍC XF5=#ypw<2O(g IےLfh/+a|~n1fD)Ht1=p5g=}--HR\WmEy!4,"btYbHۧtmעdžs?%N~ d߀!& `7\8B)3Q/̜i USf(f/>j@$- d\d)1@kXbr98Wfn 5".’$Ny('+";)'ΆGà\>Wt'Yv=iiwU29C]I:H$OMr vQe`hˁ:hJ@ւLv:EH%V8CLtv. 9k?GUw> {r[.<_"U&㛶)I}_\fpT*0L%oKP_^Ч |=H`BK͔W!/U,L^~2%Gf`7:4Zb *4fvېmz; 8^` ɸ`8vbH?A{B%[[w%!*㯩bϋmg؏O~< ?`?cdt!cB@6KGRҨT^kJLl m!w1Y }R&Pp@א6:9'LdO\0DyS 4?bV7k~u'p4C)iV&||O ?B}ANS4T/xtkzD7w`0Ԭb-& "?6b·]3_4">yP$tX\6O.ߐhŜ- 8-ߖ퓾3P (0OEevTw@6C>mҢwG(\XNOFh\CRY__.HD5ژh&@'2v~^P9-EBQ`сtb|ZY\O%D͟jȰ,D`F]}H C" ڽCRXX% U(տQ>||RUG>Qj{y`qo'j1-\} ¼(z{ nW Hj W/ht t /#~iC!)@"*4 O_Hf 0Q8x[kk[3@)mT%,F(sXy&N[5je8n؞'Ԣ`U$SK+re JNJ+R<&7h`rM{{V dY2=QP@y|~+0*œÔbZ$^O{d#@&!ogmT\Q8%BOM5}k!Lpl) 3o}U!e0" [#Üa~OGh| UhUFw拷71ZKo׵28XYK@@-_ýg_ #BlA|0-c=einF-#)x@qUa ƕ?d3u?HzC9y !N.Z ^a{6&Z?`e\QYAMAbEb>j+#Puz|NT0F/~!(H q9vu  f *(T+WWSi)~/$z?d~sFleBU(?ÁE7XKHbfc#RE~z!´5++<ZmĽἄ aW ;ݎ1 N{҈!a~?ŋw~e&%M *j@8sC`-S#_@@$.ٟ.fbɋ0e$I7eZz ,1u2?IB|l#oe@ $8B *~ mq@;ۢ+qhh?0r]ծo,0H"ݶd7mkbIY@6" ,Hm qY_p@6`*0.+ovdĆ猙Ǽ.x&Ta=fmw2pu~%?BeswFeeUr;JJ-hK?N|3XagOa ~o9XHfʗ?8(E|rdA՜ϛm1-ˣC=!%` ¹k׍6UQ̃bP HG;&Wi6 1y O>v,ʮp;0|Hf0l<9Xe*ŋW+ ʽA`V 1)K/5 ˰=H-A7mzB\lkbbn-<`eXf/q.큱h)i],܉kF8Ĩ#/Ϩc$NL|yLڙ/eCL!j1\dH:%̖\mj[#IUW"I7 =|CokУNp&y'-AKd3C|SR۝A7f42lh9B4ԟN[z=$'yZ;9ϙmrh1PN@ގ@!)m,c*5 'ݤ4ՁHwA?<] 53J YTqƓi|c;;"bo\3ͱj <LMס?v0IV飇b8*g\|bFE6~bQNW#^+v3К7Iw3W%3e`k`"o . t%{Uzgm@"05*6^{9u@dȱҝoCl> 6w~_a~Ɂn|^.snF4U(YtHT0V{|)tMz2zY(A~P&w)(kzFG55ZO[<ᅖaOmk/pm(Û~J2dm{oT?1HȎэˎIIv^ s{޸:ҞKIVRjJjg(u7$5A?H" "9p*B?}}Mnrk'>ͺ^ϮDP />>]#B;j,_4$9vċL ؝~g2QG WY]c^gN;ِEeWF )8SkQ,dGmb]bv?@ڡ8үtMntv>dZ䬑BckNxt<`.2SsB\~RfDaоHHqY}Z3gdp?\y"v?XHm0;! pXϣlT4̪_>dwk I' 抵"8y1x< ֢-/,'%na;zP`ŏfo׻e#':_ 4X褵ha g#Bg틴K!@suC h ]~t4PApjj2@A(Acq>J/ONvOގ¿K4|6 ٷ}.{ZW {#R+W`2COfV[ 70QY/J XVcGi7qvPP>_I$a"݅1̍=}$DU@Pb'n3#me'c  6Āߜ}`z5QC\ }c<otlkgzZs.K'$[ԅIL;qbSQ (O)[:`K_tWh2mS3U:dZgVriΗxYs5>%Ou|iV! 6|Fs;Oj!0Hv~5Ě~MODb6/8W³loN2SswXAO j-jTd;;T ئ;Tdd8fDs{em \% ޼q*@V8k٤ZڌV 8lQX>Kլ @ـ"#ǙZU&cILPkJZMX';ܶ]$vqMM gD"vb61gܥyx +pzp`"Zr_]mSF#̮,~Dضkie{뭠i/mW" wF H /נBK)Y?)g !Ogc5op}Q.1 G~ϗ}mK"!A<UہDVC݌_:uwqZ7QeHwxwKs{:1:oN?!^;ѼKM +&2x0L%iFZKǿPԬ"[E;p0Í%]nߠT˦ۓCҭ .i+@&p-zZfjDg9Hjtrw*oRYz7[6Ƽ y!$Hʖjҝ x& btldRW—peA-Z1s?ZNp:9E FzVmFaFO459}ѹ?QuM"{Yt-}IFAI[Ci4sRcJ=Qq%?[[37`v,$K :!V)/KI.-=?e i?oF,f+dhFZ_o%ȉQ7瓧ruavsX#/,ɐqZ1Y1Q້`z!FAUúf(J IK+ {xgMG 44+J~,iC WFk$e[m=}R.`>(b \"+Z-ǹp߈tEH8 v KDW8_N ՄM?M}Ckk=T$Z1J$36t$(k=KgbpYjTc2-Suj _6jo ?#cCFwլkȤhsy^vֆ+ ? NIzX104]%8twIG!"ۃ䡿*=/pj9M8 >:G *wM86FH\ak70%֖ߵؕOn~S+lm=%UżB`4byG,!ɸ@psbkXS}44/4osąR.w1~Vw*nU>/{v\C@X]!H2wx|O4w) *Gq Bʫ<ӛbSX(dDyϑܷ◶YNLYp{VU@+\ێ#ъvO>]uxnC<{+Y028 pntV|M ^A+j}hf@)DNesg}}YM٬~f$Z GO-JC顗Ɇ,Gw6=n Y\֢d1YCsz#CJaqny!+Φ(I= x l*g9Ӫ~;OGD-wʆ+Q\*H+Dֆإ&ח?{zMtr^h|UrBATrU29f<[j9))"˥bgi%cۜO3q55c,'~ `o94 nmWtt8b  9Ee&rG mzM) ti-:m֙ΰs $w-db v6ɿʹthyzu=pF!C?޹<RӪ q|<+A|O53b4zCv<]B]K_x/2Ug(gsza8kUu)J& A1Q)yBw 7Ў%HG gr-Q& Rf~ɹfذF[Жd}'@8yysqUADægjc̶bGq'IoyS5 %T`+'xn;QTUR8& A)2)y#۳z]m&l}S n<㌙H'Oܓtq[e3 %n9Dej%^&1QzPD?Zs.s!\!WGZ~g%Kʝw8gdWO-Z^K~R 43t?P'Z_r0S>zjע~fQӜ%>^>@5Zm(0/.NU`>#ҧ)iAE} 7 5aT9d6I8݃ LWB&>%qb\N&\"0(Hzg&h&*!t$~ȲsjWz. ,-L";|G ؅<6Rۺ'\6w+B9ʵb3.Ay)xk;yw>:TTTslZF]ЗU(ȴy+" nM]̧>JKwG/w)9}%\~z{ZwW({cQk[–Siyl_OovXJXtr7-7 \5J{ϨTLGV~GDw [qx=*p 6 %il+I?0sAhڧx''ST@\.̿DCNpQ7 {p!;$䐡j,F.h+AnHbk>?>+4wGD G@?:]\i>AUk3whoZΒ7'S\鉀vUڑBTS1")ч 7,B 7Z&J4)J\bR)=[?.֭S=ҟ?m|*Ç zcp`m%ѡֲg&l'Q垙?A/iVfai_xX`xrZY N-L!#ZI U]/3%nLYY흆oyxE9q9SXK< iLZeT>uSVD|8E4 Ԗjx21~dŔ]!T0Xj`&[U# fgkOG* > 1&nTijq -'l (b}kȄ`tAwT/00+e;~E]%V(- .hOzqo_{'a׼xٟ ϰ!r= vͅ ~^콓Aet'Lo:;7"٤J&&:4]AC_7N*on9P=N'hb"A”+nsTуbf{c`v8C 6exaib;y%Qs90Twb76Ev5݄:5_lWJ~; $h}NWӈU&)?Qᄏ*>|| b9sPTSϪ { QYT3%Sƥ& Bd8>j8c]?-zc%-urC vI9JNc'iVFB6r'1B,A0tOA.r*5 Mwx3oRML01nhBCP3LDt a4b(*I5M!swwr:.Rg{o؏O@h^cxi@ԊOɩ}}zۅ36)2owoyo5/-KՂdRp,9&`R`\ Pۋ2ȉ(s#8iTvs͑z2I em"`>,l& ^PD&8UETVXҥgFߐ.kIrwY8!x!C ; kA+Kӡ*qՋ\N֛Zb^Xh(83ʍ#45< ޿jrrk}`hJտ Fu̪񣥭9z`E@Xy"hd'󣣏oƋB~00 Ay? y1滙wn] m=+b ⻶|-i f`uS ό ɀ#k eeد5o>/5v=i_<vØ*ЋL %̩l9J!d@nIx+{NsC*IXlyy#aM4Kqg]/+E*NX[ˀ'wMLoSgzkF|,c貪0ߟ+^Y^ȃƴyd0 ys@S@fYa#y3~qg$2C~]3SEP$} ku#ԽdX 50ոoi՚}7\aq/vXLc0 2:J>!{uch @Jb=DDO,֟~ UKjv 1A+hOaBBH)U&߈H'+"j8pEo,]jM`Ko!l~)1lb|3`J?oF3aЌ { Q~_z,:O}uoVg>c ^VAޘ^(8awD{CJ5E_vh9`-/[s~_ 1NYdJ.lTqk}\ ׀p5O* P$@OU`dxrS3 h|㘭LT2obnvu;'HJ`+CC`Oꁹq4 Nh@gY^(˪3XMD&*fU&Wv_MAh Hľh󯄹[8ZSnUhZ4NAyA:bmVFF?侃 ߜ PGԾ En= c0Ĥ/|< ~Xǵ\4,Zo} $LThE-5`?l4)Ź1Ƽ2Ut?7 ?Co`~v.EA[`giN}MCYHMECKDnMFGm`z͞WXIFtY8:AY_pap>jB)[ t>uKvkDi% NsH.$ߡ{FP 1ɸ.Ȑ[i 9= LQ)`3< wI.9S=ǖF'a7\Vtw: VŻ3w']V-])ԚM`ЍXѲZt ' ̔UWfuy?K}?([0+nWk,&nQrM\,:ט]!F|jXy+6ԡρT(m!/ӮF1 PgczްoH~is/4[;yBԧ.JeÞM/j`N %n)o9UU #աzfǵUruid#`?=cC s?*%^;JH|xt240IS=i%3YK]Y ,O9dT`ļk/ :<% PApޟv=Xy\nYlC^ܜVTAQ I^6M#Bt&Yw}HgVe1 5Hur>V3i4~)X#7W)56#ɦp٘%;%FJdPT}I' T9A Xs6B(@T8@H5 E&2$"̈)n*",a:mgi_<%RmGgd,ȶJBѰq&Fx{H?htxf%ԉieOO+P%a^UOB}]y2`Gmom4;f1!Ge) !!LC]|8%Se7"33Ճb Td;fv&fXߩ𨊊Jr*6Fhia$-,?sT~Ay3F1+# fۇ y/[']bss̿b\/1zw XٰqWxg qg.Ya%M,eo5V`+8sF>›sҟs$z k55w@LMβ tſ@CY&t>Vn nD yzlXz(·n?I5Ɗ)3 ~uvKc2D=y.09jaҰ}M_QZ -GPndOQThk 5hYZc#We`@.ȲٹWܪ"zz44 F`o6@vOo.VDEm|;QJ[`mh0tERl_iN]ۯǿV_L{λN3[z\=' z8S) :Ş4h>y@LҢ! =˒ISNwG! bL#!$=xV!t+GZF8 [zCHAY6`WC RPQ3H0MOKW+U ۿg- aWhsxӜ/`63}y]i+j? " |Ǐ_?l&l(TԽM:  uc9r, {QiI<_"r4]6вۈX)?}?\_/M6пXΐɉpc'1;cM=/V>0szxɴ V_.! @#*0?L,07 g6ag;I}ӀN*MHW| &m) P q{^o֫a\8 ֕y\GOTPQeQ;LJc Eb sHg3+;2XVT<ֆ 'S;VT86+νސ1bg-,m/D(`/1vxA#/פ5v@~<+b:H`kE,:+x`\no(ǵx" 3sj m0ox%zo޳6~4ſs)Κ8"01AY;4L@/"C3 vbc5DW>fFyK Ip`Ryцt)va%obn ݞ+tko tAd#x,H_P2dL cu^XdaW ]zu]IFۛRgk,*`A әȥ< ;."f؏&O9U +9S @f`VJ,嵤 1^zpv BE68N:?^;YfOjb͊ G 1igȔ)1!@fMC h3l4+eV^usMI92_ #,?p| Sii&I-E|eNOsv<%a 6 7$Y@7Mń,2Cz?:^w&i^4M~쩛ף]MljNJll 'Cr|vR{ߺg }H;Řy w DŘT(;_nno~E"( xȍ[F1qh\QҩM^~ =$;w He]70 SqV |Yy#6H76ɵMA x_2<.>42T64әa%ø1Ѐ_8{&x>IK۪e'z yH~Q$Aeұ@I!*|Ԝ^-•Mvxin ͓JZ/F>I9Ss0as=IXMʕ'$] glTwwUp([w}Qe=noz`wH FBX5%_#4 FƺEag=/]/8@#j r{KjkU ^Nm й,YH{Dr#}:>6KFyɪ ,]r!=QUkѶ{<ŽڱoE*}a6;(KDEaa>B"ꯪ <ZA Xα7b4q t;̮dXUg-,WAl؅A) yUŏ0R{P!?<ʼn0*aæ`AOQ(aȜH  Di 1ՄU(*%Ӈ%zK}G7lE3=ܶ90EyKuhT{Bi%-7gr5<Õ?-TObkLr6UsZjdq,aݘP_04L^ԤE[ӤysaY?LF5XQRI̞|hg{Ez[)Iv5us'm ,Ư3K%W񶾛):2)ld}YF 'n1ՂkHtcK;@֕!TVڑ?ʬtS\H8Uf_K*ͩTzf~S/#1i9]:|Hd.#KҴnnRNC=uB `4;zY„Zڷ+~_R՟R$<_Yk @s|6jy՗Jʐ[ّL+ ]p NFA\CD4rNDlv'~{ =9| #]]vu洉{EWL݀׉8L_#wwt"[C1=NUØ7{VD/Ey~&}85tziM2C0J@Kڑ f!8b3fHm#LR7p!ϗ<i; %FS^}mL7~I"%8C=27*\U~g*o=.C+N+IK3|*2s*+ӗ0ȎvGImpC|;(M"2)c?<*Dsۊ%ˈO-.j5dN h/YKk.-gWNW~$fJb2ǯѠ?dy-6-2[w%V?'>GZcŭcr>gՔτ4(͖%Ce5_g.K_p΄[F`txL̑lK]cOTjaR5Xvr6y.tRLeݕq`0O^ks&ӗ[XfX۔X[7I;4M  "tcG'[*Tv5F%,>Syۋ!>\N+gKJu T-+cS޷m4NAI0,zd: 'C0-C }kDhFqF6|X{H:)YZCT2Z͗y(xe.'ou#[TM49ޙΝi9a rӋ݊:oqahVQ6޹t"ԲL q؄K ]v dpt?h Q1>#BpHrT Fxپ!g6l 2wW9۔jwrPց+6{DLUw5t̿Go'35:6X]&k": :`Dx͓ZGGMրCY ߝSB^7`z.yY6F'jնxÈ]?'uK`]Y۩}3,5Yױ;Q+.&R6ޯ>V®uKD$ʕK-ݦ o<גcOΠzQW7Tqd)mKLKY,eX`z;$hbr^xxLwպj2Ja2o#e*DSo. ,}gİ6P~ո`[%Rz}ohWtznPifzȾVck+PKh*R/Ԟo: PS |X0{o mB 1P'ɲe9li#`Y70w]{*)5*~U4Rd$B~ =&oD$*ڲ\X^^撰T*WaAyyXdqIƦ7XT&u#uo2 w(<( kSWmpURqM?KHJ$IUTz#Q3o $R*՜|`\j^UFM(^$.)$oR]!"Q63{ʗ5C]fJ~~ Q|nFMlNIs;p3[IŠH} jVӯXծtWb[&i %4meg5B.q l)\j-*ɇ@[ jgUH+OkT9ǯN'`$;[VK&C@ ͎Fłc\cS~}e ~4:MýaĹn$%ҋ`Ca}FD_9[n1?/آ|型ͤ>$@.>tՋC~j[-?(jN,%TA}77_L2tWQyY|"3Vy46QTg_摛AAQ{%ן7h=j{Yd%'yWX2;貊DcWG Dݥ[NS4NsrRa;f~ELl+ >n׾'60{w|*U}' 籈B)lDeDe$V 6--WASD@NYS]A|?jjoTtpq} ӼLZOǕr6AJ 6[}fN4>QUi Aw߿V7䎽ajtw }gLQ#^F ?K,yD/B-:PP qnC o Og;X؄DjhFJ61ى$T[:w+>PSTV '9A.&Vҹ/baBA>`,gpUgɵwG"``z8wu0 )0\"}(C:{Z=P cbD}*vGjJPtI;#)S9|Aa}<|M2i;K:c$R;,l|Y#Sv_ hEqp; \8u_JʚEz%=; ?<v| Xr?)Ud/^xd\x~+'C[Hq {i=abBJN^"B^LOa#`;'$7}TF \wŽAQ遉&D500 ;Τ-&.@j29ޚ I +CVdim.U7R#PhԽSHFmO&avFѴa F>(+(jO1!F c9Nz:rj! ً0Lf3xsx)?M L&bѷ"Kn .f(x#C|3ʢ%LhY&xMaRX`hauHA}8D~%tSdG W_tx3K)q9+ed0ny9;YٳVΆV0:vͰĺp&.Ip01-{P_0ՕP蟊ʤsS{!x[FI׌}>ΤF*Iyg5Zxuo苧JT7;S\k#F~w M͊i&GSzK܌ &YeQK?ˢ^s67'h[廛A.D\?;ŵ!c&Ne?Qџ~{5 gbA ;ٛIO87Jۭ#d5x|VN7 6ȡSdlYZ?u}(V^rawxN,sf '7K6scz7Yn Mu8:[bYG'wՅY10; NVB.v6d{&2GRZI}`1ҟ0Xs+r  t7oU>sNiO\q됴~,".ƟOee^HWc )Y*37VkroCŁvsM3黃~Ϗ^;;[Y24f8N,wD4䚴TG@_?s9HF_ eǟIґhV#Bz°Qp)Wy0`?>֐D~H#v?BAyUn+a[z3]ٔ7rА'DT_O€ZݨSNf!eaM0Ői'7'LFgʽzPeE|]g?׼*'߻M. 3N K0+;yz7'bX.l:%:tO&ۿknggd$pT8T 9#{iN\~akɮ|H$Z dy0ѠR F+ojEK|fLZi%\^Q] ήVv4c :^>lYCJi[~AÜe AV'ChfhC)OA.M9 uIߪb]~Wvy5&< 1T*e͙z&iRD]+|/:U:(޺FcEeob 9X&>lW B#|+=:G5yWBD j׈rC t41*Ee#-гyCƩL?>wf؇tмh'j3;*`H] [*-ӽvQ3& ɺ6GZ`&_|F BJ_e(E4M6=?B`CL'XnXtUunrC*JGq@xӔH8]Ճ* Ǚutbۛ9r)wD/ARdWw٨+EKV&& Uv@+:g[L2+զU:1dmŘ8Ve!=a><;-2޸ۦrty(v}L\t[e|ٽd"z-7YbcP#~Yu+ry1"+5CFVQ  #J*2"pұ‚+UEى}jSBf,;6ܕM=_m}u:w%D]'"<~rfofڍJn( f_8Áɮ1\(\,k3GIcR' { 0J 촅c{1j!Z 4@zHИ_5R /F+s "n@+zb'eYSOOlV@_&HnkRhae_+LG﵅( m10@r;~UR󦲍y9XTa+& Q,}_淎¸CZ bpjbjv-.-@)..lhVAB.1.]I#6eR HާX;cP赖< " `VGdb;\Ԑ=T:y,=noB v `0zћ ]56]wcWxӖr4*KG%2hcCz7;8=I >&%f+}$Tg0nɟ<J W:wvC?kK&_9k[$B~8m"FVh(>Š^um.g@Hp,b]?q-3ɴa`* #r&61~?)X[ϸGAA/⧖} GQ11./7&#[y:=ۿ 0FtvLa"FdHRRFa1Hc ǵ"zen(3!:\V"\J.S"^<9}ϳиnMaJɵnZ+z3Jz}Ӆ๸WWK, Ƴ~qQu>oV{PYttJ>C5ţ\b[y[˜(X8V@c䌢BR!M6b%LgG=w_1c-C⫲+& W#_'.  LdZ2h'|A:ҵ4%^_ Yћ2n~Y%}sx1Ppablܵ-PEDfDF>>.#6ooq}%)Elw\)Akx ʮSe/{4w@T~2{F<^jphɦhb+U( vuKGUwIU׃Ru:^F8 )m3E Lk`)lR+եHg1s !~^ajIe0HxvnuN'ذA-E5V@kL.tԋA>ɹvjyYb#_?XD˪^Spme@NsF짊)+{sr&6P MAN/'H 3>sl^T7{B$^B1t.h [d $ s-NC<VI~,*/vPH +|C}ckxc bd{ ,a49M,bs[ [$t~`/\ g]`*vB[aKվ2gj\KBNWEVt@.,V+>²ʅ\JPxǺAH9IKCM*u@7Š cX{#-qv<7tbIұ#YoŹu{i4*._>dVOӤaUn:h3Pb;v'-.zAvmAO_XħtN\`MݸjybJP[큣jw{.!=|#5Q#ҽKwzR{o=I}I<o6^\~s@0@ ~reP݂A,j 5SBߝhW)z^OoZd84t Mk V= "q68`= ey|gHC?&/c=R7c]t'9œYK))F/$GUM ~-*3/VzyA%o*.W~Yu ٞs沅>:,/P?i%/saU=korUr;/c\xOgߘ$<_]QpzfhO>!jojD5q7 EYk]C-xzaOWf˘*jxR#1Kx+O^G[ŧ{{Ǝ[f9ܞz'x{ ?rJXkGF㱘4yZO$etirCn;]Rwy}sIbe3κ= w3Oh߅rcy.Ӹb2a_{6A&5;ދ(Ü -&˄C7BMvify-pzeKY~*YnKI'Sl_{m>+>Y|ZR10iWj$8=+u G.e\p3mGWY[8#Sԫ2m)p͍sVL 8})6=9=tAB[31Hd޷x^ڟ7΂K]kK^PKCT|Hgpgsigned/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idxUT sbsbux _0 @<0Ƭ#ÎM?߱ /7v}-W'=z|ִ3=Gu `\; 2/KKLԍb5:kxzl݉7z1?a=4kںŞy?Vx+`WƤ6Ũim`Jg=.'X300.W@< Nxg¥ޮSٵ%/u sIι|u5)L;o-PKCT".@Hgpgsigned/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idxUT sbsbux  STkl۶mʶke۶m۶m[{\x5*@ PpX8$Tt Ll\< ڀD$4N`@'='?@0@$@"@ ``` |p t@ `@} >@v j&6./@H4@<@*@@9 5M;n YX88t:A>@4b04*@ohl PP  p;YyΠ.3֠>0 ;v4an F@x`|d[Be`em^0-V[$`o0^wl "8-tkn38[Ap 4 58.w|+! ~xB![C:!cBN(:)>8$t{L8G~s* |Y-Js1YY\  V@)#Voˠz֡x5H69T@4yxMgfWt>sQ .UcWȗ6o6,Op-zzۍa8Uy{<榋v(hѾb( 8$(oCb`6JL|a-n0S6/~"A'.Ҋ!/Ys<тFWV(LMfًj BxwjΧ6P>BWBAv/VPr"s`JP&jE "Sxe za2D@n+\_ ]B1Bչ,d$*+{<]|i~8G;W'SF*=8_?lZX<~uHu-LBІmh!O!#Lτzge,Y0vx}fBad|*L cb%h"?:zUc]CSuZ $| x<|:(:UIOYeV%ǡ+2]O?]4vP΄@Ey11XPIہ*6I;KH^FCJOi]gX=h5{;g#Et+=vc+F[ gK_/dLZi:XU\`\őUp#9z0 tH,혐.XTt'*J|Qr=A tLAf8%tk^4CY=PGQ{ăڷAH>q\Vo8BdkXȼTiO 5BxW}atzl e \;S(];8k\}YZsdHR\Fnwf),r]lл上1,Eu"n*x7j;Ͱ_])F;!TpQ*j3,Bv۱V,E`1>3{42BEJrsQ'OZe;F58Pex3\xxL+D{H$2F:lLo"HJĿʯ{]X12AhBMh>VU78lp;Ux'uOzi' o>Á@;/HBud[Tjh@`vxz}zXꚌGS9ii'\4C~sI@yj-=uWcYl_ zjN9h B/HQ}2I!]";y >Wӯ-|7NHy.Ii6\bE^{{+p'g*P5 KT޶'Qo6O`OF kkR)ufV~9n!e 2,ljʭ62a1yc_csHd%rGvg/wok[HȜJ vʵfu6Vc3T M"nArN;!Ni"wemĭwD#;+yiX< T}uԄ%!1  ɂˆJYR"ӂ+t%1{Rm2jn:)rQh-ЎؠHCuP X9M7O93̣$ gGRV4ʶ_ !PJqﬠwoOl|ΪJ39 9׻M7@slnѽ)۔N9Uod ^EML#Q2AQ5+B3˦ΦV=vzu9g0՝ !cٛnt?4^pFHW32H h9|(EЁ"1z^C(79x()T@b"˘eT~=E gf*IEy 1BWti:M/; [툧uEݮ$tM4K/ڐ'tsCHT) XZך'1*o0./䗁gHG߾Gt{_g}40̥"ǂ:NYp:x1蟸Q1._qV0Z-0V<QxZډ1/3gW12n}un S_N0r,C3H*b5G0JZVI1Z]H˻>ao@Ɇ`xG@п:UattWXhKaw& n(u a"߼,[롆5Bb!qOe4\\0TMMI@=J"ޖ&4o.M*_j-44`QCC!=!4] JI6ih_o0O^?XW*Lp<+d+y]{$?ݐEmu^G|#2WΊHM)"YeԲȡ Z| ^.sWNUG})u{zg( kx.ƃo)gY6r{˷ C/%wƴ멥J9nW?Fwz#bv J[&a{4]n檫SaA_\ ½YiΔTW0H S#oK7@/"30[[|S|1s [?4J;YrD.at,1o; x~oXHb>[z" IE{)a{dHWNH?y>}B3qg:{&TVK=cBlD;4>:yݔ ~YT y)\;$볬Lר_cg@'tH%yĜ$vp,m֓^հlD!J]3d5$I(=[ةuG+'uC3V{]V-;8Eplif\K? ϷENd\6?yaå[L+GúKB`_~OvEe^mo dlwx[}M &!waM_S) ۑ"A%9W P5 f'1Xh28$ vq\h<5zqR: }G=?b5?J`\-ܙCiqp7mf**2yq,d`ޏ.#%` [i&Zk.@ݐf.zT)>?oپz/DZ3_d, -<z/v's 6 }$*Sy,疻T8 =nZco-!('GHpwX\+"_}$O섶ya'6MIpX̽[?fEGs|@׹gxdUL@]Sp@|w D^xI̾~N??IjFѲ\o?wJ8ƲY#17SSMi-=9NtC>@Lͱ׷@x֌4bEN@+ǡltZJqLGlyj½∁3Cd8{y )w1LdV)h,Vy" F.],m{e-Jԉ{% ϛQB$kP@Zl[rui.|u׈2Qj[ݾHF=uQ>s)6s \9nS&N&n'{"DI8HGL i#i]4@.t ?TtψyxMYcZWAT2[4 m 6N25.^u;7Ȇ8!o! U}n S?9*`PcyQ{Q7!%;]ӵ%tq ! Z->~'ͷ;j jaM-x \a ypm]}S1 `DD`PtuxC;C}dxXĩ  Re,^UF(1KqB!Gt,䩇x6W$^$:mװ׺qO \D<._4x'ܲpW!s9xBi LsRݬ9^q%hlF,cnF_h|qQ$#E$ [pFhV$BfMeCfhJ, pc<|uF_x7)ct nGK5!k %F9WоdZ"IHWF+IvѾ @>qS vAcrJ'Mfu|_N4rg m`Bi~ِZk+()ې,/HӸ\*c6+yE׌ ֹ{[VGt!,8ǕnK\WGg#L絊 8ZjKW1&1ހ} }e'&@ZA]ytW~˂}tQbR|p4Cmj%"6^?5׬ 430bk'W -8 qk~?$?I DR Q6 ayY '/shdDZ|#T0r }kfqBD|1Xd| _^#0dxկP<#6HcrÅj/;cR4$[dO< V"*돠{\@8h,k+h;A,EU؉Կ ҏ;_7Y#DX%9̉"ԝOlg JD'iX "M%RO;@. A4D` I.x~J;2^"*69˦\}K >4s&j|ش{%Vx;+'+0/35Gڌ +:'" )[D 7@=aO(0ہ󬊿h))'դ9cajSRƠc`DX R ~;0nd-~KoM~5Jkc{ X]T0 Td_?`L%n6YGB(A6v)ఌXq?lFtZ&yYn 6Kh<I&]b((~Y/}UH |1,]> A-uPS>dEՖƕ?ٽHm$,wU =&ڻý=j_8`:\ԇ3zL sNA- r"dad6{$n;WxqB8^Z?罙^rm:KŬ\|B3f*n`:̣C{MS]c`j;xMض3 FN ?# ޞ1瑽4V_kX@F`F iZIK.~%7c:亴e&n5.JSg3f>Nfl#KbinfXUaۅL,C+Sr{${TJ :{X.Qdѫ!9xh)+)ȱlyXGԖ~%ȥsM2sx i?xcI鍬xd.l?yy[%⩸l0!H)Jy?ل9SFS8)܀V&~幔CSu%Y-[b|ɨp2[k|=s[ƝIS6-k;T؞7, [yc)?bZܚv䟆8f "oϯKL{t#HM r{8j{!hJ82?>+9g9s=|?&<>|?yec'T)wEm|#9Zck:Z–!J.χ6Pv  _婛"85xNt䱝4&OgF*𳫜};$`7%!xq'Br!n_ ;S,0!6ûpJQkoZ!>|+B3JhK>co95Kvbۧx ܙzuҌfHAJq4 ۽hݷ6]uk&b AQ?f+z0?*@^r 8pX +F|@AG{|um8n@%t jV4{ 4@DO'RN[agJ g O̗vxDl+y?Տڊd,MI`b]iDG" oQԗR@RYx{L1>MnQ2>TFǪ.1c;ͭ%$ʅ&r"FI{(KqBVr?;!u ny"E"I~ yu}h {M#8:-GfԒ*rH1ܦ^~+T*lu~[EPh6kyY^a訴L8H\SƏvqY{cCC.p՘LW:n(y7h7pBhZꗇ:ސ'Jp 7^ 1_bm+D?>:Q"}nJC-n#iT,*"B.[5$F}HeI1LM\C4:1ءpk ʼ Wz3-Cd;`g:71N52> T[0JVP^as6፭#җ}c9 74 <:B E2TqNbXx68RùtbT1˓$ic,/O7_bLEġ#|`qi{LR=bX%q(;br%9Mʘ &D[Q ZJ0UA'j$'w,ĺi&(T2tNJ(ip]+zţ.a3]\wviQ{+u,TP3~q< 4W8y8Iꁹ<*((8v=xJ9@k( `Z ki[H~3 *E{czj~#ÀIr.>y!I"0Dq|u{TϡP2DHK#sS'H}6CHQ&WU'k *$/O" \s{ OՒ!D{PQ~,OjDV]6+]$vZ쨛cM|7mi1CjeXi4wz&yk8idQBz\s)1BKi&뛽7@㉸z#{Aai";Q}Hݢ#!?~`=v}|6Q+8I*pD3X,\(Щ ҭS'uDDˮ"fjXZHḑٟlWgbB^57~'#>U onݩ'yXԣA ZUNvYI$Ҟk%-Vi)oi\8m^2[遍g 'RYe?FeX~jABF9b"ds ZKh]1@"E#t,7.QkqbnXY5 :b JJ/ {HMEu$1xŭz&UWrdPI^q yOP k}sKX⡥AK _NC.׉|)B >3zVVE]0G(4~u4[YEa][+/U*ISEK+ cyRNBVGj檋T!\qY/gIЎ,|_Q_?C8 YXUZB[aSŇNLQ XRr|Ig #3#,WwUuI *&rosաlBFr|V)wtAvͼ[ \:}ӴcK9lsN6:$^m6~E>w2Bz3ꏪgK+ޙ Ye \Ǵ¨hTUjbsPIFACgK x̙dl CyWX7l¥nu2WĎVGw@A哌 ʗ w<ٞ?l40ɪ[B[ XB١L8b9H)f87: P+gMY".&$ƨVy'SG0(SaMMno(z,:< vd~M?B/ ?IX[P3q?y>5)y X1B;p5Q/tt~A)3BҘ9*3hK=獏4L ӮP T)ֵ" vK#RFh'o ͙zr7-B5 ̳GFLbrl2uV8s Oŵ"ɵiSĮ_.24s"rLsLmm~3 hF_a&=|!]~J!jMՃ٬p1 ml\&O^&(bKAݙL[/IvAgkЕpU_D6Yۜ"D76M 9򣻹|&*c$lϴ:>v_E}H_走Yvw4 \ӗhRqGs̆{*h %~Kp:(J{Wc蠆$=.uIC_@'S ڎѐPFt&z)lu.dipTHz]|2P؃#z$S&Esc$CzՓ˻^w~ܼ׫s}fߘj@APWg}\٠+FXhTcdZ|qYqɑȔ#O>ƪ{Н104o9]DBf= P=7 ҉՗xOj`m~8Xоw^bR#uhWЋޑAd 8"(a۝/g\t_iG&q İ~[U,*1L%4(5XtiC;C5 [lRa?2xr}~}vk k3DKx"moMA37ԹU(ynUd^9>w44O2z"CRӔ%3L +hֳMU0쭀6;d^@#11 a[ wla: LnW61k&Ͳ)|9pc1%Z~sk56UQR&?22H_@ XX0.%1rA=ۜna$(+3a VU؜?F5b6½,8Zi_ z9*jxdGT,4 41qe&;]? i"^F&Ryx"gx&TF&0VMͦ-O&,ҋWwo'R:&"VӭXu4/}vFll7'('_(?~V1%qU1!@ ֞/3%[NDOs]u1e"RY^Zxob˅-C;|ߵu'yJ-7yA9b.prJIڑyyػY1ڦ WtxDH7<IU,c/f#-$880fH.uE i۳br"f@|19@߿,2#+:D U/`FcFMf)$ sh߮ +|$HknV/cUX:N鮸ì&+/}(M&plЍ>ZO}iLE]¼)o5P_&_]]?( 6C\.#Vʗ Q,d&B ֙PSÔ H>{g9:Ŋ=:цMvdRَ@Fj^礼mJX_;6WVMz:Uœ;Ed.Awkh46!-4jƻUZG(fv`+-^ vԒR }, bG,6O9TcTpn)yJ9h[mlRhXOjҘnlem휺yh1>*NXhmMjいxKb~bQ!CDr7Z:;t* ^y;Zfyl%w:1=T."8MTxmlW䜤$NGQ`*&ڎYKO*i'2"ь,8`g͝5SPAdOE;uxN8k. Hј=N SY,ќ$K|Wwֆɲq+.쿃p^ ¤[^ΒXԈ{d!?&yDlJ' Qc%ė2΍ Y_iN}xqWVΝ4K V T$sUߖDbM-n쯪27+:kQ*~%s]G'{{]!uu_'`L| *Ҫ!HAR" \4d6G3uszVjR-E,Y[$1߫9[c{I7 Q֙Nht`&t!a~1 4>uǠy\WUv3yd S(u {Yϯ̓c_g{ՠyv ҮkΧ$@W+y|~+LrzF]&¿ rôoG\ˇ$)\hwN ð ]!\3#Fw4]" UyEW&c (Oza .C;6dPRi8&٭P5:_PDg8܈t:~DG][( \ϟG2'GaXηR}N] Fܩ>8z7L`]Oh,Qc^1D$)sOZϽH75¸"5չ"D;!] njБLK 繧P~;M7T8-Wm"&NEb>m'ZD!hfex5.)m{qa>Ly`QL&F^cO0moe޸(h}x+[w Ȩ̝e/$ٳy˷NqF X]v텢4e_1[ű`Z:-B,sЎ-Y}X JuVfu u7ue MжY1qlӃ߲s -B7,^00KF`LP 88TV1c?o>:Nu8xPh:Z#~>KV/n\az'[H>kH*S(oW P K&'K;g+8_SVb~}7l1a OMscUe2p!"֝ܜ#}R校`b LĤ+J2j(I%<րAȩ }[3cҟ&AKؔx_Qe4 mM!RYg؎ޯ, 24 ?_ی]}WB┟#hyoʰUNM?iܳ9VNqH|UШR6^?#~z -=@d}=Ց-k=0n+!(nEI&Is cѹF׍)L$w]<>!;2[9<Ѳ{I˭'I5s-O'of ̓?/ 1xɌ.+k4ޏwڵ#>K̂HL}NHNiG @ɷ~H.49A(< &L51F{s@UaW@Ob)գuR]u{P mt$裲b"r LɕD۵w+ uN`&/\X(S$пrcَ ymjISAG^]&7Τ֗A<(gЅU ^g*<ٙRp;/_ f|LStǠe:2FΊ.D25.N}ݫv%@ Wm y^O;aef oD\4-G̛ FlkDȦӷĽd6j'#0G3ȫ.O۴F4Siͱ9\B}*4";)>3J7sʢ'rz:Zm=u'S'7^}v$-MyWUB݉3lz_$ȶÐAb"fr!!_j!SIgd~jNjyX(0cKXS1-ޡ2/s8edx9Ol%,qKތ? EgiS 膐!.BZuъFzef\r~\@uO8r ȅ~H`'5qh%;1ݸIs 򏰝LG1#) 󊋳l Y1h^QgX% +Ȩ͇ޓ4R+$ {}tJ^y3෸לZR;h_P@W#D_WP,ФkxG=u^}~sa=Pl ۛoY"3Zɫ7:3Eoc*oB}k()"3n֑~[_S?,mZ>vfr\Jᛲ@Z\AkEelhGz3Zޚ#}p61V#N$'"2gۅڟL"hq2;K"Ĉ!iRL*I:"d4 (*Ѫ@#Tt$ tVS #k"Tb4i/2J>a99Pm -Nׅ[&-ފe4d+H(4uiO Hﺌhy!IL^Fo"h5;kVϯt`ئ|xIP8XXDC"$A3LjUMhEi ܉mCT((ψ$ 7;Y8]~PeGt?<;'d@c;!+i}HA$6v ]u{cd?WjaK_^dcMw[a1)s6jcyPn:\XuUQAE{RXW0hwyE]:xYU=PBܕ'שQADTGE?fc>KTY2)'R\i|TK6"uW w+~Ռq10?{_f҈Rdzɨ3ʶ}#xkr6}I*MB:bRBBOP_"j1)_.AGꌷHK{L?-1d5$ME񝕙<1a<٣˴K w_MPT$1j/k7U` tJGڝAPW/jFn: I3lS h?}XzM3[r 0pf.J8&ς`P&-) 7wTg%_YڔVcT#1pO&~j*lB-c}=*~6ÅS7w,L]~)51+%KpYo_TZԿ3T:`#O`-q3%`X}!/UowxkGk|"U:q Eв,Uec,ؽLe(՞O#ZH?Q[̊f \f7xoԣ]M(gÇYG1c2I9ƷS_AN&գC `bR.iL+(cKCxOUe;yc&1i397J6ԦAwwy%()sTE9_uh9k̺5 %1-P@9 y?6 &ק.4Μ`ٖ&A-^1~cP{g/$]8Hs9\5Ě!.tV8G-a3;lw 7mB=Sڹ K=MX$3ЯZq_F]#oJfVݑs<|Kdfi\07%DwDNF);(eY\ GS G"9TYn'LZY*O(Rhٍ.JXYwTn!YٽmY.Ɏh+]/Yw1P I@CY;K$|(: 0{]Ot$RÓyTm+քL2]:~`)5qާ"/%oH~$8 Koʱƃ -\wIx[p>UL`r$pi2ƼUJԿg},0|̏j`-j9wECdU-Vb]Ӥ8E˼**9Š;쟈>`4N+B[0pJY1UF,͜U6$,w++il7N7yg%,-"]eTd)Tĭ/7ge ?u{+Tk8!~"b/klD-7.cY-gj|.Oj~4|TZA f~G:oW[Y%ԛϞ6hWlUf$;xX[?SSnEha+wX۟7jfY=/)Tq-j|1I\8$ p pޠPƋ#cq< %0eP+Bc |@mbJg2meZgQ&, {oEGS5:~mvF,cԒp| )ƀyz&Ȓ*V~g=m pRn 7fRbn4|R"< i툸j1҂׹%3tҲ$4]$PW湡2Q|i1\4"~ 2Ve vzе24"\K2 =umHrU'2\GS9:pfw=dieQ=~+*KUKorRe(Z%oUc j@eh33HcY3(OSe#p_<:9YyLeobj(:5vmre_Vͮ0ʻ?p'"]FO,K&?P*L4Z m.^B =oj%$Ţ[WIfSW >ںI;e'R4Jx0lGU"r+@>R!SX縄U! 6pXC%bUYǪ/˪ Ьޮ`kUX n~$pUmEL 6 |!UxA31LG4O_nEV! ɳ ͌_PڞF C+B(lMS>/s? N¹c߶Q_doaVeSG b`"F}fF^jƅ̺D e.j;êbҰp ДS<#T R)}e^md,x .Vh؆-4l=x *LOASJCUKYdБyS>FSKڻs5nV>znw_#J` hU!;c@K1Vpjkn!w1p=XDln5K<DG}j9d#߸/k|i(9LN88=l2:њE˕N]ku[ vxVDvK, ̷<ӳܶAo=.j8 rO 檽ǮۮQF >0f߈ݻLMnh^P Ih}5w0R MBŌ 2B`5 ]~{>j"̐ ;梡 /1-n Y7sƾdg-+HSH}SIWֽQ>VTCH62^ l" Crd+x G6č,$̆ tItiV􆣠^yR?5:l@7X⟁6:O3rkVwiٹͩA]{GwuЪ^w}"nжYSǫG{z=&n90ʙZrj-AfO71 :C{jrkb 7"5tj.3 3J:hjhʖ^hԂb`kmEsɫ z)}01\`Y9Zˀ=w{>ΧxA$jN]Ywշ,f[z2:ta#0Gw9ybFXd}iɯyt܏s#+Y:Ygyuk"ć,|U]ct2RXſeײpA3?nWQ ZAvf|r4L܁Cbx˪$zۺ}IbzRWYzWfׂTyq+xYа~kԷVBsQTGywHtnVۍ ;w|0vWJZ'o+ޜ4yCֺk1^Vo.C;:ֱ5R)(65Vl++8vcȯ6]#ވjYx ?e-R\ґ)"啶}Z fkxGҦ[v҇wt<&9^vShflP߱v(4%|9D( |MbBb ErzY26ٰJOi榕˟4?atS&CN}8M\טGszB ѝ46[E^ p0;{SO4:{Mۍ6sL.w.d>7%*Иw7wդvQx= ljtхAʠ6yTu`av H\rȽ#bv ƞI>(tmM>O}e٥!/6<"d9cw ^Fw%-lt*6P] Iޕctt jlGؕϧ T @'n5)})śuQv udxeTv+;Ls>ǡV|6LY|̺!rG?>oG۳5GC{|4|4UDyD+#++?\F!W <#Y0nIV=qJn5XJ"x֞E,5i.wNZ}E^.-WʮiVU)^ƃ3[AHjO+±O eE\AҽDQƼ᥶2 nưzkE.E 0nB>}*z]^shnz PX!Y?lN6w{ߜCU=|fkc<4 mH'̟"uyBId a1`o$)Pw(.'4a更DVwI8pVySGC[)Ji%$t.ԗFg$5 ΁Tmy57)Nû֍\a5b &XY"x!By/N{/cJt,[? 76 HR:Fg?f AvM> kLwI"(WdA=Kf;Kh&p9 k@:v > HOz*a3{Q!9#ht*%Voo(|Q[s5t -y (r\.o?66lOpI;w47=D\Ï _2Oh%3fM}wFi|[XhY!4Crm }!Y%/ˮOm%c+~h[L2 Ds ~n#]ۮCt C@!P~|tS ]ܳ>C,W@+Q wdK\kF\}a 6cgLi: ,SlPTYWHG #0,J@9[f/yL`foY Y 9%.{Z?/r~|vgP= yUlVtY>Qk g';e{l?1v$Q+M  C?7uZδ3}đ%#^N:'4}&=a-QI?XeVtcJ넄j>8&͍_N"ulkyIvmhVߎ\6U)ش^C߭%=4T^CLtE}Ȏ {,K4m`0:4dg,x<,K0y,=x>}INyH`}|ͅ-4x$|"7LE&$oZ3N|SQ˞bܨ7f~+5 \6Ӿ7D5r/S\ǀNOh4 Mwm͏:4:<[ZU{JnsnrM;N X9H 7;.#*M.408Tj:'R>C߸Jѻo7wٸm{7IŒZxݬTG bhxr$uvzt}SthݾDRA|o7I`v$?_op +ʖ=ݕz+2) T%l35D !?yyYDŽJH” Kˊ ckЄ۷2" 曟D٥˵h*.۠Z DПLԥt/$ć2Dâ j ^;ω7s~ )ufGYLYV`^pZcI: ̫QkoI3{9ܘݔ޾ :髰rl>̷X!GAÚ 5^W4(.v?dgedsY)t.eɰŒJ`);É'Ե?WYSZ%%}*rRSk<9' SmC2'/:,2EE*SJރpxsўX@&SSV&.at|#@JkEG? vaR)؞GxrC.q3 Dƒ )3 s[FOdR fD>ҕƧlqgdcq-^"0t~-ctBfTyԞ߆%nϨ9HB[nG@:MO6Y.Ƅ]̦~gƟ٤!9I~&JHșe52HQ!1eEg`Yݫ 5>n*hW@+J=k3uOē Ig<ؽ}Z]G8yF|={OYd+,vny%Y?{nϼ[U%Y UG>,!~+|0/P3Gm>zY /򜠺KңDDj_@]bs[E sL, wͳh<?[sxK-{n`NZe%-]"I 2lSΜgn{qJ|\$WZ&fy>P@FgWd< _0 fEXfc3_.P=B\壒gϨ{@-Z咎&$5IP˼,d5ʁ3 _,DBYPj. yhc,}meW xʴFdZ$%͸ L_ٕ_z!oI`lSNjZ\ QV$49QZlqbg#ki}ZA`jZBfC|3l݉Z8_w`>W$RFʰ% ئZ ikBNj8gfDK-y|5Jr %}l~ QH1o4g7˵$-@sJa>ݾ))ԓX"enCdyp koNdS`Q{JW5R/Nw>%bm"b %C yTz5N1, tl;}s-U_h &;/6W7=ŠI_8h 5J3=-lDU+q9 ʕÕOȹ'ԅ@{veۃ6 |K";vHf=EIL _wJ u`{w^&aw!zfs-dD~Տ{ Sq&bRp-g:(Z5ν!|z)J"g>>`8@&ۃUֳ{!G[=-03(CA/Z8B\(Ԓœzn8  \ s nD샙ɨt 6ZX-j4'8R0͕+llQ_8-f$KlTI1;7{(Qyd-jp#|FPʄR.!V2g![o`ƪ8r0. 0 ݁at(nAR~ןu:޳8d/Z\N=ai)jN@Ri+>BY.FUȮlDu-8Rcn%FW@h;:6܉HސD]9>&J-"oXf8Vgcx^ M:. W킞e{\!ңYr2 uyި|/Py:gjX@]@I 3_=/V4,>r'D7hK'G{д3H :Ǭlk'?L[">GHnWىc&hC|/@Gܰ5b_Q攂P 5YK/&-P mڳ&ClM܈=۟,:7/ z:]ZXNYJ}^4cP'Е+y^{ނe,y1h5UW? < Ëo%d@D5gFf枥=Κ;ӠL݃U)hN0`;H|T'䓜U-<|SewwvZ+aܥ]~Hs67B=*N4}W_t}%tA)*pK7n0|uܪ@Ofuy1ī^1ѣe/*BcK͈XA<1<)gLŅ_8plD$y%<8Ot[YdQmyZ'm&KΕ: dzS&9:‡E +J܅f!f|TCJpۅ"Do@>E^au[+ ]EZ GjyE :3  ?LWnf#VɤM+IL% Փ5n|q4?(%Brk |>%}C8\Ŧ#P#à'=.8NsUe~j$K[R61_e?4<"#wEHd(4PT/qu̱V#F>4 pg&\h 4'*Q|)4j:=5%RzQq3upXh U !;˃F~oR3"]1# ^+r8;c[Gxz='+ggq(g-#"9M++l% =C`15ừzŮm;%hߚ̫CDQtN?rWs(okgm{@UH(2.3}JV0"6&=|DMء5$$t;49h/9%ȘPn[Hs1 4Lxn z:ÿnЂoe0gɑ}Ka>`"nj7Nj ojeu9\| g3=%Uh[ C;{xm,i8"n[A=Y$bx[4I6>HK98y[deU=<tfabh[x[)6 Cbݴu[^9xSyg;ۛrcuvv~EM00*•S,o5t;-dɺvS.֭C^4.%SW)_D^xf>TXsd:soGe^k1_[Q\^4GԺ:i|?ċʏV:@  PRn//AYXN2s m l~Ip>c.$8d y)WȻP`-Cs댟ylSYӁ'F`_I!qt~F%}=:sJ5yFSԠӯy\҆`@8jSgý}vw$kL;Dx6ϡX[Ѷg.5M[Um9VܳLoS;i~.*sE:ˌiѭysHS6)LLp0{r!ܟP6XWih,+ɏ3~jK WfJ73 p/,jɋO;8~סY!r^tdCjשӁE _>+è C7^RՀ󍹖-cIGL?]m|qC~xo:?R=d3oƝ&gPU;_o]Ro#1`FYfsoP1 `7$B?}Gd(& NjH~J.=/3BPO!:Nf DOIƲ3™[oQ^ 0ô9W7l>c$B* x?"w|dVDkE~:h"~ѹ}HuVuYIRLDnʰoHΧJuQbBE_ay^n@q^![n; 2]9v5@ 'QIq nB]|S#!GΏh f\edSxvאָ?Jp/h8\_c}R,$z@9KB徖ߊ%pi#S_eBMG yӤ2Né|syO"!E[*E4(-${FJH-#3!D*R(!8W]qzR6rcɌ g|AӜAbkOMK(^۸V$nrbz+޽ aM=pPNz>wQ~V]ՓRYm]Ns+h:K+n;[0}3KAv뾦·e>:Ƭgcε>5n_n띝k[{).oJ %O~zu/5o72);/ۡy]YK:*5ZnǢ֡! gO\P1r|eڪͺ jJlw%5MoYŚoMwzt]W=D҇C=;ط.isRP"0sGהsKdN3J5wno ϖDuM:\-ԗ _'qxUv>s-84,ƻ]H頜ޏ^IM?){s%!*IEGu o vkY1+̘e~ݢ/Xh]*R%&p?eOh®-\=B}-['u7 58i`{) #ϵ3d]tR#`ܪzrU-^ċj瘔vdejNy#㤣bZ$;}ܜup_+ dF2?Zt hXv!hX:X+^7ݯ huaRi_/Kk!u-;OT9#ŷ׹Ww،{2%zouIļGz.P^}nM=>عU9aۯeG7_iJVjlj:6='GfoҔ{O6]?ws;i鼫,wG3 l](_S<$(ô]H8>7xҞzv4^_X/_7 M/X'WrԻL>jz\xԾEq[\V%FߗMfcYj\೓=N6~Qk?޾T3`ɠ@sVկ$O{lSXO3z,,}*%*NiT۵ ypGw-#V:eUYwoNb̧YmsްZh i%M}_+{չV0Z#DQofM?˻~JiNCLr%;/{RN^bˊzKs/֌=uMIury_!Ok>S$vN)u=ˎZ[,XT*HGWW?* (=2{O;JqxX39Y9cXe}JUEoJvPuIf ώLf}rۘ c3ƿH+ [fjo?X+̞0ӑEzi~mX=RʥCn2E /Q=,`?%Gݾ\^hA lٺ"g֍*wp+ ĹnsBgcJXu-fuZ숉JTV}" ^bOzaW[Mz|_jDZJz41eG9-A+[F hG6ZuuʍixEk,V>1~xs& *k0!"IaX!I x-t&GNٔ$JQ#N-./,Vr,wnXEiyZ*ok7[S}{Zה&%xH4[@J;zslf"\ףƔ) !Ezn\:qLn˚ow:P0]ʅO<;ݿjrZO;6.?˃̑؞&rs.*W8}}(9Eǎ=wu+o'Rw)U&83}e(,xwfié'T9{? 7Z}j7$wMV}otΫU 6-/Gs?ROδ?h)Uk3~ܭ#"9*_WKr ,fF.uk^oTT̝穷KVX8-XyK;RYb[ݼw=(`{:yAEۄ+zF]Nlq?ѶhRwysw:;2KB\=#~[bAFQO:CkSy5;ebd>vt"'[}wdݯC޽8{wQNӉc~ /g箰tzGC=?Tgu4~SdWOgϻ߰i$o/vNK~8J[:MZװn[լ؟W,E_9e|mO>uxFgD3Є}n^ks $P2Ld dtk]qe9ظlJ-۔ٗձ}Js';[O+>}iZYnB vU pyy0_ W+kV}͖UaU\)vz\l:}vSrOCgn|$yϦ~'ܮPjWvxX١Mt{jnl:w2Wnn3 yS%aCхUu}+i~sس@s}5.Y\wS#wv}ҧ+.ݯQH$6;r‰:u/̈,z'NKx$Ӧt|ELk~Z]uDIۀ?i޲M]vl 6ė:բRk.I* rߌ $H>;Łjޡ+9+IU. $t+ٵD╲%kJw{;:,K=`[]YaC$dVWm{Ί&91[^?;fdJ>>4py;/ Ik-^D!m#YZi)[n :=yͽJCJ^^t֖|,Yamv],GD(PU4;'V7iKi (Xmj/]2ǰVvo\OPAH-cSvEo{pk6^X)*{k:\ŢN^w_I_ҵk5^3wb; 2UV2<qzࣈbGum;rws#/6br1wKrNuD/e:X{BuD4iռ-R{2>U#];,{ƈ4xQӸp?O\zauyx{ZJ>Ҧ'ޫrg&g4aL1VPVwm\%fG7LzVuk ].m-z!߄X= 6sD;l&b<[?JWsWM`lw꽼hD~])BvI}7xðAy Lƌ[jٿ˗v45'PSy-Lh[7]?;{ʧ~'3۲1оԼm"={\=ϵ3ּ冴HB=*<O-]n iٔG& ,< j/ԇ4I9&{OeHǩO}Ԫy̓s7 "_57R=zo!cx93l,siYZ!?$Ϙ!|ӬJfO44u,$<|zX|2"KD>5[ΨzgssK/D_Xm8t%4!A Yd~+=Ażũf66\_燃f# gȽ5j@r+BJJwgONin]V™\(o IuV_}3lW\ݭRڕ׸8>n#s̹$#lS<+m!~k47B~'rST 12W#iڧDnćͪUy~+xI`XAzkǞC{ܝ8!eku%|yP+ţ7%b4i~7MO)U9-pı^nN]1E!*!Aa2u޳ɼY Iϧ+Yfm _0#0j=;yѲ݊?d >Y'k>(6{[E.1 ">1B엕_/)\|k]_ӟVgh{+1ǾK/޻ц/yK]KFT4KZXf9'cGާS]>*->VmȩalR~ g79VLd}??OyzкKw#mV EqSV-˨RTҷO\m622ru^{UWgͬ.N]~Ѽ+O4k7WjH I,tn80`n niO_N3m*3Ӌj.];zI+Ζ^9ŻI;5 ,V>vB7u`ΡM2XigIXU 4З.z2ɰd͋/C^d Gkɔ ov(O膪)͵Ww>bƪ+:*ߺNqmod7y\?W込l|42NK7KlJ)=|E5ywU>&kJѥtǡ\0ikiT2s[ݟ\kneeх/ajo֗ 2vX̛ihN,xVchJQ u7׻U6dQfߘK'.~AMKdg^ՋP _`G[kE熍 tLq!V]%Ym / r8׹/^K)U=Mzp̧ٝK܆s Ij߮ܭ8#ljlhg B5?l+~M&isĖE,OYT/fӸԡ#&\ã`xϔYן;pvR91IFޅGlbD0"fu" "N"qJ8.E\˚qF=D-TsLF܆č'IW1$ ^b<W!X!nyK\#NO"q q]%qzdH?ubXI\MĺiDt}vSIb%4~+z^$n1x6Kqs*QsAM%'nSC\OZ 沄غk.11'gD"d?qtTKX!b67ЋH?b #F?1M67q.1;݈{F8rCxWMD~D!v=1ɩ'#jlNtuC]ĸ'Мb}+!'t1|Ҹ}4qmsNbm nq_0%F3ĿKc'س|3<}{YXSՊ%nq9ĒBnRB\*"1B>B=]ZbG5t$fj8>h">L"&3{L̿Dy`\8d޳R5N"6 [>~ F% "}Id8HIOb 쉛MP#qM"6qέlk%1f=##M!:7h+1]+CY`n7㉛x%&^%1 pzåĭ[I2GQlb 󉫃$>K]= uʼn0ĈlLl#W%1_bە'Є/ĽI\F1 Xy;{&t5GOܛ'$9C^MUĈ@?% \5!.(tbw , {gE*{V$l;s۝,3U u1a`>) se zc:0%KH!zHL\i/^E{'4 ?+߅e 3 <[ ]ډ M9jژ;DbB]V[0y3GI}y t8{b9nO]bm$"kĚM4<.|L䌬6FRtbi t2j| nfbD=Ϡ3x'b_wcx$W<-4GS B6ڊY hTx Km8VCohSqރ3pgMvp J~WNl+@304 tgi»s;/h zA?5JqDvɇl;%R@jL5<8 xJ2>߳P p՜,NJ =9FbJ3jAO>YK ׌}54zE|a dmXl%E67^H-I>v{ =w^wtl Ϸc!I``62?؉2X)VBB%.bF F즅h:i<^{"tk Cki&N*p Y>t+ >%MċvL܌38n~p["#F'R_)[XQMx h|^܈TР|$F3Hte[ lGq_!G4"-^,|v9 OlF_E Eh!n]xoydf]9!pwkއxw1?<)ȜENؙBF >XD3y b ΅7\ R?l# }r /mϧ#ބ~G#)l nch/_h O#<>toq?xg6T] MDmn&V4b.C/M0Ӎ]'d\Wtdk!.A1@b"g( =t""uCBި^gf+n ?KLkd~)xa #E~x_MԽ8a7܎7Y<D3p^f] zAK'V)&!qnO^%N 08^ڍ7΀3C?9dWaZ~]/u"߯7D,%(t_PXw8|' z+;;3‘G0S'K~hC 8`J,m9D-wF{R`NL7:nwqx; ]:oG6#HD;P.pz#AF=붳6Sd᳹  ֘*#ΛrCK;>Q=iS>$Aֳ2qӉwfj xRg{ `q[9Kȃ''#&99 x`+Ym 0!=@§ľ~ w-1v!r 83сn܃G CPoXʤ7Eq+ lWvu"+YI39= ?D1x }rنˏ;2 BCԊ ?ۙ <#5"+#"< %`_2i67țfωKSpXˠ\[ -`f9d煸_".r)xMd MV=bd!ҍ~E .Mts`7^/wO(4-fkG \;솟 ͸+C3я44 xhC=v\L myAf8A/?@t9MTЈ}< ONFj_l4 '^-5&; >Exyb@ 1{x[܉}mཷ9qjEKuO"qO/4! YO4'—Aȩu`"aLSt͇!5 2 g)B$ W-_G>>{~A.(cb3 O9C, xn2Sz ﮻Gn,M4@b/|V6Ex_82BC g7?=T4 F->$,n$O5 Kyxrp4%I|/Fހ| "o !?C...#zb%(29tkP,**#mw.1C+PKCT?Hgpgsigned/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idxUT sbsbux _ĀbD!f&b1lLibV|}+٢],*< k}9A,٬%^VOk[ҚIm=j{~,aA3T>2m]kz'N |!] Sl 3څ{Y_)9k/]F!@ [4K֝uc#ϋřM&_ƿ׷XPK CTgpgsigned/objects/d4/UT sbsbux PK CTN&&;gpgsigned/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46UT sbsbux xKOR02a(,VHIU҉ yi9%\ PK CTgpgsigned/objects/ee/UT sbsbux PK CT@@;gpgsigned/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccfUT sbsbux xKOR021g(,VHIUy) y %E\(҉%E驉I9C[ Fq]@PK CTgpgsigned/objects/be/UT sbsbux PK CT ;gpgsigned/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644UT sbsbux xKj1D)zUB-0uV9<#+W$I0<6xSOpsx[jr&pᄀ+wo+g(L=[B?D7GPK CTgpgsigned/objects/a4/UT sbsbux PK CT ;gpgsigned/objects/a4/a7dce85cf63874e984719f4fdd239f5145052fUT sbsbux x;j1Dmdǎ|M3`V{ >QvL0I?!4Z=!צ8F!rsQy9]$D&l6A>jFWҵ IKNiZ%S  U~̽>' w [ DGڡQ-M>dO}\8g_ШoYrPK CTgpgsigned/objects/fd/UT sbsbux PK CTO[RR;gpgsigned/objects/fd/093bff70906175335656e6ce6ae05783708765UT sbsbux x+)JMU0d040031QrutueXlmmAṃJ}G;UZWRQsCtk# >-/y.7 PK CTgpgsigned/objects/87/UT sbsbux PK CTQV;gpgsigned/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80UT sbsbux xQ P9^@BibzOE5~x{#7Kfs+hMZD,.1>zO!>+*gp2}$MN4q"6_*>Z_8z_nt#lCTmAՙ,EGPK CTgpgsigned/worktrees/UT sbsbux PK CT&gpgsigned/worktrees/testrepo-worktree/UT sbsbux PK CT"խ""*gpgsigned/worktrees/testrepo-worktree/HEADUT sbsbux ref: refs/heads/testrepo-worktree PK CT+gpgsigned/worktrees/testrepo-worktree/logs/UT sbsbux PKCT/gpgsigned/worktrees/testrepo-worktree/logs/HEADUT sbsbux  !@V1w6X .!,Fm_K;|C #ja8Ek IA17= w̾?m\[T.pTJ(C 3\Z5ܦʸR7 %RPK CT0`/gpgsigned/worktrees/testrepo-worktree/commondirUT sbsbux ../.. PK CTA( gpgsigned/HEAD_TRACKERUT sbsbux ref: HEAD PK CT Agpgsigned/UTsbux PK CT+isDgpgsigned/HEADUTsbux PK CTAgpgsigned/logs/UTsbux PK CTAgpgsigned/logs/refs/UTsbux PK CTA:gpgsigned/logs/refs/heads/UTsbux PKCTq8Ղr+gpgsigned/logs/refs/heads/testrepo-worktreeUTsbux PKCTZegpgsigned/configUTsbux PK CTA?gpgsigned/refs/UTsbux PK CTAgpgsigned/refs/tags/UTsbux PK CT. b)))!gpgsigned/refs/tags/point_to_blobUTsbux PK CTz ))Zgpgsigned/refs/tags/testUTsbux PK CTAgpgsigned/refs/tags/foo/UTsbux PK CTz ))'gpgsigned/refs/tags/foo/barUTsbux PK CTAgpgsigned/refs/tags/foo/foo/UTsbux PK CTz ))gpgsigned/refs/tags/foo/foo/barUTsbux PK CTTn))}gpgsigned/refs/tags/e90810bUTsbux PK CTAgpgsigned/refs/heads/UTsbux PKCTwC() Jgpgsigned/refs/heads/packed-testUTsbux PK CT[ϖ))gpgsigned/refs/heads/masterUTsbux PK CTu))Jgpgsigned/refs/heads/subtreesUTsbux PK CTyH))gpgsigned/refs/heads/br2UTsbux PKCTr()E gpgsigned/refs/heads/identUTsbux PK CTȟ1#))# gpgsigned/refs/heads/long-file-nameUTsbux PK CT[ϖ))&G gpgsigned/refs/heads/testrepo-worktreeUTsbux PKCTz;() gpgsigned/refs/heads/dirUTsbux PK CT]:))J gpgsigned/refs/heads/testUTsbux PKCTx() gpgsigned/refs/heads/executableUTsbux PK CTX))#G gpgsigned/refs/heads/merge-conflictUTsbux PK CT+is gpgsigned/refs/symrefUTsbux PKCTՑϖ3 gpgsigned/packed-refsUTsbux PKCT \ͻ'gpgsigned/indexUTsbux PK CTA!gpgsigned/objects/UTsbux PK CTAh!gpgsigned/objects/94/UTsbux PK CTV{ww;!gpgsigned/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162UTsbux PK CTA"gpgsigned/objects/09/UTsbux PK CT>%~;"gpgsigned/objects/09/9fabac3a9ea935598528c27f866e34089c2effUTsbux PK CTA$gpgsigned/objects/76/UTsbux PK CT~V;W$gpgsigned/objects/76/3d71aadf09a7951596c9746c024e7eece7c7afUTsbux PK CTA{%gpgsigned/objects/d5/UTsbux PK CT]EH7;%gpgsigned/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83UTsbux PK CTA&gpgsigned/objects/9f/UTsbux PK CTV7;!'gpgsigned/objects/9f/d738e8f7967c078dceed8190330fc8648ee56aUTsbux PK CTA6(gpgsigned/objects/1f/UTsbux PK CTyp;(gpgsigned/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9bUTsbux PK CTA)gpgsigned/objects/ad/UTsbux PK CT(Y]ww;^)gpgsigned/objects/ad/edac69457183c8265c8a9614c1c4fed31d1ff3UTsbux PK CTAJ*gpgsigned/objects/ae/UTsbux PK CTB=;;*gpgsigned/objects/ae/90f12eea699729ed24555e40b9fd669da12a12UTsbux PK CTA+gpgsigned/objects/5b/UTsbux PK CT4;+gpgsigned/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644UTsbux PK CTA-gpgsigned/objects/32/UTsbux PK CT<222;S-gpgsigned/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54UTsbux PK CTA-gpgsigned/objects/a3/UTsbux PK CT:T;I.gpgsigned/objects/a3/8d028f71eaa590febb7d716b1ca32350cf70daUTsbux PK CTAY/gpgsigned/objects/e3/UTsbux PK CTnRc;;;/gpgsigned/objects/e3/6900c3224db4adf4c7f7a09d4ac80247978a13UTsbux PK CTAX0gpgsigned/objects/16/UTsbux PK CT;0gpgsigned/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925UTsbux PK CTA1gpgsigned/objects/af/UTsbux PK CT=Ǫ;1gpgsigned/objects/af/e4393b2b2a965f06acf2ca9658eaa01e0cd6b6UTsbux PK CTA3gpgsigned/objects/e6/UTsbux PK CT;m3gpgsigned/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391UTsbux PK CTA3gpgsigned/objects/a8/UTsbux PK CT/;@4gpgsigned/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6UTsbux PK CTA4gpgsigned/objects/84/UTsbux PK CT<-~~;5gpgsigned/objects/84/96071c1b46c854b31185ea97743be6a8774479UTsbux PK CTA6gpgsigned/objects/7b/UTsbux PK CT~eƑ;`6gpgsigned/objects/7b/4384978d2493e851f9cca7858815fac9b10980UTsbux PK CT3Ӵ1;f7gpgsigned/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785aUTsbux PK CTA8gpgsigned/objects/e7/UTsbux PK CT-U;8gpgsigned/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0UTsbux PK CTAo9gpgsigned/objects/62/UTsbux PK CTdH22;9gpgsigned/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dccUTsbux PK CTAe:gpgsigned/objects/9a/UTsbux PK CT )22;:gpgsigned/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4UTsbux PK CTA[;gpgsigned/objects/4e/UTsbux PK CT22;;gpgsigned/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63UTsbux PK CT,\ 88;Q<gpgsigned/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10UTsbux PK CTA<gpgsigned/objects/66/UTsbux PK CT ;M=gpgsigned/objects/66/3adb09143767984f7be83a91effa47e128c735UTsbux PK CTA=gpgsigned/objects/c3/UTsbux PK CT,bC;$>gpgsigned/objects/c3/6d8ea75da8cb510fcb0c408c1d7e53f9a99dbeUTsbux PK CTAY?gpgsigned/objects/99/UTsbux PK CTd%;?gpgsigned/objects/99/1f1b12603e1d78411c1b4042719f964efa7adfUTsbux PK CTAUgpgsigned/objects/6f/UTsbux PK CTb2;KVgpgsigned/objects/6f/d5c7dd2ab27b48c493023f794be09861e9045fUTsbux PK CTAtWgpgsigned/objects/b2/UTsbux PK CT=;Wgpgsigned/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1UTsbux PK CTAXgpgsigned/objects/14/UTsbux PK CT;Ygpgsigned/objects/14/4344043ba4d4a405da03de3844aa829ae8be0eUTsbux PK CTA&Zgpgsigned/objects/81/UTsbux PK CTN&RR;uZgpgsigned/objects/81/4889a078c031f61ed08ab5fa863aea9314344dUTsbux PK CTA<[gpgsigned/objects/9b/UTsbux PK CT ;[gpgsigned/objects/9b/1719f5cf069568785080a0bbabbe7c377e22aeUTsbux PK CTA\gpgsigned/objects/97/UTsbux PK CTؓ((;g\gpgsigned/objects/97/328ac7e3bd0bcd3900cb3e7a624d71dd0df888UTsbux PK CTA]gpgsigned/objects/f1/UTsbux PK CTgg;S]gpgsigned/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3UTsbux PK CTA/^gpgsigned/objects/fa/UTsbux PK CT0$Ȩ;~^gpgsigned/objects/fa/49b077972391ad58037050f2a75f74e3671e92UTsbux PK CTA _gpgsigned/objects/c0/UTsbux PK CT%d;Z_gpgsigned/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346eUTsbux PK CTA_gpgsigned/objects/d6/UTsbux PK CTq;4`gpgsigned/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487fUTsbux PK CTA`gpgsigned/objects/a7/UTsbux PK CT7w; agpgsigned/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bdUTsbux PK CTAagpgsigned/objects/75/UTsbux PK CT,Gww;agpgsigned/objects/75/057dd4114e74cca1d750d0aee1647c903cb60aUTsbux PK CTAbgpgsigned/objects/13/UTsbux PK CT;(cgpgsigned/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08UTsbux PK CTAcgpgsigned/objects/a6/UTsbux PK CT=;cgpgsigned/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750UTsbux PK CTA egpgsigned/objects/2b/UTsbux PK CT88;Yegpgsigned/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863UTsbux PK CTAfgpgsigned/objects/57/UTsbux PK CTHؠ;Ufgpgsigned/objects/57/43a3ef145d3638a0fa28233ca92897117ad74fUTsbux PK CTAjggpgsigned/objects/c4/UTsbux PK CTk[;ggpgsigned/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bdUTsbux PK CTAhgpgsigned/objects/27/UTsbux PK CT+P;igpgsigned/objects/27/0b8ea76056d5cad83af921837702d3e3c2924dUTsbux PK CTAigpgsigned/objects/36/UTsbux PK CT/;igpgsigned/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022ccUTsbux PK CTAjgpgsigned/objects/45/UTsbux PK CT#Va;jgpgsigned/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6UTsbux PK CT;kgpgsigned/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057UTsbux PK CTAjlgpgsigned/objects/1d/UTsbux PK CTYoII;lgpgsigned/objects/1d/d0968be3ff95fcaecb6fa4245662db9fdc4568UTsbux PK CTAwmgpgsigned/objects/cf/UTsbux PK CT/6f;mgpgsigned/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9UTsbux PK CTAngpgsigned/objects/f6/UTsbux PK CT#RR;,ogpgsigned/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1UTsbux PK CTAogpgsigned/objects/18/UTsbux PK CT$g'ww;Bpgpgsigned/objects/18/10dff58d8a660512d4832e740f692884338ccdUTsbux PK CT 33;.qgpgsigned/objects/18/1037049a54a1eb5fab404658a3a250b44335d7UTsbux PK CTAqgpgsigned/objects/ce/UTsbux PK CTPD/;%rgpgsigned/objects/ce/054d4c5e3c83522aed8bc061987b46b7ede3beUTsbux PK CTA\sgpgsigned/objects/4a/UTsbux PK CT;sgpgsigned/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045UTsbux PK CTAtgpgsigned/objects/pack/UTsbux PKCTsj)Iugpgsigned/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.packUTsbux PKCT7h$I6gpgsigned/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.packUTsbux PKCTo9"Igpgsigned/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.packUTsbux PKCT|Hgpgsigned/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idxUTsbux PKCT".@HKgpgsigned/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idxUTsbux PKCT?Hgpgsigned/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idxUTsbux PK CTAqgpgsigned/objects/d4/UTsbux PK CTN&&;gpgsigned/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46UTsbux PK CTA[gpgsigned/objects/ee/UTsbux PK CT@@;gpgsigned/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccfUTsbux PK CTA_gpgsigned/objects/be/UTsbux PK CT ;gpgsigned/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644UTsbux PK CTAgpgsigned/objects/b6/UTsbux PK CTAk4'PP;4gpgsigned/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593UTsbux PK CTAgpgsigned/objects/f9/UTsbux PK CT" $¢;Hgpgsigned/objects/f9/ed4af42472941da45a3ce44458455ed227a6beUTsbux PK CTA_gpgsigned/objects/6b/UTsbux PK CT≣));gpgsigned/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602eUTsbux PK CT[0ǧ;Lgpgsigned/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6UTsbux PK CTAhgpgsigned/objects/a4/UTsbux PK CT ;gpgsigned/objects/a4/a7dce85cf63874e984719f4fdd239f5145052fUTsbux PK CTAgpgsigned/objects/fd/UTsbux PK CTO[RR;Cgpgsigned/objects/fd/093bff70906175335656e6ce6ae05783708765UTsbux PK CTA gpgsigned/objects/87/UTsbux PK CTQV;Ygpgsigned/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80UTsbux PK CTAogpgsigned/worktrees/UTsbux PK CT&Agpgsigned/worktrees/testrepo-worktree/UTsbux PK CT"խ""*gpgsigned/worktrees/testrepo-worktree/HEADUTsbux PK CT+Agpgsigned/worktrees/testrepo-worktree/logs/UTsbux PKCT/gpgsigned/worktrees/testrepo-worktree/logs/HEADUTsbux PKCTo#,gpgsigned/worktrees/testrepo-worktree/gitdirUTsbux PKCT~SBQq+sgpgsigned/worktrees/testrepo-worktree/indexUTsbux PK CT0`/gpgsigned/worktrees/testrepo-worktree/commondirUTsbux PK CTA( @gpgsigned/HEAD_TRACKERUTsbux PKtIlibgit2-pygit2-a011e26/test/data/submodulerepo.zip000066400000000000000000000467731473744024100221330ustar00rootroot00000000000000PK UPsubmodulerepo/UT i^Ucbux PK UP submodulerepo/TestGitRepository/UT i^Ucbux PKUP-ZNnsubmodulerepo/.gitmodulesUT i^Ucbux ..MO)IUP I-.q, J-/,/T,H,PU,-Jd[gd&%d&yFzPK UPsubmodulerepo/.git/UT i^Ucbux PKUP#dsubmodulerepo/.git/indexUT i^Ucbux s rf```9 mxa4PLC̐'>CQm7.tSu^zfIn~JiNj10<$4<7Zg@#ݟ0ggU2K2)p5xxqӯR柙/ԟjk|ݷ/Lƺ_!]bPKUPsubmodulerepo/.git/configUT i^Ucbux MK 0 D)]zPȉ -}BA\Z!"W b-nZ h5y< DB>%3Я6\1[1Xi$uwGmgKpL? ex$~!7N*r]=N苴mĀ᜻^*z[PK UP+issubmodulerepo/.git/HEADUT i^Ucbux ref: refs/heads/master PK UPsubmodulerepo/.git/logs/UT i^Ucbux PKUPsubmodulerepo/.git/logs/HEADUT i^Ucbux  0гN&^t ~L &nb |LJ:$#PsشVQ-OFjvppD{ vh \{(hP q4)E(1btPK UPsubmodulerepo/.git/logs/refs/UT i^Ucbux PK UP#submodulerepo/.git/logs/refs/heads/UT i^Ucbux PKUP)submodulerepo/.git/logs/refs/heads/masterUT i^Ucbux  0гN&^t ~L &nb |LJ:$#PsشVQ-OFjvppD{ vh \{(hP q4)E(1btPK UP%submodulerepo/.git/logs/refs/remotes/UT i^Ucbux PK UP,submodulerepo/.git/logs/refs/remotes/origin/UT i^Ucbux PKUP0submodulerepo/.git/logs/refs/remotes/origin/HEADUT i^Ucbux  0гN&^t ~L &nb |LJ:$#PsشVQ-OFjvppD{ vh \{(hP q4)E(1btPKUPT Afrsubmodulerepo/.git/packed-refsUT i^Ucbux -A F}O1cGoÏ4jۼmA"wzqT${t|3r(XKqaxb:gg;(6Mr+O>PK UPsubmodulerepo/.git/objects/UT i^Ucbux PK UPsubmodulerepo/.git/objects/17/UT i^Ucbux PK [P0Q3]]Dsubmodulerepo/.git/objects/17/982118f7a9c886506bd90f7ad0c9b802d9259cUT h^Ucbux xKOR044`..MO)IUP I-.q, J-/,/T,H,PU,-Jd[gd&%d&yFz*:PK UPsubmodulerepo/.git/objects/80/UT i^Ucbux PK fP'C8eeDsubmodulerepo/.git/objects/80/712dfe4a6ef321c3a382faef40701a37f303d0UT /h^Ucbux x+)JMU0a040031QK,O)I-f(}剶UN`:BRK3KR 3K*<7Zg@#ݟ$PPK UPsubmodulerepo/.git/objects/2f/UT i^Ucbux PK iP@tDsubmodulerepo/.git/objects/2f/ac4becbc608e36e97a6b34e99dbd549486848cUT 6h^Ucbux xA 0@Q92IZSADA]644S-oc>,P:.iN+jҵrĮDqEAc-5 A~s 仢a ƺZJB_3[k)}>qqx%Hc; HPK UP submodulerepo/.git/objects/info/UT i^Ucbux PK UP submodulerepo/.git/objects/pack/UT i^Ucbux PK UPsubmodulerepo/.git/refs/UT i^Ucbux PK UP submodulerepo/.git/refs/remotes/UT i^Ucbux PK UP'submodulerepo/.git/refs/remotes/origin/UT i^Ucbux PK UP%Ԡ +submodulerepo/.git/refs/remotes/origin/HEADUT i^Ucbux ref: refs/remotes/origin/master PK UPsubmodulerepo/.git/refs/tags/UT i^Ucbux PK UPsubmodulerepo/.git/refs/heads/UT i^Ucbux PK UPC))$submodulerepo/.git/refs/heads/masterUT i^Ucbux 2fac4becbc608e36e97a6b34e99dbd549486848c PKUP7?Isubmodulerepo/.git/descriptionUT i^Ucbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK UPsubmodulerepo/.git/info/UT i^Ucbux PKUPw=!submodulerepo/.git/info/excludeUT i^Ucbux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PK UPsubmodulerepo/.git/hooks/UT i^Ucbux PKUP*submodulerepo/.git/hooks/commit-msg.sampleUT i^Ucbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKUP +submodulerepo/.git/hooks/post-update.sampleUT i^Ucbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPKUPD?^0submodulerepo/.git/hooks/pre-merge-commit.sampleUT i^Ucbux }OO0O0{{3@m:vj{ʇagJS%6b'D|SgaEh3 (&/X(OT!D< h_<(K4T?#X7 eՇ 6c iHۨ@Y#v/Q{K }mrAvX=!i9."՝/jcjʊ]$k2B,6~t28m5PKUPI +submodulerepo/.git/hooks/pre-receive.sampleUT i^Ucbux uPN0<_1}@[ʑ*HV"!UnM;B;vʣqzg3:ZZ; :*Ҙ lVbCZQm2<+MʷWP& Y:Q;앓hJd텄^ի%i$㒟jBk=hq/BK/E3UM2R;TG֡I/y:M4[,R +~EkâveKL q(h_uԌMkS6g9o5^q{~㱯a~"M%qpQ,kbbPKUPXQ"*submodulerepo/.git/hooks/pre-rebase.sampleUT i^Ucbux XkoF,[E>۸AndıGPbMr3(jsg"-+b﹏ѓofU$zBl4G4?<~NL9, Q*G{ԯhFi-et]ﵱ Zf"cKpZI Ţ$(KKcU<ժ`%xh!)|,̦R?V,f1(y.G*՚%TBBZ358ԕZ3ih- x&Ma0,(|fۄJ,*J1wZqԘ {(ZWjmĚj㢪,<0ɮ% &(^B5xJ4(.ǰ7aBZy"S疲A U" z`Uz3섵F,+>QDO'429wNZfd3̍l0z̦X<~~yvqCwwQ@t % $H+H+b94c(G*@SK#{H7@% w6U{R)@A6/KOSq\e乫 .` 俭ww+EϿS5JfSE,|ݳ&u5h<물>[H( ͉*%!'ۚt<|  뙸sZe>r[oBclvkj+H-QfKv0;HOJFFmXw2\3Npg9=A7:vpưHwn5ҫz2 ۛ2Vh-#kC`;hoVfd%uNISlB ?8%E$%۾#˫YWV uO+k)KQ3u;2l X/3"׏,QOk!(NΗq0ťuI3R7  [)+D7]_]D+`;8$ '*H,_vOx6T[3l4sNypHS 3Y+ED/ 3 @:lJPtT7kQuݞ_IuY2ѿ8D;6 7q)l>Y lhe dƒJXbPX\MʲNIkԇȆ]1(Q4İ_ʴFgdTxq[My^"T(GM&;YpwAg{f;t7^p+ܝ6\`74&J7Г ǓfXz Uxͦn~-Jf:=N*wPò̭Xt]CypHx­RF/)]T5Z7x䃏`?E8 @Թ/.0y^טcczgkPǾ=i_nQ PKUP602submodulerepo/.git/hooks/prepare-commit-msg.sampleUT i^Ucbux TR0|np2.[tRJ3SJobc[r% $9i6GnKB&t;]8xUR`R-j VA*aT+˜,1D+^£$TsQ:*nƝemGTa2 jk!l 0)"@gukeq`FSv*BN7 4aRIZ6fD-;/|{n $ jeUfǰƁiِԈL̅6Db DNc ,\` uc>L90b`:"lѐ%mL:㝉mdS޸hB#W5@ɤQl)0tY ٦<(,ČɩZ{B!"0([J8˽V&ZfTA=;H9/-Ov`&F3j.[K?@#rvDO BYq+7Sy:Qfm(}DJ 9CSQ ^&4VB09eٝh}6ЃV` Br)}??M PKUPL .submodulerepo/.git/hooks/pre-applypatch.sampleUT i^Ucbux U[N1 EK:@VVB=DL&Q>nq,o#S!pWcHƞjO8/ 霷.Eh_p*^L|huV#iت7 QkE!BpzvJrwnes1Ŕ6TIJ#-<7Pfa AI8>:HE\|!(ULPK6bH re6+Ղ;jAnBt5TӧI | (]XAu: ,21K >'|J7VAqTeyiNQy ijZ CץEI阞wn-ni`*\MSN&NRR8Pw;~9NM:ϪH *\!KڍPVLg_ON{0X ~rg+K'cԟ ͺmZc?|#EZ7ΔUj C}qY\lUжͅw>5woFipsr)V}UD͜*gNwgcv6qP *i)f"\Dq )`l->&5فp~Ep YM ҧ.UQhS$= epW^꺇 č'ow<3%w\ǁ55Yx#g!D(zͷ?PKUP!D%u&submodulerepo/.git/hooks/update.sampleUT i^Ucbux WQo"7~f܂`IҷHuM*Um:EfnM]]8HH1g曙ofLd&DA7GI+[2NR\ ELŏLJes2l)Ւ4<rA-2 Pc.xǐ6¤z }D%'~!ƁWfbRDI=SWs@ #'vd"X +#S8 zfJeIhڤܤ E-$_%'-\29LH52 {a֗a2Jpt)П4FaOw~˵E$S͝GKY쮤-# P^5m4#zBxJΘ9}[7wfP \jni}J\kWG:IqL{mk).Z| =mn_mF>0.pJp{q`GI*pݓ !X/u;Կcv?15EVY"eʇ!H}nfs}p-;Xqds|(Fu}Բ+&'@(s5 ϟ =wbmU,LA:fW5 :aNpC;]qc@bS*7USݝpt]+!&_󚓝ZBѓVGkXټ9Ww]gDoeg ƚح,b @vJtՎP#n>=>n%-|@Ux|` :m7Pz9YqnU4k˛3k@jFyFG2LU mmFP%;p$끕^ ؛2/[/vM gaCŴ'QJ۩A!Qޭ5' +G6 $R _>)!JÉl\k,)ޮy?PKUPg*f*submodulerepo/.git/hooks/pre-commit.sampleUT i^Ucbux mTao6l$ `I?fM˰A-Zz|,si s=Oyፎcһn~}WqWRO66>pI7$n|5:U> +tɰwޚZTŽp~-h9XxUKħN52oY*#FF(h&3zke$ RƊ<} 2Ң!O{ W3َ֕^>&ƐTCPn wd/+UFR+]s)4-I}˧V.?KNTy%2^UB??[ jeCצ!A1~R0F,Wa;4c/;(N^,/Asa79:-(f˭8u*'x9xk.p ns[4Qo~laھh[4q6?CUg t1Ҷ'meyyJPK UPAsubmodulerepo/UTi^ux PK UP AHsubmodulerepo/TestGitRepository/UTi^ux PKUP-ZNnsubmodulerepo/.gitmodulesUTi^ux PK UPACsubmodulerepo/.git/UTi^ux PKUP#dsubmodulerepo/.git/indexUTi^ux PKUPsubmodulerepo/.git/configUTi^ux PK UP+issubmodulerepo/.git/HEADUTi^ux PK UPAsubmodulerepo/.git/logs/UTi^ux PKUPIsubmodulerepo/.git/logs/HEADUTi^ux PK UPA&submodulerepo/.git/logs/refs/UTi^ux PK UP#A}submodulerepo/.git/logs/refs/heads/UTi^ux PKUP)submodulerepo/.git/logs/refs/heads/masterUTi^ux PK UP%Asubmodulerepo/.git/logs/refs/remotes/UTi^ux PK UP,A#submodulerepo/.git/logs/refs/remotes/origin/UTi^ux PKUP0submodulerepo/.git/logs/refs/remotes/origin/HEADUTi^ux PKUPT Afrzsubmodulerepo/.git/packed-refsUTi^ux PK UPA8 submodulerepo/.git/objects/UTi^ux PK UPA submodulerepo/.git/objects/17/UTi^ux PK [P0Q3]]D$ submodulerepo/.git/objects/17/982118f7a9c886506bd90f7ad0c9b802d9259cUTh^ux PK UPA submodulerepo/.git/objects/80/UTi^ux PK fP'C8eeD$ submodulerepo/.git/objects/80/712dfe4a6ef321c3a382faef40701a37f303d0UT/h^ux PK UPA submodulerepo/.git/objects/2f/UTi^ux PK iP@tD$S submodulerepo/.git/objects/2f/ac4becbc608e36e97a6b34e99dbd549486848cUT6h^ux PK UP As submodulerepo/.git/objects/info/UTi^ux PK UP A submodulerepo/.git/objects/pack/UTi^ux PK UPA'submodulerepo/.git/refs/UTi^ux PK UP Aysubmodulerepo/.git/refs/remotes/UTi^ux PK UP'Asubmodulerepo/.git/refs/remotes/origin/UTi^ux PK UP%Ԡ +4submodulerepo/.git/refs/remotes/origin/HEADUTi^ux PK UPAsubmodulerepo/.git/refs/tags/UTi^ux PK UPAsubmodulerepo/.git/refs/heads/UTi^ux PK UPC))$hsubmodulerepo/.git/refs/heads/masterUTi^ux PKUP7?Isubmodulerepo/.git/descriptionUTi^ux PK UPAsubmodulerepo/.git/info/UTi^ux PKUPw=!submodulerepo/.git/info/excludeUTi^ux PK UPAsubmodulerepo/.git/hooks/UTi^ux PKUP*1submodulerepo/.git/hooks/commit-msg.sampleUTi^ux PKUP +큌submodulerepo/.git/hooks/post-update.sampleUTi^ux PKUPD?^0{submodulerepo/.git/hooks/pre-merge-commit.sampleUTi^ux PKUPI +submodulerepo/.git/hooks/pre-receive.sampleUTi^ux PKUPXQ"*큒submodulerepo/.git/hooks/pre-rebase.sampleUTi^ux PKUP602!submodulerepo/.git/hooks/prepare-commit-msg.sampleUTi^ux PKUP؏D()%submodulerepo/.git/hooks/pre-push.sampleUTi^ux PKUPO .((submodulerepo/.git/hooks/applypatch-msg.sampleUTi^ux PKUPL .큧)submodulerepo/.git/hooks/pre-applypatch.sampleUTi^ux PKUP 2+submodulerepo/.git/hooks/fsmonitor-watchman.sampleUTi^ux PKUP!D%u&;1submodulerepo/.git/hooks/update.sampleUTi^ux PKUPg*f*6submodulerepo/.git/hooks/pre-commit.sampleUTi^ux PK009libgit2-pygit2-a011e26/test/data/testrepo.zip000066400000000000000000000465521473744024100211060ustar00rootroot00000000000000PK ;fQB testrepo/UT  Q$cbux PK -fQBڎtestrepo/.gitignoreUT  Q$cbux *.swp PK sN>((testrepo/hello.txtUT y_YM$cbux hello world hola mundo bonjour le monde PK ]sTtestrepo/.git/UT a5b$cbux PK]sTXtestrepo/.git/indexUT =5b$cbux s rf```$-{*> K_B0py߾e?tJDMkuK/=$3=/(fod|%y hi,U8OcEd+"lڸ83RsrJ*J?:sgZ5K)/ PK sN>testrepo/.git/refs/UT y_YM$cbux PK sN>testrepo/.git/refs/tags/UT y_YM$cbux PK ,fQBtestrepo/.git/refs/heads/UT  Q$cbux PK ,fQBA=))testrepo/.git/refs/heads/i18nUT  Q$cbux 5470a671a80ac3789f1a6a8cefbcf43ce7af0563 PK sN>[))testrepo/.git/refs/heads/masterUT y_YM$cbux 2be5719152d4f82c7302b1c0932d8e5f0a4a0e98 PK sN>T{))testrepo/.git/ORIG_HEADUT y_YM$cbux 5ebeeebb320790caf276b9fc8b24546d63316533 PK sN>testrepo/.git/logs/UT y_YM$cbux PK sN>testrepo/.git/logs/refs/UT y_YM$cbux PK sN>testrepo/.git/logs/refs/heads/UT y_YM$cbux PK,fQBM}Y0"testrepo/.git/logs/refs/heads/i18nUT  Q$cbux MN@ #"d~ U`QJBgbL6xgzm(Ib+6d(^&:QމUX[>?/|ҫv"ekP:wGKTH}.v::L2g[;"NmؠRb c?Fsklhv=lԷmqd)`^(&5M0kL \[mmGr^NJQ($iԘ*Me> <;_ndܵ}ɒ9+*g~ss~w=K9M@=C/PKsN>va$testrepo/.git/logs/refs/heads/masterUT y_YM$cbux MJ@9E-!T :L2<3*b}N֤-HDYE])xlᎏ5CwyuԅԦx JSP:ٚu:ե:ɛNh4E.j1ۙ<m+u@;i6W(.(RNg[:AUB2:w e5052UuSOY `:QPK-fQB/rEtestrepo/.git/logs/HEADUT  Q$cbux ݊1KEl*ɰ*27PIgKOV>/f, ŒUH8T!)&&";:`Y:e#}oX2|.IC N(ݮLe*MWe o::L<4@D$dkLFK#xgC);3:5 vx=R_U椕qAL2[|E^'/ ܌ܧݪZ|4sӯOpdD0Q+M>JmV)aR`W9Ci#C[Auer e gO:ߛK}57g`e@$.jub~II9ZA|6VĎZ4V-<ʙl,7ӷ U5@uI͒PK]sT !testrepo/.git/configUT _5b$cbux 5 0 )GQؓզPMIҁo8Y _$x"gA:"w\`JB L7{:!JW_ Al5xˬT7D"`{FEoIܚ%Jk7@FKarvPK ,fQBtestrepo/.git/objects/UT  Q$cbux PK sN>testrepo/.git/objects/pack/UT y_YM$cbux PKsN>,tLtestrepo/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.idxUT y_YM$cbux _@]HcDfƂYY)l80;bN,kbn.۷gNIciZi?-tN1u⹼fvڅSygWʷx*t{Zoгy:ӇkN.9]9UM>_mrҘ(gvEsy䍽=]p@9wAegU>Z=ni IXl=Z Yr⋀XLŲle,~x6C}0O>!/.ˌ[!}g%'PKsN>V{Mtestrepo/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.packUT y_YM$cbux  ptf```b Bsv[g9 VMg[|CʑC3Hi(j\1dze:_苵-2a'8Yfu ~.BSH`zۊ[zNh4ͶYԲncfZVEQՇ?i))5mܞ;ɚ6W^?o95=Y~ݿe ׫>ʸ;3~bND~70|Bɫ}͵;2VY.R+>.\nOLT^"ojw{[zl{-ZR10y>O{ Mt?Vd'ٓ'L1 xro5)?vk+,_tk*99/i?`b`cfܻbi'Ok?ԉ!'tz|uSA'XRC4ZD"v*(/~󗄼3e<~0 PK sN>testrepo/.git/objects/info/UT y_YM$cbux PK sN>66 testrepo/.git/objects/info/packsUT y_YM$cbux P pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.pack PK ,fQBtestrepo/.git/objects/54/UT  Q$cbux PK ,fQB.?testrepo/.git/objects/54/70a671a80ac3789f1a6a8cefbcf43ce7af0563UT  Q$cbux x1N1#N^۷ޓPQڋ8)@{D@7ӌyf063XrJf42L0bZݧl$+)")aPi\'\%7x]KnO>p|{|0Lvw~:? 'Z4Uh~XMtPK -fQB+istestrepo/.git/HEADUT  Q$cbux ref: refs/heads/master PKsN>7?Itestrepo/.git/descriptionUT y_YM$cbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK sN>testrepo/.git/info/UT y_YM$cbux PKsN>Xttestrepo/.git/info/refsUT y_YM$cbux U10:!xh"),\g՜Z`jhv eSuʵwz2v6 H "W}uw=!testrepo/.git/info/excludeUT y_YM$cbux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PK ,fQB>ktestrepo/.git/COMMIT_EDITMSGUT  Q$cbux added bye.txt and new PK sN>testrepo/.git/hooks/UT y_YM$cbux PKsN>i 0-testrepo/.git/hooks/prepare-commit-msg.sampleUT y_YM$cbux }S]s0|&3M?,[;L) ig[T$ɏ$9!2y޽S\J]nM2Ѐ?ŪW1*+{@owYeZXsłB)a #Ow 4HNP!:2Sb i&A;flK3Plota˿Q!y4R4m4ր.'.zZiһ@hC$ [ʵ @"n2r㘍.;0-eF7JVޝ0>'h^ndZaet U?DT̴Mxemzƌ; F`um̞CȊtz1E9u&Ʀgcz=8d,jiN[hJ2+Do).L1dYfӷ#G&Pƫz ,8̠́deVlXBQO&3jt4IHS zeEN'DA;V"S ~JHLpxD\?Ga׻aMGt4a_/i"X,m5Kk˔j< PKsN>}.U('testrepo/.git/hooks/post-receive.sampleUT y_YM$cbux eQr0 +T6<IaPlx vv(*C7K]yW'+5ZӅ`p Az3ECV 1$\it%7}pM?[@'g0JIS$KЪ˙W,̓B' CX42׻v DujbaEy`-td>xY]U*][ׅ]ՍRR5Xni{]enm]m]TU;5A *g D b >9BHy.f+ݸL~,"\%0Vx[5|ce 9x>6zIq21s\oPKsN>%testrepo/.git/hooks/commit-msg.sampleUT y_YM$cbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKsN>%[9)testrepo/.git/hooks/pre-applypatch.sampleUT y_YM$cbux }1O0g+i҈ $$uGNzO}})SZ{ۦdgfQJܟptV񜷄NgU@{q8ECf-ں8슗GV $ju,^)i9 G*]3cY%BT@b#_癠&ژ5uvu&QʊzB|l_6oL|ZwipW-oh~PKsN>`X )testrepo/.git/hooks/applypatch-msg.sampleUT y_YM$cbux }=O@ gWIT$$uGY/߹k j~^ܷ;-P xGE0!]( :b 0Cf=  :F{Z:} t&NY55#$|',Z& & qG 1@=¥.K7m p}>/6{sz"LyGwRU]UTIͦf)*AP-6ۏ{[h^%X7zz[=PKsN>hӆ9[%testrepo/.git/hooks/pre-rebase.sampleUT y_YM$cbux Xks7[.?tZkd,g'm2jز$".L3{]E;3{cyl3G.u5jhh~r1 PK^Ԟj_51j!Z;,_khRm@5DI,-uV $z1&Y.)%AGU:rK-C-OM`6fٮӝuAs^n\oXB%,S>\SL+\1Mdm?$susGY𨆪zgv =0i V/&>\ ywFvd;BV5n=v[,t%ZJ{E@'t $H+TN&l+s4"u0kEt xrl$zgg}]cjee"0rRJ PI>cMtJ?%zc Ge KZܫ(9s_}ɿ[?3lXQL<=}A D/HwhcDzRhXB~|{ 7{CXBtv_`o?۪}0o"ߩȠVeAPw0uMӓt*G^h\E OaYmH29MCW%x.~w'gyaWg ϻ&-ڦ3<_: p70|w2ь{CLt:U}9GN+0bU DNAp]<{ʞ_FAp1Ʒ"K D!zZUs߫ Iz۷>^]. u1 &q`GGX[Z,h_ "j.SJr}HkGr(B3L%xw ;@OE5"*efTЈl|Is'rհ3UCȩS˩okKjfQgh x7`V0&ϸoNY`<(A5B/x~f^8_6N0i|J(k&=(X3_(ӬNd3F/~t}}A`Z+.EV x861Y=Ճ ˷!:2˸d4jcĴ3ЕCPi\x)߽y,KfQ_’I >a#H]7tDFևiHP@+~!ėuu-wXa$3'}]5g*3i_v:(lګ.J>6f[y'"@:g.VJ3^!/ddA:ؔ&(`kQ߿v_YuY29D;vͷs(l>YlhnT d*ÃJ:bPX\MNIkǒm3*Q4Īm**06o[y^H"V(GO&F{YOHpwAf{t7ݗ^=u.›x2"g>M@6[c~OzHh^]R}B%ɿ"Ah{yyҬS}"o3ٔ؍/b|^LRKjX(o ExnJWO_.A|H!Dp"~ 5* c4/PKsN>lz&testrepo/.git/hooks/post-commit.sampleUT y_YM$cbux -A b WM'?!PKeIU/^g^9IQ;Auk -PCT8JosԯNr*{ k-tq$X;Cm_b)}P PKsN> \*%testrepo/.git/hooks/pre-commit.sampleUT y_YM$cbux }T[k:~ Iw!I’}9Slڒ+ɛl)$[SܾYarH+M$ڮaِ+OV'b-v~Bn'tW!Ȁ[f2.:,/eqŵ_բUU0/狤R U3}$$ 61r+\TDp;r[No{ΓqoM=Rv!testrepo/.git/hooks/update.sampleUT y_YM$cbux WQo6~~U6ڰ${K ] }PlPLY\ddKk'HIwwwݠK%l8J=s4dM3?jZI&26הjA\ 9esa(1O|dcHkaRb|i"قNXe3|)hfS1"N$ž) W Q2s,uY=S2$in4SnRﺺYFSNN6i\uzrTZ/xVFM,3qáwWXH N VXNH8KysΌP2u@Os&-C0p% exV䖇u’ד ^,Q&$ {gaa2JOpt)_4FaOw˥E$SͭGKY-# P^4#jDxJΘ)}[wzP jni~Jvܼl/X5Zuv꽷-vh3v~L~w z@}a<øX n:1Ł #%*'uO*4`yl4jSpLsCUK˃Y!G!DʔCH}nfsyp ;XrdS|(Fu}Բ+F'@(su>zo)}UQ#?zЮg713vMx^FQe `wp'vL#b>:UyVrnu|ǵҵv^|kNvj COj[Qaudl^+>uB8t65};[u;Yrmu ˡF|z|JZC&/ JWb;uboϽ'=Lk;3e^·_Rx?ϪiG-|mi}C, S-C[kNǍRH|lSBEw= ֤UHS]݅PKsN> &testrepo/.git/hooks/post-update.sampleUT y_YM$cbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPK ;fQBQ testrepo/bye.txtUT  Q$cbux bye world PK ;fQB Atestrepo/UT Qux PK -fQBڎCtestrepo/.gitignoreUT Qux PK sN>((testrepo/hello.txtUTy_YMux PK ]sTA testrepo/.git/UTa5bux PK]sTXRtestrepo/.git/indexUT=5bux PK sN>A1testrepo/.git/refs/UTy_YMux PK sN>A~testrepo/.git/refs/tags/UTy_YMux PK ,fQBAtestrepo/.git/refs/heads/UT Qux PK ,fQBA=))#testrepo/.git/refs/heads/i18nUT Qux PK sN>[))testrepo/.git/refs/heads/masterUTy_YMux PK sN>T{))%testrepo/.git/ORIG_HEADUTy_YMux PK sN>Atestrepo/.git/logs/UTy_YMux PK sN>Atestrepo/.git/logs/refs/UTy_YMux PK sN>A>testrepo/.git/logs/refs/heads/UTy_YMux PK,fQBM}Y0"testrepo/.git/logs/refs/heads/i18nUT Qux PKsN>va$"testrepo/.git/logs/refs/heads/masterUTy_YMux PK-fQB/rEntestrepo/.git/logs/HEADUT Qux PK]sT ! testrepo/.git/configUT_5bux PK ,fQBA testrepo/.git/objects/UT Qux PK sN>A testrepo/.git/objects/pack/UTy_YMux PKsN>,tL$2 testrepo/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.idxUTy_YMux PKsN>V{M$testrepo/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.packUTy_YMux PK 'fQBAtestrepo/.git/objects/32/UT Qux PK 'fQBip?$testrepo/.git/objects/32/3fae03f4606ea9991df8befbb2fca795e648faUT Qux PK 'fQBAtestrepo/.git/objects/79/UT Qux PK 'fQBR5d$$?$testrepo/.git/objects/79/9baaf570f1f03a22ef0af4f2cfc36c836635f8UT Qux PK ,fQBAutestrepo/.git/objects/97/UT Qux PK ,fQBw_oo?$testrepo/.git/objects/97/33b6ee7f6b3cb0a276586a413c8b281baeb8bdUT Qux PK sN>Atestrepo/.git/objects/info/UTy_YMux PK sN>66 testrepo/.git/objects/info/packsUTy_YMux PK ,fQBAtestrepo/.git/objects/54/UT Qux PK ,fQB.?$testrepo/.git/objects/54/70a671a80ac3789f1a6a8cefbcf43ce7af0563UT Qux PK -fQB+istestrepo/.git/HEADUT Qux PKsN>7?Ittestrepo/.git/descriptionUTy_YMux PK sN>Atestrepo/.git/info/UTy_YMux PKsN>XtStestrepo/.git/info/refsUTy_YMux PKsN>w=!testrepo/.git/info/excludeUTy_YMux PK ,fQB>ktestrepo/.git/COMMIT_EDITMSGUT Qux PK sN>Aitestrepo/.git/hooks/UTy_YMux PKsN>i 0-큷testrepo/.git/hooks/prepare-commit-msg.sampleUTy_YMux PKsN>}.U('큹testrepo/.git/hooks/post-receive.sampleUTy_YMux PKsN>%o!testrepo/.git/hooks/commit-msg.sampleUTy_YMux PKsN>%[9)#testrepo/.git/hooks/pre-applypatch.sampleUTy_YMux PKsN>`X )%%testrepo/.git/hooks/applypatch-msg.sampleUTy_YMux PKsN>hӆ9[%큒&testrepo/.git/hooks/pre-rebase.sampleUTy_YMux PKsN>lz&.testrepo/.git/hooks/post-commit.sampleUTy_YMux PKsN> \*%큨/testrepo/.git/hooks/pre-commit.sampleUTy_YMux PKsN>Rv!c3testrepo/.git/hooks/update.sampleUTy_YMux PKsN> &48testrepo/.git/hooks/post-update.sampleUTy_YMux PK ;fQBQ 9testrepo/bye.txtUT Qux PK22r9libgit2-pygit2-a011e26/test/data/testrepoformerging.zip000066400000000000000000000712411473744024100231570ustar00rootroot00000000000000PK mCtestrepoformerging/UT V$Rcbux PK mCtestrepoformerging/.git/UT V$Rcbux PK sN>testrepoformerging/.git/logs/UT y_YMcbux PKmCzSDi!testrepoformerging/.git/logs/HEADUT V$Rcbux ͘nIw/AX}>D hr@Ⲻi5xja_l !B_zFcWUUwҺ!*hI'FW lr$yVEgqgqY'eQvy"Or SrDn7S;m5üiNI7lr$i:z‘L樕QI8*D Dc,H`yS۶ lmK RX8HGJ椕pne  /FeSm(Yw.׻f> ="z/85HhRK%uB0k^@HRM~bSm{T9x`ZbB ݬq7<%߬;Huࢩfɛz끜7w'/plsXf2霺PPІ7!c]bL>$)HTi1| F%'}$=~ns0T)‡j$oCrU_*3w>V%~p*Qmx•=IP!ML{:F/j"m6Jy4%!9#lf<_vN ^}TۭV,4HΏ,\r91+=E~SdTÒÝӰV)_ڝ3A/C4%zI_q$Ȃ "l,۲,"\aM٣Diߍ8u&fynܳ2O"sv\OmA3=*SeA.e1s)Abk=+`a3|B䎶fxRåo*PK sN>"testrepoformerging/.git/logs/refs/UT y_YMcbux PK mC(testrepoformerging/.git/logs/refs/heads/UT #Rcbux PK,fQBM}Y0,testrepoformerging/.git/logs/refs/heads/i18nUT  Qcbux MN@ #"d~ U`QJBgbL6xgzm(Ib+6d(^&:QމUX[>?/|ҫv"ekP:wGKTH}.v::L2g[;"NmؠRb c?Fsklhv=lԷmqd)`^(&5M0kL \[mmGr^NJQ($iԘ*Me> <;_ndܵ}ɒ9+*g~ss~w=K9M@=C/PKόC:2testrepoformerging/.git/logs/refs/heads/pep8-fixesUT Rcbux 1N1k푢ݳ[G *J$ʵ14trYJ2$9-[?Q ޤ(l.ճY q pxJS^wϋ{hlj\d4#F1)q =^HY`sMY"ywh^m} ) ~_PK CDD1testrepoformerging/.git/logs/refs/heads/ff-branchUT ԜRcbux A 0Ez 2I&7p?L4XH^.<@:u*(^PG:،d%xWj\[P|T==b0f^8 ͒ Bi]}-E91G#b6Yc\`2bb+s\Z|dX@ilPKmCa`'O8testrepoformerging/.git/logs/refs/heads/branch-conflictsUT ?$Rcbux J1DW]NI:x$=Hw@?`K*'a T55#DL)ZT[YW &kCE͖Znti~?_l<[ PKsN>va.testrepoformerging/.git/logs/refs/heads/masterUT y_YMcbux MJ@9E-!T :L2<3*b}N֤-HDYE])xlᎏ5CwyuԅԦx JSP:ٚu:ե:ɛNh4E.j1ۙ<m+u@;i6W(.(RNg[:AUB2:w e5052UuSOY `:QPK 퐂C[))!testrepoformerging/.git/ORIG_HEADUT MRcbux 2be5719152d4f82c7302b1c0932d8e5f0a4a0e98 PKsN>7?I#testrepoformerging/.git/descriptionUT y_YMcbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK sN>testrepoformerging/.git/info/UT y_YMcbux PKsN>w=!$testrepoformerging/.git/info/excludeUT y_YMcbux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PKsN>Xt!testrepoformerging/.git/info/refsUT y_YMcbux U10:!xh"),\g՜Z`jhv eSuʵwz2v6 H "W}u%testrepoformerging/.git/objects/pack/UT y_YMcbux PKsN>V{Wtestrepoformerging/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.packUT y_YMcbux  ptf```b Bsv[g9 VMg[|CʑC3Hi(j\1dze:_苵-2a'8Yfu ~.BSH`zۊ[zNh4ͶYԲncfZVEQՇ?i))5mܞ;ɚ6W^?o95=Y~ݿe ׫>ʸ;3~bND~70|Bɫ}͵;2VY.R+>.\nOLT^"ojw{[zl{-ZR10y>O{ Mt?Vd'ٓ'L1 xro5)?vk+,_tk*99/i?`b`cfܻbi'Ok?ԉ!'tz|uSA',tVtestrepoformerging/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.idxUT y_YMcbux _@]HcDfƂYY)l80;bN,kbn.۷gNIciZi?-tN1u⹼fvڅSygWʷx*t{Zoгy:ӇkN.9]9UM>_mrҘ(gvEsy䍽=]p@9wAegU>Z=ni IXl=Z Yr⋀XLŲle,~x6C}0O>!/.ˌ[!}g%'PK 'fQB#testrepoformerging/.git/objects/32/UT  Qcbux PK 'fQBipItestrepoformerging/.git/objects/32/3fae03f4606ea9991df8befbb2fca795e648faUT  Qcbux xKOR0gHOJ,"zPK C#testrepoformerging/.git/objects/76/UT ԜRcbux PK CyyItestrepoformerging/.git/objects/76/da4e485dfdbd64dc995708f37d01be19b1fbffUT ԜRcbux x+)JMU044a040031QK,L/Je.۷gNIciZAUeT0,U8OcEd+"lڸSssS,XRC4ZD"v*(/~󗄼3e<~0 PK ,fQB#testrepoformerging/.git/objects/54/UT  Qcbux PK ,fQB.Itestrepoformerging/.git/objects/54/70a671a80ac3789f1a6a8cefbcf43ce7af0563UT  Qcbux x1N1#N^۷ޓPQڋ8)@{D@7ӌyf063XrJf42L0bZݧl$+)")aPi\'\%7x]KnO>p|{|0Lvw~:? 'Z4Uh~XMtPK mC#testrepoformerging/.git/objects/1b/UT ?$Rcbux PK mCkWMItestrepoformerging/.git/objects/1b/2bae55ac95a4be3f8983b86cd579226d0eb247UT ?$Rcbux xMj0F)f(xtt?F2BENNjuJpMˊCl)f`hI\nw 4^YoI'diQy S{mSbs G]|n\֏X/гjJ+5 :* W8Z=S!=#gOlPK sN>%testrepoformerging/.git/objects/info/UT y_YMcbux PK sN>66*testrepoformerging/.git/objects/info/packsUT y_YMcbux P pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.pack PK mC#testrepoformerging/.git/objects/8f/UT =$Rcbux PK mC--Itestrepoformerging/.git/objects/8f/a7ba34297098f11f19980a5096087d4c43a524UT =$Rcbux xKOR0d(,V(Q((/NUHTHKL.E PK όC#testrepoformerging/.git/objects/03/UT Rcbux PK όC*Itestrepoformerging/.git/objects/03/490f16b15a09913edb3a067a3dc67fbb8d41f1UT Rcbux xKj0 )/?Ӝf$#5''h!c-"]a; UZ`dKBʙ?gg8?CPK C#testrepoformerging/.git/objects/37/UT  ԜRcbux PK CxiItestrepoformerging/.git/objects/37/fc853ca1c576800d9a79de5fe83efb8ed0ebb2UT  ԜRcbux xKORd(OIMU-zPK C#testrepoformerging/.git/objects/09/UT Rcbux PK Cc4wItestrepoformerging/.git/objects/09/07563af06c7464d62a70cdd135a6ba7d2b41d8UT Rcbux xKOR04`HLU(/I6=PK uC#testrepoformerging/.git/objects/d5/UT nRcbux PK uCYSOttItestrepoformerging/.git/objects/d5/fb8a80eef91402a6faf2af3b0f56175b44a23cUT nRcbux x+)JMU044`040031QK,L/Je.۷gNIciZAU%UT0pY})IUp]ڎ7J2Rsr*m1"2SQm-PK sN>testrepoformerging/.git/hooks/UT y_YMcbux PKsN> \*/testrepoformerging/.git/hooks/pre-commit.sampleUT y_YMcbux }T[k:~ Iw!I’}9Slڒ+ɛl)$[SܾYarH+M$ڮaِ+OV'b-v~Bn'tW!Ȁ[f2.:,/eqŵ_բUU0/狤R U3}$$ 61r+\TDp;r[No{ΓqoM=%[93testrepoformerging/.git/hooks/pre-applypatch.sampleUT y_YMcbux }1O0g+i҈ $$uGNzO}})SZ{ۦdgfQJܟptV񜷄NgU@{q8ECf-ں8슗GV $ju,^)i9 G*]3cY%BT@b#_癠&ژ5uvu&QʊzB|l_6oL|ZwipW-oh~PKsN>/testrepoformerging/.git/hooks/commit-msg.sampleUT y_YMcbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKsN>hӆ9[/testrepoformerging/.git/hooks/pre-rebase.sampleUT y_YMcbux Xks7[.?tZkd,g'm2jز$".L3{]E;3{cyl3G.u5jhh~r1 PK^Ԟj_51j!Z;,_khRm@5DI,-uV $z1&Y.)%AGU:rK-C-OM`6fٮӝuAs^n\oXB%,S>\SL+\1Mdm?$susGY𨆪zgv =0i V/&>\ ywFvd;BV5n=v[,t%ZJ{E@'t $H+TN&l+s4"u0kEt xrl$zgg}]cjee"0rRJ PI>cMtJ?%zc Ge KZܫ(9s_}ɿ[?3lXQL<=}A D/HwhcDzRhXB~|{ 7{CXBtv_`o?۪}0o"ߩȠVeAPw0uMӓt*G^h\E OaYmH29MCW%x.~w'gyaWg ϻ&-ڦ3<_: p70|w2ь{CLt:U}9GN+0bU DNAp]<{ʞ_FAp1Ʒ"K D!zZUs߫ Iz۷>^]. u1 &q`GGX[Z,h_ "j.SJr}HkGr(B3L%xw ;@OE5"*efTЈl|Is'rհ3UCȩS˩okKjfQgh x7`V0&ϸoNY`<(A5B/x~f^8_6N0i|J(k&=(X3_(ӬNd3F/~t}}A`Z+.EV x861Y=Ճ ˷!:2˸d4jcĴ3ЕCPi\x)߽y,KfQ_’I >a#H]7tDFևiHP@+~!ėuu-wXa$3'}]5g*3i_v:(lګ.J>6f[y'"@:g.VJ3^!/ddA:ؔ&(`kQ߿v_YuY29D;vͷs(l>YlhnT d*ÃJ:bPX\MNIkǒm3*Q4Īm**06o[y^H"V(GO&F{YOHpwAf{t7ݗ^=u.›x2"g>M@6[c~OzHh^]R}B%ɿ"Ah{yyҬS}"o3ٔ؍/b|^LRKjX(o ExnJWO_.A|H!Dp"~ 5* c4/PKsN>}.U(1testrepoformerging/.git/hooks/post-receive.sampleUT y_YMcbux eQr0 +T6<IaPlx vv(*C7K]yW'+5ZӅ`p Az3ECV 1$\it%7}pM?[@'g0JIS$KЪ˙W,̓B' CX42׻v DujbaEy`-td>xY]U*][ׅ]ՍRR5Xni{]enm]m]TU;5A *g D b >9BHy.f+ݸL~,"\%0Vx[5|ce 9x>6zIq21s\oPKsN>Rv+testrepoformerging/.git/hooks/update.sampleUT y_YMcbux WQo6~~U6ڰ${K ] }PlPLY\ddKk'HIwwwݠK%l8J=s4dM3?jZI&26הjA\ 9esa(1O|dcHkaRb|i"قNXe3|)hfS1"N$ž) W Q2s,uY=S2$in4SnRﺺYFSNN6i\uzrTZ/xVFM,3qáwWXH N VXNH8KysΌP2u@Os&-C0p% exV䖇u’ד ^,Q&$ {gaa2JOpt)_4FaOw˥E$SͭGKY-# P^4#jDxJΘ)}[wzP jni~Jvܼl/X5Zuv꽷-vh3v~L~w z@}a<øX n:1Ł #%*'uO*4`yl4jSpLsCUK˃Y!G!DʔCH}nfsyp ;XrdS|(Fu}Բ+F'@(su>zo)}UQ#?zЮg713vMx^FQe `wp'vL#b>:UyVrnu|ǵҵv^|kNvj COj[Qaudl^+>uB8t65};[u;Yrmu ˡF|z|JZC&/ JWb;uboϽ'=Lk;3e^·_Rx?ϪiG-|mi}C, S-C[kNǍRH|lSBEw= ֤UHS]݅PKsN> 0testrepoformerging/.git/hooks/post-update.sampleUT y_YMcbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPKsN>`X 3testrepoformerging/.git/hooks/applypatch-msg.sampleUT y_YMcbux }=O@ gWIT$$uGY/߹k j~^ܷ;-P xGE0!]( :b 0Cf=  :F{Z:} t&NY55#$|',Z& & qG 1@=¥.K7m p}>/6{sz"LyGwRU]UTIͦf)*AP-6ۏ{[h^%X7zz[=PKsN>i 07testrepoformerging/.git/hooks/prepare-commit-msg.sampleUT y_YMcbux }S]s0|&3M?,[;L) ig[T$ɏ$9!2y޽S\J]nM2Ѐ?ŪW1*+{@owYeZXsłB)a #Ow 4HNP!:2Sb i&A;flK3Plota˿Q!y4R4m4ր.'.zZiһ@hC$ [ʵ @"n2r㘍.;0-eF7JVޝ0>'h^ndZaet U?DT̴Mxemzƌ; F`um̞CȊtz1E9u&Ʀgcz=8d,jiN[hJ2+Do).L1dYfӷ#G&Pƫz ,8̠́deVlXBQO&3jt4IHS zeEN'DA;V"S ~JHLpxD\?Ga׻aMGt4a_/i"X,m5Kk˔j< PKsN>lz0testrepoformerging/.git/hooks/post-commit.sampleUT y_YMcbux -A b WM'?!PKeIU/^g^9IQ;Auk -PCT8JosԯNr*{ k-tq$X;Cm_b)}P PK mC&&testrepoformerging/.git/COMMIT_EDITMSGUT ?$Rcbux commit to provoke a conflict PKmCHmtestrepoformerging/.git/indexUT V$Rcbux s rf```*a@F30c``Ka`h\؄C-٥Sؼ'jZGC \z%yE P4g;#DרfRC4ZD"v*3#5''_Xզ?Y&NPKCS(Btestrepoformerging/.git/configUT 5Rcbux 5 @ DWG MQHf5d˄LfQߡR%ItA$[JĸʄNC5D=!EN,sdVLeaz/úg3Yດ> Ltestrepoformerging/.git/refs/UT y_YMcbux PK mC#testrepoformerging/.git/refs/heads/UT ?$Rcbux PK ,fQBA=))'testrepoformerging/.git/refs/heads/i18nUT  Qcbux 5470a671a80ac3789f1a6a8cefbcf43ce7af0563 PK όC))-testrepoformerging/.git/refs/heads/pep8-fixesUT Rcbux 03490f16b15a09913edb3a067a3dc67fbb8d41f1 PK C΄F)),testrepoformerging/.git/refs/heads/ff-branchUT ԜRcbux e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87 PK mC$u))3testrepoformerging/.git/refs/heads/branch-conflictsUT ?$Rcbux 1b2bae55ac95a4be3f8983b86cd579226d0eb247 PK sN>[)))testrepoformerging/.git/refs/heads/masterUT y_YMcbux 2be5719152d4f82c7302b1c0932d8e5f0a4a0e98 PK sN>"testrepoformerging/.git/refs/tags/UT y_YMcbux PK sN>((testrepoformerging/hello.txtUT y_YMcbux hello world hola mundo bonjour le monde PK mCڎtestrepoformerging/.gitignoreUT V$Rcbux *.swp PK mCAtestrepoformerging/UTV$Rux PK mCAMtestrepoformerging/.git/UTV$Rux PK sN>Atestrepoformerging/.git/logs/UTy_YMux PKmCzSDi!testrepoformerging/.git/logs/HEADUTV$Rux PK sN>"Atestrepoformerging/.git/logs/refs/UTy_YMux PK mC(Atestrepoformerging/.git/logs/refs/heads/UT#Rux PK,fQBM}Y0,Stestrepoformerging/.git/logs/refs/heads/i18nUT Qux PKόC:2testrepoformerging/.git/logs/refs/heads/pep8-fixesUTRux PK CDD1testrepoformerging/.git/logs/refs/heads/ff-branchUTԜRux PKmCa`'O8* testrepoformerging/.git/logs/refs/heads/branch-conflictsUT?$Rux PKsN>va.Q testrepoformerging/.git/logs/refs/heads/masterUTy_YMux PK 퐂C[))! testrepoformerging/.git/ORIG_HEADUTMRux PKsN>7?I#+ testrepoformerging/.git/descriptionUTy_YMux PK sN>A testrepoformerging/.git/info/UTy_YMux PKsN>w=!$ testrepoformerging/.git/info/excludeUTy_YMux PKsN>Xt!)testrepoformerging/.git/info/refsUTy_YMux PK mC+istestrepoformerging/.git/HEADUTV$Rux PK mC AItestrepoformerging/.git/objects/UT?$Rux PK όC#Atestrepoformerging/.git/objects/de/UTRux PK όC'75QQI$testrepoformerging/.git/objects/de/9b035de7240be11378cc214ac45c427d80e241UTRux PK sN>%Atestrepoformerging/.git/objects/pack/UTy_YMux PKsN>V{W$3testrepoformerging/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.packUTy_YMux PKsN>,tV$?testrepoformerging/.git/objects/pack/pack-e6fbc15b315a0eab6005c44e5b7054b1f0043f39.idxUTy_YMux PK 'fQB#Atestrepoformerging/.git/objects/32/UT Qux PK 'fQBipI$testrepoformerging/.git/objects/32/3fae03f4606ea9991df8befbb2fca795e648faUT Qux PK C#Atestrepoformerging/.git/objects/76/UTԜRux PK CyyI$ testrepoformerging/.git/objects/76/da4e485dfdbd64dc995708f37d01be19b1fbffUTԜRux PK mC#Atestrepoformerging/.git/objects/d7/UT?$Rux PK mCzNWWI$dtestrepoformerging/.git/objects/d7/f0a991a55badf5fd32e7ffa9925e240274997cUT?$Rux PK C#A>testrepoformerging/.git/objects/e9/UTԜRux PK CwG%I$testrepoformerging/.git/objects/e9/7b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87UTԜRux PK ,fQB#Atestrepoformerging/.git/objects/97/UT Qux PK ,fQBw_ooI$"testrepoformerging/.git/objects/97/33b6ee7f6b3cb0a276586a413c8b281baeb8bdUT Qux PK ,fQB#Atestrepoformerging/.git/objects/54/UT Qux PK ,fQB.I$qtestrepoformerging/.git/objects/54/70a671a80ac3789f1a6a8cefbcf43ce7af0563UT Qux PK mC#A testrepoformerging/.git/objects/1b/UT?$Rux PK mCkWMI$!testrepoformerging/.git/objects/1b/2bae55ac95a4be3f8983b86cd579226d0eb247UT?$Rux PK sN>%A3"testrepoformerging/.git/objects/info/UTy_YMux PK sN>66*"testrepoformerging/.git/objects/info/packsUTy_YMux PK mC#A,#testrepoformerging/.git/objects/8f/UT=$Rux PK mC--I$#testrepoformerging/.git/objects/8f/a7ba34297098f11f19980a5096087d4c43a524UT=$Rux PK όC#A9$testrepoformerging/.git/objects/03/UTRux PK όC*I$$testrepoformerging/.git/objects/03/490f16b15a09913edb3a067a3dc67fbb8d41f1UTRux PK 'fQB#A%testrepoformerging/.git/objects/79/UT Qux PK 'fQBR5d$$I$&testrepoformerging/.git/objects/79/9baaf570f1f03a22ef0af4f2cfc36c836635f8UT Qux PK 퐂C#A&testrepoformerging/.git/objects/c6/UTMRux PK 퐂CI$"'testrepoformerging/.git/objects/c6/5d223d98aaa8726b4ac359af2e270ee348ad2aUTMRux PK C#A5(testrepoformerging/.git/objects/37/UT ԜRux PK CxiI$(testrepoformerging/.git/objects/37/fc853ca1c576800d9a79de5fe83efb8ed0ebb2UT ԜRux PK C#A-)testrepoformerging/.git/objects/09/UTRux PK Cc4wI$)testrepoformerging/.git/objects/09/07563af06c7464d62a70cdd135a6ba7d2b41d8UTRux PK uC#A'*testrepoformerging/.git/objects/d5/UTnRux PK uCYSOttI$*testrepoformerging/.git/objects/d5/fb8a80eef91402a6faf2af3b0f56175b44a23cUTnRux PK sN>A{+testrepoformerging/.git/hooks/UTy_YMux PKsN> \*/+testrepoformerging/.git/hooks/pre-commit.sampleUTy_YMux PKsN>%[93큘/testrepoformerging/.git/hooks/pre-applypatch.sampleUTy_YMux PKsN>/1testrepoformerging/.git/hooks/commit-msg.sampleUTy_YMux PKsN>hӆ9[/b3testrepoformerging/.git/hooks/pre-rebase.sampleUTy_YMux PKsN>}.U(1큨;testrepoformerging/.git/hooks/post-receive.sampleUTy_YMux PKsN>Rv+h=testrepoformerging/.git/hooks/update.sampleUTy_YMux PKsN> 0CBtestrepoformerging/.git/hooks/post-update.sampleUTy_YMux PKsN>`X 37Ctestrepoformerging/.git/hooks/applypatch-msg.sampleUTy_YMux PKsN>i 07큮Dtestrepoformerging/.git/hooks/prepare-commit-msg.sampleUTy_YMux PKsN>lz0큺Gtestrepoformerging/.git/hooks/post-commit.sampleUTy_YMux PK mC&&Htestrepoformerging/.git/COMMIT_EDITMSGUT?$Rux PKmCHmItestrepoformerging/.git/indexUTV$Rux PKCS(BJtestrepoformerging/.git/configUT5Rux PK sN>AJtestrepoformerging/.git/refs/UTy_YMux PK mC#ACKtestrepoformerging/.git/refs/heads/UT?$Rux PK ,fQBA=))'Ktestrepoformerging/.git/refs/heads/i18nUT Qux PK όC))-*Ltestrepoformerging/.git/refs/heads/pep8-fixesUTRux PK C΄F)),Ltestrepoformerging/.git/refs/heads/ff-branchUTԜRux PK mC$u))3IMtestrepoformerging/.git/refs/heads/branch-conflictsUT?$Rux PK sN>[)))Mtestrepoformerging/.git/refs/heads/masterUTy_YMux PK sN>"AkNtestrepoformerging/.git/refs/tags/UTy_YMux PK sN>((Ntestrepoformerging/hello.txtUTy_YMux PK mCڎEOtestrepoformerging/.gitignoreUTV$Rux PKMM"Olibgit2-pygit2-a011e26/test/data/testrepopacked.zip000066400000000000000000000432711473744024100222510ustar00rootroot00000000000000PK ;fQBtestrepopacked/UT  Qcbux PK -fQBڎtestrepopacked/.gitignoreUT  Qcbux *.swp PK sN>((testrepopacked/hello.txtUT y_YMcbux hello world hola mundo bonjour le monde PK i|Qtestrepopacked/.git/UT f_cbux PKi|Qu~testrepopacked/.git/packed-refsUT f_cbux M0 ~E$o\V+TMtlėql"MGk' 1d-!E-%BYgɤ7ߴ>o%f[lt5(:lw%*P !PK sN>testrepopacked/.git/refs/UT y_YMcbux PK sN>testrepopacked/.git/refs/tags/UT y_YMcbux PK i|Qtestrepopacked/.git/refs/heads/UT f_cbux PK sN>T{))testrepopacked/.git/ORIG_HEADUT y_YMcbux 5ebeeebb320790caf276b9fc8b24546d63316533 PK i|Qtestrepopacked/.git/logs/UT f_cbux PK i|Qtestrepopacked/.git/logs/HEADUT f_cbux PK sN>testrepopacked/.git/logs/refs/UT y_YMcbux PK i|Q$testrepopacked/.git/logs/refs/heads/UT f_cbux PK i|Q*testrepopacked/.git/logs/refs/heads/masterUT f_cbux PK i|Q(testrepopacked/.git/logs/refs/heads/i18nUT f_cbux PKʋPBXtestrepopacked/.git/configUT Qcbux 5 0 )GQؓզFv/N$I߮Q%S=5lIĸJD]3=!F,s`VLu`y+M0AͥoEiتlJ`> Llw{˴͂2,b}PK i|Qtestrepopacked/.git/objects/UT f_cbux PK i|Q!testrepopacked/.git/objects/pack/UT f_cbux PKi|Q <֮g(Rtestrepopacked/.git/objects/pack/pack-6371e8325a610822b319bbd75ccda015ae31a08d.idxUT f_cbux _@]Hc̄3a, f'sh `"1a>21?,0B  [KyOԴVZN Ciɺ}\^|31I[9SǾߛ,Y˛kWhp]8w||kBif˱ns}_QwZ#2Rd] 8eRˉe+W̗yq6bpS?*!Ɇ)[M6I7LI7.1bdSeV YRc:=m:[63#E]#A)&nY9{ׂs}tpNsglXThӭ!nޥ |[i wETT7W_i,gwW>Zoгy:ӇkN.9]9UM>_mrҘ({/$1<w8L+7tя[VjH3.ps^-WmX5 6} |ΤY`qYTSz~I e]\(*Ci1g3\QZ4w{[=|#PKi|QdStestrepopacked/.git/objects/pack/pack-6371e8325a610822b319bbd75ccda015ae31a08d.packUT f_cbux wP(CDA+C%P $H"T,EQ8 aFe!P0 (C!Lb λ}YTߥo)?3nPp7l.\k3gw:,L!;ejJX d:*9FRwe6VGADGNtV )uxDlKעiI1{9ԕW8qг ȌwO#%MJ]y\"TM7̯uBU7m. vӥ |a+mrI`Y5~'.i,h /H.MًۦFU^Tq}-}ւr<ۦar(42۽꜊7B ~ |}KL-` >,6z:ս'.%7mYY]oL½P}qJbr3 -=d!agWi$S5 J[* Oư\|˗Nq@'c4R R\Dr#1F'{Y i3| 2c_;s -I NHwNՄ: a Ʋn6Xݽ|T=i sO3H⛽?PAWWBl# 4Cw%K)a_oE:řXZ81n*m~A63ZjS!jWҢ( ss]h= 3vCN.8kG%w{1(зX=.cD=$y4KʃJIGj Fxst- [:SSɯ0m^Wёm`QQ⃳64 9=Y̿aAB^o{ػ.R@v- ǻk!KK+Hw5CZDQW~V!*< |Aj [Hd+abz2N  ېK?΋(=g:ug˖!PK i|Q!testrepopacked/.git/objects/info/UT f_cbux PKi|Qr-testrepopacked/.git/objects/info/commit-graphUT f_cbux sv`ddftqc dpvq 70k3Db1(cQL=pbЕ:LLwbVCFkLWj9RpRY=|y59n߻FN}*[%$708k^jU6}Qgr֮ysʒ zz{O ;TtT+;7ğRɕ/@yK7(zi\&馟)tYF(R<=Ӎ϶ٰ,"Ѧ[Czݎ 3j /|Q2HGʾnT Мsf-.o]ev{շf^÷xڋuMunZ'jX|S~U7?Itestrepopacked/.git/descriptionUT y_YMcbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK i|Qtestrepopacked/.git/info/UT f_cbux PKi|Q\Yttestrepopacked/.git/info/refsUT f_cbux M90:9@A&)m_i FwȚQ# ;};h(ZBhb`ZR!ǯ;PKsN>w=! testrepopacked/.git/info/excludeUT y_YMcbux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PK ,fQB>k"testrepopacked/.git/COMMIT_EDITMSGUT  Qcbux added bye.txt and new PK-fQBdtestrepopacked/.git/indexUT  Qcbux s rf```@GU(zhL :V K_դ1py߾e?tJDMkuK/=$3=/( +$]U󍌯̳nD3Oc!c-"]a;WRQ°}efR;hnPK sN>testrepopacked/.git/hooks/UT y_YMcbux PKsN>i 03testrepopacked/.git/hooks/prepare-commit-msg.sampleUT y_YMcbux }S]s0|&3M?,[;L) ig[T$ɏ$9!2y޽S\J]nM2Ѐ?ŪW1*+{@owYeZXsłB)a #Ow 4HNP!:2Sb i&A;flK3Plota˿Q!y4R4m4ր.'.zZiһ@hC$ [ʵ @"n2r㘍.;0-eF7JVޝ0>'h^ndZaet U?DT̴Mxemzƌ; F`um̞CȊtz1E9u&Ʀgcz=8d,jiN[hJ2+Do).L1dYfӷ#G&Pƫz ,8̠́deVlXBQO&3jt4IHS zeEN'DA;V"S ~JHLpxD\?Ga׻aMGt4a_/i"X,m5Kk˔j< PKsN>}.U(-testrepopacked/.git/hooks/post-receive.sampleUT y_YMcbux eQr0 +T6<IaPlx vv(*C7K]yW'+5ZӅ`p Az3ECV 1$\it%7}pM?[@'g0JIS$KЪ˙W,̓B' CX42׻v DujbaEy`-td>xY]U*][ׅ]ՍRR5Xni{]enm]m]TU;5A *g D b >9BHy.f+ݸL~,"\%0Vx[5|ce 9x>6zIq21s\oPKsN>+testrepopacked/.git/hooks/commit-msg.sampleUT y_YMcbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKsN>%[9/testrepopacked/.git/hooks/pre-applypatch.sampleUT y_YMcbux }1O0g+i҈ $$uGNzO}})SZ{ۦdgfQJܟptV񜷄NgU@{q8ECf-ں8슗GV $ju,^)i9 G*]3cY%BT@b#_癠&ژ5uvu&QʊzB|l_6oL|ZwipW-oh~PKsN>`X /testrepopacked/.git/hooks/applypatch-msg.sampleUT y_YMcbux }=O@ gWIT$$uGY/߹k j~^ܷ;-P xGE0!]( :b 0Cf=  :F{Z:} t&NY55#$|',Z& & qG 1@=¥.K7m p}>/6{sz"LyGwRU]UTIͦf)*AP-6ۏ{[h^%X7zz[=PKsN>hӆ9[+testrepopacked/.git/hooks/pre-rebase.sampleUT y_YMcbux Xks7[.?tZkd,g'm2jز$".L3{]E;3{cyl3G.u5jhh~r1 PK^Ԟj_51j!Z;,_khRm@5DI,-uV $z1&Y.)%AGU:rK-C-OM`6fٮӝuAs^n\oXB%,S>\SL+\1Mdm?$susGY𨆪zgv =0i V/&>\ ywFvd;BV5n=v[,t%ZJ{E@'t $H+TN&l+s4"u0kEt xrl$zgg}]cjee"0rRJ PI>cMtJ?%zc Ge KZܫ(9s_}ɿ[?3lXQL<=}A D/HwhcDzRhXB~|{ 7{CXBtv_`o?۪}0o"ߩȠVeAPw0uMӓt*G^h\E OaYmH29MCW%x.~w'gyaWg ϻ&-ڦ3<_: p70|w2ь{CLt:U}9GN+0bU DNAp]<{ʞ_FAp1Ʒ"K D!zZUs߫ Iz۷>^]. u1 &q`GGX[Z,h_ "j.SJr}HkGr(B3L%xw ;@OE5"*efTЈl|Is'rհ3UCȩS˩okKjfQgh x7`V0&ϸoNY`<(A5B/x~f^8_6N0i|J(k&=(X3_(ӬNd3F/~t}}A`Z+.EV x861Y=Ճ ˷!:2˸d4jcĴ3ЕCPi\x)߽y,KfQ_’I >a#H]7tDFևiHP@+~!ėuu-wXa$3'}]5g*3i_v:(lګ.J>6f[y'"@:g.VJ3^!/ddA:ؔ&(`kQ߿v_YuY29D;vͷs(l>YlhnT d*ÃJ:bPX\MNIkǒm3*Q4Īm**06o[y^H"V(GO&F{YOHpwAf{t7ݗ^=u.›x2"g>M@6[c~OzHh^]R}B%ɿ"Ah{yyҬS}"o3ٔ؍/b|^LRKjX(o ExnJWO_.A|H!Dp"~ 5* c4/PKsN>lz,testrepopacked/.git/hooks/post-commit.sampleUT y_YMcbux -A b WM'?!PKeIU/^g^9IQ;Auk -PCT8JosԯNr*{ k-tq$X;Cm_b)}P PKsN> \*+testrepopacked/.git/hooks/pre-commit.sampleUT y_YMcbux }T[k:~ Iw!I’}9Slڒ+ɛl)$[SܾYarH+M$ڮaِ+OV'b-v~Bn'tW!Ȁ[f2.:,/eqŵ_բUU0/狤R U3}$$ 61r+\TDp;r[No{ΓqoM=Rv'testrepopacked/.git/hooks/update.sampleUT y_YMcbux WQo6~~U6ڰ${K ] }PlPLY\ddKk'HIwwwݠK%l8J=s4dM3?jZI&26הjA\ 9esa(1O|dcHkaRb|i"قNXe3|)hfS1"N$ž) W Q2s,uY=S2$in4SnRﺺYFSNN6i\uzrTZ/xVFM,3qáwWXH N VXNH8KysΌP2u@Os&-C0p% exV䖇u’ד ^,Q&$ {gaa2JOpt)_4FaOw˥E$SͭGKY-# P^4#jDxJΘ)}[wzP jni~Jvܼl/X5Zuv꽷-vh3v~L~w z@}a<øX n:1Ł #%*'uO*4`yl4jSpLsCUK˃Y!G!DʔCH}nfsyp ;XrdS|(Fu}Բ+F'@(su>zo)}UQ#?zЮg713vMx^FQe `wp'vL#b>:UyVrnu|ǵҵv^|kNvj COj[Qaudl^+>uB8t65};[u;Yrmu ˡF|z|JZC&/ JWb;uboϽ'=Lk;3e^·_Rx?ϪiG-|mi}C, S-C[kNǍRH|lSBEw= ֤UHS]݅PKsN> ,testrepopacked/.git/hooks/post-update.sampleUT y_YMcbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPK ;fQBQ testrepopacked/bye.txtUT  Qcbux bye world PK ;fQBAtestrepopacked/UT Qux PK -fQBڎItestrepopacked/.gitignoreUT Qux PK sN>((testrepopacked/hello.txtUTy_YMux PK i|QAtestrepopacked/.git/UTf_ux PKi|Qu~jtestrepopacked/.git/packed-refsUTf_ux PK sN>AAtestrepopacked/.git/refs/UTy_YMux PK sN>Atestrepopacked/.git/refs/tags/UTy_YMux PK i|QAtestrepopacked/.git/refs/heads/UTf_ux PK sN>T{))Etestrepopacked/.git/ORIG_HEADUTy_YMux PK i|QAtestrepopacked/.git/logs/UTf_ux PK i|Qtestrepopacked/.git/logs/HEADUTf_ux PK sN>Aotestrepopacked/.git/logs/refs/UTy_YMux PK i|Q$Atestrepopacked/.git/logs/refs/heads/UTf_ux PK i|Q*%testrepopacked/.git/logs/refs/heads/masterUTf_ux PK i|Q(testrepopacked/.git/logs/refs/heads/i18nUTf_ux PKʋPBXtestrepopacked/.git/configUTQux PK i|QAtestrepopacked/.git/objects/UTf_ux PK i|Q!A/testrepopacked/.git/objects/pack/UTf_ux PKi|Q <֮g(R$testrepopacked/.git/objects/pack/pack-6371e8325a610822b319bbd75ccda015ae31a08d.idxUTf_ux PKi|QdS$} testrepopacked/.git/objects/pack/pack-6371e8325a610822b319bbd75ccda015ae31a08d.packUTf_ux PK i|Q!Atestrepopacked/.git/objects/info/UTf_ux PKi|Qr-testrepopacked/.git/objects/info/commit-graphUTf_ux PK i|QT͙66&testrepopacked/.git/objects/info/packsUTf_ux PK -fQB+istestrepopacked/.git/HEADUT Qux PKsN>7?Itestrepopacked/.git/descriptionUTy_YMux PK i|QAtestrepopacked/.git/info/UTf_ux PKi|Q\Yttestrepopacked/.git/info/refsUTf_ux PKsN>w=! testrepopacked/.git/info/excludeUTy_YMux PK ,fQB>k"testrepopacked/.git/COMMIT_EDITMSGUT Qux PK-fQBdtestrepopacked/.git/indexUT Qux PK sN>Atestrepopacked/.git/hooks/UTy_YMux PKsN>i 03Ttestrepopacked/.git/hooks/prepare-commit-msg.sampleUTy_YMux PKsN>}.U(-\testrepopacked/.git/hooks/post-receive.sampleUTy_YMux PKsN>+testrepopacked/.git/hooks/commit-msg.sampleUTy_YMux PKsN>%[9/ttestrepopacked/.git/hooks/pre-applypatch.sampleUTy_YMux PKsN>`X / testrepopacked/.git/hooks/applypatch-msg.sampleUTy_YMux PKsN>hӆ9[+M"testrepopacked/.git/hooks/pre-rebase.sampleUTy_YMux PKsN>lz,큏*testrepopacked/.git/hooks/post-commit.sampleUTy_YMux PKsN> \*+o+testrepopacked/.git/hooks/pre-commit.sampleUTy_YMux PKsN>Rv'0/testrepopacked/.git/hooks/update.sampleUTy_YMux PKsN> ,4testrepopacked/.git/hooks/post-update.sampleUTy_YMux PK ;fQBQ 4testrepopacked/bye.txtUT Qux PK**RQ5libgit2-pygit2-a011e26/test/data/trailerrepo.zip000066400000000000000000000420311473744024100215550ustar00rootroot00000000000000PK ]S trailerrepo/UT rk|a)cbux PK ]Strailerrepo/.git/UT Mk|a)cbux PK ]Strailerrepo/.git/refs/UT Lk|a)cbux PK ]Strailerrepo/.git/refs/heads/UT Lk|a)cbux PK ]Strailerrepo/.git/refs/tags/UT Lk|a)cbux PK ]Strailerrepo/.git/branches/UT Lk|a)cbux PK ]ScdWtrailerrepo/.git/HEADUT Lk|a)cbux ref: refs/heads/main PK ]SxAAtrailerrepo/.git/indexUT Mk|a)cbux DIRCTREE0 0 K]Bn`K֒Ir?x}2Z76FPK ]Strailerrepo/.git/hooks/UT Lk|a)cbux PK]SXQ"(trailerrepo/.git/hooks/pre-rebase.sampleUT Lk|a)cbux XkoF,[E>۸AndıGPbMr3(jsg"-+b﹏ѓofU$zBl4G4?<~NL9, Q*G{ԯhFi-et]ﵱ Zf"cKpZI Ţ$(KKcU<ժ`%xh!)|,̦R?V,f1(y.G*՚%TBBZ358ԕZ3ih- x&Ma0,(|fۄJ,*J1wZqԘ {(ZWjmĚj㢪,<0ɮ% &(^B5xJ4(.ǰ7aBZy"S疲A U" z`Uz3섵F,+>QDO'429wNZfd3̍l0z̦X<~~yvqCwwQ@t % $H+H+b94c(G*@SK#{H7@% w6U{R)@A6/KOSq\e乫 .` 俭ww+EϿS5JfSE,|ݳ&u5h<물>[H( ͉*%!'ۚt<|  뙸sZe>r[oBclvkj+H-QfKv0;HOJFFmXw2\3Npg9=A7:vpưHwn5ҫz2 ۛ2Vh-#kC`;hoVfd%uNISlB ?8%E$%۾#˫YWV uO+k)KQ3u;2l X/3"׏,QOk!(NΗq0ťuI3R7  [)+D7]_]D+`;8$ '*H,_vOx6T[3l4sNypHS 3Y+ED/ 3 @:lJPtT7kQuݞ_IuY2ѿ8D;6 7q)l>Y lhe dƒJXbPX\MʲNIkԇȆ]1(Q4İ_ʴFgdTxq[My^"T(GM&;YpwAg{f;t7^p+ܝ6\`74&J7Г ǓfXz Uxͦn~-Jf:=N*wPò̭Xt]CypHx­RF/)]T5Z7x䃏`?E8 @Թ/.0y^טcczgkPǾ=i_nQ PK]S )trailerrepo/.git/hooks/post-update.sampleUT Lk|a)cbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPK]S .trailerrepo/.git/hooks/push-to-checkout.sampleUT Lk|a)cbux VMs6=G=1qN㺓1;mO\Id޷DӍfғ-ra۷ bq"nG9=c862BulsG$Ӎb[-NsiWEU9RTR=]]GF ۑTQ irG@K3Pz_:J Nϯj' \ f`DwՐwAYMt2hhNk|&kn/jeÅ02SՉa(@Нulʥ o068l8DE/:;+l^gjϜ/T \𳽐 O)R3c~'*/An $a얜McQ5urmt#,"kh6:<)0,AGA%`!VQ)>hB4up"Όv׋T<l)5_,b,hqvƒk> Ϻk'?_-M\xA%~r9WCs|dDaLMerK vpӥy3+Nvr̝=j/\4ia5U"SEQ ͷjAՆi 90EMl]lV*oeȍMsܦE@/} HG6MYv=F&EO.Jeg`CeNV?=iwV|_zE)h.7WF&#O.?I /ܔ@TH| YXRp4zܽڬX ?KՈj~8jNky6XM3,5GQY]u{=uHlVSU3Ǩq5?a[bR:]ܠ)lt hk!:0kD3$XXkqO[jw搆QD@Twʫ m/MR4cw =ߧg'[U6'#L)>)4UEq̓f.>|fSJڲݮJ]zql4zUIdn?4/bY\˭( \J93v)'I =gnSL`#*e`_ q%a*Qp.vL%XY fƿW^8odNV^tMؑyRiIQ@e?ߜZtnK? ?~ I k{%{,f$l2f;pRJvPwUe,95X35bFA7{!m%`*P `BSIi6mrM]7pN809'8Y'gJϵ?o֖ n ͱ/EŤ 2CtnfBjPEs6aOD\ '^9 Pv+]]z<]/7?6R!aAU$pMw)5=2j@rNwxS/!TXJc(1쇄 !9cI( x)*$'DS̏΀N9\5nܶib^vl&oc"PuAIQmŴQ.'<8Cmg؎gY!sSol#gaccN9Oz ;8~rݭO0j3_w aƏ N@~M` w\4+X.p)Ks;Y z1}84Α5K5!)a!r,TnaԎ%|C"BhS@"2:_c-Y8-/O=1r<^eb+Ւ$x6rҸǠv~CeҢjdK PաAic d ?C'1X*A$ܛ8BpOӤDi}ɍjU-0=zoP1Iƣ 抍+7 yh1߯!2kJwox?(.Ӧ6ww]U֯ՐMNѬ# HTaj>_ྸuh2-JʠvpKkM{/|*h39Z)D.|Eܗ{LdYbC7UܲGұa_G}zUPیaaysZ.^N{,SƆzzhf:KP|"lb7+o=&ʕXX${8LY|2 GV{2XD̘0B~s^pQ&̷}A'|5K2WgpOw=>p,O7pŇsI/|7KW]4BY[?;dI?m53FgQ_<4,o׍g[[5W{Kv]Oh|t7}]^<~٪: ..d~O;;(*7{7PK]S ^&trailerrepo/.git/hooks/pre-push.sampleUT Lk|a)cbux Tk0 ~>Zص\Q+26D:vf;M;Os8Y>}$g8(!>~0ʹ{Cn`R2 +7&0Aa`76ABػc8+lG)*mR!,i)8Q $Xg˟ J3ic%~d&י{an3M:H/{yJ[BY-}uB[!c4"q݇who2X1ڤs]I滱GȽQ%Y{Q}.1{fEbFaɦ1m1qd6SOY6g.#Ue{nȞy7AG4^/EأTܸz AvDO7 8gwÍe:- gC.o*]H !HBa|%'C!HRRߨ٢N%lW4N,49K:2B,O+%%Dv;>KȼCRm6Zc;4.14TJ 2iRšZ*V2¾X+W/ϠxFY=?)wYLifͿ~EPK]S2 k(trailerrepo/.git/hooks/pre-commit.sampleUT Lk|a)cbux mTao6l$ `:I?fu˰A-Z 5wޚaT“$p~-h90ZxUK<0KY\`)vj @qml'dJo4Y..|Si)_,]#Rs2J~_{Hmf lcF>t{s麏!| W3֕ٓ^> 'ƤTCPn dC+UFR+]s)4-ߋ b%O撾Ӿ$U^7tPV<-aciH`%FԢ: ~}Rv[YgEzAzjRPK]SO ,trailerrepo/.git/hooks/applypatch-msg.sampleUT Lk|a)cbux U]N1 s *HHp.nS-l[^o^ܭ7>ɩZ{B!"0([J8˽V&ZfTA=;H9/-Ov`&F3j.[K?@#rvDO BYq+7Sy:Qfm(}DJ 9CSQ ^&4VB09eٝh}6ЃV` Br)}??M PK]SD?^.trailerrepo/.git/hooks/pre-merge-commit.sampleUT Lk|a)cbux }OO0O0{{3@m:vj{ʇagJS%6b'D|SgaEh3 (&/X(OT!D< h_<(K4T?#X7 eՇ 6c iHۨ@Y#v/Q{K }mrAvX=!i9."՝/jcjʊ]$k2B,6~t28m5PK]SB$trailerrepo/.git/hooks/update.sampleUT Lk|a)cbux Wmo6llDaIiya"e"HU=$%[ݼ 1&yw=wGLBNt>ĿŲ+uG:-ҐQ4-TzG+ɤT>#暲R-KK! 4\ᴢp. Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPK]SL ,trailerrepo/.git/hooks/pre-applypatch.sampleUT Lk|a)cbux U[N1 EK:@VVB=DL&Q>nq,o#S!pWcHƞjO8/ 霷.Eh_p*^L|huV#iت7 QL90b`:"lѐ%mL:㝉mdS޸hB#W5@ɤQl)0tY ٦<(,ČbN8Z#Eù깆PK]S7?Itrailerrepo/.git/descriptionUT Lk|a)cbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK ]Strailerrepo/.git/objects/UT ^k|a)cbux PK ]Strailerrepo/.git/objects/info/UT Lk|a)cbux PK ]Strailerrepo/.git/objects/4b/UT Mk|a)cbux PK ]S: Btrailerrepo/.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904UT Mk|a)cbux x+)JMU0` ,PK ]Strailerrepo/.git/objects/pack/UT Lk|a)cbux PK ]Strailerrepo/.git/objects/01/UT ^k|a)cbux PK ]S{y Btrailerrepo/.git/objects/01/0231b2fdaee6b21da4f06058cf6c6a3392dd12UT ^k|a)cbux xK0]Br>bPSK ܹf\X0lW2VQY V" nRSpdrI 2>{fPܧvx#hꔇ%3M(V&A\ ã t) |B7ۮlW)mT^PK ]S Atrailerrepo/UTrk|aux PK ]SAFtrailerrepo/.git/UTMk|aux PK ]SAtrailerrepo/.git/refs/UTLk|aux PK ]SAtrailerrepo/.git/refs/heads/UTLk|aux PK ]SA7trailerrepo/.git/refs/tags/UTLk|aux PK ]SAtrailerrepo/.git/branches/UTLk|aux PK ]ScdWtrailerrepo/.git/HEADUTLk|aux PK ]SxAADtrailerrepo/.git/indexUTMk|aux PK ]SAtrailerrepo/.git/hooks/UTLk|aux PK]SXQ"(&trailerrepo/.git/hooks/pre-rebase.sampleUTLk|aux PK]S )g trailerrepo/.git/hooks/post-update.sampleUTLk|aux PK]S .T trailerrepo/.git/hooks/push-to-checkout.sampleUTLk|aux PK]SPc/0큢trailerrepo/.git/hooks/fsmonitor-watchman.sampleUTLk|aux PK]S ^& trailerrepo/.git/hooks/pre-push.sampleUTLk|aux PK]S2 k((trailerrepo/.git/hooks/pre-commit.sampleUTLk|aux PK]SO , trailerrepo/.git/hooks/applypatch-msg.sampleUTLk|aux PK]SD?^.큓!trailerrepo/.git/hooks/pre-merge-commit.sampleUTLk|aux PK]SB$"trailerrepo/.git/hooks/update.sampleUTLk|aux PK]S('trailerrepo/.git/hooks/commit-msg.sampleUTLk|aux PK]SL ,F*trailerrepo/.git/hooks/pre-applypatch.sampleUTLk|aux PK]S600큵+trailerrepo/.git/hooks/prepare-commit-msg.sampleUTLk|aux PK]SI )/trailerrepo/.git/hooks/pre-receive.sampleUTLk|aux PK]S75O\0trailerrepo/.git/configUTLk|aux PK ]SAS1trailerrepo/.git/info/UTLk|aux PK]Sw=!1trailerrepo/.git/info/excludeUTLk|aux PK]S7?I2trailerrepo/.git/descriptionUTLk|aux PK ]SA<3trailerrepo/.git/objects/UT^k|aux PK ]SA3trailerrepo/.git/objects/info/UTLk|aux PK ]SA3trailerrepo/.git/objects/4b/UTMk|aux PK ]S: B$=4trailerrepo/.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904UTMk|aux PK ]SA4trailerrepo/.git/objects/pack/UTLk|aux PK ]SA 5trailerrepo/.git/objects/01/UT^k|aux PK ]S{y B$v5trailerrepo/.git/objects/01/0231b2fdaee6b21da4f06058cf6c6a3392dd12UT^k|aux PK!!k 6libgit2-pygit2-a011e26/test/data/utf8branchrepo.zip000066400000000000000000000411601473744024100221610ustar00rootroot00000000000000PK BLutf8branchrepo/UT tZ-cbux PK BLutf8branchrepo/emptyUT tZ-cbux PK #BLutf8branchrepo/.git/UT !tZ-cbux PKBL~utf8branchrepo/.git/configUT tZ-cbux M0 E+#U @V1S"%q$8>COeWWK,QV+<PpEF|4 ?g ]X3C2$Bh{uE+ (p96P>ڭYsߺ_ٕW{oPK BLutf8branchrepo/.git/objects/UT tZ-cbux PK BLutf8branchrepo/.git/objects/4b/UT tZ-cbux PK BL: Eutf8branchrepo/.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904UT tZ-cbux x+)JMU0` ,PK BL!utf8branchrepo/.git/objects/pack/UT tZ-cbux PK BL!utf8branchrepo/.git/objects/info/UT tZ-cbux PK BLutf8branchrepo/.git/objects/e6/UT tZ-cbux PK BLEutf8branchrepo/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391UT tZ-cbux xKOR0` PK BLutf8branchrepo/.git/objects/41/UT tZ-cbux PK BL22Eutf8branchrepo/.git/objects/41/7c01c8795a35b8e835113a85a5c0c1c77f67fbUT tZ-cbux x+)JMU06f040031QH-(dx6M9{wk+qIOD6TPK BLutf8branchrepo/.git/objects/82/UT tZ-cbux PK BL[nEutf8branchrepo/.git/objects/82/052aecedd419e87f28742a598154cd56b0fe79UT tZ-cbux xA 0={/iD$l#i"xpX`o]"!Fv[olpE4YoY32p!(_˘2\{nϣvƗ;E)빶XZN "YGmV~߬s,?P_OPK #BL+isutf8branchrepo/.git/HEADUT !tZ-cbux ref: refs/heads/master PK BLutf8branchrepo/.git/info/UT tZ-cbux PKBLw=! utf8branchrepo/.git/info/excludeUT tZ-cbux -A 0Dbv/\yq۟6؍g7Ǽ hc)iX& kOo@.miuk6p72)AȄgKlK s7Qq=ZƏȜ< uY^1+ >bN8Z#Eù깆PK BLutf8branchrepo/.git/logs/UT tZ-cbux PK#BL"9mutf8branchrepo/.git/logs/HEADUT !tZ-cbux j1s}9*KdMw_ 5AH6Q>a-KS霆a>B^BmhRtR3t(j617J<9(Tֹؗ>D<.&RʩRB sBކSm0[L^ A0!p~] FH6S'1z|GPK BLutf8branchrepo/.git/logs/refs/UT tZ-cbux PK !BL$utf8branchrepo/.git/logs/refs/heads/UT tZ-cbux PK!BL2띇6utf8branchrepo/.git/logs/refs/heads/branch-with—utf8UT tZ-cbux K 0еb2I3&  35me +C8'(vYyȤL[퐅WC*jxg0L,S=Z?sYb%gQPaTp3gdvm//PK BL8*utf8branchrepo/.git/logs/refs/heads/masterUT tZ-cbux  0{T e&nRDH !i*w4 Z: ҐN[#Al,ƐxGZ;~YTZYC˿v]fNs9N_Z>cpiC w: 8]lgPKBL7?Iutf8branchrepo/.git/descriptionUT tZ-cbux  KMMQ(J-/,/VHM,Q(,VHIUPOI-N.,(SW(Wʧ"iPK BLutf8branchrepo/.git/hooks/UT tZ-cbux PKBL+utf8branchrepo/.git/hooks/commit-msg.sampleUT tZ-cbux }_o0şOqH+VMJ" XD&7l|w&E=Re M]C͍F^S~WrݶҡZVTɷi;OЊ Lշ"0h l'>Oպo ޒ\i ap(Hk{*uFwF G'$d OYo:ye 皒MRϗ >!-7)q]ͳ`:T|G"mm/#U1EY)*R])H6ڛ*?A _H~ uPN6N p8Waj{wypjGXOw|-/]2c\+ }FLj'#(9^j=^k.P~Y8cA1PG8jG䈐d}7+Yg4 _{]EPKBL,.W+utf8branchrepo/.git/hooks/pre-rebase.sampleUT tZ-cbux Xks7[.?tZkd,g'm2jز$".L3{]E;3{cyl3G.u5jhh~r1 PK^Ԟj_51j!Z;,_khRm@5DI,-uV $z1&Y.)%AGU:rK-C-OM`6fٮӝuAs^n\oXB%,S>\SL+\1Mdm?$susGY𨆪zgv =0i V/&>\ ywFvd;BV5n=v[,t%ZJ{E@'t $H+TN&l+s4"u0kEt xrl$zgg}]cjee"0rRJ PI>cMtJ?%zc Ge KZܫ(9s_}ɿ[?3lXQL<=}A D/HwhcDzRhXB~|{ 7{CXBtv_`o?۪}0o"ߩȠVeAPw0uMӓt*G^h\E OaYm29MCOX.~w'gyaWg cGϺL^e/QGXȊ˷כ};UN?h!& :*py*W w"dF .H̽eϯG ~ېA^I=-ǪU\[`@.hdOBOq]OM#;FbeV4K{p%ZB!gw310t\ںqJ~.?wxA`?:>]n'O"FfsH$6ʸh84a*+;l1_һ bu5aP%rr[-RYB;13ދ ذ̯3@S%7O,JPK%o8^Ηq0ŧLYg2R%ZiAE  ;*4kٌ 5]_}E#wKA8.bhAMLVq?>y?L2. (5t/Dc&0x"'ej#@?^won^y; o`+Yl◰CREF7үtW =<t:%=qa"t`0R/ЊC_|g]]e|:VI@_W9' Ǘݢ ˯@6Vs)H)Ι Ҍ"WH uN 6 (: *o+b8ZT}@w]mxW|a]D(`Lwzxos ǫ@v7E!$٭·73Wgo{!vnx1qӌʹyt *ee#'3Bp1 8 rǖr҅ zѓwck]A޳4M mWO)goyK&^>t3ș6͖ .p_S&ZlfcI/r7ڞy^y4+}s_ m6%vcog_"W7RFVmnj 2|#&zp:*RW>ޮ"\'yvFi[R^GgpP|XCMh4`KLZ)0x,W㲵ol6DkȢ;xdi #FQnapB7\>cZŗ6z7"<ϑV\>%2O0 o9J[F 8UVmK(OPRpvs'+aε{@zhngPKBLO /utf8branchrepo/.git/hooks/applypatch-msg.sampleUT tZ-cbux U]N1 s *HHp.nS-l[^o^ܭ7>ɩZ{B!"0([J8˽V&ZfTA=;H9/-Ov`&F3j.[K?@#rvDO BYq+7Sy:Qfm(}DJ 9CSQ ^&4VB09eٝh}6ЃV` Br)}??M PKBLI ,utf8branchrepo/.git/hooks/pre-receive.sampleUT tZ-cbux uPN0<_1}@[ʑ*HV"!UnM;B;vʣqzg3:ZZ; :*Ҙ lVbCZQm2<+MʷWP& Y:Q;앓hJd텄^ի%i$㒟jBk=hq/BK/E3UM2R;TG֡I/y:M4[,R +~EkâveKL q(h_uԌMkS6g9o5^q{~㱯a~"M%qpQ,kbbPKBL3utf8branchrepo/.git/hooks/prepare-commit-msg.sampleUT tZ-cbux uS]s0|&3M?,[;L) ig[T\Iɏ$9! =e˥ԥ&$ Sz {a|PJzP:'Z,{)ZMb$}QZLύTJx #cQ<%ƀ1Foh#hVX̻4!H& U*G!K3F_h 8/rAbiǺ^Ω8(,Fb%`DWe$0lp u2qlupށh)4Q8Av#{J,+V!bl<-#4Gl>%6fܙO6mc=BV͠ӳ0X)Ω3i46vpq!mfQVcMp*%u 𸕕DTŕX!%B_Kqg"l!jX%H7ͧ92dt~@'rp6>.xNf-3(sCY (oP@Sz38= ;=҄‡>xMHlpk%*2'٫41NTA~!,|]|zw9i㨙,Aq8-_dt㲟Zz%)Qx:zDv~˓PKBL ,utf8branchrepo/.git/hooks/post-update.sampleUT tZ-cbux -Q0 b_3x/ tμm/Ø`KhC['cYapAQ. Bi5%}#b7X+2WVDS 8Xa_w4n/397}|GrXMrPKBLL /utf8branchrepo/.git/hooks/pre-applypatch.sampleUT tZ-cbux U[N1 EK:@VVB=DL&Q>nq,o#S!pWcHƞjO8/ 霷.Eh_p*^L|huV#iت7 Q E-$_%'-\29LH52 {a֗a2Jpt)П4FaOw~˵E$S͝GKY쮤-# P^5m4#zBxJΘ9}[7wfP \jni}J\kWG:IqL{mk).Z| =mn_mF>0.pJp{q`GI*pݓ !X/u;Կcv?15EVY"eʇ!H}nfs}p-;Xqds|(Fu}Բ+&'@(s5 ϟ =wbmU,LA:fW5 :aNpC;]qc@bS*7USݝpt]+!&_󚓝ZBѓVGkXټ9Ww]gDoeg ƚح,b @vJtՎP#n>=>n%-|@Ux|` :m7Pz9YqnU4k˛3k@jFyFG2LU mmFP%;p$끕^ ؛2/[/vM gaCŴ'QJ۩A!Qޭ5' +G6 $R _>)!JÉl\k,)ޮy?PK BLutf8branchrepo/.git/refs/UT tZ-cbux PK !BLutf8branchrepo/.git/refs/heads/UT tZ-cbux PK !BLnD?))1utf8branchrepo/.git/refs/heads/branch-with—utf8UT tZ-cbux 82052aecedd419e87f28742a598154cd56b0fe79 PK BLnD?))%utf8branchrepo/.git/refs/heads/masterUT tZ-cbux 82052aecedd419e87f28742a598154cd56b0fe79 PK BLutf8branchrepo/.git/refs/tags/UT tZ-cbux PK#BL0utf8branchrepo/.git/indexUT !tZ-cbux s rf```bƨGk! Fw) KY*'cx6M9{wk+qIOD܂J4CH+d0T0ra3b=ަj<=E PK BL9"utf8branchrepo/.git/COMMIT_EDITMSGUT tZ-cbux Initial commit PK BLAutf8branchrepo/UTtZux PK BLIutf8branchrepo/emptyUTtZux PK #BLAutf8branchrepo/.git/UT!tZux PKBL~utf8branchrepo/.git/configUTtZux PK BLAutf8branchrepo/.git/objects/UTtZux PK BLA,utf8branchrepo/.git/objects/4b/UTtZux PK BL: E$utf8branchrepo/.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904UTtZux PK BL!Autf8branchrepo/.git/objects/pack/UTtZux PK BL!Anutf8branchrepo/.git/objects/info/UTtZux PK BLAutf8branchrepo/.git/objects/e6/UTtZux PK BLE$"utf8branchrepo/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391UTtZux PK BLAutf8branchrepo/.git/objects/41/UTtZux PK BL22E$ utf8branchrepo/.git/objects/41/7c01c8795a35b8e835113a85a5c0c1c77f67fbUTtZux PK BLAutf8branchrepo/.git/objects/82/UTtZux PK BL[nE$utf8branchrepo/.git/objects/82/052aecedd419e87f28742a598154cd56b0fe79UTtZux PK #BL+is(utf8branchrepo/.git/HEADUT!tZux PK BLAutf8branchrepo/.git/info/UTtZux PKBLw=! utf8branchrepo/.git/info/excludeUTtZux PK BLAutf8branchrepo/.git/logs/UTtZux PK#BL"9m> utf8branchrepo/.git/logs/HEADUT!tZux PK BLAb utf8branchrepo/.git/logs/refs/UTtZux PK !BL$A utf8branchrepo/.git/logs/refs/heads/UTtZux PK!BL2띇6 utf8branchrepo/.git/logs/refs/heads/branch-with—utf8UTtZux PK BL8* utf8branchrepo/.git/logs/refs/heads/masterUTtZux PKBL7?I utf8branchrepo/.git/descriptionUTtZux PK BLA utf8branchrepo/.git/hooks/UTtZux PKBL+ utf8branchrepo/.git/hooks/commit-msg.sampleUTtZux PKBL,.W+Cutf8branchrepo/.git/hooks/pre-rebase.sampleUTtZux PKBL%0\j+큁utf8branchrepo/.git/hooks/pre-commit.sampleUTtZux PKBLO /큉utf8branchrepo/.git/hooks/applypatch-msg.sampleUTtZux PKBLI , utf8branchrepo/.git/hooks/pre-receive.sampleUTtZux PKBL3큸utf8branchrepo/.git/hooks/prepare-commit-msg.sampleUTtZux PKBL ,큼"utf8branchrepo/.git/hooks/post-update.sampleUTtZux PKBLL /크#utf8branchrepo/.git/hooks/pre-applypatch.sampleUTtZux PKBL؏D)%utf8branchrepo/.git/hooks/pre-push.sampleUTtZux PKBL!D%u'(utf8branchrepo/.git/hooks/update.sampleUTtZux PK BLA,utf8branchrepo/.git/refs/UTtZux PK !BLAG-utf8branchrepo/.git/refs/heads/UTtZux PK !BLnD?))1-utf8branchrepo/.git/refs/heads/branch-with—utf8UTtZux PK BLnD?))%4.utf8branchrepo/.git/refs/heads/masterUTtZux PK BLA.utf8branchrepo/.git/refs/tags/UTtZux PK#BL0/utf8branchrepo/.git/indexUT!tZux PK BL9"/utf8branchrepo/.git/COMMIT_EDITMSGUTtZux PK++V0libgit2-pygit2-a011e26/test/keys/000077500000000000000000000000001473744024100165435ustar00rootroot00000000000000libgit2-pygit2-a011e26/test/keys/pygit2_empty000066400000000000000000000051371473744024100211300ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCcT0eRuC NRvnDkorGSaQqaAAAAEAAAAAEAAAGXAAAAB3NzaC1yc2EAAAADAQABAAABgQC5bu0UuVdG eimAo2mu87uy/hWCbvie6N8H4a7ZjnSThpDIv0clcnbycCukDb/d8o9FRIPNNp2astBlWp nZ9vCHByP8z8ITO/pG5Mu0Wp0n1Hrja5UVdk4cBvvoEcr2sdNqj3wUUhqeAnNCYeeJuW9V j2JNV2wwJYPAZBDi6GJTM9FPlCJz+QA6dJLkUiIwngrL8bSPzXd+1mzHOrIcqjxKm8J+ed 3tVSlRq7YvefrACUOTI9ymXwYr8a/TiwA76nTHvNPYIhmzH8PEF65QsFTs/qk0nqSwmqKA vineapBMmrb3Vf/Hd/c0nEMgHt6gy/+dtlgPh1bTqaQaS/oeo5OtjMDK/w93SR9M3UZBX+ eeMUGfyhvYT4Nvo3TumKzaZ6peGu1T0MKprSwgRdI+v1q+58sKfzSK5QevTpKiX1+4Leo6 BiNKKu6doD19fgNSD/dwfOWehxFtD8q/1J5k0QPgqslFkqyZJXRCzzowRYunSN+AaHVD3W o4AuqtfTiazPMAAAWQVrRkwWjO1Fcw7zebagqfBufB05nc08wL911ZPCVwqVSIepcEK/hM CJ/5/N+UILn9BXGe9qmOHPUuMa9UaLBSyzmlJ1s/NMGLzYWiv62SX1QNEXPegxwLasQvbL njjzdESGX+qUHxT4okNH52zi4DcBLX4HPL/TYQsKTNxCOclOljPDo+3IfHzx76yG5dAl5L C7ghLsd1zxpwZI+ag7NhNzZ4hBxX9JUenAfGyuOL+YCTp8JnU+dXJ3XaA3WAVGnvsZlAaq GJUGCdLlMiacO0eXNTm53xc92X9tPmetEVwhuD/Af7Vc4dOmH9Zu+7n9z9bLPrOowNr7ue w8YCqCg83iuQYmSSPj/JTvCzaoGDfW+yjALlb5RJUAIMJ51k0WyVIyqS0TE8+EINKETlj7 iIx1Y5z54ZnldlqrD2vLImO2b401oOb7fJUEU9Ke5NPi93tsps8nYKhatcRYLnLq9gsFv9 YlDCueoJJobg1k9TO+IwxraPgz3jl24zskSKT/tLFvsz0fQM5QWha2vB8kyZI067ojuNLb /mj5itgLIDmISa9cf98HhafeE8kGAtKEJR2rLwvb79EAhZ2ypt2I8LVur5hCM4cC9vSVyS dq/f4sgQpyQqSByMXeLEFYJSCDDc/PL3RC0Q9PqrQYZ1+pqj/6esV3ujLogMAHqEuW4EVw tMDUvjzfnC8MVUQpc5H4yonsWjGeGhH+VEkBSVISpABTSrYFN5kBodPD16wmRTbFF4tTQq Egmj5vUmxSY+a2EjDJREQBerMhj3W5sPhAB1QGVVn5kyFvmsjM4t06zzZj/R5muIcX0cT+ Th3N+xeYIuVi9kS5v7yOBlMk0KGq8QATSL/u+niO0e0neoT5Jv6E7EIafAFrn3Ji0rNave ObCqse3yZct0pbspM4f0c9mHaVbzmvwwtjmUFGdMJgse0UARXqvOlF9PUaN/AhqQlIyVjj ednPLrOz617XDSixiP+tKzKmqjZsBASZzpGwpHKii9/k7Q7aG5/Int8ulBS3H8C6ipMSxW EKSMJ4g6k33RY1EFL3dWtJYWhReAhY6kvIc3lmSeo7I9SQWXLupx0NUnkXeO63hLmJ9tjD CXeI0cwb2a6DWKLh6c2gQ5LPNb/8xzvYJfdop2Oxr+9L2NP7pIgvYr/cmgPtF5WkLT2Ypk z+KgwWxUKRiK/3G+dVe27u0Id7Yi596wnNGxJfZmlnbfioY4i+K9KcyS08LxlmgsIsQHiY Scv6SuamPdjiHdSwK/GuAcQUVwXQRA7DoV2uxOosAaUXWMiiSjJ3n1L8IVgp17OKxVN0Bd 5phre4VhYFoXGnq43xFAY3XQJctBqLPdb47RNi3JlhVK+Q1WKzK9OWbDoiseoNnMD5NXOt Wqf/vxD6AJEyO8sOT55l6hZAkNHIfFUGx4MNmLl12hJYSZgY9tx7aizz8RMT6GMBammQcU Q0pNDF1RBFOtxgb/QE+9/Vym4dMGnJrhhdbcYZbKngcsho4Qs39qMQvv0V23zAExreQH8U TBTZYyYkiPqdUiB2fNCW89QWksvBe3CXZAC0T0tdBcEYe5UPJRQ/K2FS6bJTYmxDkDWzHD 9iHbiu3Z8JGB9kHT6B5AgM+fYgEhpCgieDEHdF85cXtGSt8rjFFW6SMS70aLkgzFpYVeD0 zgzumI6JRY3vSMpUY60NCz+FOmIxy7oIpv7nDf/6Ubvah/heUF/P6IQwOQXumVMK9/Khqx j5TxaRCZ7fXV7IXH1hjQgWSZkGNUHc+rEAZdPOYFXlQb/2+DkO8IE7SxSWwk8tDzS0L7+H hWXgIm7mjIB6HDNfRb2zPL7gOgm83qZfrhSdP76XqnuV1LvvZMIs2dC8lKFfLk6oayzUvQ z5AMR0EutUSCby7+DKyBmaYSq0s= -----END OPENSSH PRIVATE KEY----- libgit2-pygit2-a011e26/test/keys/pygit2_empty.pub000066400000000000000000000010661473744024100217120ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC5bu0UuVdGeimAo2mu87uy/hWCbvie6N8H4a7ZjnSThpDIv0clcnbycCukDb/d8o9FRIPNNp2astBlWpnZ9vCHByP8z8ITO/pG5Mu0Wp0n1Hrja5UVdk4cBvvoEcr2sdNqj3wUUhqeAnNCYeeJuW9Vj2JNV2wwJYPAZBDi6GJTM9FPlCJz+QA6dJLkUiIwngrL8bSPzXd+1mzHOrIcqjxKm8J+ed3tVSlRq7YvefrACUOTI9ymXwYr8a/TiwA76nTHvNPYIhmzH8PEF65QsFTs/qk0nqSwmqKAvineapBMmrb3Vf/Hd/c0nEMgHt6gy/+dtlgPh1bTqaQaS/oeo5OtjMDK/w93SR9M3UZBX+eeMUGfyhvYT4Nvo3TumKzaZ6peGu1T0MKprSwgRdI+v1q+58sKfzSK5QevTpKiX1+4Leo6BiNKKu6doD19fgNSD/dwfOWehxFtD8q/1J5k0QPgqslFkqyZJXRCzzowRYunSN+AaHVD3Wo4AuqtfTiazPM= pygit2_empty libgit2-pygit2-a011e26/test/test_apply_diff.py000066400000000000000000000131051473744024100213160ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pygit2 from pygit2.enums import ApplyLocation, CheckoutStrategy, FileStatus import pytest import os from pathlib import Path def read_content(testrepo): with (Path(testrepo.workdir) / 'hello.txt').open('rb') as f: return f.read().decode('utf-8') @pytest.fixture def new_content(): content = ['bye world', 'adiós', 'au revoir monde'] content = ''.join(x + os.linesep for x in content) return content @pytest.fixture def old_content(testrepo): with (Path(testrepo.workdir) / 'hello.txt').open('rb') as f: return f.read().decode('utf-8') @pytest.fixture def patch_diff(testrepo, new_content): # Create the patch with (Path(testrepo.workdir) / 'hello.txt').open('wb') as f: f.write(new_content.encode('utf-8')) patch = testrepo.diff().patch # Rollback all changes testrepo.checkout('HEAD', strategy=CheckoutStrategy.FORCE) # Return the diff return pygit2.Diff.parse_diff(patch) @pytest.fixture def foreign_patch_diff(): patch_contents = """diff --git a/this_file_does_not_exist b/this_file_does_not_exist index 7f129fd..af431f2 100644 --- a/this_file_does_not_exist +++ b/this_file_does_not_exist @@ -1 +1 @@ -a contents 2 +a contents """ return pygit2.Diff.parse_diff(patch_contents) def test_apply_type_error(testrepo): # Check apply type error with pytest.raises(TypeError): testrepo.apply('HEAD') def test_apply_diff_to_workdir(testrepo, new_content, patch_diff): # Apply the patch and compare testrepo.apply(patch_diff, ApplyLocation.WORKDIR) assert read_content(testrepo) == new_content assert testrepo.status_file('hello.txt') == FileStatus.WT_MODIFIED def test_apply_diff_to_index(testrepo, old_content, patch_diff): # Apply the patch and compare testrepo.apply(patch_diff, ApplyLocation.INDEX) assert read_content(testrepo) == old_content assert testrepo.status_file('hello.txt') & FileStatus.INDEX_MODIFIED def test_apply_diff_to_both(testrepo, new_content, patch_diff): # Apply the patch and compare testrepo.apply(patch_diff, ApplyLocation.BOTH) assert read_content(testrepo) == new_content assert testrepo.status_file('hello.txt') & FileStatus.INDEX_MODIFIED def test_diff_applies_to_workdir(testrepo, old_content, patch_diff): # See if patch applies assert testrepo.applies(patch_diff, ApplyLocation.WORKDIR) # Ensure it was a dry run assert read_content(testrepo) == old_content # Apply patch for real, then ensure it can't be applied again testrepo.apply(patch_diff, ApplyLocation.WORKDIR) assert not testrepo.applies(patch_diff, ApplyLocation.WORKDIR) # It can still be applied to the index, though assert testrepo.applies(patch_diff, ApplyLocation.INDEX) def test_diff_applies_to_index(testrepo, old_content, patch_diff): # See if patch applies assert testrepo.applies(patch_diff, ApplyLocation.INDEX) # Ensure it was a dry run assert read_content(testrepo) == old_content # Apply patch for real, then ensure it can't be applied again testrepo.apply(patch_diff, ApplyLocation.INDEX) assert not testrepo.applies(patch_diff, ApplyLocation.INDEX) # It can still be applied to the workdir, though assert testrepo.applies(patch_diff, ApplyLocation.WORKDIR) def test_diff_applies_to_both(testrepo, old_content, patch_diff): # See if patch applies assert testrepo.applies(patch_diff, ApplyLocation.BOTH) # Ensure it was a dry run assert read_content(testrepo) == old_content # Apply patch for real, then ensure it can't be applied again testrepo.apply(patch_diff, ApplyLocation.BOTH) assert not testrepo.applies(patch_diff, ApplyLocation.BOTH) assert not testrepo.applies(patch_diff, ApplyLocation.WORKDIR) assert not testrepo.applies(patch_diff, ApplyLocation.INDEX) def test_applies_error(testrepo, old_content, patch_diff, foreign_patch_diff): # Try to apply a "foreign" patch that affects files that aren't in the repo; # ensure we get OSError about the missing file (due to raise_error) with pytest.raises(OSError): testrepo.applies(foreign_patch_diff, ApplyLocation.BOTH, raise_error=True) # Apply a valid patch testrepo.apply(patch_diff, ApplyLocation.BOTH) # Ensure it can't be applied again and we get an exception about it (due to raise_error) with pytest.raises(pygit2.GitError): testrepo.applies(patch_diff, ApplyLocation.BOTH, raise_error=True) libgit2-pygit2-a011e26/test/test_archive.py000066400000000000000000000045371473744024100206330ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from pathlib import Path import tarfile from pygit2 import Index, Oid, Tree, Object TREE_HASH = 'fd937514cb799514d4b81bb24c5fcfeb6472b245' COMMIT_HASH = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' def check_writing(repo, treeish, timestamp=None): archive = tarfile.open('foo.tar', mode='w') repo.write_archive(treeish, archive) index = Index() if isinstance(treeish, Object): index.read_tree(treeish.peel(Tree)) else: index.read_tree(repo[treeish].peel(Tree)) assert len(index) == len(archive.getmembers()) if timestamp: fileinfo = archive.getmembers()[0] assert timestamp == fileinfo.mtime archive.close() path = Path('foo.tar') assert path.is_file() path.unlink() def test_write_tree(testrepo): check_writing(testrepo, TREE_HASH) check_writing(testrepo, Oid(hex=TREE_HASH)) check_writing(testrepo, testrepo[TREE_HASH]) def test_write_commit(testrepo): commit_timestamp = testrepo[COMMIT_HASH].committer.time check_writing(testrepo, COMMIT_HASH, commit_timestamp) check_writing(testrepo, Oid(hex=COMMIT_HASH), commit_timestamp) check_writing(testrepo, testrepo[COMMIT_HASH], commit_timestamp) libgit2-pygit2-a011e26/test/test_attributes.py000066400000000000000000000035731473744024100213770ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Standard Library from pathlib import Path def test_no_attr(testrepo): assert testrepo.get_attr('file', 'foo') is None with (Path(testrepo.workdir) / '.gitattributes').open('w+') as f: print('*.py text\n', file=f) print('*.jpg -text\n', file=f) print('*.sh eol=lf\n', file=f) assert testrepo.get_attr('file.py', 'foo') is None assert testrepo.get_attr('file.py', 'text') assert not testrepo.get_attr('file.jpg', 'text') assert 'lf' == testrepo.get_attr('file.sh', 'eol') def test_no_attr_aspath(testrepo): with (Path(testrepo.workdir) / '.gitattributes').open('w+') as f: print('*.py text\n', file=f) assert testrepo.get_attr(Path('file.py'), 'text') libgit2-pygit2-a011e26/test/test_blame.py000066400000000000000000000116771473744024100202750ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Blame objects.""" import pytest from pygit2 import Signature, Oid from pygit2.enums import BlameFlag PATH = 'hello.txt' HUNKS = [ ( Oid(hex='acecd5ea2924a4b900e7e149496e1f4b57976e51'), 1, Signature( 'J. David Ibañez', 'jdavid@itaapy.com', 1297179898, 60, encoding='utf-8' ), True, ), ( Oid(hex='6aaa262e655dd54252e5813c8e5acd7780ed097d'), 2, Signature( 'J. David Ibañez', 'jdavid@itaapy.com', 1297696877, 60, encoding='utf-8' ), False, ), ( Oid(hex='4ec4389a8068641da2d6578db0419484972284c8'), 3, Signature( 'J. David Ibañez', 'jdavid@itaapy.com', 1297696908, 60, encoding='utf-8' ), False, ), ] def test_blame_index(testrepo): blame = testrepo.blame(PATH) assert len(blame) == 3 for i, hunk in enumerate(blame): assert hunk.lines_in_hunk == 1 assert HUNKS[i][0] == hunk.final_commit_id assert HUNKS[i][1] == hunk.final_start_line_number assert HUNKS[i][2] == hunk.final_committer assert HUNKS[i][0] == hunk.orig_commit_id assert hunk.orig_path == PATH assert HUNKS[i][1] == hunk.orig_start_line_number assert HUNKS[i][2] == hunk.orig_committer assert HUNKS[i][3] == hunk.boundary def test_blame_flags(blameflagsrepo): blame = blameflagsrepo.blame(PATH, flags=BlameFlag.IGNORE_WHITESPACE) assert len(blame) == 3 for i, hunk in enumerate(blame): assert hunk.lines_in_hunk == 1 assert HUNKS[i][0] == hunk.final_commit_id assert HUNKS[i][1] == hunk.final_start_line_number assert HUNKS[i][2] == hunk.final_committer assert HUNKS[i][0] == hunk.orig_commit_id assert hunk.orig_path == PATH assert HUNKS[i][1] == hunk.orig_start_line_number assert HUNKS[i][2] == hunk.orig_committer assert HUNKS[i][3] == hunk.boundary def test_blame_with_invalid_index(testrepo): blame = testrepo.blame(PATH) def test(): blame[100000] blame[-1] with pytest.raises(IndexError): test() def test_blame_for_line(testrepo): blame = testrepo.blame(PATH) for i, line in zip(range(0, 2), range(1, 3)): hunk = blame.for_line(line) assert hunk.lines_in_hunk == 1 assert HUNKS[i][0] == hunk.final_commit_id assert HUNKS[i][1] == hunk.final_start_line_number assert HUNKS[i][2] == hunk.final_committer assert HUNKS[i][0] == hunk.orig_commit_id assert hunk.orig_path == PATH assert HUNKS[i][1] == hunk.orig_start_line_number assert HUNKS[i][2] == hunk.orig_committer assert HUNKS[i][3] == hunk.boundary def test_blame_with_invalid_line(testrepo): blame = testrepo.blame(PATH) def test(): blame.for_line(0) blame.for_line(100000) blame.for_line(-1) with pytest.raises(IndexError): test() def test_blame_newest(testrepo): revs = [ ('master^2', 3), ('master^2^', 2), ('master^2^^', 1), ] for rev, num_commits in revs: commit = testrepo.revparse_single(rev) blame = testrepo.blame(PATH, newest_commit=commit.id) assert len(blame) == num_commits for i, hunk in enumerate(tuple(blame)[:num_commits]): assert hunk.lines_in_hunk == 1 assert HUNKS[i][0] == hunk.final_commit_id assert HUNKS[i][1] == hunk.final_start_line_number assert HUNKS[i][2] == hunk.final_committer assert HUNKS[i][0] == hunk.orig_commit_id assert hunk.orig_path == PATH assert HUNKS[i][1] == hunk.orig_start_line_number assert HUNKS[i][2] == hunk.orig_committer assert HUNKS[i][3] == hunk.boundary libgit2-pygit2-a011e26/test/test_blob.py000066400000000000000000000155261473744024100201300ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Blob objects.""" import io from pathlib import Path from threading import Event from queue import Queue import pytest import pygit2 from pygit2.enums import ObjectType from . import utils BLOB_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b' BLOB_CONTENT = """hello world hola mundo bonjour le monde """.encode() BLOB_NEW_CONTENT = b'foo bar\n' BLOB_FILE_CONTENT = b'bye world\n' BLOB_PATCH = r"""diff --git a/file b/file index a520c24..95d09f2 100644 --- a/file +++ b/file @@ -1,3 +1 @@ -hello world -hola mundo -bonjour le monde +hello world \ No newline at end of file """ BLOB_PATCH_2 = """diff --git a/file b/file index a520c24..d675fa4 100644 --- a/file +++ b/file @@ -1,3 +1 @@ -hello world -hola mundo -bonjour le monde +foo bar """ BLOB_PATCH_DELETED = """diff --git a/file b/file deleted file mode 100644 index a520c24..0000000 --- a/file +++ /dev/null @@ -1,3 +0,0 @@ -hello world -hola mundo -bonjour le monde """ def test_read_blob(testrepo): blob = testrepo[BLOB_SHA] assert blob.id == BLOB_SHA assert blob.id == BLOB_SHA assert isinstance(blob, pygit2.Blob) assert not blob.is_binary assert ObjectType.BLOB == blob.type assert BLOB_CONTENT == blob.data assert len(BLOB_CONTENT) == blob.size assert BLOB_CONTENT == blob.read_raw() def test_create_blob(testrepo): blob_oid = testrepo.create_blob(BLOB_NEW_CONTENT) blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) assert ObjectType.BLOB == blob.type assert blob_oid == blob.id assert utils.gen_blob_sha1(BLOB_NEW_CONTENT) == blob_oid assert BLOB_NEW_CONTENT == blob.data assert len(BLOB_NEW_CONTENT) == blob.size assert BLOB_NEW_CONTENT == blob.read_raw() blob_buffer = memoryview(blob) assert len(BLOB_NEW_CONTENT) == len(blob_buffer) assert BLOB_NEW_CONTENT == blob_buffer def set_content(): blob_buffer[:2] = b'hi' with pytest.raises(TypeError): set_content() def test_create_blob_fromworkdir(testrepo): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) assert ObjectType.BLOB == blob.type assert blob_oid == blob.id assert utils.gen_blob_sha1(BLOB_FILE_CONTENT) == blob_oid assert BLOB_FILE_CONTENT == blob.data assert len(BLOB_FILE_CONTENT) == blob.size assert BLOB_FILE_CONTENT == blob.read_raw() def test_create_blob_fromworkdir_aspath(testrepo): blob_oid = testrepo.create_blob_fromworkdir(Path('bye.txt')) blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) def test_create_blob_outside_workdir(testrepo): with pytest.raises(KeyError): testrepo.create_blob_fromworkdir(__file__) def test_create_blob_fromdisk(testrepo): blob_oid = testrepo.create_blob_fromdisk(__file__) blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) assert ObjectType.BLOB == blob.type def test_create_blob_fromiobase(testrepo): with pytest.raises(TypeError): testrepo.create_blob_fromiobase('bad type') f = io.BytesIO(BLOB_CONTENT) blob_oid = testrepo.create_blob_fromiobase(f) blob = testrepo[blob_oid] assert isinstance(blob, pygit2.Blob) assert ObjectType.BLOB == blob.type assert blob_oid == blob.id assert BLOB_SHA == blob_oid def test_diff_blob(testrepo): blob = testrepo[BLOB_SHA] old_blob = testrepo['3b18e512dba79e4c8300dd08aeb37f8e728b8dad'] patch = blob.diff(old_blob, old_as_path='hello.txt') assert len(patch.hunks) == 1 def test_diff_blob_to_buffer(testrepo): blob = testrepo[BLOB_SHA] patch = blob.diff_to_buffer('hello world') assert len(patch.hunks) == 1 def test_diff_blob_to_buffer_patch_patch(testrepo): blob = testrepo[BLOB_SHA] patch = blob.diff_to_buffer('hello world') assert patch.text == BLOB_PATCH def test_diff_blob_to_buffer_delete(testrepo): blob = testrepo[BLOB_SHA] patch = blob.diff_to_buffer(None) assert patch.text == BLOB_PATCH_DELETED def test_diff_blob_create(testrepo): old = testrepo[testrepo.create_blob(BLOB_CONTENT)] new = testrepo[testrepo.create_blob(BLOB_NEW_CONTENT)] patch = old.diff(new) assert patch.text == BLOB_PATCH_2 def test_blob_from_repo(testrepo): blob = testrepo[BLOB_SHA] patch_one = blob.diff_to_buffer(None) blob = testrepo[BLOB_SHA] patch_two = blob.diff_to_buffer(None) assert patch_one.text == patch_two.text def test_blob_write_to_queue(testrepo): queue = Queue() ready = Event() done = Event() blob = testrepo[BLOB_SHA] blob._write_to_queue(queue, ready, done) assert ready.wait() assert done.wait() chunks = [] while not queue.empty(): chunks.append(queue.get()) assert BLOB_CONTENT == b''.join(chunks) def test_blob_write_to_queue_filtered(testrepo): queue = Queue() ready = Event() done = Event() blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] blob._write_to_queue(queue, ready, done, as_path='bye.txt') assert ready.wait() assert done.wait() chunks = [] while not queue.empty(): chunks.append(queue.get()) assert b'bye world\n' == b''.join(chunks) def test_blobio(testrepo): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] with pygit2.BlobIO(blob) as reader: assert b'bye world\n' == reader.read() assert not reader.raw._thread.is_alive() def test_blobio_filtered(testrepo): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] with pygit2.BlobIO(blob, as_path='bye.txt') as reader: assert b'bye world\n' == reader.read() assert not reader.raw._thread.is_alive() libgit2-pygit2-a011e26/test/test_branch.py000066400000000000000000000204741473744024100204450ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for branch methods.""" import pygit2 import pytest import os from pygit2.enums import BranchType LAST_COMMIT = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' I18N_LAST_COMMIT = '5470a671a80ac3789f1a6a8cefbcf43ce7af0563' ORIGIN_MASTER_COMMIT = '784855caf26449a1914d2cf62d12b9374d76ae78' EXCLUSIVE_MASTER_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' SHARED_COMMIT = '4ec4389a8068641da2d6578db0419484972284c8' def test_branches_getitem(testrepo): branch = testrepo.branches['master'] assert branch.target == LAST_COMMIT branch = testrepo.branches.local['i18n'] assert branch.target == I18N_LAST_COMMIT assert testrepo.branches.get('not-exists') is None with pytest.raises(KeyError): testrepo.branches['not-exists'] def test_branches(testrepo): branches = sorted(testrepo.branches) assert branches == ['i18n', 'master'] def test_branches_create(testrepo): commit = testrepo[LAST_COMMIT] reference = testrepo.branches.create('version1', commit) assert 'version1' in testrepo.branches reference = testrepo.branches['version1'] assert reference.target == LAST_COMMIT # try to create existing reference with pytest.raises(ValueError): testrepo.branches.create('version1', commit) # try to create existing reference with force reference = testrepo.branches.create('version1', commit, True) assert reference.target == LAST_COMMIT def test_branches_delete(testrepo): testrepo.branches.delete('i18n') assert testrepo.branches.get('i18n') is None def test_branches_delete_error(testrepo): with pytest.raises(pygit2.GitError): testrepo.branches.delete('master') def test_branches_is_head(testrepo): branch = testrepo.branches.get('master') assert branch.is_head() def test_branches_is_not_head(testrepo): branch = testrepo.branches.get('i18n') assert not branch.is_head() def test_branches_rename(testrepo): new_branch = testrepo.branches['i18n'].rename('new-branch') assert new_branch.target == I18N_LAST_COMMIT new_branch_2 = testrepo.branches.get('new-branch') assert new_branch_2.target == I18N_LAST_COMMIT def test_branches_rename_error(testrepo): original_branch = testrepo.branches.get('i18n') with pytest.raises(ValueError): original_branch.rename('master') def test_branches_rename_force(testrepo): original_branch = testrepo.branches.get('master') new_branch = original_branch.rename('i18n', True) assert new_branch.target == LAST_COMMIT def test_branches_rename_invalid(testrepo): original_branch = testrepo.branches.get('i18n') with pytest.raises(ValueError): original_branch.rename('abc@{123') def test_branches_name(testrepo): branch = testrepo.branches.get('master') assert branch.branch_name == 'master' assert branch.name == 'refs/heads/master' assert branch.raw_branch_name == branch.branch_name.encode('utf-8') branch = testrepo.branches.get('i18n') assert branch.branch_name == 'i18n' assert branch.name == 'refs/heads/i18n' assert branch.raw_branch_name == branch.branch_name.encode('utf-8') def test_branches_with_commit(testrepo): branches = testrepo.branches.with_commit(EXCLUSIVE_MASTER_COMMIT) assert sorted(branches) == ['master'] assert branches.get('i18n') is None assert branches['master'].branch_name == 'master' branches = testrepo.branches.with_commit(SHARED_COMMIT) assert sorted(branches) == ['i18n', 'master'] branches = testrepo.branches.with_commit(LAST_COMMIT) assert sorted(branches) == ['master'] branches = testrepo.branches.with_commit(testrepo[LAST_COMMIT]) assert sorted(branches) == ['master'] branches = testrepo.branches.remote.with_commit(LAST_COMMIT) assert sorted(branches) == [] # # Low level API written in C, repo.branches call these. # def test_lookup_branch_local(testrepo): assert testrepo.lookup_branch('master').target == LAST_COMMIT assert testrepo.lookup_branch(b'master').target == LAST_COMMIT assert testrepo.lookup_branch('i18n', BranchType.LOCAL).target == I18N_LAST_COMMIT assert testrepo.lookup_branch(b'i18n', BranchType.LOCAL).target == I18N_LAST_COMMIT assert testrepo.lookup_branch('not-exists') is None assert testrepo.lookup_branch(b'not-exists') is None if os.name == 'posix': # this call fails with an InvalidSpecError on NT assert testrepo.lookup_branch(b'\xb1') is None def test_listall_branches(testrepo): branches = sorted(testrepo.listall_branches()) assert branches == ['i18n', 'master'] branches = sorted(testrepo.raw_listall_branches()) assert branches == [b'i18n', b'master'] def test_create_branch(testrepo): commit = testrepo[LAST_COMMIT] testrepo.create_branch('version1', commit) refs = testrepo.listall_branches() assert 'version1' in refs assert testrepo.lookup_branch('version1').target == LAST_COMMIT assert testrepo.lookup_branch(b'version1').target == LAST_COMMIT # try to create existing reference with pytest.raises(ValueError): testrepo.create_branch('version1', commit) # try to create existing reference with force assert testrepo.create_branch('version1', commit, True).target == LAST_COMMIT def test_delete(testrepo): branch = testrepo.lookup_branch('i18n') branch.delete() assert testrepo.lookup_branch('i18n') is None def test_cant_delete_master(testrepo): branch = testrepo.lookup_branch('master') with pytest.raises(pygit2.GitError): branch.delete() def test_branch_is_head_returns_true_if_branch_is_head(testrepo): branch = testrepo.lookup_branch('master') assert branch.is_head() def test_branch_is_head_returns_false_if_branch_is_not_head(testrepo): branch = testrepo.lookup_branch('i18n') assert not branch.is_head() def test_branch_is_checked_out_returns_true_if_branch_is_checked_out(testrepo): branch = testrepo.lookup_branch('master') assert branch.is_checked_out() def test_branch_is_checked_out_returns_false_if_branch_is_not_checked_out(testrepo): branch = testrepo.lookup_branch('i18n') assert not branch.is_checked_out() def test_branch_rename_succeeds(testrepo): branch = testrepo.lookup_branch('i18n') assert branch.rename('new-branch').target == I18N_LAST_COMMIT assert testrepo.lookup_branch('new-branch').target == I18N_LAST_COMMIT def test_branch_rename_fails_if_destination_already_exists(testrepo): original_branch = testrepo.lookup_branch('i18n') with pytest.raises(ValueError): original_branch.rename('master') def test_branch_rename_not_fails_if_force_is_true(testrepo): branch = testrepo.lookup_branch('master') assert branch.rename('i18n', True).target == LAST_COMMIT def test_branch_rename_fails_with_invalid_names(testrepo): original_branch = testrepo.lookup_branch('i18n') with pytest.raises(ValueError): original_branch.rename('abc@{123') def test_branch_name(testrepo): branch = testrepo.lookup_branch('master') assert branch.branch_name == 'master' assert branch.name == 'refs/heads/master' branch = testrepo.lookup_branch('i18n') assert branch.branch_name == 'i18n' assert branch.name == 'refs/heads/i18n' libgit2-pygit2-a011e26/test/test_branch_empty.py000066400000000000000000000100111473744024100216450ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pytest from pygit2.enums import BranchType ORIGIN_MASTER_COMMIT = '784855caf26449a1914d2cf62d12b9374d76ae78' @pytest.fixture def repo(emptyrepo): remote = emptyrepo.remotes[0] remote.fetch() yield emptyrepo def test_branches_remote_get(repo): branch = repo.branches.remote.get('origin/master') assert branch.target == ORIGIN_MASTER_COMMIT assert repo.branches.remote.get('origin/not-exists') is None def test_branches_remote(repo): branches = sorted(repo.branches.remote) assert branches == ['origin/master'] def test_branches_remote_getitem(repo): branch = repo.branches.remote['origin/master'] assert branch.remote_name == 'origin' def test_branches_upstream(repo): remote_master = repo.branches.remote['origin/master'] master = repo.branches.create('master', repo[remote_master.target]) assert master.upstream is None master.upstream = remote_master assert master.upstream.branch_name == 'origin/master' def set_bad_upstream(): master.upstream = 2.5 with pytest.raises(TypeError): set_bad_upstream() master.upstream = None assert master.upstream is None def test_branches_upstream_name(repo): remote_master = repo.branches.remote['origin/master'] master = repo.branches.create('master', repo[remote_master.target]) master.upstream = remote_master assert master.upstream_name == 'refs/remotes/origin/master' # # Low level API written in C, repo.remotes call these. # def test_lookup_branch_remote(repo): branch = repo.lookup_branch('origin/master', BranchType.REMOTE) assert branch.target == ORIGIN_MASTER_COMMIT assert repo.lookup_branch('origin/not-exists', BranchType.REMOTE) is None def test_listall_branches(repo): branches = sorted(repo.listall_branches(BranchType.REMOTE)) assert branches == ['origin/master'] branches = sorted(repo.raw_listall_branches(BranchType.REMOTE)) assert branches == [b'origin/master'] def test_branch_remote_name(repo): branch = repo.lookup_branch('origin/master', BranchType.REMOTE) assert branch.remote_name == 'origin' def test_branch_upstream(repo): remote_master = repo.lookup_branch('origin/master', BranchType.REMOTE) master = repo.create_branch('master', repo[remote_master.target]) assert master.upstream is None master.upstream = remote_master assert master.upstream.branch_name == 'origin/master' def set_bad_upstream(): master.upstream = 2.5 with pytest.raises(TypeError): set_bad_upstream() master.upstream = None assert master.upstream is None def test_branch_upstream_name(repo): remote_master = repo.lookup_branch('origin/master', BranchType.REMOTE) master = repo.create_branch('master', repo[remote_master.target]) master.upstream = remote_master assert master.upstream_name == 'refs/remotes/origin/master' libgit2-pygit2-a011e26/test/test_cherrypick.py000066400000000000000000000053101473744024100213430ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for merging and information about it.""" from pathlib import Path import pytest import pygit2 from pygit2.enums import RepositoryState def test_cherrypick_none(mergerepo): with pytest.raises(TypeError): mergerepo.cherrypick(None) def test_cherrypick_invalid_hex(mergerepo): branch_head_hex = '12345678' with pytest.raises(KeyError): mergerepo.cherrypick(branch_head_hex) def test_cherrypick_already_something_in_index(mergerepo): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' branch_oid = mergerepo.get(branch_head_hex).id with (Path(mergerepo.workdir) / 'inindex.txt').open('w') as f: f.write('new content') mergerepo.index.add('inindex.txt') with pytest.raises(pygit2.GitError): mergerepo.cherrypick(branch_oid) def test_cherrypick_remove_conflicts(mergerepo): assert mergerepo.state() == RepositoryState.NONE assert not mergerepo.message other_branch_tip = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' mergerepo.cherrypick(other_branch_tip) assert mergerepo.state() == RepositoryState.CHERRYPICK assert mergerepo.message.startswith('commit to provoke a conflict') idx = mergerepo.index conflicts = idx.conflicts assert conflicts is not None conflicts['.gitignore'] del idx.conflicts['.gitignore'] with pytest.raises(KeyError): conflicts.__getitem__('.gitignore') assert idx.conflicts is None mergerepo.state_cleanup() assert mergerepo.state() == RepositoryState.NONE assert not mergerepo.message libgit2-pygit2-a011e26/test/test_commit.py000066400000000000000000000240141473744024100204720ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Commit objects.""" import sys import pytest from pygit2 import Signature, Oid, GitError from pygit2.enums import ObjectType from . import utils COMMIT_SHA = '5fe808e8953c12735680c257f56600cb0de44b10' COMMIT_SHA_TO_AMEND = ( '784855caf26449a1914d2cf62d12b9374d76ae78' # tip of the master branch ) @utils.refcount def test_commit_refcount(barerepo): commit = barerepo[COMMIT_SHA] start = sys.getrefcount(commit) tree = commit.tree del tree end = sys.getrefcount(commit) assert start == end def test_read_commit(barerepo): commit = barerepo[COMMIT_SHA] assert COMMIT_SHA == commit.id parents = commit.parents assert 1 == len(parents) assert 'c2792cfa289ae6321ecf2cd5806c2194b0fd070c' == parents[0].id assert commit.message_encoding is None assert commit.message == ( 'Second test data commit.\n\n' 'This commit has some additional text.\n' ) commit_time = 1288481576 assert commit_time == commit.commit_time assert commit.committer == Signature( 'Dave Borowitz', 'dborowitz@google.com', commit_time, -420 ) assert commit.author == Signature( 'Dave Borowitz', 'dborowitz@google.com', 1288477363, -420 ) assert '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' == commit.tree.id def test_new_commit(barerepo): repo = barerepo message = 'New commit.\n\nMessage with non-ascii chars: ééé.\n' committer = Signature('John Doe', 'jdoe@example.com', 12346, 0) author = Signature( 'J. David Ibáñez', 'jdavid@example.com', 12345, 0, encoding='utf-8' ) tree = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' tree_prefix = tree[:5] too_short_prefix = tree[:3] parents = [COMMIT_SHA[:5]] with pytest.raises(ValueError): repo.create_commit(None, author, committer, message, too_short_prefix, parents) sha = repo.create_commit(None, author, committer, message, tree_prefix, parents) commit = repo[sha] assert ObjectType.COMMIT == commit.type assert '98286caaab3f1fde5bf52c8369b2b0423bad743b' == commit.id assert commit.message_encoding is None assert message == commit.message assert 12346 == commit.commit_time assert committer == commit.committer assert author == commit.author assert tree == commit.tree.id assert Oid(hex=tree) == commit.tree_id assert 1 == len(commit.parents) assert COMMIT_SHA == commit.parents[0].id assert Oid(hex=COMMIT_SHA) == commit.parent_ids[0] def test_new_commit_encoding(barerepo): repo = barerepo encoding = 'iso-8859-1' message = 'New commit.\n\nMessage with non-ascii chars: ééé.\n' committer = Signature('John Doe', 'jdoe@example.com', 12346, 0, encoding) author = Signature('J. David Ibáñez', 'jdavid@example.com', 12345, 0, encoding) tree = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' tree_prefix = tree[:5] parents = [COMMIT_SHA[:5]] sha = repo.create_commit( None, author, committer, message, tree_prefix, parents, encoding ) commit = repo[sha] assert ObjectType.COMMIT == commit.type assert 'iso-8859-1' == commit.message_encoding assert message.encode(encoding) == commit.raw_message assert 12346 == commit.commit_time assert committer == commit.committer assert author == commit.author assert tree == commit.tree.id assert Oid(hex=tree) == commit.tree_id assert 1 == len(commit.parents) assert COMMIT_SHA == commit.parents[0].id assert Oid(hex=COMMIT_SHA) == commit.parent_ids[0] def test_modify_commit(barerepo): message = 'New commit.\n\nMessage.\n' committer = ('John Doe', 'jdoe@example.com', 12346) author = ('Jane Doe', 'jdoe2@example.com', 12345) commit = barerepo[COMMIT_SHA] with pytest.raises(AttributeError): setattr(commit, 'message', message) with pytest.raises(AttributeError): setattr(commit, 'committer', committer) with pytest.raises(AttributeError): setattr(commit, 'author', author) with pytest.raises(AttributeError): setattr(commit, 'tree', None) with pytest.raises(AttributeError): setattr(commit, 'parents', None) def test_amend_commit_metadata(barerepo): repo = barerepo commit = repo[COMMIT_SHA_TO_AMEND] assert commit.id == repo.head.target encoding = 'iso-8859-1' amended_message = 'Amended commit message.\n\nMessage with non-ascii chars: ééé.\n' amended_author = Signature( 'Jane Author', 'jane@example.com', 12345, 0, encoding=encoding ) amended_committer = Signature( 'John Committer', 'john@example.com', 12346, 0, encoding=encoding ) amended_oid = repo.amend_commit( commit, 'HEAD', message=amended_message, author=amended_author, committer=amended_committer, encoding=encoding, ) amended_commit = repo[amended_oid] assert repo.head.target == amended_oid assert ObjectType.COMMIT == amended_commit.type assert amended_committer == amended_commit.committer assert amended_author == amended_commit.author assert amended_message.encode(encoding) == amended_commit.raw_message assert commit.author != amended_commit.author assert commit.committer != amended_commit.committer assert commit.tree == amended_commit.tree # we didn't touch the tree def test_amend_commit_tree(barerepo): repo = barerepo commit = repo[COMMIT_SHA_TO_AMEND] assert commit.id == repo.head.target tree = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' tree_prefix = tree[:5] amended_oid = repo.amend_commit(commit, 'HEAD', tree=tree_prefix) amended_commit = repo[amended_oid] assert repo.head.target == amended_oid assert ObjectType.COMMIT == amended_commit.type assert commit.message == amended_commit.message assert commit.author == amended_commit.author assert commit.committer == amended_commit.committer assert commit.tree_id != amended_commit.tree_id assert Oid(hex=tree) == amended_commit.tree_id def test_amend_commit_not_tip_of_branch(barerepo): repo = barerepo # This commit isn't at the tip of the branch. commit = repo['5fe808e8953c12735680c257f56600cb0de44b10'] assert commit.id != repo.head.target # Can't update HEAD to the rewritten commit because it's not the tip of the branch. with pytest.raises(GitError): repo.amend_commit(commit, 'HEAD', message="this won't work!") # We can still amend the commit if we don't try to update a ref. repo.amend_commit(commit, None, message='this will work') def test_amend_commit_no_op(barerepo): repo = barerepo commit = repo[COMMIT_SHA_TO_AMEND] assert commit.id == repo.head.target amended_oid = repo.amend_commit(commit, None) assert amended_oid == commit.id def test_amend_commit_argument_types(barerepo): repo = barerepo some_tree = repo['967fce8df97cc71722d3c2a5930ef3e6f1d27b12'] commit = repo[COMMIT_SHA_TO_AMEND] alt_commit1 = Oid(hex=COMMIT_SHA_TO_AMEND) alt_commit2 = COMMIT_SHA_TO_AMEND alt_tree = some_tree alt_refname = ( repo.head ) # try this one last, because it'll change the commit at the tip # Pass bad values/types for the commit with pytest.raises(ValueError): repo.amend_commit(None, None) with pytest.raises(TypeError): repo.amend_commit(some_tree, None) # Pass bad types for signatures with pytest.raises(TypeError): repo.amend_commit(commit, None, author='Toto') with pytest.raises(TypeError): repo.amend_commit(commit, None, committer='Toto') # Pass bad refnames with pytest.raises(ValueError): repo.amend_commit(commit, 'this-ref-doesnt-exist') with pytest.raises(TypeError): repo.amend_commit(commit, repo) # Pass bad trees with pytest.raises(ValueError): repo.amend_commit(commit, None, tree="can't parse this") with pytest.raises(KeyError): repo.amend_commit(commit, None, tree='baaaaad') # Pass an Oid for the commit amended_oid = repo.amend_commit(alt_commit1, None, message='Hello') amended_commit = repo[amended_oid] assert ObjectType.COMMIT == amended_commit.type assert amended_oid != COMMIT_SHA_TO_AMEND # Pass a str for the commit amended_oid = repo.amend_commit(alt_commit2, None, message='Hello', tree=alt_tree) amended_commit = repo[amended_oid] assert ObjectType.COMMIT == amended_commit.type assert amended_oid != COMMIT_SHA_TO_AMEND assert repo[COMMIT_SHA_TO_AMEND].tree != amended_commit.tree assert alt_tree.id == amended_commit.tree_id # Pass an actual reference object for refname # (Warning: the tip of the branch will be altered after this test!) amended_oid = repo.amend_commit(alt_commit2, alt_refname, message='Hello') amended_commit = repo[amended_oid] assert ObjectType.COMMIT == amended_commit.type assert amended_oid != COMMIT_SHA_TO_AMEND assert repo.head.target == amended_oid libgit2-pygit2-a011e26/test/test_commit_gpg.py000066400000000000000000000125151473744024100213320ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from pygit2 import Oid, Signature from pygit2.enums import ObjectType content = """\ tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 parent 8496071c1b46c854b31185ea97743be6a8774479 author Ben Burkert 1358451456 -0800 committer Ben Burkert 1358451456 -0800 a simple commit which works\ """ gpgsig = """\ -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (Darwin) iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8 JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9 ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc cpxtDQQMGYFpXK/71stq =ozeK -----END PGP SIGNATURE-----\ """ gpgsig_content = """\ tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 parent 8496071c1b46c854b31185ea97743be6a8774479 author Ben Burkert 1358451456 -0800 committer Ben Burkert 1358451456 -0800 gpgsig -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (Darwin) iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8 JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9 ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc cpxtDQQMGYFpXK/71stq =ozeK -----END PGP SIGNATURE----- a simple commit which works\ """ # NOTE: ^^^ mind the gap (space must exist after GnuPG header) ^^^ # XXX: seems macos wants the space while linux does not def test_commit_signing(gpgsigned): repo = gpgsigned message = 'a simple commit which works' author = Signature( name='Ben Burkert', email='ben@benburkert.com', time=1358451456, offset=-480, ) committer = Signature( name='Ben Burkert', email='ben@benburkert.com', time=1358451456, offset=-480, ) tree = '4b825dc642cb6eb9a060e54bf8d69288fbee4904' parent = '8496071c1b46c854b31185ea97743be6a8774479' # create commit string commit_string = repo.create_commit_string( author, committer, message, tree, [parent] ) assert commit_string == content # create/retrieve signed commit oid = repo.create_commit_with_signature(content, gpgsig) commit = repo.get(oid) signature, payload = commit.gpg_signature # validate signed commit assert content == payload.decode('utf-8') assert gpgsig == signature.decode('utf-8') assert gpgsig_content == commit.read_raw().decode('utf-8') # perform sanity checks assert ObjectType.COMMIT == commit.type assert '6569fdf71dbd99081891154641869c537784a3ba' == commit.id assert commit.message_encoding is None assert message == commit.message assert 1358451456 == commit.commit_time assert committer == commit.committer assert author == commit.author assert tree == commit.tree.id assert Oid(hex=tree) == commit.tree_id assert 1 == len(commit.parents) assert parent == commit.parents[0].id assert Oid(hex=parent) == commit.parent_ids[0] def test_get_gpg_signature_when_unsigned(gpgsigned): unhash = '5b5b025afb0b4c913b4c338a42934a3863bf3644' repo = gpgsigned commit = repo.get(unhash) signature, payload = commit.gpg_signature assert signature is None assert payload is None libgit2-pygit2-a011e26/test/test_commit_trailer.py000066400000000000000000000034261473744024100222200ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pygit2 import pytest from . import utils @pytest.fixture def repo(tmp_path): with utils.TemporaryRepository('trailerrepo.zip', tmp_path) as path: yield pygit2.Repository(path) def test_get_trailers_array(repo): commit_hash = '010231b2fdaee6b21da4f06058cf6c6a3392dd12' expected_trailers = { 'Bug': '1234', 'Signed-off-by': 'Tyler Cipriani ', } commit = repo.get(commit_hash) trailers = commit.message_trailers assert trailers['Bug'] == expected_trailers['Bug'] assert trailers['Signed-off-by'] == expected_trailers['Signed-off-by'] libgit2-pygit2-a011e26/test/test_config.py000066400000000000000000000130431473744024100204470ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from pathlib import Path import pytest from pygit2 import Config from . import utils CONFIG_FILENAME = 'test_config' @pytest.fixture def config(testrepo): yield testrepo.config try: Path(CONFIG_FILENAME).unlink() except OSError: pass def test_config(config): assert config is not None def test_global_config(): try: assert Config.get_global_config() is not None except IOError: # There is no user config pass def test_system_config(): try: assert Config.get_system_config() is not None except IOError: # There is no system config pass def test_new(): # Touch file open(CONFIG_FILENAME, 'w').close() config_write = Config(CONFIG_FILENAME) assert config_write is not None config_write['core.bare'] = False config_write['core.editor'] = 'ed' config_read = Config(CONFIG_FILENAME) assert 'core.bare' in config_read assert not config_read.get_bool('core.bare') assert 'core.editor' in config_read assert config_read['core.editor'] == 'ed' def test_add(): with open(CONFIG_FILENAME, 'w') as new_file: new_file.write('[this]\n\tthat = true\n') new_file.write('[something "other"]\n\there = false') config = Config() config.add_file(CONFIG_FILENAME, 0) assert 'this.that' in config assert config.get_bool('this.that') assert 'something.other.here' in config assert not config.get_bool('something.other.here') def test_add_aspath(): with open(CONFIG_FILENAME, 'w') as new_file: new_file.write('[this]\n\tthat = true\n') config = Config() config.add_file(Path(CONFIG_FILENAME), 0) assert 'this.that' in config def test_read(config): with pytest.raises(TypeError): config[()] with pytest.raises(TypeError): config[-4] utils.assertRaisesWithArg( ValueError, "invalid config item name 'abc'", lambda: config['abc'] ) utils.assertRaisesWithArg(KeyError, 'abc.def', lambda: config['abc.def']) assert 'core.bare' in config assert not config.get_bool('core.bare') assert 'core.editor' in config assert config['core.editor'] == 'ed' assert 'core.repositoryformatversion' in config assert config.get_int('core.repositoryformatversion') == 0 def test_write(config): with pytest.raises(TypeError): config.__setitem__((), 'This should not work') assert 'core.dummy1' not in config config['core.dummy1'] = 42 assert 'core.dummy1' in config assert config.get_int('core.dummy1') == 42 assert 'core.dummy2' not in config config['core.dummy2'] = 'foobar' assert 'core.dummy2' in config assert config['core.dummy2'] == 'foobar' assert 'core.dummy3' not in config config['core.dummy3'] = True assert 'core.dummy3' in config assert config['core.dummy3'] del config['core.dummy1'] assert 'core.dummy1' not in config del config['core.dummy2'] assert 'core.dummy2' not in config del config['core.dummy3'] assert 'core.dummy3' not in config def test_multivar(): with open(CONFIG_FILENAME, 'w') as new_file: new_file.write('[this]\n\tthat = foobar\n\tthat = foobeer\n') config = Config() config.add_file(CONFIG_FILENAME, 6) assert 'this.that' in config assert ['foobar', 'foobeer'] == list(config.get_multivar('this.that')) assert ['foobar'] == list(config.get_multivar('this.that', 'bar')) assert ['foobar', 'foobeer'] == list(config.get_multivar('this.that', 'foo.*')) config.set_multivar('this.that', '^.*beer', 'fool') assert ['fool'] == list(config.get_multivar('this.that', 'fool')) config.set_multivar('this.that', 'foo.*', 'foo-123456') assert ['foo-123456', 'foo-123456'] == list( config.get_multivar('this.that', 'foo.*') ) config.delete_multivar('this.that', 'bar') assert ['foo-123456', 'foo-123456'] == list(config.get_multivar('this.that', '')) config.delete_multivar('this.that', 'foo-[0-9]+') assert [] == list(config.get_multivar('this.that', '')) def test_iterator(config): lst = {} for entry in config: assert entry.level > -1 lst[entry.name] = entry.value assert 'core.bare' in lst assert lst['core.bare'] def test_parsing(): assert Config.parse_bool('on') assert Config.parse_bool('1') assert 5 == Config.parse_int('5') assert 1024 == Config.parse_int('1k') libgit2-pygit2-a011e26/test/test_credentials.py000066400000000000000000000145561473744024100215110ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for credentials""" from pathlib import Path import platform import pytest import pygit2 from pygit2 import Username, UserPass, Keypair, KeypairFromAgent, KeypairFromMemory from pygit2.enums import CredentialType from . import utils REMOTE_NAME = 'origin' REMOTE_URL = 'git://github.com/libgit2/pygit2.git' REMOTE_FETCHSPEC_SRC = 'refs/heads/*' REMOTE_FETCHSPEC_DST = 'refs/remotes/origin/*' REMOTE_REPO_OBJECTS = 30 REMOTE_REPO_BYTES = 2758 ORIGIN_REFSPEC = '+refs/heads/*:refs/remotes/origin/*' def test_username(): username = 'git' cred = Username(username) assert (username,) == cred.credential_tuple def test_userpass(): username = 'git' password = 'sekkrit' cred = UserPass(username, password) assert (username, password) == cred.credential_tuple def test_ssh_key(): username = 'git' pubkey = 'id_rsa.pub' privkey = 'id_rsa' passphrase = 'bad wolf' cred = Keypair(username, pubkey, privkey, passphrase) assert (username, pubkey, privkey, passphrase) == cred.credential_tuple def test_ssh_key_aspath(): username = 'git' pubkey = Path('id_rsa.pub') privkey = Path('id_rsa') passphrase = 'bad wolf' cred = Keypair(username, pubkey, privkey, passphrase) assert (username, pubkey, privkey, passphrase) == cred.credential_tuple def test_ssh_agent(): username = 'git' cred = KeypairFromAgent(username) assert (username, None, None, None) == cred.credential_tuple def test_ssh_from_memory(): username = 'git' pubkey = 'public key data' privkey = 'private key data' passphrase = 'secret passphrase' cred = KeypairFromMemory(username, pubkey, privkey, passphrase) assert (username, pubkey, privkey, passphrase) == cred.credential_tuple @utils.requires_network @utils.requires_ssh def test_keypair(tmp_path, pygit2_empty_key): url = 'ssh://git@github.com/pygit2/empty' with pytest.raises(pygit2.GitError): pygit2.clone_repository(url, tmp_path) prv, pub, secret = pygit2_empty_key keypair = pygit2.Keypair('git', pub, prv, secret) callbacks = pygit2.RemoteCallbacks(credentials=keypair) pygit2.clone_repository(url, tmp_path, callbacks=callbacks) @utils.requires_network @utils.requires_ssh def test_keypair_from_memory(tmp_path, pygit2_empty_key): url = 'ssh://git@github.com/pygit2/empty' with pytest.raises(pygit2.GitError): pygit2.clone_repository(url, tmp_path) prv, pub, secret = pygit2_empty_key with open(prv) as f: prv_mem = f.read() with open(pub) as f: pub_mem = f.read() keypair = pygit2.KeypairFromMemory('git', pub_mem, prv_mem, secret) callbacks = pygit2.RemoteCallbacks(credentials=keypair) pygit2.clone_repository(url, tmp_path, callbacks=callbacks) def test_callback(testrepo): class MyCallbacks(pygit2.RemoteCallbacks): def credentials(testrepo, url, username, allowed): assert allowed & CredentialType.USERPASS_PLAINTEXT raise Exception("I don't know the password") url = 'https://github.com/github/github' remote = testrepo.remotes.create('github', url) with pytest.raises(Exception): remote.fetch(callbacks=MyCallbacks()) @utils.requires_network def test_bad_cred_type(testrepo): class MyCallbacks(pygit2.RemoteCallbacks): def credentials(testrepo, url, username, allowed): assert allowed & CredentialType.USERPASS_PLAINTEXT return Keypair('git', 'foo.pub', 'foo', 'sekkrit') url = 'https://github.com/github/github' remote = testrepo.remotes.create('github', url) with pytest.raises(TypeError): remote.fetch(callbacks=MyCallbacks()) @utils.requires_network def test_fetch_certificate_check(testrepo): class MyCallbacks(pygit2.RemoteCallbacks): def certificate_check(testrepo, certificate, valid, host): assert certificate is None assert valid is True assert host == b'github.com' return False url = 'https://github.com/libgit2/pygit2.git' remote = testrepo.remotes.create('https', url) with pytest.raises(pygit2.GitError) as exc: remote.fetch(callbacks=MyCallbacks()) # libgit2 uses different error message for Linux and Windows value = str(exc.value) if platform.system() == 'Windows': assert value == 'user cancelled certificate check' # winhttp else: assert value == 'user rejected certificate for github.com' # httpclient # TODO Add GitError.error_code # assert exc.value.error_code == pygit2.GIT_ERROR_HTTP @utils.requires_network def test_user_pass(testrepo): credentials = UserPass('libgit2', 'libgit2') callbacks = pygit2.RemoteCallbacks(credentials=credentials) url = 'https://github.com/libgit2/TestGitRepository' remote = testrepo.remotes.create('bb', url) remote.fetch(callbacks=callbacks) @utils.requires_proxy @utils.requires_network @utils.requires_future_libgit2 def test_proxy(testrepo): credentials = UserPass('libgit2', 'libgit2') callbacks = pygit2.RemoteCallbacks(credentials=credentials) url = 'https://github.com/libgit2/TestGitRepository' remote = testrepo.remotes.create('bb', url) remote.fetch(callbacks=callbacks, proxy='http://localhost:8888') libgit2-pygit2-a011e26/test/test_describe.py000066400000000000000000000101521473744024100207600ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for describing commits.""" import pytest from pygit2.enums import DescribeStrategy, ObjectType import pygit2 def add_tag(repo, name, target): message = 'Example tag.\n' tagger = pygit2.Signature('John Doe', 'jdoe@example.com', 12347, 0) sha = repo.create_tag(name, target, ObjectType.COMMIT, tagger, message) return sha def test_describe(testrepo): add_tag(testrepo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8') assert 'thetag-2-g2be5719' == testrepo.describe() def test_describe_without_ref(testrepo): with pytest.raises(pygit2.GitError): testrepo.describe() def test_describe_default_oid(testrepo): assert '2be5719' == testrepo.describe(show_commit_oid_as_fallback=True) def test_describe_strategies(testrepo): assert 'heads/master' == testrepo.describe(describe_strategy=DescribeStrategy.ALL) testrepo.create_reference( 'refs/tags/thetag', '4ec4389a8068641da2d6578db0419484972284c8' ) with pytest.raises(KeyError): testrepo.describe() assert 'thetag-2-g2be5719' == testrepo.describe( describe_strategy=DescribeStrategy.TAGS ) def test_describe_pattern(testrepo): add_tag(testrepo, 'private/tag1', '5ebeeebb320790caf276b9fc8b24546d63316533') add_tag(testrepo, 'public/tag2', '4ec4389a8068641da2d6578db0419484972284c8') assert 'public/tag2-2-g2be5719' == testrepo.describe(pattern='public/*') def test_describe_committish(testrepo): add_tag(testrepo, 'thetag', 'acecd5ea2924a4b900e7e149496e1f4b57976e51') assert 'thetag-4-g2be5719' == testrepo.describe(committish='HEAD') assert 'thetag-1-g5ebeeeb' == testrepo.describe(committish='HEAD^') assert 'thetag-4-g2be5719' == testrepo.describe(committish=testrepo.head) assert 'thetag-1-g6aaa262' == testrepo.describe( committish='6aaa262e655dd54252e5813c8e5acd7780ed097d' ) assert 'thetag-1-g6aaa262' == testrepo.describe(committish='6aaa262') def test_describe_follows_first_branch_only(testrepo): add_tag(testrepo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8') with pytest.raises(KeyError): testrepo.describe(only_follow_first_parent=True) def test_describe_abbreviated_size(testrepo): add_tag(testrepo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8') assert 'thetag-2-g2be5719152d4f82c' == testrepo.describe(abbreviated_size=16) assert 'thetag' == testrepo.describe(abbreviated_size=0) def test_describe_long_format(testrepo): add_tag(testrepo, 'thetag', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98') assert 'thetag-0-g2be5719' == testrepo.describe(always_use_long_format=True) def test_describe_dirty(dirtyrepo): add_tag(dirtyrepo, 'thetag', 'a763aa560953e7cfb87ccbc2f536d665aa4dff22') assert 'thetag' == dirtyrepo.describe() def test_describe_dirty_with_suffix(dirtyrepo): add_tag(dirtyrepo, 'thetag', 'a763aa560953e7cfb87ccbc2f536d665aa4dff22') assert 'thetag-dirty' == dirtyrepo.describe(dirty_suffix='-dirty') libgit2-pygit2-a011e26/test/test_diff.py000066400000000000000000000272731473744024100201240ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Diff objects.""" from itertools import chain import textwrap import pytest import pygit2 from pygit2.enums import DeltaStatus, DiffFlag, DiffOption, DiffStatsFormat, FileMode COMMIT_SHA1_1 = '5fe808e8953c12735680c257f56600cb0de44b10' COMMIT_SHA1_2 = 'c2792cfa289ae6321ecf2cd5806c2194b0fd070c' COMMIT_SHA1_3 = '2cdae28389c059815e951d0bb9eed6533f61a46b' COMMIT_SHA1_4 = 'ccca47fbb26183e71a7a46d165299b84e2e6c0b3' COMMIT_SHA1_5 = '056e626e51b1fc1ee2182800e399ed8d84c8f082' COMMIT_SHA1_6 = 'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87' COMMIT_SHA1_7 = '784855caf26449a1914d2cf62d12b9374d76ae78' PATCH = """diff --git a/a b/a index 7f129fd..af431f2 100644 --- a/a +++ b/a @@ -1 +1 @@ -a contents 2 +a contents diff --git a/c/d b/c/d deleted file mode 100644 index 297efb8..0000000 --- a/c/d +++ /dev/null @@ -1 +0,0 @@ -c/d contents """ PATCHID = 'f31412498a17e6c3fbc635f2c5f9aa3ef4c1a9b7' DIFF_HEAD_TO_INDEX_EXPECTED = [ '.gitignore', 'staged_changes', 'staged_changes_file_deleted', 'staged_changes_file_modified', 'staged_delete', 'staged_delete_file_modified', 'staged_new', 'staged_new_file_deleted', 'staged_new_file_modified', ] DIFF_HEAD_TO_WORKDIR_EXPECTED = [ 'file_deleted', 'modified_file', 'staged_changes', 'staged_changes_file_deleted', 'staged_changes_file_modified', 'staged_delete', 'staged_delete_file_modified', 'subdir/deleted_file', 'subdir/modified_file', ] DIFF_INDEX_TO_WORK_EXPECTED = [ '.gitignore', 'file_deleted', 'modified_file', 'staged_changes_file_deleted', 'staged_changes_file_modified', 'staged_new_file_deleted', 'staged_new_file_modified', 'subdir/deleted_file', 'subdir/modified_file', ] HUNK_EXPECTED = """- a contents 2 + a contents """ STATS_EXPECTED = """ a | 2 +- c/d | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 c/d """ def test_diff_empty_index(dirtyrepo): repo = dirtyrepo head = repo[repo.lookup_reference('HEAD').resolve().target] diff = head.tree.diff_to_index(repo.index) files = [patch.delta.new_file.path for patch in diff] assert DIFF_HEAD_TO_INDEX_EXPECTED == files diff = repo.diff('HEAD', cached=True) files = [patch.delta.new_file.path for patch in diff] assert DIFF_HEAD_TO_INDEX_EXPECTED == files def test_workdir_to_tree(dirtyrepo): repo = dirtyrepo head = repo[repo.lookup_reference('HEAD').resolve().target] diff = head.tree.diff_to_workdir() files = [patch.delta.new_file.path for patch in diff] assert DIFF_HEAD_TO_WORKDIR_EXPECTED == files diff = repo.diff('HEAD') files = [patch.delta.new_file.path for patch in diff] assert DIFF_HEAD_TO_WORKDIR_EXPECTED == files def test_index_to_workdir(dirtyrepo): diff = dirtyrepo.diff() files = [patch.delta.new_file.path for patch in diff] assert DIFF_INDEX_TO_WORK_EXPECTED == files def test_diff_invalid(barerepo): commit_a = barerepo[COMMIT_SHA1_1] commit_b = barerepo[COMMIT_SHA1_2] with pytest.raises(TypeError): commit_a.tree.diff_to_tree(commit_b) with pytest.raises(TypeError): commit_a.tree.diff_to_index(commit_b) def test_diff_empty_index_bare(barerepo): repo = barerepo head = repo[repo.lookup_reference('HEAD').resolve().target] diff = barerepo.index.diff_to_tree(head.tree) files = [patch.delta.new_file.path.split('/')[0] for patch in diff] assert [x.name for x in head.tree] == files diff = head.tree.diff_to_index(repo.index) files = [patch.delta.new_file.path.split('/')[0] for patch in diff] assert [x.name for x in head.tree] == files diff = repo.diff('HEAD', cached=True) files = [patch.delta.new_file.path.split('/')[0] for patch in diff] assert [x.name for x in head.tree] == files def test_diff_tree(barerepo): commit_a = barerepo[COMMIT_SHA1_1] commit_b = barerepo[COMMIT_SHA1_2] def _test(diff): assert diff is not None assert 2 == sum(map(lambda x: len(x.hunks), diff)) patch = diff[0] hunk = patch.hunks[0] assert hunk.old_start == 1 assert hunk.old_lines == 1 assert hunk.new_start == 1 assert hunk.new_lines == 1 assert not patch.delta.is_binary assert patch.delta.flags & DiffFlag.NOT_BINARY for dfile in patch.delta.old_file, patch.delta.new_file: assert dfile.path == 'a' assert ( dfile.flags == DiffFlag.NOT_BINARY | DiffFlag.VALID_ID | DiffFlag.VALID_SIZE | DiffFlag.EXISTS ) assert dfile.mode == FileMode.BLOB _test(commit_a.tree.diff_to_tree(commit_b.tree)) _test(barerepo.diff(COMMIT_SHA1_1, COMMIT_SHA1_2)) def test_diff_empty_tree(barerepo): commit_a = barerepo[COMMIT_SHA1_1] diff = commit_a.tree.diff_to_tree() def get_context_for_lines(diff): hunks = chain.from_iterable(map(lambda x: x.hunks, diff)) lines = chain.from_iterable(map(lambda x: x.lines, hunks)) return map(lambda x: x.origin, lines) entries = [p.delta.new_file.path for p in diff] assert all(commit_a.tree[x] for x in entries) assert all('-' == x for x in get_context_for_lines(diff)) diff_swaped = commit_a.tree.diff_to_tree(swap=True) entries = [p.delta.new_file.path for p in diff_swaped] assert all(commit_a.tree[x] for x in entries) assert all('+' == x for x in get_context_for_lines(diff_swaped)) def test_diff_revparse(barerepo): diff = barerepo.diff('HEAD', 'HEAD~6') assert type(diff) == pygit2.Diff def test_diff_tree_opts(barerepo): commit_c = barerepo[COMMIT_SHA1_3] commit_d = barerepo[COMMIT_SHA1_4] for flag in [DiffOption.IGNORE_WHITESPACE, DiffOption.IGNORE_WHITESPACE_EOL]: diff = commit_c.tree.diff_to_tree(commit_d.tree, flag) assert diff is not None assert 0 == len(diff[0].hunks) diff = commit_c.tree.diff_to_tree(commit_d.tree) assert diff is not None assert 1 == len(diff[0].hunks) def test_diff_merge(barerepo): commit_a = barerepo[COMMIT_SHA1_1] commit_b = barerepo[COMMIT_SHA1_2] commit_c = barerepo[COMMIT_SHA1_3] diff_b = commit_a.tree.diff_to_tree(commit_b.tree) assert diff_b is not None diff_c = commit_b.tree.diff_to_tree(commit_c.tree) assert diff_c is not None assert 'b' not in [patch.delta.new_file.path for patch in diff_b] assert 'b' in [patch.delta.new_file.path for patch in diff_c] diff_b.merge(diff_c) assert 'b' in [patch.delta.new_file.path for patch in diff_b] patch = diff_b[0] hunk = patch.hunks[0] assert hunk.old_start == 1 assert hunk.old_lines == 1 assert hunk.new_start == 1 assert hunk.new_lines == 1 assert patch.delta.old_file.path == 'a' assert patch.delta.new_file.path == 'a' def test_diff_patch(barerepo): commit_a = barerepo[COMMIT_SHA1_1] commit_b = barerepo[COMMIT_SHA1_2] diff = commit_a.tree.diff_to_tree(commit_b.tree) assert diff.patch == PATCH assert len(diff) == len([patch for patch in diff]) def test_diff_ids(barerepo): commit_a = barerepo[COMMIT_SHA1_1] commit_b = barerepo[COMMIT_SHA1_2] patch = commit_a.tree.diff_to_tree(commit_b.tree)[0] delta = patch.delta assert delta.old_file.id == '7f129fd57e31e935c6d60a0c794efe4e6927664b' assert delta.new_file.id == 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' def test_diff_patchid(barerepo): commit_a = barerepo[COMMIT_SHA1_1] commit_b = barerepo[COMMIT_SHA1_2] diff = commit_a.tree.diff_to_tree(commit_b.tree) assert diff.patch == PATCH assert diff.patchid == PATCHID def test_hunk_content(barerepo): commit_a = barerepo[COMMIT_SHA1_1] commit_b = barerepo[COMMIT_SHA1_2] patch = commit_a.tree.diff_to_tree(commit_b.tree)[0] hunk = patch.hunks[0] lines = (f'{x.origin} {x.content}' for x in hunk.lines) assert HUNK_EXPECTED == ''.join(lines) for line in hunk.lines: assert line.content == line.raw_content.decode() def test_find_similar(barerepo): commit_a = barerepo[COMMIT_SHA1_6] commit_b = barerepo[COMMIT_SHA1_7] # ~ Must pass INCLUDE_UNMODIFIED if you expect to emulate # ~ --find-copies-harder during rename transformion... diff = commit_a.tree.diff_to_tree(commit_b.tree, DiffOption.INCLUDE_UNMODIFIED) assert all(x.delta.status != DeltaStatus.RENAMED for x in diff) assert all(x.delta.status_char() != 'R' for x in diff) diff.find_similar() assert any(x.delta.status == DeltaStatus.RENAMED for x in diff) assert any(x.delta.status_char() == 'R' for x in diff) def test_diff_stats(barerepo): commit_a = barerepo[COMMIT_SHA1_1] commit_b = barerepo[COMMIT_SHA1_2] diff = commit_a.tree.diff_to_tree(commit_b.tree) stats = diff.stats assert 1 == stats.insertions assert 2 == stats.deletions assert 2 == stats.files_changed formatted = stats.format( format=DiffStatsFormat.FULL | DiffStatsFormat.INCLUDE_SUMMARY, width=80 ) assert STATS_EXPECTED == formatted def test_deltas(barerepo): commit_a = barerepo[COMMIT_SHA1_1] commit_b = barerepo[COMMIT_SHA1_2] diff = commit_a.tree.diff_to_tree(commit_b.tree) deltas = list(diff.deltas) patches = list(diff) assert len(deltas) == len(patches) for i, delta in enumerate(deltas): patch_delta = patches[i].delta assert isinstance(delta.status, DeltaStatus) assert isinstance(patch_delta.status, DeltaStatus) assert delta.status == patch_delta.status assert delta.similarity == patch_delta.similarity assert delta.nfiles == patch_delta.nfiles assert delta.old_file.id == patch_delta.old_file.id assert delta.new_file.id == patch_delta.new_file.id assert delta.old_file.mode == patch_delta.old_file.mode assert delta.new_file.mode == patch_delta.new_file.mode # As explained in the libgit2 documentation, flags are not set # assert delta.flags == patch_delta.flags def test_diff_parse(barerepo): diff = pygit2.Diff.parse_diff(PATCH) stats = diff.stats assert 2 == stats.deletions assert 1 == stats.insertions assert 2 == stats.files_changed deltas = list(diff.deltas) assert 2 == len(deltas) def test_parse_diff_null(): with pytest.raises(TypeError): pygit2.Diff.parse_diff(None) def test_parse_diff_bad(): diff = textwrap.dedent( """ diff --git a/file1 b/file1 old mode 0644 new mode 0644 @@ -1,1 +1,1 @@ -Hi! """ ) with pytest.raises(pygit2.GitError): pygit2.Diff.parse_diff(diff) libgit2-pygit2-a011e26/test/test_diff_binary.py000066400000000000000000000042621473744024100214610ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pytest import pygit2 from pygit2.enums import DiffOption from . import utils @pytest.fixture def repo(tmp_path): with utils.TemporaryRepository('binaryfilerepo.zip', tmp_path) as path: yield pygit2.Repository(path) PATCH_BINARY = """diff --git a/binary_file b/binary_file index 86e5c10..b835d73 100644 Binary files a/binary_file and b/binary_file differ """ PATCH_BINARY_SHOW = """diff --git a/binary_file b/binary_file index 86e5c1008b5ce635d3e3fffa4434c5eccd8f00b6..b835d73543244b6694f36a8c5dfdffb71b153db7 100644 GIT binary patch literal 8 Pc${NM%FIhFs^kIy3n&7R literal 8 Pc${NM&PdElPvrst3ey5{ """ def test_binary_diff(repo): diff = repo.diff('HEAD', 'HEAD^') assert PATCH_BINARY == diff.patch diff = repo.diff('HEAD', 'HEAD^', flags=DiffOption.SHOW_BINARY) assert PATCH_BINARY_SHOW == diff.patch diff = repo.diff(b'HEAD', b'HEAD^') assert PATCH_BINARY == diff.patch diff = repo.diff(b'HEAD', b'HEAD^', flags=DiffOption.SHOW_BINARY) assert PATCH_BINARY_SHOW == diff.patch libgit2-pygit2-a011e26/test/test_filter.py000066400000000000000000000070031473744024100204660ustar00rootroot00000000000000from io import BytesIO import codecs import pytest import pygit2 from pygit2.enums import BlobFilter from pygit2.errors import Passthrough def _rot13(data): return codecs.encode(data.decode('utf-8'), 'rot_13').encode('utf-8') class _Rot13Filter(pygit2.Filter): attributes = 'text' def write(self, data, src, write_next): return super().write(_rot13(data), src, write_next) class _BufferedFilter(pygit2.Filter): attributes = 'text' def __init__(self): super().__init__() self.buf = BytesIO() def write(self, data, src, write_next): self.buf.write(data) def close(self, write_next): write_next(_rot13(self.buf.getvalue())) class _PassthroughFilter(_Rot13Filter): def check(self, src, attr_values): assert attr_values == [None] assert src.repo raise Passthrough class _UnmatchedFilter(_Rot13Filter): attributes = 'filter=rot13' @pytest.fixture def rot13_filter(): pygit2.filter_register('rot13', _Rot13Filter) yield pygit2.filter_unregister('rot13') @pytest.fixture def passthrough_filter(): pygit2.filter_register('passthrough-rot13', _PassthroughFilter) yield pygit2.filter_unregister('passthrough-rot13') @pytest.fixture def buffered_filter(): pygit2.filter_register('buffered-rot13', _BufferedFilter) yield pygit2.filter_unregister('buffered-rot13') @pytest.fixture def unmatched_filter(): pygit2.filter_register('unmatched-rot13', _UnmatchedFilter) yield pygit2.filter_unregister('unmatched-rot13') def test_filter(testrepo, rot13_filter): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD assert b'olr jbeyq\n' == blob.data with pygit2.BlobIO(blob) as reader: assert b'olr jbeyq\n' == reader.read() with pygit2.BlobIO(blob, as_path='bye.txt', flags=flags) as reader: assert b'bye world\n' == reader.read() def test_filter_buffered(testrepo, buffered_filter): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD assert b'olr jbeyq\n' == blob.data with pygit2.BlobIO(blob) as reader: assert b'olr jbeyq\n' == reader.read() with pygit2.BlobIO(blob, 'bye.txt', flags=flags) as reader: assert b'bye world\n' == reader.read() def test_filter_passthrough(testrepo, passthrough_filter): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD assert b'bye world\n' == blob.data with pygit2.BlobIO(blob) as reader: assert b'bye world\n' == reader.read() with pygit2.BlobIO(blob, 'bye.txt', flags=flags) as reader: assert b'bye world\n' == reader.read() def test_filter_unmatched(testrepo, unmatched_filter): blob_oid = testrepo.create_blob_fromworkdir('bye.txt') blob = testrepo[blob_oid] flags = BlobFilter.CHECK_FOR_BINARY | BlobFilter.ATTRIBUTES_FROM_HEAD assert b'bye world\n' == blob.data with pygit2.BlobIO(blob) as reader: assert b'bye world\n' == reader.read() with pygit2.BlobIO(blob, as_path='bye.txt', flags=flags) as reader: assert b'bye world\n' == reader.read() def test_filter_cleanup(dirtyrepo, rot13_filter): # Indirectly test that pygit2_filter_cleanup has the GIL # before calling pygit2_filter_payload_free. dirtyrepo.diff() libgit2-pygit2-a011e26/test/test_index.py000066400000000000000000000201531473744024100203110ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Index files.""" from pathlib import Path import pytest import pygit2 from pygit2 import Repository, Index, Oid from pygit2.enums import FileMode from . import utils def test_bare(barerepo): assert len(barerepo.index) == 0 def test_index(testrepo): assert testrepo.index is not None def test_read(testrepo): index = testrepo.index assert len(index) == 2 with pytest.raises(TypeError): index[()] utils.assertRaisesWithArg(ValueError, -4, lambda: index[-4]) utils.assertRaisesWithArg(KeyError, 'abc', lambda: index['abc']) sha = 'a520c24d85fbfc815d385957eed41406ca5a860b' assert 'hello.txt' in index assert index['hello.txt'].id == sha assert index['hello.txt'].path == 'hello.txt' assert index[1].id == sha def test_add(testrepo): index = testrepo.index sha = '0907563af06c7464d62a70cdd135a6ba7d2b41d8' assert 'bye.txt' not in index index.add('bye.txt') assert 'bye.txt' in index assert len(index) == 3 assert index['bye.txt'].id == sha def test_add_aspath(testrepo): index = testrepo.index assert 'bye.txt' not in index index.add(Path('bye.txt')) assert 'bye.txt' in index def test_add_all(testrepo): clear(testrepo) sha_bye = '0907563af06c7464d62a70cdd135a6ba7d2b41d8' sha_hello = 'a520c24d85fbfc815d385957eed41406ca5a860b' index = testrepo.index index.add_all(['*.txt']) assert 'bye.txt' in index assert 'hello.txt' in index assert index['bye.txt'].id == sha_bye assert index['hello.txt'].id == sha_hello clear(testrepo) index.add_all(['bye.t??', 'hello.*']) assert 'bye.txt' in index assert 'hello.txt' in index assert index['bye.txt'].id == sha_bye assert index['hello.txt'].id == sha_hello clear(testrepo) index.add_all(['[byehlo]*.txt']) assert 'bye.txt' in index assert 'hello.txt' in index assert index['bye.txt'].id == sha_bye assert index['hello.txt'].id == sha_hello def test_add_all_aspath(testrepo): clear(testrepo) index = testrepo.index index.add_all([Path('bye.txt'), Path('hello.txt')]) assert 'bye.txt' in index assert 'hello.txt' in index def clear(repo): index = repo.index assert len(index) == 2 index.clear() assert len(index) == 0 def test_write(testrepo): index = testrepo.index index.add('bye.txt') index.write() index.clear() assert 'bye.txt' not in index index.read() assert 'bye.txt' in index def test_read_tree(testrepo): tree_oid = '68aba62e560c0ebc3396e8ae9335232cd93a3f60' # Test reading first tree index = testrepo.index assert len(index) == 2 index.read_tree(tree_oid) assert len(index) == 1 # Test read-write returns the same oid assert index.write_tree() == tree_oid # Test the index is only modified in memory index.read() assert len(index) == 2 def test_write_tree(testrepo): assert testrepo.index.write_tree() == 'fd937514cb799514d4b81bb24c5fcfeb6472b245' def test_iter(testrepo): index = testrepo.index n = len(index) assert len(list(index)) == n # Compare SHAs, not IndexEntry object identity entries = [index[x].id for x in range(n)] assert list(x.id for x in index) == entries def test_mode(testrepo): """ Testing that we can access an index entry mode. """ index = testrepo.index hello_mode = index['hello.txt'].mode assert hello_mode == 33188 def test_bare_index(testrepo): index = pygit2.Index(Path(testrepo.path) / 'index') assert [x.id for x in index] == [x.id for x in testrepo.index] with pytest.raises(pygit2.GitError): index.add('bye.txt') def test_remove(testrepo): index = testrepo.index assert 'hello.txt' in index index.remove('hello.txt') assert 'hello.txt' not in index def test_remove_all(testrepo): index = testrepo.index assert 'hello.txt' in index index.remove_all(['*.txt']) assert 'hello.txt' not in index index.remove_all(['not-existing']) # this doesn't error def test_remove_aspath(testrepo): index = testrepo.index assert 'hello.txt' in index index.remove(Path('hello.txt')) assert 'hello.txt' not in index def test_remove_all_aspath(testrepo): index = testrepo.index assert 'hello.txt' in index index.remove_all([Path('hello.txt')]) assert 'hello.txt' not in index def test_change_attributes(testrepo): index = testrepo.index entry = index['hello.txt'] ign_entry = index['.gitignore'] assert ign_entry.id != entry.id assert entry.mode != FileMode.BLOB_EXECUTABLE entry.path = 'foo.txt' entry.id = ign_entry.id entry.mode = FileMode.BLOB_EXECUTABLE assert 'foo.txt' == entry.path assert ign_entry.id == entry.id assert FileMode.BLOB_EXECUTABLE == entry.mode def test_write_tree_to(testrepo, tmp_path): pygit2.option(pygit2.enums.Option.ENABLE_STRICT_OBJECT_CREATION, False) with utils.TemporaryRepository('emptyrepo.zip', tmp_path) as path: nrepo = Repository(path) id = testrepo.index.write_tree(nrepo) assert nrepo[id] is not None def test_create_entry(testrepo): index = testrepo.index hello_entry = index['hello.txt'] entry = pygit2.IndexEntry('README.md', hello_entry.id, hello_entry.mode) index.add(entry) assert '60e769e57ae1d6a2ab75d8d253139e6260e1f912' == index.write_tree() def test_create_entry_aspath(testrepo): index = testrepo.index hello_entry = index[Path('hello.txt')] entry = pygit2.IndexEntry(Path('README.md'), hello_entry.id, hello_entry.mode) index.add(entry) index.write_tree() def test_entry_eq(testrepo): index = testrepo.index hello_entry = index['hello.txt'] entry = pygit2.IndexEntry(hello_entry.path, hello_entry.id, hello_entry.mode) assert hello_entry == entry entry = pygit2.IndexEntry('README.md', hello_entry.id, hello_entry.mode) assert hello_entry != entry oid = Oid(hex='0907563af06c7464d62a70cdd135a6ba7d2b41d8') entry = pygit2.IndexEntry(hello_entry.path, oid, hello_entry.mode) assert hello_entry != entry entry = pygit2.IndexEntry( hello_entry.path, hello_entry.id, FileMode.BLOB_EXECUTABLE ) assert hello_entry != entry def test_entry_repr(testrepo): index = testrepo.index hello_entry = index['hello.txt'] assert ( repr(hello_entry) == '' ) assert ( str(hello_entry) == '' ) def test_create_empty(): Index() def test_create_empty_read_tree_as_string(): index = Index() # no repo associated, so we don't know where to read from with pytest.raises(TypeError): index('read_tree', 'fd937514cb799514d4b81bb24c5fcfeb6472b245') def test_create_empty_read_tree(testrepo): index = Index() index.read_tree(testrepo['fd937514cb799514d4b81bb24c5fcfeb6472b245']) libgit2-pygit2-a011e26/test/test_mailmap.py000066400000000000000000000066741473744024100206360ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Mailmap.""" from pygit2 import Mailmap TEST_MAILMAP = """\ # Simple Comment line Some Dude nick1 Other Author nick2 Other Author Phil Hill # Comment at end of line Joseph Santa Claus """ TEST_ENTRIES = [ (None, 'cto@company.xx', None, 'cto@coompany.xx'), ('Some Dude', 'some@dude.xx', 'nick1', 'bugs@company.xx'), ('Other Author', 'other@author.xx', 'nick2', 'bugs@company.xx'), ('Other Author', 'other@author.xx', None, 'nick2@company.xx'), ('Phil Hill', None, None, 'phil@company.xx'), (None, 'joseph@company.xx', 'Joseph', 'bugs@company.xx'), ('Santa Claus', 'santa.claus@northpole.xx', None, 'me@company.xx'), ] TEST_RESOLVE = [ ('Brad', 'cto@company.xx', 'Brad', 'cto@coompany.xx'), ('Brad L', 'cto@company.xx', 'Brad L', 'cto@coompany.xx'), ('Some Dude', 'some@dude.xx', 'nick1', 'bugs@company.xx'), ('Other Author', 'other@author.xx', 'nick2', 'bugs@company.xx'), ('nick3', 'bugs@company.xx', 'nick3', 'bugs@company.xx'), ('Other Author', 'other@author.xx', 'Some Garbage', 'nick2@company.xx'), ('Phil Hill', 'phil@company.xx', 'unknown', 'phil@company.xx'), ('Joseph', 'joseph@company.xx', 'Joseph', 'bugs@company.xx'), ('Santa Claus', 'santa.claus@northpole.xx', 'Clause', 'me@company.xx'), ('Charles', 'charles@charles.xx', 'Charles', 'charles@charles.xx'), ] def test_empty(): mailmap = Mailmap() for _, _, name, email in TEST_RESOLVE: assert mailmap.resolve(name, email) == (name, email) def test_new(): mailmap = Mailmap() # Add entries to the mailmap for entry in TEST_ENTRIES: mailmap.add_entry(*entry) for real_name, real_email, name, email in TEST_RESOLVE: assert mailmap.resolve(name, email) == (real_name, real_email) def test_parsed(): mailmap = Mailmap.from_buffer(TEST_MAILMAP) for real_name, real_email, name, email in TEST_RESOLVE: assert mailmap.resolve(name, email) == (real_name, real_email) # TODO: Add a testcase which uses .mailmap in a repo libgit2-pygit2-a011e26/test/test_merge.py000066400000000000000000000270111473744024100203010ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for merging and information about it.""" from pathlib import Path import pytest import pygit2 from pygit2.enums import FileStatus, MergeAnalysis, MergeFavor, MergeFlag, MergeFileFlag @pytest.mark.parametrize('id', [None, 42]) def test_merge_invalid_type(mergerepo, id): with pytest.raises(TypeError): mergerepo.merge(id) def test_merge_analysis_uptodate(mergerepo): branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533' branch_id = mergerepo.get(branch_head_hex).id analysis, preference = mergerepo.merge_analysis(branch_id) assert analysis & MergeAnalysis.UP_TO_DATE assert not analysis & MergeAnalysis.FASTFORWARD assert {} == mergerepo.status() analysis, preference = mergerepo.merge_analysis(branch_id, 'refs/heads/ff-branch') assert analysis & MergeAnalysis.UP_TO_DATE assert not analysis & MergeAnalysis.FASTFORWARD assert {} == mergerepo.status() def test_merge_analysis_fastforward(mergerepo): branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87' branch_id = mergerepo.get(branch_head_hex).id analysis, preference = mergerepo.merge_analysis(branch_id) assert not analysis & MergeAnalysis.UP_TO_DATE assert analysis & MergeAnalysis.FASTFORWARD assert {} == mergerepo.status() analysis, preference = mergerepo.merge_analysis(branch_id, 'refs/heads/master') assert not analysis & MergeAnalysis.UP_TO_DATE assert analysis & MergeAnalysis.FASTFORWARD assert {} == mergerepo.status() def test_merge_no_fastforward_no_conflicts(mergerepo): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' branch_id = mergerepo.get(branch_head_hex).id analysis, preference = mergerepo.merge_analysis(branch_id) assert not analysis & MergeAnalysis.UP_TO_DATE assert not analysis & MergeAnalysis.FASTFORWARD # Asking twice to assure the reference counting is correct assert {} == mergerepo.status() assert {} == mergerepo.status() def test_merge_invalid_hex(mergerepo): branch_head_hex = '12345678' with pytest.raises(KeyError): mergerepo.merge(branch_head_hex) def test_merge_already_something_in_index(mergerepo): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' branch_oid = mergerepo.get(branch_head_hex).id with (Path(mergerepo.workdir) / 'inindex.txt').open('w') as f: f.write('new content') mergerepo.index.add('inindex.txt') with pytest.raises(pygit2.GitError): mergerepo.merge(branch_oid) def test_merge_no_fastforward_conflicts(mergerepo): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' branch_id = mergerepo.get(branch_head_hex).id analysis, preference = mergerepo.merge_analysis(branch_id) assert not analysis & MergeAnalysis.UP_TO_DATE assert not analysis & MergeAnalysis.FASTFORWARD mergerepo.merge(branch_id) assert mergerepo.index.conflicts is not None with pytest.raises(KeyError): mergerepo.index.conflicts.__getitem__('some-file') assert 'some-file' not in mergerepo.index.conflicts assert '.gitignore' in mergerepo.index.conflicts status = FileStatus.CONFLICTED # Asking twice to assure the reference counting is correct assert {'.gitignore': status} == mergerepo.status() assert {'.gitignore': status} == mergerepo.status() ancestor, ours, theirs = mergerepo.index.conflicts['.gitignore'] assert ancestor is None assert ours is not None assert theirs is not None assert '.gitignore' == ours.path assert '.gitignore' == theirs.path assert 1 == len(list(mergerepo.index.conflicts)) # Checking the index works as expected mergerepo.index.add('.gitignore') mergerepo.index.write() assert mergerepo.index.conflicts is None assert {'.gitignore': FileStatus.INDEX_MODIFIED} == mergerepo.status() def test_merge_remove_conflicts(mergerepo): other_branch_tip = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' mergerepo.merge(other_branch_tip) idx = mergerepo.index conflicts = idx.conflicts assert conflicts is not None assert '.gitignore' in conflicts try: conflicts['.gitignore'] except KeyError: mergerepo.fail("conflicts['.gitignore'] raised KeyError unexpectedly") del idx.conflicts['.gitignore'] with pytest.raises(KeyError): conflicts.__getitem__('.gitignore') assert '.gitignore' not in conflicts assert idx.conflicts is None @pytest.mark.parametrize( 'favor', [ MergeFavor.OURS, MergeFavor.THEIRS, MergeFavor.UNION, ], ) def test_merge_favor(mergerepo, favor): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' mergerepo.merge(branch_head_hex, favor=favor) assert mergerepo.index.conflicts is None def test_merge_fail_on_conflict(mergerepo): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' with pytest.raises(pygit2.GitError): mergerepo.merge( branch_head_hex, flags=MergeFlag.FIND_RENAMES | MergeFlag.FAIL_ON_CONFLICT ) def test_merge_commits(mergerepo): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' branch_id = mergerepo.get(branch_head_hex).id merge_index = mergerepo.merge_commits(mergerepo.head.target, branch_head_hex) assert merge_index.conflicts is None merge_commits_tree = merge_index.write_tree(mergerepo) mergerepo.merge(branch_id) index = mergerepo.index assert index.conflicts is None merge_tree = index.write_tree() assert merge_tree == merge_commits_tree def test_merge_commits_favor(mergerepo): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' merge_index = mergerepo.merge_commits( mergerepo.head.target, branch_head_hex, favor=MergeFavor.OURS ) assert merge_index.conflicts is None # Incorrect favor value with pytest.raises(TypeError): mergerepo.merge_commits(mergerepo.head.target, branch_head_hex, favor='foo') def test_merge_trees(mergerepo): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' branch_id = mergerepo.get(branch_head_hex).id ancestor_id = mergerepo.merge_base(mergerepo.head.target, branch_id) merge_index = mergerepo.merge_trees( ancestor_id, mergerepo.head.target, branch_head_hex ) assert merge_index.conflicts is None merge_commits_tree = merge_index.write_tree(mergerepo) mergerepo.merge(branch_id) index = mergerepo.index assert index.conflicts is None merge_tree = index.write_tree() assert merge_tree == merge_commits_tree def test_merge_trees_favor(mergerepo): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' ancestor_id = mergerepo.merge_base(mergerepo.head.target, branch_head_hex) merge_index = mergerepo.merge_trees( ancestor_id, mergerepo.head.target, branch_head_hex, favor=MergeFavor.OURS ) assert merge_index.conflicts is None with pytest.raises(TypeError): mergerepo.merge_trees( ancestor_id, mergerepo.head.target, branch_head_hex, favor='foo' ) def test_merge_options(): favor = MergeFavor.OURS flags = MergeFlag.FIND_RENAMES | MergeFlag.FAIL_ON_CONFLICT file_flags = MergeFileFlag.IGNORE_WHITESPACE | MergeFileFlag.DIFF_PATIENCE o1 = pygit2.Repository._merge_options( favor=favor, flags=flags, file_flags=file_flags ) assert favor == o1.file_favor assert flags == o1.flags assert file_flags == o1.file_flags favor = MergeFavor.THEIRS flags = 0 file_flags = 0 o1 = pygit2.Repository._merge_options( favor=favor, flags=flags, file_flags=file_flags ) assert favor == o1.file_favor assert flags == o1.flags assert file_flags == o1.file_flags favor = MergeFavor.UNION flags = MergeFlag.FIND_RENAMES | MergeFlag.NO_RECURSIVE file_flags = ( MergeFileFlag.STYLE_DIFF3 | MergeFileFlag.IGNORE_WHITESPACE | MergeFileFlag.DIFF_PATIENCE ) o1 = pygit2.Repository._merge_options( favor=favor, flags=flags, file_flags=file_flags ) assert favor == o1.file_favor assert flags == o1.flags assert file_flags == o1.file_flags def test_merge_many(mergerepo): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' branch_id = mergerepo.get(branch_head_hex).id ancestor_id = mergerepo.merge_base_many([mergerepo.head.target, branch_id]) merge_index = mergerepo.merge_trees( ancestor_id, mergerepo.head.target, branch_head_hex ) assert merge_index.conflicts is None merge_commits_tree = merge_index.write_tree(mergerepo) mergerepo.merge(branch_id) index = mergerepo.index assert index.conflicts is None merge_tree = index.write_tree() assert merge_tree == merge_commits_tree def test_merge_octopus(mergerepo): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' branch_id = mergerepo.get(branch_head_hex).id ancestor_id = mergerepo.merge_base_octopus([mergerepo.head.target, branch_id]) merge_index = mergerepo.merge_trees( ancestor_id, mergerepo.head.target, branch_head_hex ) assert merge_index.conflicts is None merge_commits_tree = merge_index.write_tree(mergerepo) mergerepo.merge(branch_id) index = mergerepo.index assert index.conflicts is None merge_tree = index.write_tree() assert merge_tree == merge_commits_tree def test_merge_mergeheads(mergerepo): assert mergerepo.listall_mergeheads() == [] branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' mergerepo.merge(branch_head_hex) assert mergerepo.listall_mergeheads() == [pygit2.Oid(hex=branch_head_hex)] mergerepo.state_cleanup() assert ( mergerepo.listall_mergeheads() == [] ), 'state_cleanup() should wipe the mergeheads' def test_merge_message(mergerepo): assert not mergerepo.message assert not mergerepo.raw_message branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' mergerepo.merge(branch_head_hex) assert mergerepo.message.startswith(f"Merge commit '{branch_head_hex}'") assert mergerepo.message.encode('utf-8') == mergerepo.raw_message mergerepo.state_cleanup() assert not mergerepo.message def test_merge_remove_message(mergerepo): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' mergerepo.merge(branch_head_hex) assert mergerepo.message.startswith(f"Merge commit '{branch_head_hex}'") mergerepo.remove_message() assert not mergerepo.message libgit2-pygit2-a011e26/test/test_note.py000066400000000000000000000053261473744024100201540ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for note objects.""" from pygit2 import Signature import pytest NOTE = ('6c8980ba963cad8b25a9bcaf68d4023ee57370d8', 'note message') NOTES = [ ( 'ab533997b80705767be3dae8cbb06a0740809f79', 'First Note - HEAD\n', '784855caf26449a1914d2cf62d12b9374d76ae78', ), ( 'd879714d880671ed84f8aaed8b27fca23ba01f27', 'Second Note - HEAD~1\n', 'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87', ), ] def test_create_note(barerepo): annotated_id = barerepo.revparse_single('HEAD~3').id author = committer = Signature('Foo bar', 'foo@bar.com', 12346, 0) note_id = barerepo.create_note(NOTE[1], author, committer, str(annotated_id)) assert NOTE[0] == note_id # check the note blob assert NOTE[1].encode() == barerepo[note_id].data def test_lookup_note(barerepo): annotated_id = str(barerepo.head.target) note = barerepo.lookup_note(annotated_id) assert NOTES[0][0] == note.id assert NOTES[0][1] == note.message def test_remove_note(barerepo): head = barerepo.head note = barerepo.lookup_note(str(head.target)) author = committer = Signature('Foo bar', 'foo@bar.com', 12346, 0) note.remove(author, committer) with pytest.raises(KeyError): barerepo.lookup_note(str(head.target)) def test_iterate_notes(barerepo): for i, note in enumerate(barerepo.notes()): assert NOTES[i] == (note.id, note.message, note.annotated_id) def test_iterate_non_existing_ref(barerepo): with pytest.raises(KeyError): barerepo.notes('refs/notes/bad_ref') libgit2-pygit2-a011e26/test/test_object.py000066400000000000000000000106431473744024100204530ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Object objects.""" import pytest from pygit2 import Tree, Tag from pygit2.enums import ObjectType BLOB_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b' BLOB_CONTENT = """hello world hola mundo bonjour le monde """.encode() BLOB_NEW_CONTENT = b'foo bar\n' BLOB_FILE_CONTENT = b'bye world\n' def test_equality(testrepo): # get a commit object twice and see if it equals ittestrepo commit_id = testrepo.lookup_reference('refs/heads/master').target commit_a = testrepo[commit_id] commit_b = testrepo[commit_id] assert commit_a is not commit_b assert commit_a == commit_b assert not (commit_a != commit_b) def test_hashing(testrepo): # get a commit object twice and compare hashes commit_id = testrepo.lookup_reference('refs/heads/master').target commit_a = testrepo[commit_id] commit_b = testrepo[commit_id] assert hash(commit_a) assert commit_a is not commit_b assert commit_a == commit_b # if the commits are equal then their hash *must* be equal # but different objects can have the same commit assert hash(commit_a) == hash(commit_b) # sanity check that python container types work as expected s = set() s.add(commit_a) s.add(commit_b) assert len(s) == 1 assert commit_a in s assert commit_b in s d = {} d[commit_a] = True assert commit_b in d assert d[commit_b] assert commit_b == commit_a def test_peel_commit(testrepo): # start by looking up the commit commit_id = testrepo.lookup_reference('refs/heads/master').target commit = testrepo[commit_id] # and peel to the tree tree = commit.peel(ObjectType.TREE) assert type(tree) == Tree assert tree.id == 'fd937514cb799514d4b81bb24c5fcfeb6472b245' def test_peel_commit_type(testrepo): commit_id = testrepo.lookup_reference('refs/heads/master').target commit = testrepo[commit_id] tree = commit.peel(Tree) assert type(tree) == Tree assert tree.id == 'fd937514cb799514d4b81bb24c5fcfeb6472b245' def test_invalid(testrepo): commit_id = testrepo.lookup_reference('refs/heads/master').target commit = testrepo[commit_id] with pytest.raises(ValueError): commit.peel(ObjectType.TAG) def test_invalid_type(testrepo): commit_id = testrepo.lookup_reference('refs/heads/master').target commit = testrepo[commit_id] with pytest.raises(ValueError): commit.peel(Tag) def test_short_id(testrepo): seen = {} # from short_id to full hex id def test_obj(obj, msg): short_id = obj.short_id msg = msg + f' short_id={short_id}' already = seen.get(short_id) if already: assert already == obj.id else: seen[short_id] = obj.id lookup = testrepo[short_id] assert obj.id == lookup.id for commit in testrepo.walk(testrepo.head.target): test_obj(commit, f'commit#{commit.id}') tree = commit.tree test_obj(tree, f'tree#{tree.id}') for entry in tree: test_obj(testrepo[entry.id], f'entry={entry.name}#{entry.id}') def test_repr(testrepo): commit_id = testrepo.lookup_reference('refs/heads/master').target commit_a = testrepo[commit_id] assert repr(commit_a) == '' % commit_id libgit2-pygit2-a011e26/test/test_odb.py000066400000000000000000000051361473744024100177520ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Odb objects.""" # Standard Library import binascii from pathlib import Path import pytest # pygit2 from pygit2 import Odb, Oid from pygit2.enums import ObjectType from . import utils BLOB_HEX = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' BLOB_RAW = binascii.unhexlify(BLOB_HEX.encode('ascii')) BLOB_OID = Oid(raw=BLOB_RAW) def test_emptyodb(barerepo): odb = Odb() assert len(list(odb)) == 0 assert BLOB_HEX not in odb path = Path(barerepo.path) / 'objects' odb.add_disk_alternate(path) assert BLOB_HEX in odb @pytest.fixture def odb(barerepo): odb = barerepo.odb yield odb def test_iterable(odb): assert BLOB_HEX in odb def test_contains(odb): assert BLOB_HEX in odb def test_read(odb): with pytest.raises(TypeError): odb.read(123) utils.assertRaisesWithArg(KeyError, '1' * 40, odb.read, '1' * 40) ab = odb.read(BLOB_OID) a = odb.read(BLOB_HEX) assert ab == a assert (ObjectType.BLOB, b'a contents\n') == a a2 = odb.read('7f129fd57e31e935c6d60a0c794efe4e6927664b') assert (ObjectType.BLOB, b'a contents 2\n') == a2 a_hex_prefix = BLOB_HEX[:4] a3 = odb.read(a_hex_prefix) assert (ObjectType.BLOB, b'a contents\n') == a3 def test_write(odb): data = b'hello world' # invalid object type with pytest.raises(ValueError): odb.write(ObjectType.ANY, data) oid = odb.write(ObjectType.BLOB, data) assert type(oid) == Oid libgit2-pygit2-a011e26/test/test_odb_backend.py000066400000000000000000000102101473744024100214060ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Odb backends.""" # Standard Library import binascii from pathlib import Path import pytest # pygit2 import pygit2 from pygit2.enums import ObjectType from . import utils BLOB_HEX = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' BLOB_RAW = binascii.unhexlify(BLOB_HEX.encode('ascii')) BLOB_OID = pygit2.Oid(raw=BLOB_RAW) @pytest.fixture def odb(barerepo): yield barerepo.odb, Path(barerepo.path) / 'objects' def test_pack(odb): odb, path = odb pack = pygit2.OdbBackendPack(path) assert len(list(pack)) > 0 for obj in pack: assert obj in odb def test_loose(odb): odb, path = odb pack = pygit2.OdbBackendLoose(path, 5, False) assert len(list(pack)) > 0 for obj in pack: assert obj in odb class ProxyBackend(pygit2.OdbBackend): def __init__(self, source): super().__init__() self.source = source def read_cb(self, oid): return self.source.read(oid) def read_prefix_cb(self, oid): return self.source.read_prefix(oid) def read_header_cb(self, oid): typ, data = self.source.read(oid) return typ, len(data) def exists_cb(self, oid): return self.source.exists(oid) def exists_prefix_cb(self, oid): return self.source.exists_prefix(oid) def refresh_cb(self): self.source.refresh() def __iter__(self): return iter(self.source) # # Test a custom object backend alone (without adding it to an ODB) # This doesn't make much sense, but it's possible. # @pytest.fixture def proxy(barerepo): path = Path(barerepo.path) / 'objects' yield ProxyBackend(pygit2.OdbBackendPack(path)) def test_iterable(proxy): assert BLOB_HEX in [o for o in proxy] def test_read(proxy): with pytest.raises(TypeError): proxy.read(123) utils.assertRaisesWithArg(KeyError, '1' * 40, proxy.read, '1' * 40) ab = proxy.read(BLOB_OID) a = proxy.read(BLOB_HEX) assert ab == a assert (ObjectType.BLOB, b'a contents\n') == a def test_read_prefix(proxy): a_hex_prefix = BLOB_HEX[:4] a3 = proxy.read_prefix(a_hex_prefix) assert (ObjectType.BLOB, b'a contents\n', BLOB_OID) == a3 def test_exists(proxy): with pytest.raises(TypeError): proxy.exists(123) assert not proxy.exists('1' * 40) assert proxy.exists(BLOB_HEX) def test_exists_prefix(proxy): a_hex_prefix = BLOB_HEX[:4] assert BLOB_HEX == proxy.exists_prefix(a_hex_prefix) # # Test a custom object backend, through a Repository. # @pytest.fixture def repo(barerepo): odb = pygit2.Odb() path = Path(barerepo.path) / 'objects' backend = pygit2.OdbBackendPack(path) backend = ProxyBackend(backend) odb.add_backend(backend, 1) repo = pygit2.Repository() repo.set_odb(odb) yield repo def test_repo_read(repo): with pytest.raises(TypeError): repo[123] utils.assertRaisesWithArg(KeyError, '1' * 40, repo.__getitem__, '1' * 40) ab = repo[BLOB_OID] a = repo[BLOB_HEX] assert ab == a libgit2-pygit2-a011e26/test/test_oid.py000066400000000000000000000050321473744024100177540ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Object ids.""" # Standard Library from binascii import unhexlify from pygit2 import Oid import pytest HEX = '15b648aec6ed045b5ca6f57f8b7831a8b4757298' RAW = unhexlify(HEX.encode('ascii')) def test_raw(): oid = Oid(raw=RAW) assert oid.raw == RAW assert oid == HEX def test_hex(): oid = Oid(hex=HEX) assert oid.raw == RAW assert oid == HEX def test_hex_bytes(): hex = bytes(HEX, 'ascii') with pytest.raises(TypeError): Oid(hex=hex) def test_none(): with pytest.raises(ValueError): Oid() def test_both(): with pytest.raises(ValueError): Oid(raw=RAW, hex=HEX) def test_long(): with pytest.raises(ValueError): Oid(raw=RAW + b'a') with pytest.raises(ValueError): Oid(hex=HEX + 'a') def test_cmp(): oid1 = Oid(raw=RAW) # Equal oid2 = Oid(hex=HEX) assert oid1 == oid2 # Not equal oid2 = Oid(hex='15b648aec6ed045b5ca6f57f8b7831a8b4757299') assert oid1 != oid2 # Other assert oid1 < oid2 assert oid1 <= oid2 assert not oid1 == oid2 assert not oid1 > oid2 assert not oid1 >= oid2 def test_hash(): s = set() s.add(Oid(raw=RAW)) s.add(Oid(hex=HEX)) assert len(s) == 1 s.add(Oid(hex='0000000000000000000000000000000000000000')) s.add(Oid(hex='0000000000000000000000000000000000000001')) assert len(s) == 3 libgit2-pygit2-a011e26/test/test_options.py000066400000000000000000000100441473744024100206730ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pygit2 from pygit2 import option from pygit2.enums import ConfigLevel, ObjectType, Option def __option(getter, setter, value): old_value = option(getter) option(setter, value) assert value == option(getter) # Reset to avoid side effects in later tests option(setter, old_value) def __proxy(name, value): old_value = getattr(pygit2.settings, name) setattr(pygit2.settings, name, value) assert value == getattr(pygit2.settings, name) # Reset to avoid side effects in later tests setattr(pygit2.settings, name, old_value) def test_mwindow_size(): __option(Option.GET_MWINDOW_SIZE, Option.SET_MWINDOW_SIZE, 200 * 1024) def test_mwindow_size_proxy(): __proxy('mwindow_size', 300 * 1024) def test_mwindow_mapped_limit_200(): __option( Option.GET_MWINDOW_MAPPED_LIMIT, Option.SET_MWINDOW_MAPPED_LIMIT, 200 * 1024 ) def test_mwindow_mapped_limit_300(): __proxy('mwindow_mapped_limit', 300 * 1024) def test_cache_object_limit(): new_limit = 2 * 1024 option(Option.SET_CACHE_OBJECT_LIMIT, ObjectType.BLOB, new_limit) def test_cache_object_limit_proxy(): new_limit = 4 * 1024 pygit2.settings.cache_object_limit(ObjectType.BLOB, new_limit) def test_cached_memory(): value = option(Option.GET_CACHED_MEMORY) assert value[1] == 256 * 1024**2 def test_cached_memory_proxy(): assert pygit2.settings.cached_memory[1] == 256 * 1024**2 def test_enable_caching(): pygit2.settings.enable_caching(False) pygit2.settings.enable_caching(True) # Lower level API option(Option.ENABLE_CACHING, False) option(Option.ENABLE_CACHING, True) def test_disable_pack_keep_file_checks(): pygit2.settings.disable_pack_keep_file_checks(False) pygit2.settings.disable_pack_keep_file_checks(True) # Lower level API option(Option.DISABLE_PACK_KEEP_FILE_CHECKS, False) option(Option.DISABLE_PACK_KEEP_FILE_CHECKS, True) def test_cache_max_size_proxy(): pygit2.settings.cache_max_size(128 * 1024**2) assert pygit2.settings.cached_memory[1] == 128 * 1024**2 pygit2.settings.cache_max_size(256 * 1024**2) assert pygit2.settings.cached_memory[1] == 256 * 1024**2 def test_search_path(): paths = [ (ConfigLevel.GLOBAL, '/tmp/global'), (ConfigLevel.XDG, '/tmp/xdg'), (ConfigLevel.SYSTEM, '/tmp/etc'), ] for level, path in paths: option(Option.SET_SEARCH_PATH, level, path) assert path == option(Option.GET_SEARCH_PATH, level) def test_search_path_proxy(): paths = [ (ConfigLevel.GLOBAL, '/tmp2/global'), (ConfigLevel.XDG, '/tmp2/xdg'), (ConfigLevel.SYSTEM, '/tmp2/etc'), ] for level, path in paths: pygit2.settings.search_path[level] = path assert path == pygit2.settings.search_path[level] def test_owner_validation(): __option(Option.GET_OWNER_VALIDATION, Option.SET_OWNER_VALIDATION, 0) libgit2-pygit2-a011e26/test/test_packbuilder.py000066400000000000000000000100341473744024100214640ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Index files.""" from pathlib import Path import pygit2 from pygit2 import PackBuilder from . import utils def test_create_packbuilder(testrepo): # simple test of PackBuilder creation packbuilder = PackBuilder(testrepo) assert len(packbuilder) == 0 def test_add(testrepo): # Add a few objects and confirm that the count is correct packbuilder = PackBuilder(testrepo) objects_to_add = [obj for obj in testrepo] packbuilder.add(objects_to_add[0]) assert len(packbuilder) == 1 packbuilder.add(objects_to_add[1]) assert len(packbuilder) == 2 def test_add_recursively(testrepo): # Add the head object and referenced objects recursively and confirm that the count is correct packbuilder = PackBuilder(testrepo) packbuilder.add_recur(testrepo.head.target) # expect a count of 4 made up of the following referenced objects: # Commit # Tree # Blob: hello.txt # Blob: .gitignore assert len(packbuilder) == 4 def test_repo_pack(testrepo, tmp_path): # pack the repo with the default strategy confirm_same_repo_after_packing(testrepo, tmp_path, None) def test_pack_with_delegate(testrepo, tmp_path): # loop through all branches and add each commit to the packbuilder def pack_delegate(pb): for branch in pb._repo.branches: br = pb._repo.branches.get(branch) for commit in br.log(): pb.add_recur(commit.oid_new) confirm_same_repo_after_packing(testrepo, tmp_path, pack_delegate) def setup_second_repo(tmp_path): # helper method to set up a second repo for comparison tmp_path_2 = tmp_path / 'test_repo2' with utils.TemporaryRepository('testrepo.zip', tmp_path_2) as path: testrepo = pygit2.Repository(path) return testrepo def confirm_same_repo_after_packing(testrepo, tmp_path, pack_delegate): # Helper method to confirm the contents of two repos before and after packing pack_repo = setup_second_repo(tmp_path) pack_repo_path = Path(pack_repo.path) objects_dir = pack_repo_path / 'objects' utils.rmtree(objects_dir) pack_path = objects_dir / 'pack' pack_path.mkdir(parents=True) # assert that the number of written objects is the same as the number of objects in the repo written_objects = testrepo.pack(pack_path, pack_delegate=pack_delegate) assert written_objects == len([obj for obj in testrepo]) # assert that the number of objects in the pack repo is the same as the original repo orig_objects = [obj for obj in testrepo.odb] packed_objects = [obj for obj in pack_repo.odb] assert len(packed_objects) == len(orig_objects) # assert that the objects in the packed repo are the same objects as the original repo for i, obj in enumerate(orig_objects): assert pack_repo[obj].type == testrepo[obj].type assert pack_repo[obj].read_raw() == testrepo[obj].read_raw() libgit2-pygit2-a011e26/test/test_patch.py000066400000000000000000000151011473744024100202760ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pygit2 import pytest BLOB_OLD_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b' BLOB_NEW_SHA = '3b18e512dba79e4c8300dd08aeb37f8e728b8dad' BLOB_OLD_CONTENT = b"""hello world hola mundo bonjour le monde """ BLOB_NEW_CONTENT = b'foo bar\n' BLOB_OLD_PATH = 'a/file' BLOB_NEW_PATH = 'b/file' BLOB_PATCH2 = """diff --git a/a/file b/b/file index a520c24..3b18e51 100644 --- a/a/file +++ b/b/file @@ -1,3 +1 @@ hello world -hola mundo -bonjour le monde """ BLOB_PATCH = """diff --git a/a/file b/b/file index a520c24..d675fa4 100644 --- a/a/file +++ b/b/file @@ -1,3 +1 @@ -hello world -hola mundo -bonjour le monde +foo bar """ BLOB_PATCH_ADDED = """diff --git a/a/file b/b/file new file mode 100644 index 0000000..d675fa4 --- /dev/null +++ b/b/file @@ -0,0 +1 @@ +foo bar """ BLOB_PATCH_DELETED = """diff --git a/a/file b/b/file deleted file mode 100644 index a520c24..0000000 --- a/a/file +++ /dev/null @@ -1,3 +0,0 @@ -hello world -hola mundo -bonjour le monde """ def test_patch_create_from_buffers(): patch = pygit2.Patch.create_from( BLOB_OLD_CONTENT, BLOB_NEW_CONTENT, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) assert patch.text == BLOB_PATCH def test_patch_create_from_blobs(testrepo): old_blob = testrepo[BLOB_OLD_SHA] new_blob = testrepo[BLOB_NEW_SHA] patch = pygit2.Patch.create_from( old_blob, new_blob, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) assert patch.text == BLOB_PATCH2 def test_patch_create_from_blob_buffer(testrepo): old_blob = testrepo[BLOB_OLD_SHA] patch = pygit2.Patch.create_from( old_blob, BLOB_NEW_CONTENT, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) assert patch.text == BLOB_PATCH def test_patch_create_from_blob_buffer_add(testrepo): patch = pygit2.Patch.create_from( None, BLOB_NEW_CONTENT, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) assert patch.text == BLOB_PATCH_ADDED def test_patch_create_from_blob_buffer_delete(testrepo): old_blob = testrepo[BLOB_OLD_SHA] patch = pygit2.Patch.create_from( old_blob, None, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) assert patch.text == BLOB_PATCH_DELETED def test_patch_create_from_bad_old_type_arg(testrepo): with pytest.raises(TypeError): pygit2.Patch.create_from(testrepo, BLOB_NEW_CONTENT) def test_patch_create_from_bad_new_type_arg(testrepo): with pytest.raises(TypeError): pygit2.Patch.create_from(None, testrepo) def test_context_lines(testrepo): old_blob = testrepo[BLOB_OLD_SHA] new_blob = testrepo[BLOB_NEW_SHA] patch = pygit2.Patch.create_from( old_blob, new_blob, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) context_count = len( [line for line in patch.text.splitlines() if line.startswith(' ')] ) assert context_count != 0 def test_no_context_lines(testrepo): old_blob = testrepo[BLOB_OLD_SHA] new_blob = testrepo[BLOB_NEW_SHA] patch = pygit2.Patch.create_from( old_blob, new_blob, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, context_lines=0, ) context_count = len( [line for line in patch.text.splitlines() if line.startswith(' ')] ) assert context_count == 0 def test_patch_create_blob_blobs(testrepo): old_blob = testrepo[testrepo.create_blob(BLOB_OLD_CONTENT)] new_blob = testrepo[testrepo.create_blob(BLOB_NEW_CONTENT)] patch = pygit2.Patch.create_from( old_blob, new_blob, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) assert patch.text == BLOB_PATCH def test_patch_create_blob_buffer(testrepo): blob = testrepo[testrepo.create_blob(BLOB_OLD_CONTENT)] patch = pygit2.Patch.create_from( blob, BLOB_NEW_CONTENT, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) assert patch.text == BLOB_PATCH def test_patch_create_blob_delete(testrepo): blob = testrepo[testrepo.create_blob(BLOB_OLD_CONTENT)] patch = pygit2.Patch.create_from( blob, None, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) assert patch.text == BLOB_PATCH_DELETED def test_patch_create_blob_add(testrepo): blob = testrepo[testrepo.create_blob(BLOB_NEW_CONTENT)] patch = pygit2.Patch.create_from( None, blob, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) assert patch.text == BLOB_PATCH_ADDED def test_patch_delete_blob(testrepo): blob = testrepo[BLOB_OLD_SHA] patch = pygit2.Patch.create_from( blob, None, old_as_path=BLOB_OLD_PATH, new_as_path=BLOB_NEW_PATH, ) # Make sure that even after deleting the blob the patch still has the # necessary references to generate its patch del blob assert patch.text == BLOB_PATCH_DELETED def test_patch_multi_blob(testrepo): blob = testrepo[BLOB_OLD_SHA] patch = pygit2.Patch.create_from(blob, None) patch_text = patch.text blob = testrepo[BLOB_OLD_SHA] patch2 = pygit2.Patch.create_from(blob, None) patch_text2 = patch.text assert patch_text == patch_text2 assert patch_text == patch.text assert patch_text2 == patch2.text assert patch.text == patch2.text libgit2-pygit2-a011e26/test/test_patch_encoding.py000066400000000000000000000051041473744024100221460ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pygit2 expected_diff = b"""diff --git a/iso-8859-1.txt b/iso-8859-1.txt index e84e339..201e0c9 100644 --- a/iso-8859-1.txt +++ b/iso-8859-1.txt @@ -1 +1,2 @@ Kristian H\xf8gsberg +foo """ def test_patch_from_non_utf8(): # blobs encoded in ISO-8859-1 old_content = b'Kristian H\xf8gsberg\n' new_content = old_content + b'foo\n' patch = pygit2.Patch.create_from( old_content, new_content, old_as_path='iso-8859-1.txt', new_as_path='iso-8859-1.txt', ) assert patch.data == expected_diff assert patch.text == expected_diff.decode('utf-8', errors='replace') # `patch.text` corrupted the ISO-8859-1 content as it forced UTF-8 # decoding, so assert that we cannot get the original content back: assert patch.text.encode('utf-8') != expected_diff def test_patch_create_from_blobs(encodingrepo): patch = pygit2.Patch.create_from( encodingrepo['e84e339ac7fcc823106efa65a6972d7a20016c85'], encodingrepo['201e0c908e3d9f526659df3e556c3d06384ef0df'], old_as_path='iso-8859-1.txt', new_as_path='iso-8859-1.txt', ) assert patch.data == expected_diff assert patch.text == expected_diff.decode('utf-8', errors='replace') # `patch.text` corrupted the ISO-8859-1 content as it forced UTF-8 # decoding, so assert that we cannot get the original content back: assert patch.text.encode('utf-8') != expected_diff libgit2-pygit2-a011e26/test/test_refdb_backend.py000066400000000000000000000105021473744024100217300ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Refdb objects.""" from pathlib import Path import pygit2 import pytest # Note: the refdb abstraction from libgit2 is meant to provide information # which libgit2 transforms into something more useful, and in general YMMV by # using the backend directly. So some of these tests are a bit vague or # incomplete, to avoid hitting the semi-valid states that refdbs produce by # design. class ProxyRefdbBackend(pygit2.RefdbBackend): def __init__(testrepo, source): testrepo.source = source def exists(testrepo, ref): return testrepo.source.exists(ref) def lookup(testrepo, ref): return testrepo.source.lookup(ref) def write(testrepo, ref, force, who, message, old, old_target): return testrepo.source.write(ref, force, who, message, old, old_target) def rename(testrepo, old_name, new_name, force, who, message): return testrepo.source.rename(old_name, new_name, force, who, message) def delete(testrepo, ref_name, old_id, old_target): return testrepo.source.delete(ref_name, old_id, old_target) def compress(testrepo): return testrepo.source.compress() def has_log(testrepo, ref_name): return testrepo.source.has_log(ref_name) def ensure_log(testrepo, ref_name): return testrepo.source.ensure_log(ref_name) def __iter__(testrepo): return iter(testrepo.source) @pytest.fixture def repo(testrepo): testrepo.backend = ProxyRefdbBackend(pygit2.RefdbFsBackend(testrepo)) yield testrepo def test_exists(repo): assert not repo.backend.exists('refs/heads/does-not-exist') assert repo.backend.exists('refs/heads/master') def test_lookup(repo): assert repo.backend.lookup('refs/heads/does-not-exist') is None assert repo.backend.lookup('refs/heads/master').name == 'refs/heads/master' def test_write(repo): master = repo.backend.lookup('refs/heads/master') commit = repo.get(master.target) ref = pygit2.Reference('refs/heads/test-write', master.target, None) repo.backend.write(ref, False, commit.author, 'Create test-write', None, None) assert repo.backend.lookup('refs/heads/test-write').target == master.target def test_rename(repo): old_ref = repo.backend.lookup('refs/heads/i18n') target = repo.get(old_ref.target) repo.backend.rename( 'refs/heads/i18n', 'refs/heads/intl', False, target.committer, target.message ) assert repo.backend.lookup('refs/heads/intl').target == target.id def test_delete(repo): old = repo.backend.lookup('refs/heads/i18n') repo.backend.delete('refs/heads/i18n', old.target, None) assert not repo.backend.lookup('refs/heads/i18n') def test_compress(repo): repo = repo packed_refs_file = Path(repo.path) / 'packed-refs' assert not packed_refs_file.exists() repo.backend.compress() assert packed_refs_file.exists() def test_has_log(repo): assert repo.backend.has_log('refs/heads/master') assert not repo.backend.has_log('refs/heads/does-not-exist') def test_ensure_log(repo): assert not repo.backend.has_log('refs/heads/new-log') repo.backend.ensure_log('refs/heads/new-log') assert repo.backend.has_log('refs/heads/new-log') libgit2-pygit2-a011e26/test/test_refs.py000066400000000000000000000621521473744024100201460ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for reference objects.""" from pathlib import Path import pytest from pygit2 import Commit, Signature, Tree, reference_is_valid_name from pygit2 import AlreadyExistsError, GitError, InvalidSpecError from pygit2.enums import ReferenceType LAST_COMMIT = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' def test_refs_list_objects(testrepo): refs = [(ref.name, ref.target) for ref in testrepo.references.objects] assert sorted(refs) == [ ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'), ('refs/heads/master', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ] def test_refs_list(testrepo): # Without argument assert sorted(testrepo.references) == ['refs/heads/i18n', 'refs/heads/master'] # We add a symbolic reference testrepo.create_reference('refs/tags/version1', 'refs/heads/master') assert sorted(testrepo.references) == [ 'refs/heads/i18n', 'refs/heads/master', 'refs/tags/version1', ] def test_head(testrepo): head = testrepo.head assert LAST_COMMIT == testrepo[head.target].id assert LAST_COMMIT == testrepo[head.raw_target].id def test_refs_getitem(testrepo): refname = 'refs/foo' # Raise KeyError ? with pytest.raises(KeyError): testrepo.references[refname] # Return None ? assert testrepo.references.get(refname) is None # Test a lookup reference = testrepo.references.get('refs/heads/master') assert reference.name == 'refs/heads/master' def test_refs_get_sha(testrepo): reference = testrepo.references['refs/heads/master'] assert reference.target == LAST_COMMIT def test_refs_set_sha(testrepo): NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' reference = testrepo.references.get('refs/heads/master') reference.set_target(NEW_COMMIT) assert reference.target == NEW_COMMIT def test_refs_set_sha_prefix(testrepo): NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' reference = testrepo.references.get('refs/heads/master') reference.set_target(NEW_COMMIT[0:6]) assert reference.target == NEW_COMMIT def test_refs_get_type(testrepo): reference = testrepo.references.get('refs/heads/master') assert reference.type == ReferenceType.DIRECT def test_refs_get_target(testrepo): reference = testrepo.references.get('HEAD') assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' def test_refs_set_target(testrepo): reference = testrepo.references.get('HEAD') assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' reference.set_target('refs/heads/i18n') assert reference.target == 'refs/heads/i18n' assert reference.raw_target == b'refs/heads/i18n' def test_refs_get_shorthand(testrepo): reference = testrepo.references.get('refs/heads/master') assert reference.shorthand == 'master' reference = testrepo.references.create('refs/remotes/origin/master', LAST_COMMIT) assert reference.shorthand == 'origin/master' def test_refs_set_target_with_message(testrepo): reference = testrepo.references.get('HEAD') assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' sig = Signature('foo', 'bar') testrepo.set_ident('foo', 'bar') msg = 'Hello log' reference.set_target('refs/heads/i18n', message=msg) assert reference.target == 'refs/heads/i18n' assert reference.raw_target == b'refs/heads/i18n' first = list(reference.log())[0] assert first.message == msg assert first.committer == sig def test_refs_delete(testrepo): # We add a tag as a new reference that points to "origin/master" reference = testrepo.references.create('refs/tags/version1', LAST_COMMIT) assert 'refs/tags/version1' in testrepo.references # And we delete it reference.delete() assert 'refs/tags/version1' not in testrepo.references # Access the deleted reference with pytest.raises(GitError): getattr(reference, 'name') with pytest.raises(GitError): getattr(reference, 'type') with pytest.raises(GitError): getattr(reference, 'target') with pytest.raises(GitError): reference.delete() with pytest.raises(GitError): reference.resolve() with pytest.raises(GitError): reference.rename('refs/tags/version2') def test_refs_rename(testrepo): # We add a tag as a new reference that points to "origin/master" reference = testrepo.references.create('refs/tags/version1', LAST_COMMIT) assert reference.name == 'refs/tags/version1' reference.rename('refs/tags/version2') assert reference.name == 'refs/tags/version2' with pytest.raises(AlreadyExistsError): reference.rename('refs/tags/version2') with pytest.raises(InvalidSpecError): reference.rename('b1') # def test_reload(testrepo): # name = 'refs/tags/version1' # ref = testrepo.create_reference(name, "refs/heads/master", symbolic=True) # ref2 = testrepo.lookup_reference(name) # ref.delete() # assert ref2.name == name # with pytest.raises(KeyError): ref2.reload() # with pytest.raises(GitError): getattr(ref2, 'name') def test_refs_resolve(testrepo): reference = testrepo.references.get('HEAD') assert reference.type == ReferenceType.SYMBOLIC reference = reference.resolve() assert reference.type == ReferenceType.DIRECT assert reference.target == LAST_COMMIT def test_refs_resolve_identity(testrepo): head = testrepo.references.get('HEAD') ref = head.resolve() assert ref.resolve() is ref def test_refs_create(testrepo): # We add a tag as a new reference that points to "origin/master" reference = testrepo.references.create('refs/tags/version1', LAST_COMMIT) refs = testrepo.references assert 'refs/tags/version1' in refs reference = testrepo.references.get('refs/tags/version1') assert reference.target == LAST_COMMIT # try to create existing reference with pytest.raises(ValueError): testrepo.references.create('refs/tags/version1', LAST_COMMIT) # try to create existing reference with force reference = testrepo.references.create( 'refs/tags/version1', LAST_COMMIT, force=True ) assert reference.target == LAST_COMMIT def test_refs_create_symbolic(testrepo): # We add a tag as a new symbolic reference that always points to # "refs/heads/master" reference = testrepo.references.create('refs/tags/beta', 'refs/heads/master') assert reference.type == ReferenceType.SYMBOLIC assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' # try to create existing symbolic reference with pytest.raises(ValueError): testrepo.references.create('refs/tags/beta', 'refs/heads/master') # try to create existing symbolic reference with force reference = testrepo.references.create( 'refs/tags/beta', 'refs/heads/master', force=True ) assert reference.type == ReferenceType.SYMBOLIC assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' # def test_packall_references(testrepo): # testrepo.packall_references() def test_refs_peel(testrepo): ref = testrepo.references.get('refs/heads/master') assert testrepo[ref.target].id == ref.peel().id assert testrepo[ref.raw_target].id == ref.peel().id commit = ref.peel(Commit) assert commit.tree.id == ref.peel(Tree).id def test_refs_equality(testrepo): ref1 = testrepo.references.get('refs/heads/master') ref2 = testrepo.references.get('refs/heads/master') ref3 = testrepo.references.get('refs/heads/i18n') assert ref1 is not ref2 assert ref1 == ref2 assert not ref1 != ref2 assert ref1 != ref3 assert not ref1 == ref3 def test_refs_compress(testrepo): packed_refs_file = Path(testrepo.path) / 'packed-refs' assert not packed_refs_file.exists() old_refs = [(ref.name, ref.target) for ref in testrepo.references.objects] testrepo.references.compress() assert packed_refs_file.exists() new_refs = [(x.name, x.target) for x in testrepo.references.objects] assert old_refs == new_refs # # Low level API written in C, repo.references call these. # def test_list_all_reference_objects(testrepo): repo = testrepo refs = [(ref.name, ref.target) for ref in repo.listall_reference_objects()] assert sorted(refs) == [ ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'), ('refs/heads/master', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ] def test_list_all_references(testrepo): repo = testrepo # Without argument assert sorted(repo.listall_references()) == ['refs/heads/i18n', 'refs/heads/master'] assert sorted(repo.raw_listall_references()) == [ b'refs/heads/i18n', b'refs/heads/master', ] # We add a symbolic reference repo.create_reference('refs/tags/version1', 'refs/heads/master') assert sorted(repo.listall_references()) == [ 'refs/heads/i18n', 'refs/heads/master', 'refs/tags/version1', ] assert sorted(repo.raw_listall_references()) == [ b'refs/heads/i18n', b'refs/heads/master', b'refs/tags/version1', ] def test_references_iterator_init(testrepo): repo = testrepo iter = repo.references_iterator_init() assert iter.__class__.__name__ == 'RefsIterator' def test_references_iterator_next(testrepo): repo = testrepo repo.create_reference( 'refs/tags/version1', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' ) repo.create_reference( 'refs/tags/version2', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' ) iter_all = repo.references_iterator_init() all_refs = [] for _ in range(4): curr_ref = repo.references_iterator_next(iter_all) if curr_ref: all_refs.append((curr_ref.name, curr_ref.target)) assert sorted(all_refs) == [ ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'), ('refs/heads/master', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ('refs/tags/version1', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ('refs/tags/version2', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ] iter_branches = repo.references_iterator_init() all_branches = [] for _ in range(4): curr_ref = repo.references_iterator_next(iter_branches, 1) if curr_ref: all_branches.append((curr_ref.name, curr_ref.target)) assert sorted(all_branches) == [ ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'), ('refs/heads/master', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ] iter_tags = repo.references_iterator_init() all_tags = [] for _ in range(4): curr_ref = repo.references_iterator_next(iter_tags, 2) if curr_ref: all_tags.append((curr_ref.name, curr_ref.target)) assert sorted(all_tags) == [ ('refs/tags/version1', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ('refs/tags/version2', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ] def test_references_iterator_next_python(testrepo): repo = testrepo repo.create_reference( 'refs/tags/version1', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' ) repo.create_reference( 'refs/tags/version2', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' ) refs = [(x.name, x.target) for x in repo.references.iterator()] assert sorted(refs) == [ ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'), ('refs/heads/master', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ('refs/tags/version1', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ('refs/tags/version2', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ] branches = [(x.name, x.target) for x in repo.references.iterator(1)] assert sorted(branches) == [ ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'), ('refs/heads/master', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ] tags = [(x.name, x.target) for x in repo.references.iterator(2)] assert sorted(tags) == [ ('refs/tags/version1', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ('refs/tags/version2', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ] def test_references_iterator_invalid_filter(testrepo): repo = testrepo iter_all = repo.references_iterator_init() all_refs = [] for _ in range(4): curr_ref = repo.references_iterator_next(iter_all, 5) if curr_ref: all_refs.append((curr_ref.name, curr_ref.target)) assert all_refs == [] def test_references_iterator_invalid_filter_python(testrepo): repo = testrepo refs = [] with pytest.raises(ValueError): for ref in repo.references.iterator(5): refs.append((ref.name, ref.target)) def test_lookup_reference(testrepo): repo = testrepo # Raise KeyError ? with pytest.raises(KeyError): repo.lookup_reference('refs/foo') # Test a lookup reference = repo.lookup_reference('refs/heads/master') assert reference.name == 'refs/heads/master' def test_lookup_reference_dwim(testrepo): repo = testrepo # remote ref reference = testrepo.create_reference('refs/remotes/origin/master', LAST_COMMIT) assert reference.shorthand == 'origin/master' # tag repo.create_reference('refs/tags/version1', LAST_COMMIT) # Test dwim lookups # Raise KeyError ? with pytest.raises(KeyError): repo.lookup_reference_dwim('foo') with pytest.raises(KeyError): repo.lookup_reference_dwim('refs/foo') reference = repo.lookup_reference_dwim('refs/heads/master') assert reference.name == 'refs/heads/master' reference = repo.lookup_reference_dwim('master') assert reference.name == 'refs/heads/master' reference = repo.lookup_reference_dwim('origin/master') assert reference.name == 'refs/remotes/origin/master' reference = repo.lookup_reference_dwim('version1') assert reference.name == 'refs/tags/version1' def test_resolve_refish(testrepo): repo = testrepo # remote ref reference = testrepo.create_reference('refs/remotes/origin/master', LAST_COMMIT) assert reference.shorthand == 'origin/master' # tag repo.create_reference('refs/tags/version1', LAST_COMMIT) # Test dwim lookups # Raise KeyError ? with pytest.raises(KeyError): repo.resolve_refish('foo') with pytest.raises(KeyError): repo.resolve_refish('refs/foo') commit, ref = repo.resolve_refish('refs/heads/i18n') assert ref.name == 'refs/heads/i18n' assert commit.id == '5470a671a80ac3789f1a6a8cefbcf43ce7af0563' commit, ref = repo.resolve_refish('master') assert ref.name == 'refs/heads/master' assert commit.id == LAST_COMMIT commit, ref = repo.resolve_refish('origin/master') assert ref.name == 'refs/remotes/origin/master' assert commit.id == LAST_COMMIT commit, ref = repo.resolve_refish('version1') assert ref.name == 'refs/tags/version1' assert commit.id == LAST_COMMIT commit, ref = repo.resolve_refish(LAST_COMMIT) assert ref is None assert commit.id == LAST_COMMIT commit, ref = repo.resolve_refish('HEAD~1') assert ref is None assert commit.id == '5ebeeebb320790caf276b9fc8b24546d63316533' def test_reference_get_sha(testrepo): reference = testrepo.lookup_reference('refs/heads/master') assert reference.target == LAST_COMMIT def test_reference_set_sha(testrepo): NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' reference = testrepo.lookup_reference('refs/heads/master') reference.set_target(NEW_COMMIT) assert reference.target == NEW_COMMIT def test_reference_set_sha_prefix(testrepo): NEW_COMMIT = '5ebeeebb320790caf276b9fc8b24546d63316533' reference = testrepo.lookup_reference('refs/heads/master') reference.set_target(NEW_COMMIT[0:6]) assert reference.target == NEW_COMMIT def test_reference_get_type(testrepo): reference = testrepo.lookup_reference('refs/heads/master') assert reference.type == ReferenceType.DIRECT def test_get_target(testrepo): reference = testrepo.lookup_reference('HEAD') assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' def test_set_target(testrepo): reference = testrepo.lookup_reference('HEAD') assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' reference.set_target('refs/heads/i18n') assert reference.target == 'refs/heads/i18n' assert reference.raw_target == b'refs/heads/i18n' def test_get_shorthand(testrepo): reference = testrepo.lookup_reference('refs/heads/master') assert reference.shorthand == 'master' reference = testrepo.create_reference('refs/remotes/origin/master', LAST_COMMIT) assert reference.shorthand == 'origin/master' def test_set_target_with_message(testrepo): reference = testrepo.lookup_reference('HEAD') assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' sig = Signature('foo', 'bar') testrepo.set_ident('foo', 'bar') msg = 'Hello log' reference.set_target('refs/heads/i18n', message=msg) assert reference.target == 'refs/heads/i18n' assert reference.raw_target == b'refs/heads/i18n' first = list(reference.log())[0] assert first.message == msg assert first.committer == sig def test_delete(testrepo): repo = testrepo # We add a tag as a new reference that points to "origin/master" reference = repo.create_reference('refs/tags/version1', LAST_COMMIT) assert 'refs/tags/version1' in repo.listall_references() assert b'refs/tags/version1' in repo.raw_listall_references() # And we delete it reference.delete() assert 'refs/tags/version1' not in repo.listall_references() assert b'refs/tags/version1' not in repo.raw_listall_references() # Access the deleted reference with pytest.raises(GitError): getattr(reference, 'name') with pytest.raises(GitError): getattr(reference, 'type') with pytest.raises(GitError): getattr(reference, 'target') with pytest.raises(GitError): reference.delete() with pytest.raises(GitError): reference.resolve() with pytest.raises(GitError): reference.rename('refs/tags/version2') def test_rename(testrepo): # We add a tag as a new reference that points to "origin/master" reference = testrepo.create_reference('refs/tags/version1', LAST_COMMIT) assert reference.name == 'refs/tags/version1' reference.rename('refs/tags/version2') assert reference.name == 'refs/tags/version2' # def test_reload(testrepo): # name = 'refs/tags/version1' # repo = testrepo # ref = repo.create_reference(name, "refs/heads/master", symbolic=True) # ref2 = repo.lookup_reference(name) # ref.delete() # assert ref2.name == name # with pytest.raises(KeyError): ref2.reload() # with pytest.raises(GitError): getattr(ref2, 'name') def test_reference_resolve(testrepo): reference = testrepo.lookup_reference('HEAD') assert reference.type == ReferenceType.SYMBOLIC reference = reference.resolve() assert reference.type == ReferenceType.DIRECT assert reference.target == LAST_COMMIT def test_reference_resolve_identity(testrepo): head = testrepo.lookup_reference('HEAD') ref = head.resolve() assert ref.resolve() is ref def test_create_reference(testrepo): # We add a tag as a new reference that points to "origin/master" reference = testrepo.create_reference('refs/tags/version1', LAST_COMMIT) assert 'refs/tags/version1' in testrepo.listall_references() assert b'refs/tags/version1' in testrepo.raw_listall_references() reference = testrepo.lookup_reference('refs/tags/version1') assert reference.target == LAST_COMMIT # try to create existing reference with pytest.raises(AlreadyExistsError) as error: testrepo.create_reference('refs/tags/version1', LAST_COMMIT) assert isinstance(error.value, ValueError) # Clear error del error # try to create existing reference with force reference = testrepo.create_reference('refs/tags/version1', LAST_COMMIT, force=True) assert reference.target == LAST_COMMIT def test_create_reference_with_message(testrepo): sig = Signature('foo', 'bar') testrepo.set_ident('foo', 'bar') msg = 'Hello log' reference = testrepo.create_reference( 'refs/heads/feature', LAST_COMMIT, message=msg ) first = list(reference.log())[0] assert first.message == msg assert first.committer == sig def test_create_symbolic_reference(testrepo): repo = testrepo # We add a tag as a new symbolic reference that always points to # "refs/heads/master" reference = repo.create_reference('refs/tags/beta', 'refs/heads/master') assert reference.type == ReferenceType.SYMBOLIC assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' # try to create existing symbolic reference with pytest.raises(AlreadyExistsError) as error: repo.create_reference('refs/tags/beta', 'refs/heads/master') assert isinstance(error.value, ValueError) # try to create existing symbolic reference with force reference = repo.create_reference('refs/tags/beta', 'refs/heads/master', force=True) assert reference.type == ReferenceType.SYMBOLIC assert reference.target == 'refs/heads/master' assert reference.raw_target == b'refs/heads/master' def test_create_symbolic_reference_with_message(testrepo): sig = Signature('foo', 'bar') testrepo.set_ident('foo', 'bar') msg = 'Hello log' reference = testrepo.create_reference( 'HEAD', 'refs/heads/i18n', force=True, message=msg ) first = list(reference.log())[0] assert first.message == msg assert first.committer == sig def test_create_invalid_reference(testrepo): repo = testrepo # try to create a reference with an invalid name with pytest.raises(InvalidSpecError) as error: repo.create_reference('refs/tags/in..valid', 'refs/heads/master') assert isinstance(error.value, ValueError) # def test_packall_references(testrepo): # testrepo.packall_references() def test_peel(testrepo): repo = testrepo ref = repo.lookup_reference('refs/heads/master') assert repo[ref.target].id == ref.peel().id assert repo[ref.raw_target].id == ref.peel().id commit = ref.peel(Commit) assert commit.tree.id == ref.peel(Tree).id def test_valid_reference_names_ascii(): assert reference_is_valid_name('HEAD') assert reference_is_valid_name('refs/heads/master') assert reference_is_valid_name('refs/heads/perfectly/valid') assert reference_is_valid_name('refs/tags/v1') assert reference_is_valid_name('refs/special/ref') def test_valid_reference_names_unicode(): assert reference_is_valid_name('refs/heads/ünicöde') assert reference_is_valid_name('refs/tags/😀') def test_invalid_reference_names(): assert not reference_is_valid_name('') assert not reference_is_valid_name(' refs/heads/master') assert not reference_is_valid_name('refs/heads/in..valid') assert not reference_is_valid_name('refs/heads/invalid~') assert not reference_is_valid_name('refs/heads/invalid^') assert not reference_is_valid_name('refs/heads/invalid:') assert not reference_is_valid_name('refs/heads/invalid\\') assert not reference_is_valid_name('refs/heads/invalid?') assert not reference_is_valid_name('refs/heads/invalid[') assert not reference_is_valid_name('refs/heads/invalid*') assert not reference_is_valid_name('refs/heads/@{no}') assert not reference_is_valid_name('refs/heads/foo//bar') def test_invalid_arguments(): with pytest.raises(TypeError): reference_is_valid_name() with pytest.raises(TypeError): reference_is_valid_name(None) with pytest.raises(TypeError): reference_is_valid_name(1) with pytest.raises(TypeError): reference_is_valid_name('too', 'many') libgit2-pygit2-a011e26/test/test_remote.py000066400000000000000000000305001473744024100204720ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Remote objects.""" from unittest.mock import patch import sys import pytest import pygit2 from pygit2 import Oid from pygit2.ffi import ffi from . import utils REMOTE_NAME = 'origin' REMOTE_URL = 'https://github.com/libgit2/pygit2.git' REMOTE_FETCHSPEC_SRC = 'refs/heads/*' REMOTE_FETCHSPEC_DST = 'refs/remotes/origin/*' REMOTE_REPO_OBJECTS = 30 REMOTE_FETCHTEST_FETCHSPECS = ['refs/tags/v1.13.2'] REMOTE_REPO_FETCH_ALL_OBJECTS = 13276 REMOTE_REPO_FETCH_HEAD_COMMIT_OBJECTS = 238 ORIGIN_REFSPEC = '+refs/heads/*:refs/remotes/origin/*' def test_remote_create(testrepo): name = 'upstream' url = 'https://github.com/libgit2/pygit2.git' remote = testrepo.remotes.create(name, url) assert type(remote) == pygit2.Remote assert name == remote.name assert url == remote.url assert remote.push_url is None with pytest.raises(ValueError): testrepo.remotes.create(*(name, url)) def test_remote_create_with_refspec(testrepo): name = 'upstream' url = 'https://github.com/libgit2/pygit2.git' fetch = '+refs/*:refs/*' remote = testrepo.remotes.create(name, url, fetch) assert type(remote) == pygit2.Remote assert name == remote.name assert url == remote.url assert [fetch] == remote.fetch_refspecs assert remote.push_url is None def test_remote_create_anonymous(testrepo): url = 'https://github.com/libgit2/pygit2.git' remote = testrepo.remotes.create_anonymous(url) assert remote.name is None assert url == remote.url assert remote.push_url is None assert [] == remote.fetch_refspecs assert [] == remote.push_refspecs def test_remote_delete(testrepo): name = 'upstream' url = 'https://github.com/libgit2/pygit2.git' testrepo.remotes.create(name, url) assert 2 == len(testrepo.remotes) remote = testrepo.remotes[1] assert name == remote.name testrepo.remotes.delete(remote.name) assert 1 == len(testrepo.remotes) def test_remote_rename(testrepo): remote = testrepo.remotes[0] assert REMOTE_NAME == remote.name problems = testrepo.remotes.rename(remote.name, 'new') assert [] == problems assert 'new' != remote.name with pytest.raises(ValueError): testrepo.remotes.rename('', '') with pytest.raises(ValueError): testrepo.remotes.rename(None, None) def test_remote_set_url(testrepo): remote = testrepo.remotes['origin'] assert REMOTE_URL == remote.url new_url = 'https://github.com/cholin/pygit2.git' testrepo.remotes.set_url('origin', new_url) remote = testrepo.remotes['origin'] assert new_url == remote.url with pytest.raises(ValueError): testrepo.remotes.set_url('origin', '') testrepo.remotes.set_push_url('origin', new_url) remote = testrepo.remotes['origin'] assert new_url == remote.push_url with pytest.raises(ValueError): testrepo.remotes.set_push_url('origin', '') def test_refspec(testrepo): remote = testrepo.remotes['origin'] assert remote.refspec_count == 1 refspec = remote.get_refspec(0) assert refspec.src == REMOTE_FETCHSPEC_SRC assert refspec.dst == REMOTE_FETCHSPEC_DST assert refspec.force is True assert ORIGIN_REFSPEC == refspec.string assert list == type(remote.fetch_refspecs) assert 1 == len(remote.fetch_refspecs) assert ORIGIN_REFSPEC == remote.fetch_refspecs[0] assert refspec.src_matches('refs/heads/master') assert refspec.dst_matches('refs/remotes/origin/master') assert 'refs/remotes/origin/master' == refspec.transform('refs/heads/master') assert 'refs/heads/master' == refspec.rtransform('refs/remotes/origin/master') assert list == type(remote.push_refspecs) assert 0 == len(remote.push_refspecs) push_specs = remote.push_refspecs assert list == type(push_specs) assert 0 == len(push_specs) testrepo.remotes.add_fetch('origin', '+refs/test/*:refs/test/remotes/*') remote = testrepo.remotes['origin'] fetch_specs = remote.fetch_refspecs assert list == type(fetch_specs) assert 2 == len(fetch_specs) assert [ '+refs/heads/*:refs/remotes/origin/*', '+refs/test/*:refs/test/remotes/*', ] == fetch_specs testrepo.remotes.add_push('origin', '+refs/test/*:refs/test/remotes/*') with pytest.raises(TypeError): testrepo.remotes.add_fetch(['+refs/*:refs/*', 5]) remote = testrepo.remotes['origin'] assert ['+refs/test/*:refs/test/remotes/*'] == remote.push_refspecs def test_remote_list(testrepo): assert 1 == len(testrepo.remotes) remote = testrepo.remotes[0] assert REMOTE_NAME == remote.name assert REMOTE_URL == remote.url name = 'upstream' url = 'https://github.com/libgit2/pygit2.git' remote = testrepo.remotes.create(name, url) assert remote.name in testrepo.remotes.names() assert remote.name in [x.name for x in testrepo.remotes] @utils.requires_network def test_ls_remotes(testrepo): assert 1 == len(testrepo.remotes) remote = testrepo.remotes[0] refs = remote.ls_remotes() assert refs # Check that a known ref is returned. assert next(iter(r for r in refs if r['name'] == 'refs/tags/v0.28.2')) def test_remote_collection(testrepo): remote = testrepo.remotes['origin'] assert REMOTE_NAME == remote.name assert REMOTE_URL == remote.url with pytest.raises(KeyError): testrepo.remotes['upstream'] name = 'upstream' url = 'https://github.com/libgit2/pygit2.git' remote = testrepo.remotes.create(name, url) assert remote.name in testrepo.remotes.names() assert remote.name in [x.name for x in testrepo.remotes] @utils.refcount def test_remote_refcount(testrepo): start = sys.getrefcount(testrepo) remote = testrepo.remotes[0] del remote end = sys.getrefcount(testrepo) assert start == end def test_fetch(emptyrepo): remote = emptyrepo.remotes[0] stats = remote.fetch() assert stats.received_bytes > 2700 assert stats.received_bytes < 3100 assert stats.indexed_objects == REMOTE_REPO_OBJECTS assert stats.received_objects == REMOTE_REPO_OBJECTS @utils.requires_network def test_fetch_depth_zero(testrepo): remote = testrepo.remotes[0] stats = remote.fetch(REMOTE_FETCHTEST_FETCHSPECS, depth=0) assert stats.indexed_objects == REMOTE_REPO_FETCH_ALL_OBJECTS assert stats.received_objects == REMOTE_REPO_FETCH_ALL_OBJECTS @utils.requires_network def test_fetch_depth_one(testrepo): remote = testrepo.remotes[0] stats = remote.fetch(REMOTE_FETCHTEST_FETCHSPECS, depth=1) assert stats.indexed_objects == REMOTE_REPO_FETCH_HEAD_COMMIT_OBJECTS assert stats.received_objects == REMOTE_REPO_FETCH_HEAD_COMMIT_OBJECTS def test_transfer_progress(emptyrepo): class MyCallbacks(pygit2.RemoteCallbacks): def transfer_progress(emptyrepo, stats): emptyrepo.tp = stats callbacks = MyCallbacks() remote = emptyrepo.remotes[0] stats = remote.fetch(callbacks=callbacks) assert stats.received_bytes == callbacks.tp.received_bytes assert stats.indexed_objects == callbacks.tp.indexed_objects assert stats.received_objects == callbacks.tp.received_objects def test_update_tips(emptyrepo): remote = emptyrepo.remotes[0] tips = [ ( 'refs/remotes/origin/master', Oid(hex='0' * 40), Oid(hex='784855caf26449a1914d2cf62d12b9374d76ae78'), ), ( 'refs/tags/root', Oid(hex='0' * 40), Oid(hex='3d2962987c695a29f1f80b6c3aa4ec046ef44369'), ), ] class MyCallbacks(pygit2.RemoteCallbacks): def __init__(self, tips): self.tips = tips self.i = 0 def update_tips(self, name, old, new): assert self.tips[self.i] == (name, old, new) self.i += 1 callbacks = MyCallbacks(tips) remote.fetch(callbacks=callbacks) assert callbacks.i > 0 @utils.requires_network def test_ls_remotes_certificate_check(): url = 'https://github.com/pygit2/empty.git' class MyCallbacks(pygit2.RemoteCallbacks): def __init__(self): self.i = 0 def certificate_check(self, certificate, valid, host): self.i += 1 assert certificate is None assert valid is True assert host == b'github.com' return True # We create an in-memory repository git = pygit2.Repository() remote = git.remotes.create_anonymous(url) callbacks = MyCallbacks() refs = remote.ls_remotes(callbacks=callbacks) # Sanity check that we indeed got some refs. assert len(refs) > 0 # Make sure our certificate_check callback triggered. assert callbacks.i > 0 @pytest.fixture def origin(tmp_path): with utils.TemporaryRepository('barerepo.zip', tmp_path) as path: yield pygit2.Repository(path) @pytest.fixture def clone(tmp_path): clone = tmp_path / 'clone' clone.mkdir() with utils.TemporaryRepository('barerepo.zip', clone) as path: yield pygit2.Repository(path) @pytest.fixture def remote(origin, clone): yield clone.remotes.create('origin', origin.path) def test_push_fast_forward_commits_to_remote_succeeds(origin, clone, remote): tip = clone[clone.head.target] oid = clone.create_commit( 'refs/heads/master', tip.author, tip.author, 'empty commit', tip.tree.id, [tip.id], ) remote.push(['refs/heads/master']) assert origin[origin.head.target].id == oid def test_push_when_up_to_date_succeeds(origin, clone, remote): remote.push(['refs/heads/master']) origin_tip = origin[origin.head.target].id clone_tip = clone[clone.head.target].id assert origin_tip == clone_tip def test_push_non_fast_forward_commits_to_remote_fails(origin, clone, remote): tip = origin[origin.head.target] origin.create_commit( 'refs/heads/master', tip.author, tip.author, 'some commit', tip.tree.id, [tip.id], ) tip = clone[clone.head.target] clone.create_commit( 'refs/heads/master', tip.author, tip.author, 'other commit', tip.tree.id, [tip.id], ) with pytest.raises(pygit2.GitError): remote.push(['refs/heads/master']) @patch.object(pygit2.callbacks, 'RemoteCallbacks') def test_push_options(mock_callbacks, origin, clone, remote): remote.push(['refs/heads/master']) remote_push_options = mock_callbacks.return_value.push_options.remote_push_options assert remote_push_options.count == 0 remote.push(['refs/heads/master'], push_options=[]) remote_push_options = mock_callbacks.return_value.push_options.remote_push_options assert remote_push_options.count == 0 remote.push(['refs/heads/master'], push_options=['foo']) remote_push_options = mock_callbacks.return_value.push_options.remote_push_options assert remote_push_options.count == 1 # strings pointed to by remote_push_options.strings[] are already freed remote.push(['refs/heads/master'], push_options=['Option A', 'Option B']) remote_push_options = mock_callbacks.return_value.push_options.remote_push_options assert remote_push_options.count == 2 # strings pointed to by remote_push_options.strings[] are already freed libgit2-pygit2-a011e26/test/test_remote_prune.py000066400000000000000000000047621473744024100217160ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pytest import pygit2 from pygit2.enums import FetchPrune @pytest.fixture def clonerepo(testrepo, tmp_path): cloned_repo_path = tmp_path / 'test_remote_prune' pygit2.clone_repository(testrepo.workdir, cloned_repo_path) clonerepo = pygit2.Repository(cloned_repo_path) testrepo.branches.delete('i18n') yield clonerepo def test_fetch_remote_default(clonerepo): clonerepo.remotes[0].fetch() assert 'origin/i18n' in clonerepo.branches def test_fetch_remote_prune(clonerepo): clonerepo.remotes[0].fetch(prune=FetchPrune.PRUNE) assert 'origin/i18n' not in clonerepo.branches def test_fetch_no_prune(clonerepo): clonerepo.remotes[0].fetch(prune=FetchPrune.NO_PRUNE) assert 'origin/i18n' in clonerepo.branches def test_remote_prune(clonerepo): pruned = [] class MyCallbacks(pygit2.RemoteCallbacks): def update_tips(self, name, old, new): pruned.append(name) callbacks = MyCallbacks() remote = clonerepo.remotes['origin'] # We do a fetch in order to establish the connection to the remote. # Prune operation requires an active connection. remote.fetch(prune=FetchPrune.NO_PRUNE) assert 'origin/i18n' in clonerepo.branches remote.prune(callbacks) assert pruned == ['refs/remotes/origin/i18n'] assert 'origin/i18n' not in clonerepo.branches libgit2-pygit2-a011e26/test/test_remote_utf8.py000066400000000000000000000027211473744024100214440ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pygit2 import pytest from . import utils @pytest.fixture def repo(tmp_path): with utils.TemporaryRepository('utf8branchrepo.zip', tmp_path) as path: yield pygit2.Repository(path) def test_fetch(repo): remote = repo.remotes.create('origin', repo.workdir) remote.fetch() libgit2-pygit2-a011e26/test/test_repository.py000066400000000000000000001007031473744024100214210ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from pathlib import Path import shutil import tempfile import pytest # pygit2 import pygit2 from pygit2 import init_repository, clone_repository, discover_repository from pygit2 import Oid from pygit2.enums import ( CheckoutNotify, CheckoutStrategy, FileStatus, ObjectType, RepositoryOpenFlag, RepositoryState, ResetMode, StashApplyProgress, ) from . import utils def test_is_empty(testrepo): assert not testrepo.is_empty def test_is_bare(testrepo): assert not testrepo.is_bare def test_get_path(testrepo_path): testrepo, path = testrepo_path assert Path(testrepo.path).resolve() == (path / '.git').resolve() def test_get_workdir(testrepo_path): testrepo, path = testrepo_path assert Path(testrepo.workdir).resolve() == path.resolve() def test_set_workdir(testrepo): directory = tempfile.mkdtemp() testrepo.workdir = directory assert Path(testrepo.workdir).resolve() == Path(directory).resolve() def test_checkout_ref(testrepo): ref_i18n = testrepo.lookup_reference('refs/heads/i18n') # checkout i18n with conflicts and default strategy should # not be possible with pytest.raises(pygit2.GitError): testrepo.checkout(ref_i18n) # checkout i18n with GIT_CHECKOUT_FORCE head = testrepo.head head = testrepo[head.target] assert 'new' not in head.tree testrepo.checkout(ref_i18n, strategy=CheckoutStrategy.FORCE) head = testrepo.head head = testrepo[head.target] assert head.id == ref_i18n.target assert 'new' in head.tree assert 'bye.txt' not in testrepo.status() def test_checkout_callbacks(testrepo): ref_i18n = testrepo.lookup_reference('refs/heads/i18n') class MyCheckoutCallbacks(pygit2.CheckoutCallbacks): def __init__(self): super().__init__() self.conflicting_paths = set() self.updated_paths = set() def checkout_notify_flags(self) -> CheckoutNotify: return CheckoutNotify.CONFLICT | CheckoutNotify.UPDATED def checkout_notify(self, why, path, baseline, target, workdir): if why == CheckoutNotify.CONFLICT: self.conflicting_paths.add(path) elif why == CheckoutNotify.UPDATED: self.updated_paths.add(path) # checkout i18n with conflicts and default strategy should not be possible callbacks = MyCheckoutCallbacks() with pytest.raises(pygit2.GitError): testrepo.checkout(ref_i18n, callbacks=callbacks) # make sure the callbacks caught that assert {'bye.txt'} == callbacks.conflicting_paths # checkout i18n with GIT_CHECKOUT_FORCE head = testrepo.head head = testrepo[head.target] assert 'new' not in head.tree callbacks = MyCheckoutCallbacks() testrepo.checkout(ref_i18n, strategy=CheckoutStrategy.FORCE, callbacks=callbacks) # make sure the callbacks caught the files affected by the checkout assert set() == callbacks.conflicting_paths assert {'bye.txt', 'new'} == callbacks.updated_paths def test_checkout_aborted_from_callbacks(testrepo): ref_i18n = testrepo.lookup_reference('refs/heads/i18n') def read_bye_txt(): return testrepo[testrepo.create_blob_fromworkdir('bye.txt')].data s = testrepo.status() assert s == {'bye.txt': FileStatus.WT_NEW} class MyCheckoutCallbacks(pygit2.CheckoutCallbacks): def __init__(self): super().__init__() self.invoked_times = 0 def checkout_notify(self, why, path, baseline, target, workdir): self.invoked_times += 1 # skip one file so we're certain that NO files are affected, # even if aborting the checkout from the second file if self.invoked_times == 2: raise InterruptedError('Stop the checkout!') head = testrepo.head head = testrepo[head.target] assert 'new' not in head.tree assert b'bye world\n' == read_bye_txt() callbacks = MyCheckoutCallbacks() # checkout i18n with GIT_CHECKOUT_FORCE - callbacks should prevent checkout from completing with pytest.raises(InterruptedError): testrepo.checkout( ref_i18n, strategy=CheckoutStrategy.FORCE, callbacks=callbacks ) assert callbacks.invoked_times == 2 assert 'new' not in head.tree assert b'bye world\n' == read_bye_txt() def test_checkout_branch(testrepo): branch_i18n = testrepo.lookup_branch('i18n') # checkout i18n with conflicts and default strategy should # not be possible with pytest.raises(pygit2.GitError): testrepo.checkout(branch_i18n) # checkout i18n with GIT_CHECKOUT_FORCE head = testrepo.head head = testrepo[head.target] assert 'new' not in head.tree testrepo.checkout(branch_i18n, strategy=CheckoutStrategy.FORCE) head = testrepo.head head = testrepo[head.target] assert head.id == branch_i18n.target assert 'new' in head.tree assert 'bye.txt' not in testrepo.status() def test_checkout_index(testrepo): # some changes to working dir with (Path(testrepo.workdir) / 'hello.txt').open('w') as f: f.write('new content') # checkout index assert 'hello.txt' in testrepo.status() testrepo.checkout(strategy=CheckoutStrategy.FORCE) assert 'hello.txt' not in testrepo.status() def test_checkout_head(testrepo): # some changes to the index with (Path(testrepo.workdir) / 'bye.txt').open('w') as f: f.write('new content') testrepo.index.add('bye.txt') # checkout from index should not change anything assert 'bye.txt' in testrepo.status() testrepo.checkout(strategy=CheckoutStrategy.FORCE) assert 'bye.txt' in testrepo.status() # checkout from head will reset index as well testrepo.checkout('HEAD', strategy=CheckoutStrategy.FORCE) assert 'bye.txt' not in testrepo.status() def test_checkout_alternative_dir(testrepo): ref_i18n = testrepo.lookup_reference('refs/heads/i18n') extra_dir = Path(testrepo.workdir) / 'extra-dir' extra_dir.mkdir() assert len(list(extra_dir.iterdir())) == 0 testrepo.checkout(ref_i18n, directory=extra_dir) assert not len(list(extra_dir.iterdir())) == 0 def test_checkout_paths(testrepo): ref_i18n = testrepo.lookup_reference('refs/heads/i18n') ref_master = testrepo.lookup_reference('refs/heads/master') testrepo.checkout(ref_master) testrepo.checkout(ref_i18n, paths=['new']) status = testrepo.status() assert status['new'] == FileStatus.INDEX_NEW def test_merge_base(testrepo): commit = testrepo.merge_base( '5ebeeebb320790caf276b9fc8b24546d63316533', '4ec4389a8068641da2d6578db0419484972284c8', ) assert commit == 'acecd5ea2924a4b900e7e149496e1f4b57976e51' # Create a commit without any merge base to any other sig = pygit2.Signature('me', 'me@example.com') indep = testrepo.create_commit( None, sig, sig, 'a new root commit', testrepo[commit].peel(pygit2.Tree).id, [] ) assert testrepo.merge_base(indep, commit) is None def test_descendent_of(testrepo): assert not testrepo.descendant_of( '5ebeeebb320790caf276b9fc8b24546d63316533', '4ec4389a8068641da2d6578db0419484972284c8', ) assert not testrepo.descendant_of( '5ebeeebb320790caf276b9fc8b24546d63316533', '5ebeeebb320790caf276b9fc8b24546d63316533', ) assert testrepo.descendant_of( '5ebeeebb320790caf276b9fc8b24546d63316533', 'acecd5ea2924a4b900e7e149496e1f4b57976e51', ) assert not testrepo.descendant_of( 'acecd5ea2924a4b900e7e149496e1f4b57976e51', '5ebeeebb320790caf276b9fc8b24546d63316533', ) with pytest.raises(pygit2.GitError): testrepo.descendant_of( '2' * 40, # a valid but inexistent SHA '5ebeeebb320790caf276b9fc8b24546d63316533', ) def test_ahead_behind(testrepo): ahead, behind = testrepo.ahead_behind( '5ebeeebb320790caf276b9fc8b24546d63316533', '4ec4389a8068641da2d6578db0419484972284c8', ) assert 1 == ahead assert 2 == behind ahead, behind = testrepo.ahead_behind( '4ec4389a8068641da2d6578db0419484972284c8', '5ebeeebb320790caf276b9fc8b24546d63316533', ) assert 2 == ahead assert 1 == behind def test_reset_hard(testrepo): ref = '5ebeeebb320790caf276b9fc8b24546d63316533' with (Path(testrepo.workdir) / 'hello.txt').open() as f: lines = f.readlines() assert 'hola mundo\n' in lines assert 'bonjour le monde\n' in lines testrepo.reset(ref, ResetMode.HARD) assert testrepo.head.target == ref with (Path(testrepo.workdir) / 'hello.txt').open() as f: lines = f.readlines() # Hard reset will reset the working copy too assert 'hola mundo\n' not in lines assert 'bonjour le monde\n' not in lines def test_reset_soft(testrepo): ref = '5ebeeebb320790caf276b9fc8b24546d63316533' with (Path(testrepo.workdir) / 'hello.txt').open() as f: lines = f.readlines() assert 'hola mundo\n' in lines assert 'bonjour le monde\n' in lines testrepo.reset(ref, ResetMode.SOFT) assert testrepo.head.target == ref with (Path(testrepo.workdir) / 'hello.txt').open() as f: lines = f.readlines() # Soft reset will not reset the working copy assert 'hola mundo\n' in lines assert 'bonjour le monde\n' in lines # soft reset will keep changes in the index diff = testrepo.diff(cached=True) with pytest.raises(KeyError): diff[0] def test_reset_mixed(testrepo): ref = '5ebeeebb320790caf276b9fc8b24546d63316533' with (Path(testrepo.workdir) / 'hello.txt').open() as f: lines = f.readlines() assert 'hola mundo\n' in lines assert 'bonjour le monde\n' in lines testrepo.reset(ref, ResetMode.MIXED) assert testrepo.head.target == ref with (Path(testrepo.workdir) / 'hello.txt').open() as f: lines = f.readlines() # mixed reset will not reset the working copy assert 'hola mundo\n' in lines assert 'bonjour le monde\n' in lines # mixed reset will set the index to match working copy diff = testrepo.diff(cached=True) assert 'hola mundo\n' in diff.patch assert 'bonjour le monde\n' in diff.patch def test_stash(testrepo): stash_hash = '6aab5192f88018cb98a7ede99c242f43add5a2fd' stash_message = 'custom stash message' sig = pygit2.Signature( name='Stasher', email='stasher@example.com', time=1641000000, # fixed time so the oid is stable offset=0, ) # make sure we're starting with no stashes assert [] == testrepo.listall_stashes() # some changes to working dir with (Path(testrepo.workdir) / 'hello.txt').open('w') as f: f.write('new content') testrepo.stash(sig, include_untracked=True, message=stash_message) assert 'hello.txt' not in testrepo.status() repo_stashes = testrepo.listall_stashes() assert 1 == len(repo_stashes) assert repr(repo_stashes[0]) == f'' assert repo_stashes[0].commit_id == stash_hash assert repo_stashes[0].message == 'On master: ' + stash_message testrepo.stash_apply() assert 'hello.txt' in testrepo.status() assert repo_stashes == testrepo.listall_stashes() # still the same stashes testrepo.stash_drop() assert [] == testrepo.listall_stashes() with pytest.raises(KeyError): testrepo.stash_pop() def test_stash_partial(testrepo): stash_message = 'custom stash message' sig = pygit2.Signature( name='Stasher', email='stasher@example.com', time=1641000000, offset=0 ) # make sure we're starting with no stashes assert [] == testrepo.listall_stashes() # some changes to working dir with (Path(testrepo.workdir) / 'hello.txt').open('w') as f: f.write('stash me') with (Path(testrepo.workdir) / 'untracked2.txt').open('w') as f: f.write('do not stash me') assert testrepo.status()['hello.txt'] == FileStatus.WT_MODIFIED assert testrepo.status()['bye.txt'] == FileStatus.WT_NEW assert testrepo.status()['untracked2.txt'] == FileStatus.WT_NEW def stash_pathspecs(paths): stash_id = testrepo.stash( sig, message=stash_message, keep_all=True, paths=paths ) stash_commit = testrepo[stash_id].peel(pygit2.Commit) stash_diff = testrepo.diff(stash_commit.parents[0], stash_commit) stash_files = set(patch.delta.new_file.path for patch in stash_diff) return stash_files == set(paths) # Stash a modified file assert stash_pathspecs(['hello.txt']) # Stash one of several untracked files assert stash_pathspecs(['bye.txt']) # Stash a modified file and an untracked file assert stash_pathspecs(['hello.txt', 'bye.txt']) def test_stash_progress_callback(testrepo): sig = pygit2.Signature( name='Stasher', email='stasher@example.com', time=1641000000, offset=0 ) # some changes to working dir with (Path(testrepo.workdir) / 'hello.txt').open('w') as f: f.write('new content') # create the stash testrepo.stash(sig, include_untracked=True, message='custom stash message') progress_sequence = [] class MyStashApplyCallbacks(pygit2.StashApplyCallbacks): def stash_apply_progress(self, progress: StashApplyProgress): progress_sequence.append(progress) # apply the stash testrepo.stash_apply(callbacks=MyStashApplyCallbacks()) # make sure the callbacks were notified of all the steps assert progress_sequence == [ StashApplyProgress.LOADING_STASH, StashApplyProgress.ANALYZE_INDEX, StashApplyProgress.ANALYZE_MODIFIED, StashApplyProgress.ANALYZE_UNTRACKED, StashApplyProgress.CHECKOUT_UNTRACKED, StashApplyProgress.CHECKOUT_MODIFIED, StashApplyProgress.DONE, ] def test_stash_aborted_from_callbacks(testrepo): sig = pygit2.Signature( name='Stasher', email='stasher@example.com', time=1641000000, offset=0 ) # some changes to working dir with (Path(testrepo.workdir) / 'hello.txt').open('w') as f: f.write('new content') with (Path(testrepo.workdir) / 'untracked.txt').open('w') as f: f.write('yo') # create the stash testrepo.stash(sig, include_untracked=True, message='custom stash message') # define callbacks that will abort the unstash process # just as libgit2 is ready to write the files to disk class MyStashApplyCallbacks(pygit2.StashApplyCallbacks): def stash_apply_progress(self, progress: StashApplyProgress): if progress == StashApplyProgress.CHECKOUT_UNTRACKED: raise InterruptedError('Stop applying the stash!') # attempt to apply and delete the stash; the callbacks will interrupt that with pytest.raises(InterruptedError): testrepo.stash_pop(callbacks=MyStashApplyCallbacks()) # we interrupted right before the checkout part of the unstashing process, # so the untracked file shouldn't be here assert not (Path(testrepo.workdir) / 'untracked.txt').exists() # and hello.txt should be as it is on master with (Path(testrepo.workdir) / 'hello.txt').open('r') as f: assert f.read() == 'hello world\nhola mundo\nbonjour le monde\n' # and since we didn't let stash_pop run to completion, the stash itself should still be here repo_stashes = testrepo.listall_stashes() assert 1 == len(repo_stashes) assert repo_stashes[0].message == 'On master: custom stash message' def test_stash_apply_checkout_options(testrepo): sig = pygit2.Signature( name='Stasher', email='stasher@example.com', time=1641000000, offset=0 ) hello_txt = Path(testrepo.workdir) / 'hello.txt' # some changes to working dir with hello_txt.open('w') as f: f.write('stashed content') # create the stash testrepo.stash(sig, include_untracked=True, message='custom stash message') # define callbacks that raise an InterruptedError when checkout detects a conflict class MyStashApplyCallbacks(pygit2.StashApplyCallbacks): def checkout_notify(self, why, path, baseline, target, workdir): if why == CheckoutNotify.CONFLICT: raise InterruptedError('Applying the stash would create a conflict') # overwrite hello.txt so that applying the stash would create a conflict with hello_txt.open('w') as f: f.write('conflicting content') # apply the stash with the default (safe) strategy; # the callbacks should detect a conflict on checkout with pytest.raises(InterruptedError): testrepo.stash_apply( strategy=CheckoutStrategy.SAFE, callbacks=MyStashApplyCallbacks() ) # hello.txt should be intact with hello_txt.open('r') as f: assert f.read() == 'conflicting content' # force apply the stash; this should work testrepo.stash_apply( strategy=CheckoutStrategy.FORCE, callbacks=MyStashApplyCallbacks() ) with hello_txt.open('r') as f: assert f.read() == 'stashed content' def test_revert_commit(testrepo): master = testrepo.head.peel() commit_to_revert = testrepo['4ec4389a8068641da2d6578db0419484972284c8'] parent = commit_to_revert.parents[0] commit_diff_stats = parent.tree.diff_to_tree(commit_to_revert.tree).stats revert_index = testrepo.revert_commit(commit_to_revert, master) revert_diff_stats = revert_index.diff_to_tree(master.tree).stats assert revert_diff_stats.insertions == commit_diff_stats.deletions assert revert_diff_stats.deletions == commit_diff_stats.insertions assert revert_diff_stats.files_changed == commit_diff_stats.files_changed def test_revert(testrepo): hello_txt = Path(testrepo.workdir) / 'hello.txt' commit_to_revert = testrepo['4ec4389a8068641da2d6578db0419484972284c8'] assert testrepo.state() == RepositoryState.NONE assert not testrepo.message assert 'bonjour le monde' in hello_txt.read_text() # Revert addition of French line in hello.txt testrepo.revert(commit_to_revert) assert 'bonjour le monde' not in hello_txt.read_text() assert testrepo.status()['hello.txt'] == FileStatus.INDEX_MODIFIED assert testrepo.state() == RepositoryState.REVERT assert ( testrepo.message == f'Revert "Say hello in French"\n\nThis reverts commit {commit_to_revert.id}.\n' ) def test_default_signature(testrepo): config = testrepo.config config['user.name'] = 'Random J Hacker' config['user.email'] = 'rjh@example.com' sig = testrepo.default_signature assert 'Random J Hacker' == sig.name assert 'rjh@example.com' == sig.email def test_new_repo(tmp_path): repo = init_repository(tmp_path, False) oid = repo.write(ObjectType.BLOB, 'Test') assert type(oid) == Oid assert (tmp_path / '.git').exists() def test_no_arg(tmp_path): repo = init_repository(tmp_path) assert not repo.is_bare def test_no_arg_aspath(tmp_path): repo = init_repository(Path(tmp_path)) assert not repo.is_bare def test_pos_arg_false(tmp_path): repo = init_repository(tmp_path, False) assert not repo.is_bare def test_pos_arg_true(tmp_path): repo = init_repository(tmp_path, True) assert repo.is_bare def test_keyword_arg_false(tmp_path): repo = init_repository(tmp_path, bare=False) assert not repo.is_bare def test_keyword_arg_true(tmp_path): repo = init_repository(tmp_path, bare=True) assert repo.is_bare def test_discover_repo(tmp_path): repo = init_repository(tmp_path, False) subdir = tmp_path / 'test1' / 'test2' subdir.mkdir(parents=True) assert repo.path == discover_repository(str(subdir)) @utils.fspath def test_discover_repo_aspath(tmp_path): repo = init_repository(Path(tmp_path), False) subdir = Path(tmp_path) / 'test1' / 'test2' subdir.mkdir(parents=True) assert repo.path == discover_repository(subdir) def test_discover_repo_not_found(): assert discover_repository(tempfile.tempdir) is None def test_repository_init(barerepo_path): barerepo, path = barerepo_path assert isinstance(path, Path) pygit2.Repository(path) pygit2.Repository(str(path)) pygit2.Repository(bytes(path)) def test_clone_repository(barerepo, tmp_path): assert barerepo.is_bare repo = clone_repository(Path(barerepo.path), tmp_path / 'clonepath') assert not repo.is_empty assert not repo.is_bare repo = clone_repository(str(barerepo.path), str(tmp_path / 'clonestr')) assert not repo.is_empty assert not repo.is_bare def test_clone_bare_repository(barerepo, tmp_path): repo = clone_repository(barerepo.path, tmp_path / 'clone', bare=True) assert not repo.is_empty assert repo.is_bare @utils.requires_network def test_clone_shallow_repository(tmp_path): # shallow cloning currently only works with remote repositories url = 'https://github.com/libgit2/TestGitRepository' repo = clone_repository(url, tmp_path / 'clone-shallow', depth=1) assert not repo.is_empty assert repo.is_shallow def test_clone_repository_and_remote_callbacks(barerepo, tmp_path): url = Path(barerepo.path).resolve().as_uri() repo_path = tmp_path / 'clone-into' def create_repository(path, bare): return init_repository(path, bare) # here we override the name def create_remote(repo, name, url): return repo.remotes.create('custom_remote', url) repo = clone_repository( url, repo_path, repository=create_repository, remote=create_remote ) assert not repo.is_empty assert 'refs/remotes/custom_remote/master' in repo.listall_references() assert b'refs/remotes/custom_remote/master' in repo.raw_listall_references() assert repo.remotes['custom_remote'] is not None @utils.requires_network def test_clone_with_credentials(tmp_path): url = 'https://github.com/libgit2/TestGitRepository' credentials = pygit2.UserPass('libgit2', 'libgit2') callbacks = pygit2.RemoteCallbacks(credentials=credentials) repo = clone_repository(url, tmp_path, callbacks=callbacks) assert not repo.is_empty @utils.requires_network def test_clone_bad_credentials(tmp_path): class MyCallbacks(pygit2.RemoteCallbacks): def credentials(self, url, username, allowed): raise RuntimeError('Unexpected error') url = 'https://github.com/github/github' with pytest.raises(RuntimeError) as exc: clone_repository(url, tmp_path, callbacks=MyCallbacks()) assert str(exc.value) == 'Unexpected error' def test_clone_with_checkout_branch(barerepo, tmp_path): # create a test case which isolates the remote test_repo = clone_repository( barerepo.path, tmp_path / 'testrepo-orig.git', bare=True ) test_repo.create_branch('test', test_repo[test_repo.head.target]) repo = clone_repository( test_repo.path, tmp_path / 'testrepo.git', checkout_branch='test', bare=True ) assert repo.lookup_reference('HEAD').target == 'refs/heads/test' # FIXME The tests below are commented because they are broken: # # - test_clone_push_url: Passes, but does nothing useful. # # - test_clone_fetch_spec: Segfaults because of a bug in libgit2 0.19, # this has been fixed already, so wait for 0.20 # # - test_clone_push_spec: Passes, but does nothing useful. # # def test_clone_push_url(): # repo_path = "./test/data/testrepo.git/" # repo = clone_repository( # repo_path, tmp_path, push_url="custom_push_url" # ) # assert not repo.is_empty # # FIXME: When pygit2 supports retrieving the pushurl parameter, # # enable this test # # assert repo.remotes[0].pushurl == "custom_push_url" # # def test_clone_fetch_spec(): # repo_path = "./test/data/testrepo.git/" # repo = clone_repository(repo_path, tmp_path, # fetch_spec="refs/heads/test") # assert not repo.is_empty # # FIXME: When pygit2 retrieve the fetchspec we passed to git clone. # # fetchspec seems to be going through, but the Repository class is # # not getting it. # # assert repo.remotes[0].fetchspec == "refs/heads/test" # # def test_clone_push_spec(): # repo_path = "./test/data/testrepo.git/" # repo = clone_repository(repo_path, tmp_path, # push_spec="refs/heads/test") # assert not repo.is_empty # # FIXME: When pygit2 supports retrieving the pushspec parameter, # # enable this test # # not sure how to test this either... couldn't find pushspec # # assert repo.remotes[0].fetchspec == "refs/heads/test" def test_worktree(testrepo): worktree_name = 'foo' worktree_dir = Path(tempfile.mkdtemp()) # Delete temp path so that it's not present when we attempt to add the # worktree later worktree_dir.rmdir() def _check_worktree(worktree): # Confirm the name attribute matches the specified name assert worktree.name == worktree_name # Confirm the path attribute points to the correct path assert Path(worktree.path).resolve() == worktree_dir.resolve() # The "gitdir" in a worktree should be a file with a reference to # the actual gitdir. Let's make sure that the path exists and is a # file. assert (worktree_dir / '.git').is_file() # We should have zero worktrees assert testrepo.list_worktrees() == [] # Add a worktree worktree = testrepo.add_worktree(worktree_name, str(worktree_dir)) # Check that the worktree was added properly _check_worktree(worktree) # We should have one worktree now assert testrepo.list_worktrees() == [worktree_name] # We should also have a branch of the same name assert worktree_name in testrepo.listall_branches() # Test that lookup_worktree() returns a properly-instantiated # pygit2._Worktree object _check_worktree(testrepo.lookup_worktree(worktree_name)) # Remove the worktree dir shutil.rmtree(worktree_dir) # Prune the worktree. For some reason, libgit2 treats a worktree as # valid unless both the worktree directory and data dir under # $GIT_DIR/worktrees are gone. This doesn't make much sense since the # normal usage involves removing the worktree directory and then # pruning. So, for now we have to force the prune. This may be # something to take up with libgit2. worktree.prune(True) assert testrepo.list_worktrees() == [] @utils.fspath def test_worktree_aspath(testrepo): worktree_name = 'foo' worktree_dir = Path(tempfile.mkdtemp()) # Delete temp path so that it's not present when we attempt to add the # worktree later worktree_dir.rmdir() testrepo.add_worktree(worktree_name, worktree_dir) assert testrepo.list_worktrees() == [worktree_name] def test_worktree_custom_ref(testrepo): worktree_name = 'foo' worktree_dir = Path(tempfile.mkdtemp()) branch_name = 'version1' # New branch based on head tip = testrepo.revparse_single('HEAD') worktree_ref = testrepo.branches.create(branch_name, tip) # Delete temp path so that it's not present when we attempt to add the # worktree later worktree_dir.rmdir() # Add a worktree for the given ref worktree = testrepo.add_worktree(worktree_name, str(worktree_dir), worktree_ref) # We should have one worktree now assert testrepo.list_worktrees() == [worktree_name] # We should not have a branch of the same name assert worktree_name not in testrepo.listall_branches() # The given ref is checked out in the "worktree repository" assert worktree_ref.is_checked_out() # Remove the worktree dir and prune the worktree shutil.rmtree(worktree_dir) worktree.prune(True) assert testrepo.list_worktrees() == [] # The ref is no longer checked out assert worktree_ref.is_checked_out() is False # The branch still exists assert branch_name in testrepo.branches def test_open_extended(tmp_path): with utils.TemporaryRepository('dirtyrepo.zip', tmp_path) as path: orig_repo = pygit2.Repository(path) assert not orig_repo.is_bare assert orig_repo.path assert orig_repo.workdir # GIT_REPOSITORY_OPEN_NO_SEARCH subdir_path = path / 'subdir' repo = pygit2.Repository(subdir_path) assert not repo.is_bare assert repo.path == orig_repo.path assert repo.workdir == orig_repo.workdir with pytest.raises(pygit2.GitError): repo = pygit2.Repository(subdir_path, RepositoryOpenFlag.NO_SEARCH) # GIT_REPOSITORY_OPEN_NO_DOTGIT gitdir_path = path / '.git' with pytest.raises(pygit2.GitError): repo = pygit2.Repository(path, RepositoryOpenFlag.NO_DOTGIT) repo = pygit2.Repository(gitdir_path, RepositoryOpenFlag.NO_DOTGIT) assert not repo.is_bare assert repo.path == orig_repo.path assert repo.workdir == orig_repo.workdir # GIT_REPOSITORY_OPEN_BARE repo = pygit2.Repository(gitdir_path, RepositoryOpenFlag.BARE) assert repo.is_bare assert repo.path == orig_repo.path assert not repo.workdir def test_is_shallow(testrepo): assert not testrepo.is_shallow # create a dummy shallow file with (Path(testrepo.path) / 'shallow').open('wt') as f: f.write('abcdef0123456789abcdef0123456789abcdef00\n') assert testrepo.is_shallow def test_repository_hashfile(testrepo): original_hash = testrepo.index['hello.txt'].id # Test simple use h = testrepo.hashfile('hello.txt') assert h == original_hash # Test absolute path # For best results on Windows, pass a pure POSIX path. (See https://github.com/libgit2/libgit2/issues/6825) absolute_path = Path(testrepo.workdir, 'hello.txt') absolute_path = absolute_path.as_posix() # Windows compatibility h = testrepo.hashfile(str(absolute_path)) assert h == original_hash # Test missing path with pytest.raises(KeyError): testrepo.hashfile('missing-file') # Test invalid object type with pytest.raises(pygit2.GitError): testrepo.hashfile('hello.txt', ObjectType.OFS_DELTA) def test_repository_hashfile_filter(testrepo): original_hash = testrepo.index['hello.txt'].id with open(Path(testrepo.workdir, 'hello.txt'), 'rb') as f: original_text = f.read() crlf_data = original_text.replace(b'\n', b'\r\n') crlf_hash = utils.gen_blob_sha1(crlf_data) assert crlf_hash != original_hash # Write hellocrlf.txt as a copy of hello.txt with CRLF line endings with open(Path(testrepo.workdir, 'hellocrlf.txt'), 'wb') as f: f.write(crlf_data) # Set up a CRLF filter testrepo.config['core.autocrlf'] = True with open(Path(testrepo.workdir, '.gitattributes'), 'wt') as f: f.write('*.txt text\n*.bin binary\n\n') # By default, hellocrlf.txt should have the same hash as the original, # due to core.autocrlf=True h = testrepo.hashfile('hellocrlf.txt') assert h == original_hash # Treat absolute path with filters. # For best results on Windows, pass a pure POSIX path. (See https://github.com/libgit2/libgit2/issues/6825) absolute_path = Path(testrepo.workdir, 'hellocrlf.txt') absolute_path = absolute_path.as_posix() # Windows compatibility h = testrepo.hashfile(str(absolute_path)) assert h == original_hash # Bypass filters h = testrepo.hashfile('hellocrlf.txt', as_path='') assert h == crlf_hash # Bypass filters via .gitattributes h = testrepo.hashfile('hellocrlf.txt', as_path='foobar.bin') assert h == crlf_hash # If core.safecrlf=fail, hashing a non-CRLF file will fail testrepo.config['core.safecrlf'] = 'fail' with pytest.raises(pygit2.GitError): h = testrepo.hashfile('hello.txt') libgit2-pygit2-a011e26/test/test_repository_bare.py000066400000000000000000000162061473744024100224160ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Standard Library import binascii import os from pathlib import Path import sys import tempfile import pytest import pygit2 from pygit2.enums import FileMode, ObjectType from . import utils HEAD_SHA = '784855caf26449a1914d2cf62d12b9374d76ae78' PARENT_SHA = 'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87' # HEAD^ BLOB_HEX = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' BLOB_RAW = binascii.unhexlify(BLOB_HEX.encode('ascii')) BLOB_OID = pygit2.Oid(raw=BLOB_RAW) def test_is_empty(barerepo): assert not barerepo.is_empty def test_is_bare(barerepo): assert barerepo.is_bare def test_head(barerepo): head = barerepo.head assert HEAD_SHA == head.target assert type(head) == pygit2.Reference assert not barerepo.head_is_unborn assert not barerepo.head_is_detached def test_set_head(barerepo): # Test setting a detatched HEAD. barerepo.set_head(pygit2.Oid(hex=PARENT_SHA)) assert barerepo.head.target == PARENT_SHA # And test setting a normal HEAD. barerepo.set_head('refs/heads/master') assert barerepo.head.name == 'refs/heads/master' assert barerepo.head.target == HEAD_SHA def test_read(barerepo): with pytest.raises(TypeError): barerepo.read(123) utils.assertRaisesWithArg(KeyError, '1' * 40, barerepo.read, '1' * 40) ab = barerepo.read(BLOB_OID) a = barerepo.read(BLOB_HEX) assert ab == a assert (ObjectType.BLOB, b'a contents\n') == a a2 = barerepo.read('7f129fd57e31e935c6d60a0c794efe4e6927664b') assert (ObjectType.BLOB, b'a contents 2\n') == a2 a_hex_prefix = BLOB_HEX[:4] a3 = barerepo.read(a_hex_prefix) assert (ObjectType.BLOB, b'a contents\n') == a3 def test_write(barerepo): data = b'hello world' # invalid object type with pytest.raises(ValueError): barerepo.write(ObjectType.ANY, data) oid = barerepo.write(ObjectType.BLOB, data) assert type(oid) == pygit2.Oid def test_contains(barerepo): with pytest.raises(TypeError): 123 in barerepo assert BLOB_OID in barerepo assert BLOB_HEX in barerepo assert BLOB_HEX[:10] in barerepo assert ('a' * 40) not in barerepo assert ('a' * 20) not in barerepo def test_iterable(barerepo): oid = pygit2.Oid(hex=BLOB_HEX) assert oid in [obj for obj in barerepo] def test_lookup_blob(barerepo): with pytest.raises(TypeError): barerepo[123] assert barerepo[BLOB_OID].id == BLOB_HEX a = barerepo[BLOB_HEX] assert b'a contents\n' == a.read_raw() assert BLOB_HEX == a.id assert ObjectType.BLOB == a.type def test_lookup_blob_prefix(barerepo): a = barerepo[BLOB_HEX[:5]] assert b'a contents\n' == a.read_raw() assert BLOB_HEX == a.id assert ObjectType.BLOB == a.type def test_lookup_commit(barerepo): commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' commit = barerepo[commit_sha] assert commit_sha == commit.id assert ObjectType.COMMIT == commit.type assert commit.message == ( 'Second test data commit.\n\n' 'This commit has some additional text.\n' ) def test_lookup_commit_prefix(barerepo): commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' commit_sha_prefix = commit_sha[:7] too_short_prefix = commit_sha[:3] commit = barerepo[commit_sha_prefix] assert commit_sha == commit.id assert ObjectType.COMMIT == commit.type assert ( 'Second test data commit.\n\n' 'This commit has some additional text.\n' == commit.message ) with pytest.raises(ValueError): barerepo.__getitem__(too_short_prefix) def test_expand_id(barerepo): commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' expanded = barerepo.expand_id(commit_sha[:7]) assert commit_sha == expanded @utils.refcount def test_lookup_commit_refcount(barerepo): start = sys.getrefcount(barerepo) commit_sha = '5fe808e8953c12735680c257f56600cb0de44b10' commit = barerepo[commit_sha] del commit end = sys.getrefcount(barerepo) assert start == end def test_get_path(barerepo_path): barerepo, path = barerepo_path directory = Path(barerepo.path).resolve() assert directory == path.resolve() def test_get_workdir(barerepo): assert barerepo.workdir is None def test_revparse_single(barerepo): parent = barerepo.revparse_single('HEAD^') assert parent.id == PARENT_SHA def test_hash(barerepo): data = 'foobarbaz' hashed_sha1 = pygit2.hash(data) written_sha1 = barerepo.create_blob(data) assert hashed_sha1 == written_sha1 def test_hashfile(barerepo): data = 'bazbarfoo' handle, tempfile_path = tempfile.mkstemp() with os.fdopen(handle, 'w') as fh: fh.write(data) hashed_sha1 = pygit2.hashfile(tempfile_path) Path(tempfile_path).unlink() written_sha1 = barerepo.create_blob(data) assert hashed_sha1 == written_sha1 def test_conflicts_in_bare_repository(barerepo): def create_conflict_file(repo, branch, content): oid = repo.create_blob(content.encode('utf-8')) tb = repo.TreeBuilder() tb.insert('conflict', oid, FileMode.BLOB) tree = tb.write() sig = pygit2.Signature('Author', 'author@example.com') commit = repo.create_commit( branch.name, sig, sig, 'Conflict', tree, [branch.target] ) assert commit is not None return commit b1 = barerepo.create_branch('b1', barerepo.head.peel()) c1 = create_conflict_file(barerepo, b1, 'ASCII - abc') b2 = barerepo.create_branch('b2', barerepo.head.peel()) c2 = create_conflict_file(barerepo, b2, 'Unicode - äüö') index = barerepo.merge_commits(c1, c2) assert index.conflicts is not None # ConflictCollection does not allow calling len(...) on it directly so # we have to calculate length by iterating over its entries assert sum(1 for _ in index.conflicts) == 1 (a, t, o) = index.conflicts['conflict'] diff = barerepo.merge_file_from_index(a, t, o) assert ( diff == """<<<<<<< conflict ASCII - abc ======= Unicode - äüö >>>>>>> conflict """ ) libgit2-pygit2-a011e26/test/test_repository_custom.py000066400000000000000000000041641473744024100230170ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. from pathlib import Path import pytest import pygit2 from pygit2.enums import ObjectType @pytest.fixture def repo(testrepopacked): testrepo = testrepopacked odb = pygit2.Odb() object_path = Path(testrepo.path) / 'objects' odb.add_backend(pygit2.OdbBackendPack(object_path), 1) odb.add_backend(pygit2.OdbBackendLoose(object_path, 0, False), 1) refdb = pygit2.Refdb.new(testrepo) refdb.set_backend(pygit2.RefdbFsBackend(testrepo)) repo = pygit2.Repository() repo.set_odb(odb) repo.set_refdb(refdb) yield repo def test_references(repo): refs = [(ref.name, ref.target) for ref in repo.references.objects] assert sorted(refs) == [ ('refs/heads/i18n', '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'), ('refs/heads/master', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98'), ] def test_objects(repo): a = repo.read('323fae03f4606ea9991df8befbb2fca795e648fa') assert (ObjectType.BLOB, b'foobar\n') == a libgit2-pygit2-a011e26/test/test_repository_empty.py000066400000000000000000000026141473744024100226410ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. def test_is_empty(emptyrepo): assert emptyrepo.is_empty def test_is_base(emptyrepo): assert not emptyrepo.is_bare def test_head(emptyrepo): assert emptyrepo.head_is_unborn assert not emptyrepo.head_is_detached libgit2-pygit2-a011e26/test/test_revparse.py000066400000000000000000000063731473744024100210410ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for revision parsing.""" from pygit2 import InvalidSpecError from pygit2.enums import RevSpecFlag from pytest import raises HEAD_SHA = '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98' PARENT_SHA = '5ebeeebb320790caf276b9fc8b24546d63316533' # HEAD^ def test_revparse_single(testrepo): assert testrepo.revparse_single('HEAD').id == HEAD_SHA assert testrepo.revparse_single('HEAD^').id == PARENT_SHA o = testrepo.revparse_single('@{-1}') assert o.id == '5470a671a80ac3789f1a6a8cefbcf43ce7af0563' def test_revparse_ext(testrepo): o, r = testrepo.revparse_ext('master') assert o.id == HEAD_SHA assert r == testrepo.references['refs/heads/master'] o, r = testrepo.revparse_ext('HEAD^') assert o.id == PARENT_SHA assert r is None o, r = testrepo.revparse_ext('i18n') assert str(o.id).startswith('5470a67') assert r == testrepo.references['refs/heads/i18n'] def test_revparse_1(testrepo): s = testrepo.revparse('master') assert s.from_object.id == HEAD_SHA assert s.to_object is None assert s.flags == RevSpecFlag.SINGLE def test_revparse_range_1(testrepo): s = testrepo.revparse('HEAD^1..acecd5e') assert s.from_object.id == PARENT_SHA assert str(s.to_object.id).startswith('acecd5e') assert s.flags == RevSpecFlag.RANGE def test_revparse_range_2(testrepo): s = testrepo.revparse('HEAD...i18n') assert str(s.from_object.id).startswith('2be5719') assert str(s.to_object.id).startswith('5470a67') assert s.flags == RevSpecFlag.RANGE | RevSpecFlag.MERGE_BASE assert testrepo.merge_base(s.from_object.id, s.to_object.id) is not None def test_revparse_range_errors(testrepo): with raises(KeyError): testrepo.revparse('nope..2be571915') with raises(InvalidSpecError): testrepo.revparse('master............2be571915') def test_revparse_repr(testrepo): s = testrepo.revparse('HEAD...i18n') assert ( repr(s) == ',to=}>' ) libgit2-pygit2-a011e26/test/test_revwalk.py000066400000000000000000000074551473744024100206670ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for revision walk.""" from pygit2.enums import SortMode # In the order given by git log log = [ '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98', '5ebeeebb320790caf276b9fc8b24546d63316533', '4ec4389a8068641da2d6578db0419484972284c8', '6aaa262e655dd54252e5813c8e5acd7780ed097d', 'acecd5ea2924a4b900e7e149496e1f4b57976e51', ] REVLOGS = [ ('Nico von Geyso', 'checkout: moving from i18n to master'), ('Nico von Geyso', 'commit: added bye.txt and new'), ('Nico von Geyso', 'checkout: moving from master to i18n'), ('J. David Ibañez', 'merge i18n: Merge made by recursive.'), ('J. David Ibañez', 'commit: Add .gitignore file'), ('J. David Ibañez', 'checkout: moving from i18n to master'), ('J. David Ibañez', 'commit: Say hello in French'), ('J. David Ibañez', 'commit: Say hello in Spanish'), ('J. David Ibañez', 'checkout: moving from master to i18n'), ('J. David Ibañez', 'commit (initial): First commit'), ] def test_log(testrepo): ref = testrepo.lookup_reference('HEAD') for i, entry in enumerate(ref.log()): assert entry.committer.name == REVLOGS[i][0] assert entry.message == REVLOGS[i][1] def test_walk(testrepo): walker = testrepo.walk(log[0], SortMode.TIME) assert [x.id for x in walker] == log def test_reverse(testrepo): walker = testrepo.walk(log[0], SortMode.TIME | SortMode.REVERSE) assert [x.id for x in walker] == list(reversed(log)) def test_hide(testrepo): walker = testrepo.walk(log[0], SortMode.TIME) walker.hide('4ec4389a8068641da2d6578db0419484972284c8') assert len(list(walker)) == 2 def test_hide_prefix(testrepo): walker = testrepo.walk(log[0], SortMode.TIME) walker.hide('4ec4389a') assert len(list(walker)) == 2 def test_reset(testrepo): walker = testrepo.walk(log[0], SortMode.TIME) walker.reset() assert list(walker) == [] def test_push(testrepo): walker = testrepo.walk(log[-1], SortMode.TIME) assert [x.id for x in walker] == log[-1:] walker.reset() walker.push(log[0]) assert [x.id for x in walker] == log def test_sort(testrepo): walker = testrepo.walk(log[0], SortMode.TIME) walker.sort(SortMode.TIME | SortMode.REVERSE) assert [x.id for x in walker] == list(reversed(log)) def test_simplify_first_parent(testrepo): walker = testrepo.walk(log[0], SortMode.TIME) walker.simplify_first_parent() assert len(list(walker)) == 3 def test_default_sorting(testrepo): walker = testrepo.walk(log[0], SortMode.NONE) list1 = list([x.id for x in walker]) walker = testrepo.walk(log[0]) list2 = list([x.id for x in walker]) assert list1 == list2 libgit2-pygit2-a011e26/test/test_signature.py000066400000000000000000000076131473744024100212110ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import re import time import pytest import pygit2 def __assert(signature, encoding): encoding = encoding or 'utf-8' assert signature._encoding == encoding assert signature.name == signature.raw_name.decode(encoding) assert signature.name.encode(encoding) == signature.raw_name assert signature.email == signature.raw_email.decode(encoding) assert signature.email.encode(encoding) == signature.raw_email @pytest.mark.parametrize('encoding', [None, 'utf-8', 'iso-8859-1']) def test_encoding(encoding): signature = pygit2.Signature('Foo Ibáñez', 'foo@example.com', encoding=encoding) __assert(signature, encoding) assert abs(signature.time - time.time()) < 5 assert str(signature) == 'Foo Ibáñez ' def test_default_encoding(): signature = pygit2.Signature('Foo Ibáñez', 'foo@example.com', 1322174594, 60) __assert(signature, 'utf-8') def test_ascii(): with pytest.raises(UnicodeEncodeError): pygit2.Signature('Foo Ibáñez', 'foo@example.com', encoding='ascii') @pytest.mark.parametrize('encoding', [None, 'utf-8', 'iso-8859-1']) def test_repr(encoding): signature = pygit2.Signature( 'Foo Ibáñez', 'foo@bar.com', 1322174594, 60, encoding=encoding ) expected = f"pygit2.Signature('Foo Ibáñez', 'foo@bar.com', 1322174594, 60, {repr(encoding)})" assert repr(signature) == expected assert signature == eval(expected) def test_repr_from_commit(barerepo): repo = barerepo signature = pygit2.Signature('Foo Ibáñez', 'foo@example.com', encoding=None) tree = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' parents = ['5fe808e8953c12735680c257f56600cb0de44b10'] sha = repo.create_commit(None, signature, signature, 'New commit.', tree, parents) commit = repo[sha] assert repr(signature) == repr(commit.author) assert repr(signature) == repr(commit.committer) def test_incorrect_encoding(): gbk_bytes = 'Café'.encode('GBK') # deliberately specifying a mismatching encoding (mojibake) signature = pygit2.Signature(gbk_bytes, 'foo@example.com', 999, 0, encoding='utf-8') # repr() and str() may display junk, but they must not crash assert re.match( r"pygit2.Signature\('Caf.+', 'foo@example.com', 999, 0, 'utf-8'\)", repr(signature), ) assert re.match(r'Caf.+ ', str(signature)) # deliberately specifying an unsupported encoding signature = pygit2.Signature( gbk_bytes, 'foo@example.com', 999, 0, encoding='this-encoding-does-not-exist' ) # repr() and str() may display junk, but they must not crash assert "pygit2.Signature('(error)', '(error)', 999, 0, '(error)')" == repr( signature ) assert '(error) <(error)>' == str(signature) libgit2-pygit2-a011e26/test/test_status.py000066400000000000000000000053611473744024100205310ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import pytest from pygit2.enums import FileStatus def test_status(dirtyrepo): """ For every file in the status, check that the flags are correct. """ git_status = dirtyrepo.status() for filepath, status in git_status.items(): assert filepath in git_status assert status == git_status[filepath] def test_status_untracked_no(dirtyrepo): git_status = dirtyrepo.status(untracked_files='no') assert not any(status & FileStatus.WT_NEW for status in git_status.values()) @pytest.mark.parametrize( 'untracked_files,expected', [ ('no', set()), ( 'normal', { 'untracked_dir/', 'staged_delete_file_modified', 'subdir/new_file', 'new_file', }, ), ( 'all', { 'new_file', 'subdir/new_file', 'staged_delete_file_modified', 'untracked_dir/untracked_file', }, ), ], ) def test_status_untracked_normal(dirtyrepo, untracked_files, expected): git_status = dirtyrepo.status(untracked_files=untracked_files) assert { file for file, status in git_status.items() if status & FileStatus.WT_NEW } == expected @pytest.mark.parametrize('ignored,expected', [(True, {'ignored'}), (False, set())]) def test_status_ignored(dirtyrepo, ignored, expected): git_status = dirtyrepo.status(ignored=ignored) assert { file for file, status in git_status.items() if status & FileStatus.IGNORED } == expected libgit2-pygit2-a011e26/test/test_submodule.py000066400000000000000000000244251473744024100212070ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Submodule objects.""" from pathlib import Path import pygit2 import pytest from . import utils from pygit2.enums import SubmoduleIgnore as SI, SubmoduleStatus as SS SUBM_NAME = 'TestGitRepository' SUBM_PATH = 'TestGitRepository' SUBM_URL = 'https://github.com/libgit2/TestGitRepository' SUBM_HEAD_SHA = '49322bb17d3acc9146f98c97d078513228bbf3c0' SUBM_BOTTOM_SHA = '6c8b137b1c652731597c89668f417b8695f28dd7' @pytest.fixture def repo(tmp_path): with utils.TemporaryRepository('submodulerepo.zip', tmp_path) as path: yield pygit2.Repository(path) def test_lookup_submodule(repo): s = repo.submodules[SUBM_PATH] assert s is not None s = repo.submodules.get(SUBM_PATH) assert s is not None def test_lookup_submodule_aspath(repo): s = repo.submodules[Path(SUBM_PATH)] assert s is not None def test_lookup_missing_submodule(repo): with pytest.raises(KeyError): repo.submodules['does-not-exist'] assert repo.submodules.get('does-not-exist') is None def test_listall_submodules(repo): submodules = repo.listall_submodules() assert len(submodules) == 1 assert submodules[0] == SUBM_PATH def test_contains_submodule(repo): assert SUBM_PATH in repo.submodules assert 'does-not-exist' not in repo.submodules def test_submodule_iterator(repo): for s in repo.submodules: assert isinstance(s, pygit2.Submodule) assert s.path == repo.submodules[s.path].path @utils.requires_network def test_submodule_open(repo): s = repo.submodules[SUBM_PATH] repo.submodules.init() repo.submodules.update() r = s.open() assert r is not None assert r.head.target == SUBM_HEAD_SHA @utils.requires_network def test_submodule_open_from_repository_subclass(repo): class CustomRepoClass(pygit2.Repository): pass custom_repo = CustomRepoClass(repo.workdir) s = custom_repo.submodules[SUBM_PATH] custom_repo.submodules.init() custom_repo.submodules.update() r = s.open() assert isinstance(r, CustomRepoClass) assert r.head.target == SUBM_HEAD_SHA def test_name(repo): s = repo.submodules[SUBM_PATH] assert SUBM_NAME == s.name def test_path(repo): s = repo.submodules[SUBM_PATH] assert SUBM_PATH == s.path def test_url(repo): s = repo.submodules[SUBM_PATH] assert SUBM_URL == s.url def test_missing_url(repo): # Remove "url" from .gitmodules with open(Path(repo.workdir, '.gitmodules'), 'wt') as f: f.write('[submodule "TestGitRepository"]\n') f.write('\tpath = TestGitRepository\n') s = repo.submodules[SUBM_PATH] assert not s.url @utils.requires_network def test_init_and_update(repo): subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt' assert not subrepo_file_path.exists() status = repo.submodules.status(SUBM_NAME) assert status == (SS.IN_HEAD | SS.IN_INDEX | SS.IN_CONFIG | SS.WD_UNINITIALIZED) repo.submodules.init() repo.submodules.update() assert subrepo_file_path.exists() status = repo.submodules.status(SUBM_NAME) assert status == (SS.IN_HEAD | SS.IN_INDEX | SS.IN_CONFIG | SS.IN_WD) @utils.requires_network def test_specified_update(repo): subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt' assert not subrepo_file_path.exists() repo.submodules.init(submodules=['TestGitRepository']) repo.submodules.update(submodules=['TestGitRepository']) assert subrepo_file_path.exists() @utils.requires_network def test_update_instance(repo): subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt' assert not subrepo_file_path.exists() sm = repo.submodules['TestGitRepository'] sm.init() sm.update() assert subrepo_file_path.exists() @utils.requires_network @pytest.mark.parametrize('depth', [0, 1]) def test_oneshot_update(repo, depth): status = repo.submodules.status(SUBM_NAME) assert status == (SS.IN_HEAD | SS.IN_INDEX | SS.IN_CONFIG | SS.WD_UNINITIALIZED) subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt' assert not subrepo_file_path.exists() repo.submodules.update(init=True, depth=depth) assert subrepo_file_path.exists() status = repo.submodules.status(SUBM_NAME) assert status == (SS.IN_HEAD | SS.IN_INDEX | SS.IN_CONFIG | SS.IN_WD) sm_repo = repo.submodules[SUBM_NAME].open() if depth == 0: sm_repo[SUBM_BOTTOM_SHA] # full history must be available else: with pytest.raises(KeyError): sm_repo[SUBM_BOTTOM_SHA] # shallow clone @utils.requires_network @pytest.mark.parametrize('depth', [0, 1]) def test_oneshot_update_instance(repo, depth): subrepo_file_path = Path(repo.workdir) / SUBM_PATH / 'master.txt' assert not subrepo_file_path.exists() sm = repo.submodules[SUBM_NAME] sm.update(init=True, depth=depth) assert subrepo_file_path.exists() sm_repo = sm.open() if depth == 0: sm_repo[SUBM_BOTTOM_SHA] # full history must be available else: with pytest.raises(KeyError): sm_repo[SUBM_BOTTOM_SHA] # shallow clone @utils.requires_network def test_head_id(repo): assert repo.submodules[SUBM_PATH].head_id == SUBM_HEAD_SHA @utils.requires_network def test_head_id_null(repo): gitmodules_newlines = ( '\n' '[submodule "uncommitted_submodule"]\n' ' path = pygit2\n' ' url = https://github.com/libgit2/pygit2\n' '\n' ) with open(Path(repo.workdir, '.gitmodules'), 'a') as f: f.write(gitmodules_newlines) subm = repo.submodules['uncommitted_submodule'] # The submodule isn't in the HEAD yet, so head_id should be None assert subm.head_id is None @utils.requires_network @pytest.mark.parametrize('depth', [0, 1]) def test_add_submodule(repo, depth): sm_repo_path = 'test/testrepo' sm = repo.submodules.add(SUBM_URL, sm_repo_path, depth=depth) status = repo.submodules.status(sm_repo_path) assert status == (SS.IN_INDEX | SS.IN_CONFIG | SS.IN_WD | SS.INDEX_ADDED) sm_repo = sm.open() assert sm_repo_path == sm.path assert SUBM_URL == sm.url assert not sm_repo.is_empty if depth == 0: sm_repo[SUBM_BOTTOM_SHA] # full history must be available else: with pytest.raises(KeyError): sm_repo[SUBM_BOTTOM_SHA] # shallow clone @utils.requires_network def test_submodule_status(repo): common_status = SS.IN_HEAD | SS.IN_INDEX | SS.IN_CONFIG # Submodule needs initializing assert repo.submodules.status(SUBM_PATH) == common_status | SS.WD_UNINITIALIZED # If ignoring ALL, don't look at WD assert repo.submodules.status(SUBM_PATH, ignore=SI.ALL) == common_status # Update the submodule repo.submodules.update(init=True) # It's in our WD now assert repo.submodules.status(SUBM_PATH) == common_status | SS.IN_WD # Open submodule repo sm_repo: pygit2.Repository = repo.submodules[SUBM_PATH].open() # Move HEAD in the submodule (WD_MODIFIED) sm_repo.checkout('refs/tags/annotated_tag') assert ( repo.submodules.status(SUBM_PATH) == common_status | SS.IN_WD | SS.WD_MODIFIED ) # Move HEAD back to master sm_repo.checkout('refs/heads/master') # Touch some file in the submodule's workdir (WD_WD_MODIFIED) with open(Path(repo.workdir, SUBM_PATH, 'master.txt'), 'wt') as f: f.write('modifying master.txt') assert ( repo.submodules.status(SUBM_PATH) == common_status | SS.IN_WD | SS.WD_WD_MODIFIED ) # Add an untracked file in the submodule's workdir (WD_UNTRACKED) with open(Path(repo.workdir, SUBM_PATH, 'some_untracked_file.txt'), 'wt') as f: f.write('hi') assert ( repo.submodules.status(SUBM_PATH) == common_status | SS.IN_WD | SS.WD_WD_MODIFIED | SS.WD_UNTRACKED ) # Add modified files to the submodule's index (WD_INDEX_MODIFIED) sm_repo.index.add_all() sm_repo.index.write() assert ( repo.submodules.status(SUBM_PATH) == common_status | SS.IN_WD | SS.WD_INDEX_MODIFIED ) def test_submodule_cache(repo): # When the cache is turned on, looking up the same submodule twice must return the same git_submodule object repo.submodules.cache_all() sm1 = repo.submodules[SUBM_NAME] sm2 = repo.submodules[SUBM_NAME] assert sm1._subm == sm2._subm # After turning off the cache, each lookup must return a new git_submodule object repo.submodules.cache_clear() sm3 = repo.submodules[SUBM_NAME] sm4 = repo.submodules[SUBM_NAME] assert sm1._subm != sm3._subm assert sm3._subm != sm4._subm def test_submodule_reload(repo): sm = repo.submodules[SUBM_NAME] assert sm.url == 'https://github.com/libgit2/TestGitRepository' # Doctor the config file outside of libgit2 with open(Path(repo.workdir, '.gitmodules'), 'wt') as f: f.write('[submodule "TestGitRepository"]\n') f.write('\tpath = TestGitRepository\n') f.write('\turl = https://github.com/libgit2/pygit2\n') # Submodule object is oblivious to the change assert sm.url == 'https://github.com/libgit2/TestGitRepository' # Tell it to refresh its cache sm.reload() assert sm.url == 'https://github.com/libgit2/pygit2' libgit2-pygit2-a011e26/test/test_tag.py000066400000000000000000000062271473744024100177630ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. """Tests for Tag objects.""" import pytest import pygit2 from pygit2.enums import ObjectType TAG_SHA = '3d2962987c695a29f1f80b6c3aa4ec046ef44369' def test_read_tag(barerepo): repo = barerepo tag = repo[TAG_SHA] target = repo[tag.target] assert isinstance(tag, pygit2.Tag) assert ObjectType.TAG == tag.type assert ObjectType.COMMIT == target.type assert 'root' == tag.name assert 'Tagged root commit.\n' == tag.message assert 'Initial test data commit.\n' == target.message assert tag.tagger == pygit2.Signature( 'Dave Borowitz', 'dborowitz@google.com', 1288724692, -420 ) def test_new_tag(barerepo): name = 'thetag' target = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' message = 'Tag a blob.\n' tagger = pygit2.Signature('John Doe', 'jdoe@example.com', 12347, 0) target_prefix = target[:5] too_short_prefix = target[:3] with pytest.raises(ValueError): barerepo.create_tag(name, too_short_prefix, ObjectType.BLOB, tagger, message) sha = barerepo.create_tag(name, target_prefix, ObjectType.BLOB, tagger, message) tag = barerepo[sha] assert '3ee44658fd11660e828dfc96b9b5c5f38d5b49bb' == tag.id assert name == tag.name assert target == tag.target assert tagger == tag.tagger assert message == tag.message assert name == barerepo[tag.id].name def test_modify_tag(barerepo): name = 'thetag' target = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' message = 'Tag a blob.\n' tagger = ('John Doe', 'jdoe@example.com', 12347) tag = barerepo[TAG_SHA] with pytest.raises(AttributeError): setattr(tag, 'name', name) with pytest.raises(AttributeError): setattr(tag, 'target', target) with pytest.raises(AttributeError): setattr(tag, 'tagger', tagger) with pytest.raises(AttributeError): setattr(tag, 'message', message) def test_get_object(barerepo): repo = barerepo tag = repo[TAG_SHA] assert repo[tag.target].id == tag.get_object().id libgit2-pygit2-a011e26/test/test_tree.py000066400000000000000000000154371473744024100201520ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. import operator import pytest import pygit2 from pygit2.enums import FileMode, ObjectType from . import utils TREE_SHA = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' SUBTREE_SHA = '614fd9a3094bf618ea938fffc00e7d1a54f89ad0' def assertTreeEntryEqual(entry, sha, name, filemode): assert entry.id == sha assert entry.name == name assert entry.filemode == filemode assert entry.raw_name == name.encode('utf-8') def test_read_tree(barerepo): tree = barerepo[TREE_SHA] with pytest.raises(TypeError): tree[()] with pytest.raises(TypeError): tree / 123 utils.assertRaisesWithArg(KeyError, 'abcd', lambda: tree['abcd']) utils.assertRaisesWithArg(IndexError, -4, lambda: tree[-4]) utils.assertRaisesWithArg(IndexError, 3, lambda: tree[3]) utils.assertRaisesWithArg(KeyError, 'abcd', lambda: tree / 'abcd') assert 3 == len(tree) sha = '7f129fd57e31e935c6d60a0c794efe4e6927664b' assert 'a' in tree assertTreeEntryEqual(tree[0], sha, 'a', 0o0100644) assertTreeEntryEqual(tree[-3], sha, 'a', 0o0100644) assertTreeEntryEqual(tree['a'], sha, 'a', 0o0100644) assertTreeEntryEqual(tree / 'a', sha, 'a', 0o0100644) sha = '85f120ee4dac60d0719fd51731e4199aa5a37df6' assert 'b' in tree assertTreeEntryEqual(tree[1], sha, 'b', 0o0100644) assertTreeEntryEqual(tree[-2], sha, 'b', 0o0100644) assertTreeEntryEqual(tree['b'], sha, 'b', 0o0100644) assertTreeEntryEqual(tree / 'b', sha, 'b', 0o0100644) sha = '297efb891a47de80be0cfe9c639e4b8c9b450989' assertTreeEntryEqual(tree['c/d'], sha, 'd', 0o0100644) assertTreeEntryEqual(tree / 'c/d', sha, 'd', 0o0100644) assertTreeEntryEqual(tree / 'c' / 'd', sha, 'd', 0o0100644) assertTreeEntryEqual(tree['c']['d'], sha, 'd', 0o0100644) assertTreeEntryEqual((tree / 'c')['d'], sha, 'd', 0o0100644) utils.assertRaisesWithArg(KeyError, 'ab/cd', lambda: tree['ab/cd']) utils.assertRaisesWithArg(KeyError, 'ab/cd', lambda: tree / 'ab/cd') utils.assertRaisesWithArg(KeyError, 'ab', lambda: tree / 'c' / 'ab') with pytest.raises(TypeError): tree / 'a' / 'cd' def test_equality(barerepo): tree_a = barerepo['18e2d2e9db075f9eb43bcb2daa65a2867d29a15e'] tree_b = barerepo['2ad1d3456c5c4a1c9e40aeeddb9cd20b409623c8'] assert tree_a['a'] != tree_b['a'] assert tree_a['a'] != tree_b['b'] assert tree_a['b'] == tree_b['b'] def test_sorting(barerepo): tree_a = barerepo['18e2d2e9db075f9eb43bcb2daa65a2867d29a15e'] assert list(tree_a) == sorted(reversed(list(tree_a)), key=pygit2.tree_entry_key) assert list(tree_a) != reversed(list(tree_a)) def test_read_subtree(barerepo): tree = barerepo[TREE_SHA] subtree_entry = tree['c'] assertTreeEntryEqual(subtree_entry, SUBTREE_SHA, 'c', 0o0040000) assert subtree_entry.type == ObjectType.TREE assert subtree_entry.type_str == 'tree' subtree_entry = tree / 'c' assertTreeEntryEqual(subtree_entry, SUBTREE_SHA, 'c', 0o0040000) assert subtree_entry.type == ObjectType.TREE assert subtree_entry.type_str == 'tree' subtree = barerepo[subtree_entry.id] assert 1 == len(subtree) sha = '297efb891a47de80be0cfe9c639e4b8c9b450989' assertTreeEntryEqual(subtree[0], sha, 'd', 0o0100644) subtree_entry = tree / 'c' assert subtree_entry == barerepo[subtree_entry.id] def test_new_tree(barerepo): repo = barerepo b0 = repo.create_blob('1') b1 = repo.create_blob('2') st = repo.TreeBuilder() st.insert('a', b0, 0o0100644) subtree = repo[st.write()] t = repo.TreeBuilder() t.insert('x', b0, 0o0100644) t.insert('y', b1, 0o0100755) t.insert('z', subtree.id, FileMode.TREE) tree = repo[t.write()] for name, oid, cls, filemode, type, type_str in [ ('x', b0, pygit2.Blob, FileMode.BLOB, ObjectType.BLOB, 'blob'), ('y', b1, pygit2.Blob, FileMode.BLOB_EXECUTABLE, ObjectType.BLOB, 'blob'), ('z', subtree.id, pygit2.Tree, FileMode.TREE, ObjectType.TREE, 'tree'), ]: assert name in tree obj = tree[name] assert isinstance(obj, cls) assert obj.name == name assert obj.filemode == filemode assert obj.type == type assert obj.type_str == type_str assert repo[obj.id].id == oid assert obj == repo[obj.id] obj = tree / name assert isinstance(obj, cls) assert obj.name == name assert obj.filemode == filemode assert obj.type == type assert obj.type_str == type_str assert repo[obj.id].id == oid assert obj == repo[obj.id] def test_modify_tree(barerepo): tree = barerepo[TREE_SHA] with pytest.raises(TypeError): operator.setitem('c', tree['a']) with pytest.raises(TypeError): operator.delitem('c') def test_iterate_tree(barerepo): """ Testing that we're able to iterate of a Tree object and that the resulting sha strings are consitent with the sha strings we could get with other Tree access methods. """ tree = barerepo[TREE_SHA] for tree_entry in tree: assert tree_entry == tree[tree_entry.name] def test_iterate_tree_nested(barerepo): """ Testing that we're able to iterate of a Tree object and then iterate trees we receive as a result. """ tree = barerepo[TREE_SHA] for tree_entry in tree: if isinstance(tree_entry, pygit2.Tree): for tree_entry2 in tree_entry: pass def test_deep_contains(barerepo): tree = barerepo[TREE_SHA] assert 'a' in tree assert 'c' in tree assert 'c/d' in tree assert 'c/e' not in tree assert 'd' not in tree assert 'd' in tree['c'] assert 'e' not in tree['c'] libgit2-pygit2-a011e26/test/test_treebuilder.py000066400000000000000000000040531473744024100215110ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. TREE_SHA = '967fce8df97cc71722d3c2a5930ef3e6f1d27b12' def test_new_empty_treebuilder(barerepo): barerepo.TreeBuilder() def test_noop_treebuilder(barerepo): tree = barerepo[TREE_SHA] bld = barerepo.TreeBuilder(TREE_SHA) result = bld.write() assert len(bld) == len(tree) assert tree.id == result def test_noop_treebuilder_from_tree(barerepo): tree = barerepo[TREE_SHA] bld = barerepo.TreeBuilder(tree) result = bld.write() assert len(bld) == len(tree) assert tree.id == result def test_rebuild_treebuilder(barerepo): tree = barerepo[TREE_SHA] bld = barerepo.TreeBuilder() for entry in tree: name = entry.name assert bld.get(name) is None bld.insert(name, entry.id, entry.filemode) assert bld.get(name).id == entry.id result = bld.write() assert len(bld) == len(tree) assert tree.id == result libgit2-pygit2-a011e26/test/utils.py000066400000000000000000000105341473744024100173050ustar00rootroot00000000000000# Copyright 2010-2025 The pygit2 contributors # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, # as published by the Free Software Foundation. # # In addition to the permissions in the GNU General Public License, # the authors give you unlimited permission to link the compiled # version of this file into combinations with other programs, # and to distribute those combinations without any restriction # coming from the use of this file. (The General Public License # restrictions do apply in other respects; for example, they cover # modification of the file, and distribution when not linked into # a combined executable.) # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. # Standard library import hashlib from pathlib import Path import shutil import socket import stat import sys import zipfile # Requirements import pytest # Pygit2 import pygit2 requires_future_libgit2 = pytest.mark.skipif( pygit2.LIBGIT2_VER < (2, 0, 0), reason='This test may work with a future version of libgit2', ) try: socket.gethostbyname('github.com') has_network = True except socket.gaierror: has_network = False requires_network = pytest.mark.skipif(not has_network, reason='Requires network') with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: has_proxy = sock.connect_ex(('', 8888)) == 0 requires_proxy = pytest.mark.skipif(not has_proxy, reason='Requires proxy at port 8888') requires_ssh = pytest.mark.skipif( pygit2.enums.Feature.SSH not in pygit2.features, reason='Requires SSH' ) is_pypy = '__pypy__' in sys.builtin_module_names fspath = pytest.mark.skipif( is_pypy, reason="PyPy doesn't fully support fspath, see https://foss.heptapod.net/pypy/pypy/-/issues/3168", ) refcount = pytest.mark.skipif(is_pypy, reason='skip refcounts checks in pypy') def gen_blob_sha1(data): # http://stackoverflow.com/questions/552659/assigning-git-sha1s-without-git m = hashlib.sha1() m.update(f'blob {len(data)}\0'.encode()) m.update(data) return m.hexdigest() def force_rm_handle(remove_path, path, excinfo): path = Path(path) path.chmod(path.stat().st_mode | stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) remove_path(path) def rmtree(path): """In Windows a read-only file cannot be removed, and shutil.rmtree fails. So we implement our own version of rmtree to address this issue. """ if Path(path).exists(): shutil.rmtree(path, onerror=force_rm_handle) class TemporaryRepository: def __init__(self, name, tmp_path): self.name = name self.tmp_path = tmp_path def __enter__(self): path = Path(__file__).parent / 'data' / self.name temp_repo_path = Path(self.tmp_path) / path.stem if path.suffix == '.zip': with zipfile.ZipFile(path) as zipf: zipf.extractall(self.tmp_path) elif path.suffix == '.git': shutil.copytree(path, temp_repo_path) else: raise ValueError(f'Unexpected {path.suffix} extension') return temp_repo_path def __exit__(self, exc_type, exc_value, traceback): pass def assertRaisesWithArg(exc_class, arg, func, *args, **kwargs): with pytest.raises(exc_class) as excinfo: func(*args, **kwargs) assert excinfo.value.args == (arg,) # Explicitly clear the Exception Info. Citing # https://docs.pytest.org/en/latest/reference.html#pytest-raises: # # Clearing those references breaks a reference cycle # (ExceptionInfo –> caught exception –> frame stack raising the exception # –> current frame stack –> local variables –> ExceptionInfo) which makes # Python keep all objects referenced from that cycle (including all local # variables in the current frame) alive until the next cyclic garbage # collection run. See the official Python try statement documentation for # more detailed information. del excinfo