├── .github ├── dependabot.yml └── workflows │ ├── minimum-vcs.yml │ ├── no-vcs.yml │ ├── test.yml │ └── tools │ ├── minimum-vcs │ └── Dockerfile │ └── no-vcs │ └── Dockerfile ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGELOG.md ├── LICENSE ├── README.rst ├── docs ├── api.rst ├── changelog.rst ├── command.rst ├── conf.py ├── configuration.rst ├── hatch.rst ├── how.rst ├── index.rst ├── notes.rst ├── requirements.txt ├── runtime-version.rst └── writing-methods.rst ├── pyproject.toml ├── src └── versioningit │ ├── __init__.py │ ├── __main__.py │ ├── basics.py │ ├── cmdclass.py │ ├── config.py │ ├── core.py │ ├── errors.py │ ├── get_cmdclasses.py │ ├── git.py │ ├── hatch.py │ ├── hg.py │ ├── hook.py │ ├── logging.py │ ├── methods.py │ ├── next_version.py │ ├── onbuild.py │ ├── py.typed │ └── util.py ├── test ├── conftest.py ├── data │ ├── config-error │ │ ├── bad-method.toml │ │ ├── bad-method.txt │ │ ├── bad-module-dir.toml │ │ ├── bad-module-dir.txt │ │ ├── bad-module.toml │ │ ├── bad-module.txt │ │ ├── bad-section.toml │ │ ├── bad-section.txt │ │ ├── bad-value.toml │ │ ├── bad-value.txt │ │ ├── bad-ving.toml │ │ ├── bad-ving.txt │ │ ├── no-module.toml │ │ ├── no-module.txt │ │ ├── no-value.toml │ │ ├── no-value.txt │ │ ├── no-ving.toml │ │ ├── no-ving.txt │ │ ├── other-hatch.toml │ │ └── other-hatch.txt │ ├── config │ │ ├── custom-methods.py │ │ ├── custom-methods.toml │ │ ├── custom-paramless.py │ │ ├── custom-paramless.toml │ │ ├── empty.py │ │ ├── empty.toml │ │ ├── hatch-and-std.py │ │ ├── hatch-and-std.toml │ │ ├── hatch.py │ │ ├── hatch.toml │ │ ├── other-hatch-and-std.py │ │ ├── other-hatch-and-std.toml │ │ ├── paramless.py │ │ ├── paramless.toml │ │ ├── params.py │ │ ├── params.toml │ │ ├── step-args.py │ │ └── step-args.toml │ ├── hg-archival │ │ ├── distance.json │ │ ├── distance.txt │ │ ├── exact.json │ │ ├── exact.txt │ │ ├── multi-latesttag.json │ │ ├── multi-latesttag.txt │ │ ├── multi-tag.json │ │ ├── multi-tag.txt │ │ ├── null-latesttag.json │ │ └── null-latesttag.txt │ ├── metadata │ │ ├── desc-in-header.eml │ │ ├── desc-in-payload.eml │ │ ├── no-version-payload.eml │ │ ├── no-version.eml │ │ ├── version-after-desc.eml │ │ └── version-in-desc.eml │ ├── mypackage-0.1.0.post2+gd338b00.tar.gz │ ├── replace-version │ │ ├── append-newline.py │ │ ├── append-with-date.py │ │ ├── append.py │ │ ├── base │ │ │ ├── comment.py │ │ │ ├── empty.py │ │ │ ├── latin1.txt │ │ │ ├── line-sep.py │ │ │ ├── no-eof-nl.dat │ │ │ ├── repeats.py │ │ │ ├── source_file.py │ │ │ └── wheel_file.py │ │ ├── latin1-edited.txt │ │ ├── line-sepped.py │ │ ├── line2block.py │ │ ├── multi-matches.py │ │ ├── nl-append.txt │ │ ├── nomatch.py │ │ ├── not-source.py │ │ ├── replace-nonl.py │ │ ├── replacement.py │ │ ├── set-line.py │ │ ├── source.py │ │ ├── still-empty.py │ │ └── with-date.py │ └── repos │ │ ├── archives │ │ ├── git-archive-distance.json │ │ ├── git-archive-distance.zip │ │ ├── git-archive-exact.json │ │ ├── git-archive-exact.zip │ │ ├── hg-archive-default-tag.json │ │ ├── hg-archive-default-tag.zip │ │ ├── hg-archive-distance.json │ │ ├── hg-archive-distance.zip │ │ ├── hg-archive-exact.json │ │ └── hg-archive-exact.zip │ │ ├── git-errors │ │ ├── archive-no-tag.json │ │ ├── archive-no-tag.marks │ │ ├── archive-no-tag.zip │ │ ├── hatch-no-tag.json │ │ ├── hatch-no-tag.marks │ │ ├── hatch-no-tag.zip │ │ ├── no-tag.json │ │ ├── no-tag.marks │ │ ├── no-tag.zip │ │ ├── template-fields-error.json │ │ └── template-fields-error.zip │ │ ├── git │ │ ├── added-no-commits-default-tag.json │ │ ├── added-no-commits-default-tag.zip │ │ ├── archive-repo-exclude.json │ │ ├── archive-repo-exclude.marks │ │ ├── archive-repo-exclude.zip │ │ ├── archive-repo-match.json │ │ ├── archive-repo-match.zip │ │ ├── archive-repo-mixed-tags.json │ │ ├── archive-repo-mixed-tags.zip │ │ ├── archive-repo-mixed.json │ │ ├── archive-repo-mixed.zip │ │ ├── archive-repo.json │ │ ├── archive-repo.zip │ │ ├── custom-format-dirty.json │ │ ├── custom-format-dirty.zip │ │ ├── custom-format-distance-dirty.json │ │ ├── custom-format-distance-dirty.zip │ │ ├── custom-format-distance.json │ │ ├── custom-format-distance.zip │ │ ├── custom-method-pkg.json │ │ ├── custom-method-pkg.zip │ │ ├── custom-method-src.json │ │ ├── custom-method-src.zip │ │ ├── custom-method.json │ │ ├── custom-method.zip │ │ ├── default-tag.fields.json │ │ ├── default-tag.json │ │ ├── default-tag.zip │ │ ├── default-version-bad.json │ │ ├── default-version-bad.marks │ │ ├── default-version-bad.zip │ │ ├── default-version-onbuild-write.json │ │ ├── default-version-onbuild-write.zip │ │ ├── default-version.json │ │ ├── default-version.zip │ │ ├── detached-exact.fields.json │ │ ├── detached-exact.json │ │ ├── detached-exact.zip │ │ ├── dirty.json │ │ ├── dirty.zip │ │ ├── distance-dirty.fields.json │ │ ├── distance-dirty.json │ │ ├── distance-dirty.zip │ │ ├── distance.fields.json │ │ ├── distance.json │ │ ├── distance.zip │ │ ├── exact-annotated.json │ │ ├── exact-annotated.zip │ │ ├── exact.exclude.fields.json │ │ ├── exact.fields.json │ │ ├── exact.json │ │ ├── exact.zip │ │ ├── exclude.fields.json │ │ ├── exclude.json │ │ ├── exclude.marks │ │ ├── exclude.zip │ │ ├── match.fields.json │ │ ├── match.json │ │ ├── match.zip │ │ ├── onbuild-bases.json │ │ ├── onbuild-bases.zip │ │ ├── onbuild-cfg.json │ │ ├── onbuild-cfg.zip │ │ ├── onbuild-nonidem.json │ │ ├── onbuild-nonidem.zip │ │ ├── onbuild-write-norming.json │ │ ├── onbuild-write-norming.zip │ │ ├── onbuild-write.json │ │ ├── onbuild-write.zip │ │ ├── onbuild.json │ │ ├── onbuild.zip │ │ ├── vgit-toml.json │ │ ├── vgit-toml.zip │ │ ├── write-encoding.json │ │ ├── write-encoding.zip │ │ ├── write-py.json │ │ ├── write-py.zip │ │ ├── write-template.json │ │ ├── write-template.zip │ │ ├── write-txt.json │ │ └── write-txt.zip │ │ ├── hatch │ │ ├── both-config.json │ │ ├── both-config.zip │ │ ├── dirty.json │ │ ├── dirty.zip │ │ ├── distance-dirty.json │ │ ├── distance-dirty.zip │ │ ├── distance.json │ │ ├── distance.zip │ │ ├── exact.json │ │ ├── exact.zip │ │ ├── hatch-config-empty-std.json │ │ ├── hatch-config-empty-std.zip │ │ ├── hatch-config.json │ │ ├── hatch-config.zip │ │ ├── no-std-config.json │ │ ├── no-std-config.zip │ │ ├── onbuild-both-config.json │ │ ├── onbuild-both-config.zip │ │ ├── onbuild-fields.json │ │ ├── onbuild-fields.zip │ │ ├── onbuild.json │ │ ├── onbuild.zip │ │ ├── std-config.json │ │ ├── std-config.zip │ │ ├── write-py.json │ │ ├── write-py.zip │ │ ├── write-txt.json │ │ └── write-txt.zip │ │ ├── hg-errors │ │ ├── hg-no-tag.json │ │ └── hg-no-tag.zip │ │ ├── hg │ │ ├── added-no-commits-default-tag.json │ │ ├── added-no-commits-default-tag.zip │ │ ├── default-tag-fallback.fields.json │ │ ├── default-tag-fallback.json │ │ ├── default-tag-fallback.zip │ │ ├── default-tag.fields.json │ │ ├── default-tag.json │ │ ├── default-tag.zip │ │ ├── dirty.json │ │ ├── dirty.zip │ │ ├── distance-dirty.fields.json │ │ ├── distance-dirty.json │ │ ├── distance-dirty.zip │ │ ├── distance.fields.json │ │ ├── distance.json │ │ ├── distance.zip │ │ ├── exact.fields.json │ │ ├── exact.json │ │ ├── exact.zip │ │ ├── multi-tag.json │ │ ├── multi-tag.zip │ │ ├── pattern.fields.json │ │ ├── pattern.json │ │ └── pattern.zip │ │ ├── no-git.zip │ │ ├── no-pyproject.zip │ │ ├── no-versioningit.txt │ │ ├── no-versioningit.zip │ │ └── shallow.zip ├── test_config.py ├── test_end2end.py ├── test_get_version.py ├── test_logging.py ├── test_main.py ├── test_methods │ ├── test_format.py │ ├── test_git.py │ ├── test_hg.py │ ├── test_next_version.py │ ├── test_onbuild.py │ ├── test_tag2version.py │ ├── test_template_fields.py │ └── test_write.py └── test_util.py └── tox.ini /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | commit-message: 8 | prefix: "[python]" 9 | labels: 10 | - dependencies 11 | - d:python 12 | 13 | - package-ecosystem: pip 14 | directory: /docs 15 | versioning-strategy: increase-if-necessary 16 | schedule: 17 | interval: weekly 18 | commit-message: 19 | prefix: "[python+docs]" 20 | labels: 21 | - dependencies 22 | - d:python 23 | 24 | - package-ecosystem: github-actions 25 | directory: / 26 | schedule: 27 | interval: weekly 28 | commit-message: 29 | prefix: "[gh-actions]" 30 | include: scope 31 | labels: 32 | - dependencies 33 | - d:github-actions 34 | -------------------------------------------------------------------------------- /.github/workflows/minimum-vcs.yml: -------------------------------------------------------------------------------- 1 | name: Test Against Minimum VCS Versions 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | schedule: 9 | - cron: '0 7 * * *' 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Check out repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Build Docker image 23 | run: docker build -t minimum-vcs .github/workflows/tools/minimum-vcs 24 | 25 | - name: Run tests 26 | run: | 27 | docker run \ 28 | --rm \ 29 | -v $PWD:/docked \ 30 | -w /docked \ 31 | minimum-vcs \ 32 | tox -e py -- -vv -m "not describe_exclude" 33 | 34 | # vim:set et sts=2: 35 | -------------------------------------------------------------------------------- /.github/workflows/no-vcs.yml: -------------------------------------------------------------------------------- 1 | name: Test with No VCSes 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | schedule: 9 | - cron: '0 7 * * *' 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Check out repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Build Docker image 23 | run: docker build -t no-vcs .github/workflows/tools/no-vcs 24 | 25 | - name: Run tests 26 | run: | 27 | docker run --rm -v $PWD:/docked -w /docked no-vcs \ 28 | tox -e py -- -vv --cov-report=xml 29 | 30 | - name: Upload coverage to Codecov 31 | uses: codecov/codecov-action@v5 32 | with: 33 | fail_ci_if_error: false 34 | token: ${{ secrets.CODECOV_TOKEN }} 35 | name: no-vcs 36 | 37 | # vim:set et sts=2: 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | schedule: 9 | - cron: '0 7 * * *' 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: 22 | - macos-latest 23 | - ubuntu-latest 24 | - windows-latest 25 | python-version: 26 | - '3.8' 27 | - '3.9' 28 | - '3.10' 29 | - '3.11' 30 | - '3.12' 31 | - '3.13' 32 | - 'pypy-3.8' 33 | - 'pypy-3.9' 34 | - 'pypy-3.10' 35 | toxenv: [py] 36 | include: 37 | - python-version: '3.8' 38 | toxenv: lint 39 | os: ubuntu-latest 40 | - python-version: '3.8' 41 | toxenv: typing 42 | os: ubuntu-latest 43 | - python-version: '3.8' 44 | toxenv: py-oldsetup 45 | os: ubuntu-latest 46 | steps: 47 | - name: Check out repository 48 | uses: actions/checkout@v4 49 | 50 | - name: Install Mercurial 51 | # Mercurial seems to be broken on PyPy, so we need to install it before 52 | # overriding the default Python. 53 | if: startsWith(matrix.os, 'macos-') 54 | run: brew install mercurial 55 | 56 | - name: Set up Python 57 | uses: actions/setup-python@v5 58 | with: 59 | python-version: ${{ matrix.python-version }} 60 | 61 | - name: Install dependencies 62 | run: | 63 | python -m pip install --upgrade pip wheel 64 | python -m pip install --upgrade --upgrade-strategy=eager tox 65 | virtualenv --upgrade-embed-wheels 66 | 67 | - name: Run tests with coverage 68 | if: startsWith(matrix.toxenv, 'py') 69 | run: tox -e py -- -vv --cov-report=xml 70 | 71 | - name: Run generic tests 72 | if: "!startsWith(matrix.toxenv, 'py')" 73 | run: tox -e ${{ matrix.toxenv }} 74 | 75 | - name: Upload coverage to Codecov 76 | if: startsWith(matrix.toxenv, 'py') 77 | uses: codecov/codecov-action@v5 78 | with: 79 | fail_ci_if_error: false 80 | token: ${{ secrets.CODECOV_TOKEN }} 81 | name: ${{ matrix.python-version }} 82 | 83 | # vim:set et sts=2: 84 | -------------------------------------------------------------------------------- /.github/workflows/tools/minimum-vcs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-buster 2 | RUN set -ex; \ 3 | cd tmp; \ 4 | wget http://snapshot.debian.org/archive/debian/20121102T092448Z/pool/main/g/git/git-man_1.8.0-1_all.deb; \ 5 | dpkg -i git-man_1.8.0-1_all.deb; \ 6 | wget http://snapshot.debian.org/archive/debian/20121102T151957Z/pool/main/g/git/git_1.8.0-1_"$(dpkg --print-architecture)".deb; \ 7 | dpkg -i git_1.8.0-1_*.deb 8 | RUN set -ex; \ 9 | pip install --upgrade pip wheel; \ 10 | pip install --upgrade --upgrade-strategy=eager tox; \ 11 | pip install mercurial==5.2 12 | -------------------------------------------------------------------------------- /.github/workflows/tools/no-vcs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-slim 2 | RUN set -ex; \ 3 | pip install --upgrade pip wheel; \ 4 | pip install --upgrade --upgrade-strategy=eager tox 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage* 2 | .mypy_cache/ 3 | .nox/ 4 | .tox/ 5 | __pycache__/ 6 | dist/ 7 | docs/_build/ 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.6.0 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-json 7 | - id: check-toml 8 | - id: check-yaml 9 | - id: end-of-file-fixer 10 | exclude: '\.(dat|eml)$' 11 | - id: trailing-whitespace 12 | exclude: '\.eml$' 13 | 14 | - repo: https://github.com/psf/black 15 | rev: 24.4.2 16 | hooks: 17 | - id: black 18 | exclude: ^test/data 19 | 20 | - repo: https://github.com/PyCQA/isort 21 | rev: 5.13.2 22 | hooks: 23 | - id: isort 24 | exclude: ^test/data 25 | 26 | - repo: https://github.com/PyCQA/flake8 27 | rev: 7.0.0 28 | hooks: 29 | - id: flake8 30 | additional_dependencies: 31 | - flake8-bugbear 32 | - flake8-builtins 33 | - flake8-unused-arguments 34 | exclude: ^test/data 35 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | formats: all 3 | python: 4 | install: 5 | - requirements: docs/requirements.txt 6 | - method: pip 7 | path: . 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3" 12 | sphinx: 13 | configuration: docs/conf.py 14 | fail_on_warning: true 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2025 John Thorvald Wodder II and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: versioningit 2 | 3 | Library API 4 | =========== 5 | 6 | High-Level Functions and Classes 7 | -------------------------------- 8 | 9 | .. autofunction:: get_version 10 | .. autofunction:: get_next_version 11 | .. autofunction:: get_cmdclasses 12 | 13 | .. class:: versioningit.cmdclass.sdist 14 | 15 | .. versionadded:: 2.2.0 16 | 17 | A custom subclass of `setuptools.command.sdist.sdist` that runs the 18 | ``onbuild`` step when building an sdist. This class is equivalent to 19 | ``get_cmdclasses()["sdist"]``, except that it can also be used in the 20 | ``[options]cmdclass`` field in :file:`setup.cfg`. 21 | 22 | .. class:: versioningit.cmdclass.build_py 23 | 24 | .. versionadded:: 2.2.0 25 | 26 | A custom subclass of `setuptools.command.build_py.build_py` that runs the 27 | ``onbuild`` step when building a wheel. This class is equivalent to 28 | ``get_cmdclasses()["build_py"]``, except that it can also be used in the 29 | ``[options]cmdclass`` field in :file:`setup.cfg`. 30 | 31 | .. note:: 32 | 33 | When importing or referring to the ``sdist`` and ``build_py`` command 34 | classes, the ``.cmdclass`` submodule needs to be specified; unlike the rest 35 | of the library API, they are not importable directly from ``versioningit``. 36 | 37 | .. code:: ini 38 | 39 | [options] 40 | cmdclass = 41 | # Right! 42 | sdist = versioningit.cmdclass.sdist 43 | build_py = versioningit.cmdclass.build_py 44 | 45 | [options] 46 | cmdclass = 47 | # Wrong! 48 | sdist = versioningit.sdist 49 | build_py = versioningit.build_py 50 | 51 | 52 | Low-Level Class 53 | --------------- 54 | 55 | .. autoclass:: Versioningit() 56 | :member-order: bysource 57 | 58 | Exceptions 59 | ---------- 60 | 61 | .. autoexception:: Error 62 | .. autoexception:: ConfigError 63 | :show-inheritance: 64 | .. autoexception:: InvalidTagError 65 | :show-inheritance: 66 | .. autoexception:: InvalidVersionError 67 | :show-inheritance: 68 | .. autoexception:: MethodError 69 | :show-inheritance: 70 | .. autoexception:: NoTagError 71 | :show-inheritance: 72 | .. autoexception:: NotSdistError 73 | :show-inheritance: 74 | .. autoexception:: NotVCSError 75 | :show-inheritance: 76 | .. autoexception:: NotVersioningitError 77 | :show-inheritance: 78 | .. autoexception:: NoConfigFileError 79 | :show-inheritance: 80 | .. autoexception:: NoConfigSectionError 81 | :show-inheritance: 82 | 83 | Utilities 84 | --------- 85 | 86 | .. autoclass:: VCSDescription 87 | .. autoclass:: Report 88 | .. autoclass:: FallbackReport 89 | .. autofunction:: get_version_from_pkg_info 90 | .. autofunction:: run_onbuild 91 | .. autofunction:: get_template_fields_from_distribution 92 | 93 | .. _config_dict: 94 | 95 | Passing Explicit Configuration 96 | ------------------------------ 97 | 98 | The functions & methods that take a path to a project directory normally read 99 | the project's configuration from the :file:`versioningit.toml` or 100 | :file:`pyproject.toml` file therein, but they can also be passed a ``config`` 101 | argument to take the configuration from instead, in which case any 102 | configuration files will be ignored and need not even exist. 103 | 104 | A ``config`` argument must be a `dict` whose structure mirrors the structure of 105 | the ``[tool.versioningit]`` table in :file:`pyproject.toml`. For example, the 106 | following TOML configuration: 107 | 108 | .. code:: toml 109 | 110 | [tool.versioningit.vcs] 111 | method = "git" 112 | match = ["v*"] 113 | 114 | [tool.versioningit.next-version] 115 | method = { module = "setup", value = "my_next_version" } 116 | 117 | [tool.versioningit.format] 118 | distance = "{next_version}.dev{distance}+{vcs}{rev}" 119 | dirty = "{base_version}+dirty" 120 | distance-dirty = "{next_version}.dev{distance}+{vcs}{rev}.dirty" 121 | 122 | corresponds to the following Python ``config`` value: 123 | 124 | .. code:: python 125 | 126 | { 127 | "vcs": { 128 | "method": "git", 129 | "match": ["v*"], 130 | }, 131 | "next-version": { 132 | "method": { 133 | "module": "setup", 134 | "value": "my_next_version", 135 | }, 136 | }, 137 | "format": { 138 | "distance": "{next_version}.dev{distance}+{vcs}{rev}", 139 | "dirty": "{base_version}+dirty", 140 | "distance-dirty": "{next_version}.dev{distance}+{vcs}{rev}.dirty", 141 | }, 142 | } 143 | 144 | This is the same structure that you would get by reading from the 145 | :file:`pyproject.toml` file like so: 146 | 147 | .. code:: python 148 | 149 | import tomli 150 | 151 | with open("pyproject.toml", "rb") as fp: 152 | config = tomli.load(fp)["tool"]["versioningit"] 153 | 154 | When passing ``versioningit`` configuration as a ``config`` argument, an 155 | alternative way to specify methods becomes available: in place of a method 156 | specification, one can pass a callable object directly. 157 | -------------------------------------------------------------------------------- /docs/command.rst: -------------------------------------------------------------------------------- 1 | .. index:: versioningit (command) 2 | 3 | .. _command: 4 | 5 | Command 6 | ======= 7 | 8 | :: 9 | 10 | versioningit [] [] 11 | 12 | When ``versioningit`` is installed in the current Python environment, a command 13 | of the same name will be available that prints out the version for a given 14 | ``versioningit``-enabled project (by default, the project rooted in the current 15 | directory). This can be used to test out your ``versioningit`` setup before 16 | publishing. 17 | 18 | Options 19 | ------- 20 | 21 | .. program:: versioningit 22 | 23 | .. option:: -n, --next-version 24 | 25 | Instead of printing the current version of the project, print the value of 26 | the next release version as computed by the ``next-version`` step 27 | 28 | .. option:: --traceback 29 | 30 | Normally, any library errors are shown as just the error message. Specify 31 | this option to show the complete error traceback. 32 | 33 | .. option:: -v, --verbose 34 | 35 | Increase the amount of log messages displayed. Specify twice for maximum 36 | information. 37 | 38 | The logging level can also be set via the :envvar:`VERSIONINGIT_LOG_LEVEL` 39 | environment variable. If both :option:`-v` and 40 | :envvar:`VERSIONINGIT_LOG_LEVEL` are specified, the more verbose log level 41 | of the two will be used, where one :option:`-v` corresponds to ``INFO`` 42 | level and two or more correspond to ``DEBUG`` level. (If neither are 43 | specified, the default level of ``WARNING`` is used.) 44 | 45 | .. option:: -w, --write 46 | 47 | Write the version to the file specified in the 48 | ``[tool.versioningit.write]`` subtable, if so configured 49 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from versioningit import __version__ 2 | 3 | project = "versioningit" 4 | author = "John Thorvald Wodder II" 5 | copyright = "2021-2025 John Thorvald Wodder II" # noqa: A001 6 | 7 | extensions = [ 8 | "sphinx.ext.autodoc", 9 | "sphinx.ext.intersphinx", 10 | "sphinx.ext.viewcode", 11 | "sphinx_copybutton", 12 | "sphinx_inline_tabs", 13 | ] 14 | 15 | autodoc_default_options = { 16 | "members": True, 17 | "undoc-members": True, 18 | } 19 | 20 | intersphinx_mapping = { 21 | "python": ("https://docs.python.org/3", None), 22 | } 23 | 24 | exclude_patterns = ["_build"] 25 | source_suffix = ".rst" 26 | source_encoding = "utf-8" 27 | master_doc = "index" 28 | version = __version__ 29 | release = __version__ 30 | today_fmt = "%Y %b %d" 31 | default_role = "py:obj" 32 | pygments_style = "sphinx" 33 | 34 | html_theme = "sphinx_rtd_theme" 35 | html_theme_options = { 36 | "collapse_navigation": False, 37 | "prev_next_buttons_location": "both", 38 | } 39 | html_last_updated_fmt = "%Y %b %d" 40 | html_show_sourcelink = True 41 | html_show_sphinx = True 42 | html_show_copyright = True 43 | 44 | copybutton_prompt_text = r">>> |\.\.\. |\$ " 45 | copybutton_prompt_is_regexp = True 46 | -------------------------------------------------------------------------------- /docs/hatch.rst: -------------------------------------------------------------------------------- 1 | Hatch Integration 2 | ================= 3 | 4 | .. versionadded:: 2.3.0 5 | 6 | If you're not a setuptools user, ``versioningit`` can also be used as a version 7 | source plugin for the Hatch_ build backend. You use it in pretty much the same 8 | way as for setuptools: 9 | 10 | .. _Hatch: https://hatch.pypa.io 11 | 12 | - Include ``versioningit`` in your build requirements like so: 13 | 14 | .. code:: toml 15 | 16 | [build-system] 17 | requires = ["hatchling", "versioningit"] 18 | build-backend = "hatchling.build" 19 | 20 | - Tell Hatch that you're using a dynamic version source by including 21 | ``"version"`` in the ``project.dynamic`` key: 22 | 23 | .. code:: toml 24 | 25 | [project] 26 | name = "your-project-name" 27 | dynamic = ["version"] 28 | # The rest of your project metadata follows after. 29 | 30 | # Do not set the `version` key in [project]. If it's currently set, 31 | # remove it. 32 | 33 | - Tell Hatch to use ``versioningit`` as the version source: 34 | 35 | .. code:: toml 36 | 37 | [tool.hatch.version] 38 | source = "versioningit" 39 | 40 | - Configure ``versioningit`` as normal (mostly; see the note about ``onbuild`` 41 | below). However, with Hatch, you have two possible locations to put 42 | ``versioningit``'s configuration in: either the ``[tool.versioningit]`` table 43 | as used with setuptools or under the ``[tool.hatch.version]`` table. 44 | Moreover, unlike when using setuptools, you don't even need the 45 | ``[tool.versioningit]`` table if it's just going to be empty. 46 | 47 | For example, the following configurations are equivalent: 48 | 49 | - Using ``[tool.versioningit]``: 50 | 51 | .. code:: toml 52 | 53 | [tool.hatch.version] 54 | source = "versioningit" 55 | 56 | [tool.versioningit] 57 | default-version = "0.0.0+unknown" 58 | 59 | [tool.versioningit.format] 60 | distance = "{next_version}.dev{distance}+{vcs}{rev}" 61 | dirty = "{version}+dirty" 62 | distance-dirty = "{next_version}.dev{distance}+{vcs}{rev}.dirty" 63 | 64 | - Using ``[tool.hatch.version]``: 65 | 66 | .. code:: toml 67 | 68 | [tool.hatch.version] 69 | source = "versioningit" 70 | default-version = "0.0.0+unknown" 71 | 72 | [tool.hatch.version.format] 73 | distance = "{next_version}.dev{distance}+{vcs}{rev}" 74 | dirty = "{version}+dirty" 75 | distance-dirty = "{next_version}.dev{distance}+{vcs}{rev}.dirty" 76 | 77 | If you configure ``versioningit`` via ``[tool.hatch.version]`` and also 78 | define a ``[tool.versioningit]`` table (even if it's empty), a warning will 79 | be emitted, and only the ``[tool.hatch.version]`` configuration will be 80 | used. 81 | 82 | - If you use the ``write`` step to create a file containing your project 83 | version, and this file is listed in your :file:`.gitignore` or 84 | :file:`.hgignore`, you will need to tell Hatch to include the file in sdists 85 | & wheels like so: 86 | 87 | .. code:: toml 88 | 89 | [tool.hatch.build] 90 | # Replace the path below with the path to the file created by the 91 | # `write` step: 92 | artifacts = ["src/mypackage/_version.py"] 93 | 94 | - The configuration for the ``onbuild`` step is placed in the 95 | ``[tool.hatch.build.hooks.versioningit-onbuild]`` table (not in 96 | ``[tool.versioningit.onbuild]`` or ``[tool.hatch.version.onbuild]``). In 97 | addition, filling out this table is all you need to do to enable the 98 | ``onbuild`` step — no fiddling with command classes necessary! 99 | 100 | .. note:: 101 | 102 | If you use ``versioningit`` with Hatch, you will not be able to set your 103 | project's version by running ``hatch version x.y.z``. Just create a tag 104 | instead! 105 | -------------------------------------------------------------------------------- /docs/how.rst: -------------------------------------------------------------------------------- 1 | How it Works 2 | ============ 3 | 4 | ``versioningit`` divides its operation into seven :dfn:`steps`: ``vcs``, 5 | ``tag2version``, ``next-version``, ``format``, ``template-fields``, ``write``, 6 | and ``onbuild``. The first four steps make up the actual version calculation, 7 | while the rest normally only happen while building with setuptools or Hatch. 8 | 9 | Version Calculation 10 | ------------------- 11 | 12 | The version for a given project is determined as follows: 13 | 14 | - ``vcs`` step: The version control system specified in the project's 15 | ``versioningit`` configuration is queried for information about the project's 16 | working directory: the most recent tag, the number of commits since that tag, 17 | whether there are any uncommitted changes, and other data points. 18 | 19 | - ``tag2version`` step: A version is extracted from the tag returned by the 20 | ``vcs`` step 21 | 22 | - ``next-version`` step: The next version after the most recent version is 23 | calculated 24 | 25 | - If there have been no commits or uncommitted changes since the most recent 26 | tag, the version returned by the ``tag2version`` step is used as the project 27 | version. Otherwise: 28 | 29 | - ``format`` step: The results of the preceding steps are combined to produce 30 | a final project version. 31 | 32 | - ``template-fields`` step: Values from the preceding steps are used to 33 | calculate a set of template fields for use by the ``write`` and/or 34 | ``onbuild`` steps 35 | 36 | 37 | Build Integration 38 | ----------------- 39 | 40 | Setting the Version 41 | ^^^^^^^^^^^^^^^^^^^ 42 | 43 | ``versioningit`` registers plugins with both setuptools and Hatch that cause it 44 | to be run whenever one of those backends computes the metadata for a project in 45 | an environment in which ``versioningit`` is installed. If the project in 46 | question has a :file:`versioningit.toml` or :file:`pyproject.toml` file with a 47 | ``[tool.versioningit]`` table (or, for Hatch only, a ``[tool.hatch.version]`` 48 | table containing more than just a ``source`` key), then ``versioningit`` 49 | performs the version calculations described above and sets the project's 50 | version to the final value. (If a version cannot be determined because the 51 | project is not in a repository or repository archive, then ``versioningit`` 52 | will assume the project is an unpacked sdist and will look for a 53 | :file:`PKG-INFO` file to fetch the version from instead.) If the configuration 54 | table contains a ``write`` subtable, then the ``write`` step will also be run 55 | at this time; the default ``write`` method creates a file at a specified path 56 | containing the project's version. 57 | 58 | ``onbuild`` Step 59 | ^^^^^^^^^^^^^^^^ 60 | 61 | When a project is built that uses ``versioningit``'s custom setuptools commands 62 | or Hatch build hook, the ``onbuild`` step becomes added to the build process. 63 | The default ``onbuild`` method updates one of the files in the built 64 | distribution to contain the project version while leaving the source files in 65 | the actual project alone. See ":ref:`onbuild`" for more information. 66 | -------------------------------------------------------------------------------- /docs/notes.rst: -------------------------------------------------------------------------------- 1 | Notes 2 | ===== 3 | 4 | Restrictions & Caveats 5 | ---------------------- 6 | 7 | - When building or installing a project that uses ``versioningit``, the entire 8 | repository history (or at least everything back through the most recent tag) 9 | must be available. This means that installing from a shallow clone (the 10 | default on most CI systems) will not work. If you are using the ``"git"`` or 11 | ``"git-archive"`` ``vcs`` method and have ``default-tag`` set in 12 | ``[tool.versioningit.vcs]``, then shallow clones will end up assigned the 13 | default tag, which may or may not be what you want. 14 | 15 | - If using the ``[tool.versioningit.write]`` subtable to write the version to a 16 | file, this file will only be updated whenever the project is built or 17 | installed. If using editable installs, this means that you must re-run 18 | ``python setup.py develop`` or ``pip install -e .`` after each 19 | commit if you want the version to be up-to-date. 20 | 21 | - If you define & use a custom method inside your Python project's package, you 22 | will not be able to retrieve your project version by calling 23 | `importlib.metadata.version()` inside :file:`__init__.py` — at least, not 24 | without a ``try: ... except ...`` wrapper. This is because ``versioningit`` 25 | loads the package containing the custom method before the package is 26 | installed, but `importlib.metadata.version()` only works after the package is 27 | installed. 28 | 29 | - If you generate a conda package from your sdist (e.g., for a conda-forge 30 | feedstock), you will likely want to include ``versioningit`` as a ``host`` 31 | dependency in your conda ``meta.yaml`` file. This is needed for the package 32 | produced from your sdist to contain the correct version number in its 33 | ``dist-info``. 34 | 35 | 36 | Backwards Compatibility Policy 37 | ------------------------------ 38 | 39 | ``versioningit`` follows `Semantic Versioning`_, in which the major version 40 | component is incremented whenever a breaking change is made. Moreover, the 41 | basic :file:`pyproject.toml` interface to ``versioningit`` can be considered 42 | very stable; the only changes to expect to it will be the addition of new 43 | features and the occasional patching over of corner-case bugs. Nearly all 44 | breaking changes will be to the library or custom method API; if you've written 45 | any code that uses this part of the API, you are advised to declare the next 46 | major version of ``versioningit`` as an upper bound on your ``versioningit`` 47 | dependency. 48 | 49 | .. _Semantic Versioning: https://semver.org 50 | 51 | Bug fixes may, on occasion, be backported to previous major versions of 52 | ``versioningit``, but this is in general unlikely to happen unless a user 53 | specifically requests it. 54 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx~=8.0 2 | sphinx-copybutton~=0.5.0 3 | sphinx-inline-tabs 4 | sphinx_rtd_theme~=3.0 5 | -------------------------------------------------------------------------------- /docs/runtime-version.rst: -------------------------------------------------------------------------------- 1 | Getting Package Version at Runtime 2 | ================================== 3 | 4 | Automatically setting your project's version is all well and good, but you 5 | usually also want to expose that version at runtime, usually via a 6 | ``__version__`` variable. There are three options for doing this: 7 | 8 | 1. Use the `~importlib.metadata.version()` function from `importlib.metadata` 9 | to get your package's version, like so: 10 | 11 | .. code:: python 12 | 13 | from importlib.metadata import version 14 | 15 | __version__ = version("mypackage") 16 | 17 | Note that `importlib.metadata` was only added to Python in version 3.8. If 18 | you wish to support older Python versions, use the `importlib-metadata`_ 19 | backport available on PyPI for those versions, e.g.: 20 | 21 | .. code:: python 22 | 23 | import sys 24 | 25 | if sys.version_info >= (3, 8): 26 | from importlib.metadata import version 27 | else: 28 | from importlib_metadata import version 29 | 30 | __version__ = version("mypackage") 31 | 32 | If relying on the backport, don't forget to include ``importlib-metadata; 33 | python_version < "3.8"`` in your project's ``install_requires``! 34 | 35 | .. _importlib-metadata: https://pypi.org/project/importlib-metadata/ 36 | 37 | 2. Fill out the ``[tool.versioningit.write]`` subtable in 38 | :file:`pyproject.toml` so that the project version will be written to a file 39 | in your Python package which you can then import or read. For example, if 40 | your package is named ``mypackage`` and is stored in a :file:`src/` 41 | directory, you can write the version to a Python file 42 | :file:`src/mypackage/_version.py` like so: 43 | 44 | .. code:: toml 45 | 46 | [tool.versioningit.write] 47 | file = "src/mypackage/_version.py" 48 | 49 | Then, within :file:`mypackage/__init__.py`, you can import the version like 50 | so: 51 | 52 | .. code:: python 53 | 54 | from ._version import __version__ 55 | 56 | Alternatively, you can write the version to a text file, say, 57 | :file:`src/mypackage/VERSION`: 58 | 59 | .. code:: toml 60 | 61 | [tool.versioningit.write] 62 | file = "src/mypackage/VERSION" 63 | 64 | and then read the version in at runtime with: 65 | 66 | .. code:: python 67 | 68 | from pathlib import Path 69 | __version__ = Path(__file__).with_name("VERSION").read_text().strip() 70 | 71 | 3. *(New in version 1.1.0)* Use the :ref:`onbuild step ` and its 72 | custom hooks to create sdists & wheels in which some file has been modified 73 | to contain the line ``__version__ = ""`` or similar while 74 | leaving your repository's contents alone. 75 | 76 | .. tip:: 77 | 78 | Wondering which of ``write`` and ``onbuild`` is right for your project? 79 | See this table for a comparison: 80 | 81 | .. table:: 82 | :widths: auto 83 | :align: center 84 | 85 | ============================================== ========= =========== 86 | \ ``write`` ``onbuild`` 87 | ============================================== ========= =========== 88 | Should affected file be under version control? **No** **Yes** 89 | Affected file must already exist? **No** **Yes** 90 | Modifies working tree? [#f1]_ **Yes** **No** 91 | Run when installing in editable mode? **Yes** **No** 92 | ============================================== ========= =========== 93 | 94 | .. [#f1] That is, the ``write`` method causes a file to be present (though 95 | likely ignored) in your repository after running, while the ``onbuild`` 96 | method only modifies a file inside sdists & wheels and leaves the 97 | original copy in your repository unchanged. 98 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "versioningit" 7 | dynamic = ["version"] 8 | description = "Versioning It with your Version In Git" 9 | readme = "README.rst" 10 | requires-python = ">=3.8" 11 | license = "MIT" 12 | license-files = ["LICENSE"] 13 | authors = [ 14 | { name = "John Thorvald Wodder II", email = "versioningit@varonathe.org" } 15 | ] 16 | 17 | keywords = [ 18 | "Git", 19 | "Mercurial", 20 | "VCS", 21 | "packaging", 22 | "version", 23 | ] 24 | 25 | classifiers = [ 26 | "Development Status :: 5 - Production/Stable", 27 | "Programming Language :: Python :: 3 :: Only", 28 | "Programming Language :: Python :: 3", 29 | "Programming Language :: Python :: 3.8", 30 | "Programming Language :: Python :: 3.9", 31 | "Programming Language :: Python :: 3.10", 32 | "Programming Language :: Python :: 3.11", 33 | "Programming Language :: Python :: 3.12", 34 | "Programming Language :: Python :: 3.13", 35 | "Programming Language :: Python :: Implementation :: CPython", 36 | "Programming Language :: Python :: Implementation :: PyPy", 37 | "Framework :: Hatch", 38 | "Framework :: Setuptools Plugin", 39 | "Intended Audience :: Developers", 40 | "Topic :: Software Development :: Build Tools", 41 | "Topic :: Software Development :: Version Control", 42 | "Topic :: Software Development :: Version Control :: Git", 43 | "Topic :: Software Development :: Version Control :: Mercurial", 44 | "Typing :: Typed", 45 | ] 46 | 47 | dependencies = [ 48 | "importlib-metadata >= 3.6; python_version < '3.10'", 49 | "packaging >= 17.1", 50 | "tomli >= 1.2, < 3.0; python_version < '3.11'", 51 | ] 52 | 53 | [project.scripts] 54 | versioningit = "versioningit.__main__:main" 55 | 56 | [project.entry-points."setuptools.finalize_distribution_options"] 57 | versioningit = "versioningit.hook:setuptools_finalizer" 58 | 59 | [project.entry-points."hatch"] 60 | versioningit = "versioningit.hatch" 61 | 62 | [project.entry-points."versioningit.vcs"] 63 | git = "versioningit.git:describe_git" 64 | git-archive = "versioningit.git:describe_git_archive" 65 | hg = "versioningit.hg:describe_hg" 66 | 67 | [project.entry-points."versioningit.tag2version"] 68 | basic = "versioningit.basics:basic_tag2version" 69 | 70 | [project.entry-points."versioningit.next_version"] 71 | minor = "versioningit.next_version:next_minor_version" 72 | minor-release = "versioningit.next_version:next_minor_release_version" 73 | smallest = "versioningit.next_version:next_smallest_version" 74 | smallest-release = "versioningit.next_version:next_smallest_release_version" 75 | null = "versioningit.next_version:null_next_version" 76 | 77 | [project.entry-points."versioningit.format"] 78 | basic = "versioningit.basics:basic_format" 79 | 80 | [project.entry-points."versioningit.template_fields"] 81 | basic = "versioningit.basics:basic_template_fields" 82 | 83 | [project.entry-points."versioningit.write"] 84 | basic = "versioningit.basics:basic_write" 85 | 86 | [project.entry-points."versioningit.onbuild"] 87 | replace-version = "versioningit.onbuild:replace_version_onbuild" 88 | 89 | [project.urls] 90 | "Source Code" = "https://github.com/jwodder/versioningit" 91 | "Bug Tracker" = "https://github.com/jwodder/versioningit/issues" 92 | "Documentation" = "https://versioningit.readthedocs.io" 93 | 94 | [tool.hatch.version] 95 | path = "src/versioningit/__init__.py" 96 | 97 | [tool.hatch.build.targets.sdist] 98 | include = [ 99 | "/docs", 100 | "/src", 101 | "/test", 102 | "CHANGELOG.*", 103 | "CONTRIBUTORS.*", 104 | "tox.ini", 105 | ] 106 | 107 | [tool.hatch.envs.default] 108 | python = "3" 109 | 110 | [tool.mypy] 111 | allow_incomplete_defs = false 112 | allow_untyped_defs = false 113 | ignore_missing_imports = false 114 | # : 115 | no_implicit_optional = true 116 | implicit_reexport = false 117 | local_partial_types = true 118 | pretty = true 119 | show_error_codes = true 120 | show_traceback = true 121 | strict_equality = true 122 | warn_redundant_casts = true 123 | warn_return_any = true 124 | warn_unreachable = true 125 | exclude = "test/data/" 126 | -------------------------------------------------------------------------------- /src/versioningit/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Versioning It with your Version In Git 3 | 4 | ``versioningit`` is yet another Python packaging plugin for automatically 5 | determining your package's version based on your version control repository's 6 | tags. Unlike others, it allows easy customization of the version format and 7 | even lets you easily override the separate functions used for version 8 | extraction & calculation. 9 | 10 | **Features:** 11 | 12 | - Works with both setuptools and Hatch_ 13 | 14 | .. _hatch: https://hatch.pypa.io 15 | 16 | - Installed & configured through :pep:`518`'s ``pyproject.toml`` (or, 17 | alternatively, through ``versioningit.toml`` for non-Python projects) 18 | 19 | - Supports Git, modern Git archives, and Mercurial 20 | 21 | - Formatting of the final version uses format template strings, with fields for 22 | basic VCS information and separate template strings for distanced vs. dirty 23 | vs. distanced-and-dirty repository states 24 | 25 | - Can optionally write the final version to a file for loading at runtime 26 | 27 | - Provides custom hooks for inserting the final version into a source file at 28 | build time 29 | 30 | - The individual methods for VCS querying, tag-to-version calculation, version 31 | bumping, version formatting, and writing the version to a file can all be 32 | customized using either functions defined alongside one's project code or via 33 | publicly-distributed entry points 34 | 35 | - Can alternatively be used as a library for use in ``setup.py`` or the like, 36 | in case you don't want to or can't configure it via ``pyproject.toml`` 37 | 38 | - The only thing it does is calculate your version and optionally write it to a 39 | file; there's no overriding of your sdist contents based on what's in your 40 | Git repository, especially not without a way to turn it off, because that 41 | would just be rude. 42 | 43 | Visit or 44 | for more information. 45 | """ 46 | 47 | __version__ = "3.3.0" 48 | __author__ = "John Thorvald Wodder II" 49 | __author_email__ = "versioningit@varonathe.org" 50 | __license__ = "MIT" 51 | __url__ = "https://github.com/jwodder/versioningit" 52 | 53 | from .core import ( 54 | FallbackReport, 55 | Report, 56 | VCSDescription, 57 | Versioningit, 58 | get_next_version, 59 | get_template_fields_from_distribution, 60 | get_version, 61 | get_version_from_pkg_info, 62 | run_onbuild, 63 | ) 64 | from .errors import ( 65 | ConfigError, 66 | Error, 67 | InvalidTagError, 68 | InvalidVersionError, 69 | MethodError, 70 | NoConfigFileError, 71 | NoConfigSectionError, 72 | NoTagError, 73 | NotSdistError, 74 | NotVCSError, 75 | NotVersioningitError, 76 | ) 77 | from .get_cmdclasses import get_cmdclasses 78 | from .onbuild import OnbuildFile, OnbuildFileProvider 79 | 80 | __all__ = [ 81 | "ConfigError", 82 | "Error", 83 | "FallbackReport", 84 | "InvalidTagError", 85 | "InvalidVersionError", 86 | "MethodError", 87 | "NoConfigFileError", 88 | "NoConfigSectionError", 89 | "NoTagError", 90 | "NotSdistError", 91 | "NotVCSError", 92 | "NotVersioningitError", 93 | "OnbuildFile", 94 | "OnbuildFileProvider", 95 | "Report", 96 | "VCSDescription", 97 | "Versioningit", 98 | "get_cmdclasses", 99 | "get_next_version", 100 | "get_template_fields_from_distribution", 101 | "get_version", 102 | "get_version_from_pkg_info", 103 | "run_onbuild", 104 | ] 105 | -------------------------------------------------------------------------------- /src/versioningit/__main__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import argparse 3 | import logging 4 | import os 5 | import subprocess 6 | import sys 7 | import traceback 8 | from typing import Optional 9 | from . import __version__ 10 | from .core import get_next_version, get_version 11 | from .errors import Error 12 | from .logging import get_env_loglevel, log 13 | from .util import showcmd 14 | 15 | 16 | def main(argv: Optional[list[str]] = None) -> None: 17 | parser = argparse.ArgumentParser( 18 | description="Show the version of a versioningit-enabled project" 19 | ) 20 | parser.add_argument( 21 | "-n", 22 | "--next-version", 23 | action="store_true", 24 | help="Show the next version after the current VCS tag", 25 | ) 26 | parser.add_argument( 27 | "--traceback", action="store_true", help="Show full traceback on library error" 28 | ) 29 | parser.add_argument( 30 | "-v", "--verbose", action="count", default=0, help="Show more log messages" 31 | ) 32 | parser.add_argument( 33 | "-w", "--write", action="store_true", help="Write version to configured file" 34 | ) 35 | parser.add_argument( 36 | "-V", "--version", action="version", version=f"%(prog)s {__version__}" 37 | ) 38 | parser.add_argument("project_dir", nargs="?", default=os.curdir) 39 | args = parser.parse_args(argv) 40 | env_loglevel = get_env_loglevel() 41 | if args.verbose == 0: 42 | if env_loglevel is None: 43 | log_level = logging.WARNING 44 | else: 45 | log_level = env_loglevel 46 | else: 47 | if args.verbose == 1: 48 | log_level = logging.INFO 49 | else: 50 | log_level = logging.DEBUG 51 | if env_loglevel is not None: 52 | log_level = min(log_level, env_loglevel) 53 | logging.basicConfig( 54 | format="[%(levelname)-8s] %(name)s: %(message)s", 55 | level=log_level, 56 | ) 57 | try: 58 | if args.next_version: 59 | print(get_next_version(args.project_dir)) 60 | else: 61 | print(get_version(args.project_dir, write=args.write, fallback=True)) 62 | except Error as e: 63 | if args.traceback: 64 | traceback.print_exc() 65 | else: 66 | print(f"versioningit: {type(e).__name__}: {e}", file=sys.stderr) 67 | sys.exit(1) 68 | except subprocess.CalledProcessError as e: 69 | if args.traceback: 70 | traceback.print_exc() 71 | else: 72 | if isinstance(e.cmd, list): 73 | cmd = showcmd(e.cmd) 74 | else: 75 | cmd = os.fsdecode(e.cmd) 76 | log.error("%s: command returned %d", cmd, e.returncode) 77 | sys.exit(e.returncode) 78 | 79 | 80 | if __name__ == "__main__": 81 | main() # pragma: no cover 82 | -------------------------------------------------------------------------------- /src/versioningit/basics.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from copy import deepcopy 3 | from pathlib import Path 4 | import re 5 | from typing import Any, Optional 6 | from packaging.version import Version 7 | from .core import VCSDescription 8 | from .errors import ConfigError, InvalidTagError 9 | from .logging import log, warn_extra_fields 10 | from .util import ( 11 | bool_guard, 12 | optional_str_guard, 13 | split_pep440_version, 14 | split_version, 15 | str_guard, 16 | strip_prefix, 17 | strip_suffix, 18 | ) 19 | 20 | #: The default formats for the ``"basic"`` ``format`` method 21 | DEFAULT_FORMATS = { 22 | "distance": "{version}.post{distance}+{vcs}{rev}", 23 | "dirty": "{version}+d{build_date:%Y%m%d}", 24 | "distance-dirty": "{version}.post{distance}+{vcs}{rev}.d{build_date:%Y%m%d}", 25 | } 26 | 27 | 28 | def basic_tag2version(*, tag: str, params: dict[str, Any]) -> str: 29 | """Implements the ``"basic"`` ``tag2version`` method""" 30 | params = params.copy() 31 | try: 32 | rmprefix = str_guard(params.pop("rmprefix"), "tag2version.rmprefix") 33 | except KeyError: 34 | pass 35 | else: 36 | tag = strip_prefix(tag, rmprefix) 37 | try: 38 | rmsuffix = str_guard(params.pop("rmsuffix"), "tag2version.rmsuffix") 39 | except KeyError: 40 | pass 41 | else: 42 | tag = strip_suffix(tag, rmsuffix) 43 | require_match = bool_guard( 44 | params.pop("require-match", False), 45 | "tag2version.require-match", 46 | ) 47 | try: 48 | regex = str_guard(params.pop("regex"), "tag2version.regex") 49 | except KeyError: 50 | pass 51 | else: 52 | m = re.search(regex, tag) 53 | if m is None: 54 | if require_match: 55 | raise InvalidTagError(f"tag2version.regex did not match tag {tag!r}") 56 | else: 57 | log.info( 58 | "tag2version.regex did not match tag %r; leaving unmodified", tag 59 | ) 60 | else: 61 | if "version" in m.groupdict(): 62 | tag = m["version"] 63 | else: 64 | tag = m[0] 65 | if tag is None: 66 | raise InvalidTagError( 67 | "'version' group in versioningit's tag2version.regex did" 68 | " not participate in match" 69 | ) 70 | warn_extra_fields( 71 | params, 72 | "tag2version", 73 | ["rmprefix", "rmsuffix", "regex", "require-match"], 74 | ) 75 | return tag.lstrip("v") 76 | 77 | 78 | def basic_format( 79 | *, 80 | description: VCSDescription, 81 | base_version: str, 82 | next_version: str, 83 | params: dict[str, Any], 84 | ) -> str: 85 | """Implements the ``"basic"`` ``format`` method""" 86 | branch: Optional[str] 87 | if description.branch is not None: 88 | branch = re.sub(r"[^A-Za-z0-9.]", ".", description.branch) 89 | else: 90 | branch = None 91 | fields = { 92 | **description.fields, 93 | "branch": branch, 94 | "version": base_version, 95 | "base_version": base_version, 96 | "next_version": next_version, 97 | } 98 | formats = {**DEFAULT_FORMATS, **params} 99 | try: 100 | fmt = formats[description.state] 101 | except KeyError: 102 | raise ConfigError( 103 | f"No format string for {description.state!r} state found in" 104 | " versioningit's format table" 105 | ) 106 | if not isinstance(fmt, str): 107 | raise ConfigError("versioningit: format.* values must be strings") 108 | return fmt.format_map(fields) 109 | 110 | 111 | def basic_write( 112 | *, 113 | project_dir: str | Path, 114 | template_fields: dict[str, Any], 115 | params: dict[str, Any], 116 | ) -> None: 117 | """Implements the ``"basic"`` ``write`` method""" 118 | params = params.copy() 119 | filename = str_guard(params.pop("file", None), "write.file") 120 | path = Path(project_dir, filename) 121 | encoding = str_guard(params.pop("encoding", "utf-8"), "write.encoding") 122 | try: 123 | template = str_guard(params.pop("template"), "write.template") 124 | except KeyError: 125 | if path.suffix == ".py": 126 | template = '__version__ = "{version}"' 127 | elif path.suffix == ".txt" or path.suffix == "": 128 | template = "{version}" 129 | else: 130 | raise ConfigError( 131 | "versioningit: write.template not specified and file has" 132 | f" unknown suffix {path.suffix!r}" 133 | ) 134 | warn_extra_fields(params, "write", ["file", "encoding", "template"]) 135 | log.debug("Ensuring parent directories of %s exist", path) 136 | path.parent.mkdir(parents=True, exist_ok=True) 137 | log.info("Writing version to file %s", path) 138 | path.write_text(template.format_map(template_fields) + "\n", encoding=encoding) 139 | 140 | 141 | def basic_template_fields( 142 | *, 143 | version: str, 144 | description: Optional[VCSDescription], 145 | base_version: Optional[str], 146 | next_version: Optional[str], 147 | params: dict[str, Any], 148 | ) -> dict[str, Any]: 149 | """Implements the ``"basic"`` ``template-fields`` method""" 150 | params = deepcopy(params) 151 | vtuple_params = params.pop("version-tuple", {}) 152 | SUBTABLE = "template-fields.version-tuple" 153 | if not isinstance(vtuple_params, dict): 154 | raise ConfigError(f"versioningit: {SUBTABLE} must be a table") 155 | pep440 = bool_guard(vtuple_params.pop("pep440", False), f"{SUBTABLE}.pep440") 156 | epoch: Optional[bool] 157 | try: 158 | epoch = bool_guard(vtuple_params.pop("epoch"), f"{SUBTABLE}.epoch") 159 | except KeyError: 160 | epoch = None 161 | else: 162 | if not pep440: 163 | log.warning( 164 | "versioningit's %s.epoch is ignored when pep440 is false", SUBTABLE 165 | ) 166 | split_on = optional_str_guard( 167 | vtuple_params.pop("split-on", None), f"{SUBTABLE}.split-on" 168 | ) 169 | if pep440 and split_on is not None: 170 | log.warning( 171 | "versioningit's %s.split-on is ignored when pep440 is true", SUBTABLE 172 | ) 173 | double_quote = bool_guard( 174 | vtuple_params.pop("double-quote", True), f"{SUBTABLE}.double-quote" 175 | ) 176 | warn_extra_fields( 177 | vtuple_params, SUBTABLE, ["pep440", "epoch", "split-on", "double-quote"] 178 | ) 179 | warn_extra_fields(params, "template-fields", ["version-tuple"]) 180 | if pep440: 181 | version_tuple = split_pep440_version( 182 | version, epoch=epoch, double_quote=double_quote 183 | ) 184 | else: 185 | version_tuple = split_version( 186 | version, split_on=split_on, double_quote=double_quote 187 | ) 188 | fields: dict[str, Any] = {} 189 | if description is not None: 190 | fields.update(description.fields) 191 | fields["branch"] = description.branch 192 | if base_version is not None: 193 | fields["base_version"] = base_version 194 | if next_version is not None: 195 | fields["next_version"] = next_version 196 | fields["version"] = version 197 | fields["version_tuple"] = version_tuple 198 | try: 199 | fields["normalized_version"] = str(Version(version)) 200 | except ValueError: 201 | fields["normalized_version"] = version 202 | return fields 203 | -------------------------------------------------------------------------------- /src/versioningit/cmdclass.py: -------------------------------------------------------------------------------- 1 | # Note: The classes in this module are not to be re-exported by `__init__.py`, 2 | # as that would mean unconditionally importing setuptools whenever versioningit 3 | # is imported, slowing things down. 4 | 5 | from .get_cmdclasses import get_cmdclasses 6 | 7 | _cmdclasses = get_cmdclasses() 8 | sdist = _cmdclasses["sdist"] 9 | build_py = _cmdclasses["build_py"] 10 | -------------------------------------------------------------------------------- /src/versioningit/errors.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | class Error(Exception): 5 | """Base class of all ``versioningit``-specific errors""" 6 | 7 | pass 8 | 9 | 10 | class ConfigError(Error, ValueError): 11 | """ 12 | Raised when the ``versioningit`` configuration contain invalid settings 13 | """ 14 | 15 | pass 16 | 17 | 18 | class MethodError(Error): 19 | """Raised when a method is invalid or returns an invalid value""" 20 | 21 | pass 22 | 23 | 24 | class NotVersioningitError(Error): 25 | """ 26 | Raised when ``versioningit`` is used on a project that does not have 27 | ``versioningit`` enabled 28 | """ 29 | 30 | pass 31 | 32 | 33 | class NoConfigFileError(NotVersioningitError): 34 | """ 35 | .. versionadded:: 3.2.0 36 | 37 | Raised when ``versioningit`` is used on a project that does not contain a 38 | :file:`versioningit.toml` or :file:`pyproject.toml` file 39 | """ 40 | 41 | def __init__(self, project_dir: Path) -> None: 42 | #: The path to the project directory 43 | self.project_dir: Path = project_dir 44 | 45 | def __str__(self) -> str: 46 | return f"No pyproject.toml or versioningit.toml file in {self.project_dir}" 47 | 48 | 49 | class NoConfigSectionError(NotVersioningitError): 50 | """ 51 | .. versionadded:: 3.2.0 52 | 53 | Raised when ``versioningit`` is used on a project whose 54 | :file:`versioningit.toml` or :file:`pyproject.toml` file does not contain a 55 | ``versioningit`` configuration table 56 | """ 57 | 58 | def __init__(self, config_path: Path) -> None: 59 | #: The path to the configuration file 60 | self.config_path: Path = config_path 61 | 62 | def __str__(self) -> str: 63 | return f"versioningit not configured in {self.config_path}" 64 | 65 | 66 | class NotSdistError(Error): 67 | """ 68 | Raised when attempting to read a :file:`PKG-INFO` file from a directory 69 | that doesn't have one 70 | """ 71 | 72 | pass 73 | 74 | 75 | class NotVCSError(Error): 76 | """ 77 | Raised when ``versioningit`` is run in a directory that is not under 78 | version control or when the relevant VCS program is not installed 79 | """ 80 | 81 | pass 82 | 83 | 84 | class NoTagError(Error): 85 | """Raised when a tag cannot be found in version control""" 86 | 87 | pass 88 | 89 | 90 | class InvalidTagError(Error, ValueError): 91 | """ 92 | Raised by ``tag2version`` methods when passed a tag that they cannot work 93 | with 94 | """ 95 | 96 | pass 97 | 98 | 99 | class InvalidVersionError(Error, ValueError): 100 | """ 101 | Raised by ``next-version`` and ``template-fields`` methods when passed a 102 | version that they cannot work with 103 | """ 104 | 105 | pass 106 | -------------------------------------------------------------------------------- /src/versioningit/get_cmdclasses.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from pathlib import Path 3 | from typing import TYPE_CHECKING, Any, Optional 4 | from .core import get_template_fields_from_distribution, run_onbuild 5 | from .logging import init_logging, log 6 | 7 | if TYPE_CHECKING: 8 | from setuptools import Command 9 | 10 | 11 | def get_cmdclasses( 12 | bases: Optional[dict[str, type[Command]]] = None 13 | ) -> dict[str, type[Command]]: 14 | """ 15 | .. versionadded:: 1.1.0 16 | 17 | Return a `dict` of custom setuptools `Command` classes, suitable for 18 | passing to the ``cmdclass`` argument of `setuptools.setup()`, that run the 19 | ``onbuild`` step for the project when building an sdist or wheel. 20 | Specifically, the `dict` contains a subclass of 21 | `setuptools.command.sdist.sdist` at the ``"sdist"`` key and a subclass of 22 | `setuptools.command.build_py.build_py` at the ``"build_py"`` key. 23 | 24 | A `dict` of alternative base classes can optionally be supplied; if the 25 | `dict` contains an ``"sdist"`` entry, that entry will be used as the base 26 | class for the customized ``sdist`` command, and likewise for 27 | ``"build_py"``. All other classes in the input `dict` are passed through 28 | unchanged. 29 | """ 30 | # Import setuptools here so there isn't a slowdown from importing it 31 | # unconditionally whenever versioningit is imported 32 | from setuptools.command.build_py import build_py 33 | from setuptools.command.sdist import sdist 34 | 35 | cmds = {} if bases is None else bases.copy() 36 | 37 | sdist_base = cmds.get("sdist", sdist) 38 | 39 | class VersioningitSdist(sdist_base): # type: ignore[valid-type,misc] 40 | def make_release_tree(self, base_dir: str, files: Any) -> None: 41 | super().make_release_tree(base_dir, files) 42 | init_logging() 43 | template_fields = get_template_fields_from_distribution(self.distribution) 44 | if template_fields is not None: 45 | PROJECT_ROOT = Path().resolve() 46 | log.debug("Running onbuild step; cwd=%s", PROJECT_ROOT) 47 | run_onbuild( 48 | project_dir=PROJECT_ROOT, 49 | build_dir=base_dir, 50 | is_source=True, 51 | template_fields=template_fields, 52 | ) 53 | else: 54 | log.debug( 55 | "Appear to be building from an sdist; not running onbuild step" 56 | ) 57 | 58 | cmds["sdist"] = VersioningitSdist 59 | 60 | build_py_base = cmds.get("build_py", build_py) 61 | 62 | class VersioningitBuildPy(build_py_base): # type: ignore[valid-type,misc] 63 | editable_mode: bool = False 64 | 65 | def run(self) -> None: 66 | super().run() 67 | init_logging() 68 | template_fields = get_template_fields_from_distribution(self.distribution) 69 | if not self.editable_mode and template_fields is not None: 70 | PROJECT_ROOT = Path().resolve() 71 | log.debug("Running onbuild step; cwd=%s", PROJECT_ROOT) 72 | run_onbuild( 73 | project_dir=PROJECT_ROOT, 74 | build_dir=self.build_lib, 75 | is_source=False, 76 | template_fields=template_fields, 77 | ) 78 | else: 79 | log.debug( 80 | "Appear to be building from an sdist; not running onbuild step" 81 | ) 82 | 83 | cmds["build_py"] = VersioningitBuildPy 84 | 85 | return cmds 86 | -------------------------------------------------------------------------------- /src/versioningit/hatch.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from dataclasses import fields 3 | from pathlib import Path 4 | import shutil 5 | import tempfile 6 | from typing import Any 7 | from hatchling.builders.hooks.plugin.interface import BuildHookInterface 8 | from hatchling.plugin import hookimpl 9 | from hatchling.version.source.plugin.interface import VersionSourceInterface 10 | from .config import Config 11 | from .core import Report, Versioningit 12 | from .errors import NoTagError, NotSdistError, NotVersioningitError 13 | from .logging import init_logging, log 14 | from .onbuild import HatchFileProvider 15 | 16 | 17 | class VersioningitSource(VersionSourceInterface): 18 | PLUGIN_NAME = "versioningit" 19 | 20 | def __init__(self, *args: Any, **kwargs: Any) -> None: 21 | super().__init__(*args, **kwargs) 22 | self.__fields: dict[str, Any] | None = None 23 | 24 | def get_version_data(self) -> dict: 25 | """ 26 | The entry point called by hatch to retrieve the version for a project 27 | """ 28 | init_logging() 29 | PROJECT_ROOT = Path(self.root) 30 | log.info("Project dir: %s", PROJECT_ROOT) 31 | try: 32 | vgit = Versioningit.from_project_dir(PROJECT_ROOT) 33 | report = vgit.run(write=True, fallback=True) 34 | except NotVersioningitError as e: # pragma: no cover 35 | raise RuntimeError(str(e)) 36 | except (NotSdistError, NoTagError) as e: 37 | raise RuntimeError( 38 | # If an error occurs in `get_version_data()`, hatchling throws 39 | # away its cause, so we need to include `str(e)` in the 40 | # RuntimeError for it to be seen. 41 | f"{type(e).__name__}: {e}\n" 42 | "\nversioningit could not find a version for the project in" 43 | f" {PROJECT_ROOT}!\n\n" 44 | "You may be installing from a shallow clone, in which case you" 45 | " need to unshallow it first.\n\n" 46 | "Alternatively, you may be installing from a Git archive, which is" 47 | " not supported by default. Install from a git+https://... URL" 48 | " instead.\n\n" 49 | ) 50 | if isinstance(report, Report): 51 | self.__fields = report.template_fields 52 | return {"version": report.version} 53 | 54 | def set_version( 55 | self, _version: str, _version_data: dict 56 | ) -> None: # pragma: no cover 57 | # Can't be tested due to hatch using isolated environments for `hatch 58 | # version` 59 | raise NotImplementedError( 60 | "The versioningit plugin does not support setting the version." 61 | " Create a tag instead." 62 | ) 63 | 64 | def get_template_fields(self) -> dict[str, Any] | None: 65 | return self.__fields 66 | 67 | 68 | class OnbuildHook(BuildHookInterface): 69 | PLUGIN_NAME = "versioningit-onbuild" 70 | 71 | def __init__(self, *args: Any, **kwargs: Any) -> None: 72 | super().__init__(*args, **kwargs) 73 | self.__tmpdir = Path(tempfile.mkdtemp()) 74 | 75 | def initialize(self, version: str, build_data: dict[str, Any]) -> None: 76 | init_logging() 77 | if self.target_name == "wheel" and version == "editable": 78 | log.debug("Not running onbuild step for editable build") 79 | return None 80 | version_source = self.metadata.hatch.version.source 81 | if not isinstance(version_source, VersioningitSource): 82 | raise RuntimeError( 83 | "versioningit-onbuild can only be used with 'versioningit'" 84 | " version source, but version source is" 85 | f" {self.metadata.hatch.version.source_name!r}" 86 | ) 87 | template_fields = version_source.get_template_fields() 88 | if template_fields is None: 89 | log.debug("Appear to be building from an sdist; not running onbuild step") 90 | return 91 | config = self.config.copy() 92 | for key in [ 93 | "dependencies", 94 | "require-runtime-dependencies", 95 | "require-runtime-features", 96 | ]: 97 | config.pop(key, None) 98 | (onbuild_field,) = [f for f in fields(Config) if f.name == "onbuild"] 99 | onbuild_section = Config.parse_section(onbuild_field, config) 100 | assert onbuild_section is not None 101 | onbuild_method = onbuild_section.load(self.root) 102 | file_provider = HatchFileProvider( 103 | src_dir=Path(self.root), 104 | tmp_dir=self.__tmpdir, 105 | ) 106 | onbuild_method( 107 | file_provider=file_provider, 108 | is_source=self.target_name == "sdist", 109 | template_fields=template_fields, 110 | ) 111 | build_data.setdefault("force_include", {}).update( 112 | file_provider.get_force_include() 113 | ) 114 | 115 | def finalize( 116 | self, _version: str, _build_data: dict[str, Any], _artifact_path: str 117 | ) -> None: 118 | shutil.rmtree(self.__tmpdir, ignore_errors=True) 119 | 120 | 121 | @hookimpl 122 | def hatch_register_version_source() -> type[VersionSourceInterface]: 123 | # This function must be named "hatch_register_version_source" exactly. 124 | return VersioningitSource 125 | 126 | 127 | @hookimpl 128 | def hatch_register_build_hook() -> type[BuildHookInterface]: 129 | return OnbuildHook 130 | -------------------------------------------------------------------------------- /src/versioningit/hg.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from dataclasses import dataclass 3 | import os 4 | from pathlib import Path 5 | import subprocess 6 | from typing import Any 7 | from .core import VCSDescription 8 | from .errors import NoTagError, NotVCSError 9 | from .logging import log, warn_extra_fields 10 | from .util import get_build_date, is_sdist, optional_str_guard, readcmd, runcmd 11 | 12 | 13 | @dataclass 14 | class HGRepo: 15 | """Methods for querying a Mercurial repository""" 16 | 17 | #: The repository's working tree or a subdirectory thereof 18 | path: str | Path 19 | 20 | def ensure_is_repo(self) -> None: 21 | """ 22 | Test whether `path` is under Mercurial revision control; if it is not 23 | (or if Mercurial is not installed), raise a `NotVCSError` 24 | """ 25 | try: 26 | runcmd( 27 | "hg", 28 | "--cwd", 29 | self.path, 30 | "files", 31 | ".", 32 | stdout=subprocess.DEVNULL, 33 | stderr=subprocess.DEVNULL, 34 | env={**os.environ, "HGPLAIN": "1"}, 35 | ) 36 | except FileNotFoundError: 37 | raise NotVCSError( 38 | "hg not installed; assuming this isn't a Mercurial repository" 39 | ) 40 | except subprocess.CalledProcessError: 41 | raise NotVCSError(f"{self.path} is not tracked by Mercurial") 42 | 43 | def read(self, *args: str, **kwargs: Any) -> str: 44 | """ 45 | Run a Mercurial command with the given arguments in `path` and return 46 | the stripped stdout 47 | """ 48 | return readcmd( 49 | "hg", 50 | "--cwd", 51 | self.path, 52 | *args, 53 | env={**os.environ, "HGPLAIN": "1"}, 54 | **kwargs, 55 | ) 56 | 57 | 58 | def describe_hg(*, project_dir: str | Path, params: dict[str, Any]) -> VCSDescription: 59 | """Implements the ``"hg"`` ``vcs`` method""" 60 | params = params.copy() 61 | pattern = optional_str_guard(params.pop("pattern", None), "vcs.pattern") 62 | default_tag = optional_str_guard(params.pop("default-tag", None), "vcs.default-tag") 63 | warn_extra_fields(params, "vcs", ["pattern", "default-tag"]) 64 | build_date = get_build_date() 65 | repo = HGRepo(project_dir) 66 | try: 67 | repo.ensure_is_repo() 68 | except NotVCSError: 69 | if not is_sdist(project_dir) and Path(project_dir, ".hg_archival.txt").exists(): 70 | log.info("%s is a Mercurial archive; parsing .hg_archival.txt", project_dir) 71 | data = parse_hg_archival(Path(project_dir, ".hg_archival.txt")) 72 | if "tag" in data: 73 | tag = data["tag"] 74 | distance = 0 75 | else: 76 | tag = data["latesttag"] 77 | distance = int(data["changessincelatesttag"]) 78 | branch = data["branch"] 79 | revision = data["node"] 80 | rev = revision[:12] 81 | else: 82 | raise 83 | else: 84 | # Use "{changes}" instead of "{distance}", as the former counts all 85 | # different commits across all parent paths (which is what `git 86 | # describe` does), while the latter is just the length of the longest 87 | # path. 88 | if pattern is None: 89 | template = "{latesttag() % '{tag}:{changes}:{node}\n'}" 90 | else: 91 | template = "{latesttag(" + repr(pattern) + ") % '{tag}:{changes}:{node}\n'}" 92 | tag, sdistance, revision = ( 93 | repo.read("log", "-r", ".", "--template", template) 94 | .splitlines()[0] 95 | .split(":") 96 | ) 97 | distance = int(sdistance) 98 | rev, _, branch = repo.read("id", "-i", "-b").partition(" ") 99 | if rev.endswith("+"): 100 | dirty = True 101 | rev = rev[:-1] 102 | else: 103 | dirty = False 104 | if tag == "null": 105 | if pattern is None: 106 | suffix = "" 107 | else: 108 | suffix = f" (pattern = {pattern!r})" 109 | # Unlike the Git methods, don't show the full `hg log` command run, as 110 | # shlex.quote() on `--template` arguments returns something *ugly*. 111 | if default_tag is not None: 112 | log.info( 113 | "No latest tag%s; falling back to default tag %r", suffix, default_tag 114 | ) 115 | tag = default_tag 116 | # Act as though the first commit is the one with the default tag, 117 | # i.e., don't count it (unless there is no first commit, of course) 118 | if distance > 0: 119 | distance -= 1 120 | else: 121 | raise NoTagError(f"No latest tag in Mercurial repository{suffix}") 122 | if distance and dirty: 123 | state = "distance-dirty" 124 | elif distance: 125 | state = "distance" 126 | elif dirty: 127 | state = "dirty" 128 | else: 129 | state = "exact" 130 | return VCSDescription( 131 | tag=tag, 132 | state=state, 133 | branch=branch, 134 | fields={ 135 | "distance": distance, 136 | "rev": rev, 137 | "revision": revision, 138 | "build_date": build_date, 139 | "vcs": "h", 140 | "vcs_name": "hg", 141 | }, 142 | ) 143 | 144 | 145 | def parse_hg_archival(path: Path) -> dict[str, str]: 146 | data: dict[str, str] = {} 147 | with path.open(encoding="utf-8") as fp: 148 | for line in fp: 149 | key, _, value = line.strip().partition(": ") 150 | data.setdefault(key, value) 151 | return data 152 | -------------------------------------------------------------------------------- /src/versioningit/hook.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from pathlib import Path 3 | from typing import TYPE_CHECKING 4 | from .core import Report, Versioningit 5 | from .errors import NoTagError, NotSdistError, NotVersioningitError 6 | from .logging import init_logging, log 7 | 8 | if TYPE_CHECKING: 9 | from setuptools import Distribution 10 | 11 | 12 | def setuptools_finalizer(dist: Distribution) -> None: 13 | """ 14 | The entry point called by setuptools to retrieve the version for a project 15 | """ 16 | init_logging() 17 | # PEP 517 says that "All hooks are run with working directory set to the 18 | # root of the source tree". 19 | PROJECT_ROOT = Path().resolve() 20 | log.info("Project dir: %s", PROJECT_ROOT) 21 | try: 22 | vgit = Versioningit.from_project_dir(PROJECT_ROOT) 23 | report = vgit.run(write=True, fallback=True) 24 | except NotVersioningitError as e: 25 | log.info(f"versioningit not enabled: {e}; doing nothing") 26 | return 27 | except (NotSdistError, NoTagError): 28 | raise RuntimeError( 29 | "\nversioningit could not find a version for the project in" 30 | f" {PROJECT_ROOT}!\n\n" 31 | "You may be installing from a shallow clone, in which case you" 32 | " need to unshallow it first.\n\n" 33 | "Alternatively, you may be installing from a Git archive, which is" 34 | " not supported by default. Install from a git+https://... URL" 35 | " instead.\n\n" 36 | ) 37 | dist.metadata.version = report.version 38 | if isinstance(report, Report): 39 | dist._versioningit_template_fields = ( # type: ignore[attr-defined] 40 | report.template_fields 41 | ) 42 | -------------------------------------------------------------------------------- /src/versioningit/logging.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from difflib import get_close_matches 3 | import logging 4 | import os 5 | from typing import Iterable, Optional 6 | from packaging.version import Version 7 | 8 | log = logging.getLogger("versioningit") 9 | 10 | 11 | def get_env_loglevel() -> Optional[int]: 12 | """ 13 | Return the logging level specified in the :envvar:`VERSIONINGIT_LOG_LEVEL` 14 | environment variable, if any 15 | """ 16 | try: 17 | return parse_log_level(os.environ["VERSIONINGIT_LOG_LEVEL"]) 18 | except (KeyError, ValueError): 19 | return None 20 | 21 | 22 | def init_logging(level: Optional[int] = None) -> None: 23 | """ 24 | Configure the `versioningit` logger and set its level to ``level``. If 25 | ``level`` is not specified, the value is taken from the 26 | :envvar:`VERSIONINGIT_LOG_LEVEL` environment variable, with a default of 27 | ``WARNING``. 28 | """ 29 | if log.handlers: 30 | return 31 | if level is None: 32 | level = get_env_loglevel() 33 | if level is None: 34 | level = logging.WARNING 35 | log.setLevel(level) 36 | handler = logging.StreamHandler() 37 | handler.setFormatter(logging.Formatter("[%(levelname)-8s] %(name)s: %(message)s")) 38 | log.addHandler(handler) 39 | log.propagate = False 40 | 41 | 42 | def parse_log_level(level: str) -> int: 43 | """ 44 | Convert a log level name (case-insensitive) or number to its numeric value 45 | """ 46 | try: 47 | return int(level) 48 | except ValueError: 49 | levelup = level.upper() 50 | if levelup in {"CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"}: 51 | ll = getattr(logging, levelup) 52 | assert isinstance(ll, int) 53 | return ll 54 | else: 55 | raise ValueError(f"Invalid log level: {level!r}") 56 | 57 | 58 | def warn_extra_fields( 59 | params: dict, fieldname: str | None, valid: Optional[list[str]] = None 60 | ) -> None: 61 | """ 62 | For each key in ``params``, emit a log message indicating that the given 63 | parameter is ignored, along with a "Did you mean?" message if ``valid`` is 64 | set and the key resembles any elements of ``valid``. ``fieldname`` is the 65 | name of the table in which ``params`` were found relative to the root of 66 | the versioningit config; `None` means that the params were found at the top 67 | of the config. 68 | """ 69 | if fieldname is None: 70 | where = "versioningit configuration" 71 | else: 72 | where = f"versioningit's {fieldname}" 73 | for p in params.keys(): 74 | suggestions = didyoumean(p, valid) if valid is not None else "" 75 | log.warning("Ignoring unknown parameter %r in %s%s", p, where, suggestions) 76 | 77 | 78 | def warn_bad_version(version: str, desc: str) -> None: 79 | """ 80 | If ``version`` is not :pep:`440`-compliant, log a warning. ``desc`` is a 81 | description of the version's provenance. 82 | """ 83 | try: 84 | Version(version) 85 | except ValueError: 86 | log.warning("%s %r is not PEP 440-compliant", desc, version) 87 | 88 | 89 | def didyoumean(mistake: str, valid: Iterable[str]) -> str: 90 | """ 91 | If ``mistake`` resembles any elements of ``valid``, return a "Did you 92 | mean?" message enclosed in parentheses and starting with a space. If 93 | ``mistake`` is not similar to any elements of ``valid``, return an empty 94 | string. 95 | """ 96 | candidates = get_close_matches(mistake, valid) 97 | if candidates: 98 | return " (Did you mean:" + "".join(f" {c}?" for c in candidates) + ")" 99 | else: 100 | return "" 101 | -------------------------------------------------------------------------------- /src/versioningit/methods.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from abc import ABC, abstractmethod 3 | from collections.abc import Callable 4 | from contextlib import suppress 5 | from dataclasses import dataclass 6 | from importlib import import_module 7 | import os.path 8 | from pathlib import Path 9 | import sys 10 | from typing import Any, Optional, cast 11 | from .errors import ConfigError, MethodError 12 | from .logging import didyoumean, log 13 | 14 | if sys.version_info[:2] >= (3, 10): 15 | from importlib.metadata import entry_points 16 | else: 17 | from importlib_metadata import entry_points 18 | 19 | 20 | # Call `entry_points()` only once and save the results for a speedup. See 21 | # . 22 | ENTRY_POINTS = entry_points() 23 | 24 | 25 | class MethodSpec(ABC): 26 | """ 27 | An abstract base class for method specifications parsed from `versioningit` 28 | configurations 29 | """ 30 | 31 | @abstractmethod 32 | def load(self, project_dir: str | Path) -> Callable: 33 | """ 34 | Load & return the callable specified by the `MethodSpec`. 35 | ``project_dir`` is provided in case the method needs to load anything 36 | from the project directory itself. 37 | """ 38 | ... 39 | 40 | 41 | @dataclass 42 | class EntryPointSpec(MethodSpec): 43 | """ 44 | A parsed method specification identifying a Python packaging entry point 45 | """ 46 | 47 | #: The name of the group in which to look up the entry point 48 | group: str 49 | 50 | #: The name of the entry point 51 | name: str 52 | 53 | def load(self, _project_dir: str | Path) -> Callable: 54 | """ 55 | Loads & returns the entry point 56 | 57 | :raises ConfigError: if no such entry point exists 58 | :raises MethodError: if the loaded entry point is not a callable 59 | """ 60 | log.debug("Loading entry point %r in group %s", self.name, self.group) 61 | eps = list(ENTRY_POINTS.select(group=self.group, name=self.name)) 62 | if len(eps) == 0: 63 | valid = [ep.name for ep in ENTRY_POINTS.select(group=self.group)] 64 | raise ConfigError( 65 | f"{self.group} entry point {self.name!r} not" 66 | f" found{didyoumean(self.name, valid)}" 67 | ) 68 | elif len(eps) > 1: 69 | raise ConfigError( 70 | "Packaging conflict! Multiple entry points named" 71 | f" {self.name!r} registered in group {self.group}" 72 | ) 73 | else: 74 | ep = eps[0] 75 | c = ep.load() 76 | if not callable(c): 77 | raise MethodError( 78 | f"{self.group} entry point {self.name!r} did not resolve to a" 79 | " callable object" 80 | ) 81 | return cast(Callable, c) 82 | 83 | 84 | @dataclass 85 | class CustomMethodSpec(MethodSpec): 86 | """ 87 | A parsed method specification identifying a callable in a local Python 88 | module 89 | """ 90 | 91 | #: The dotted name of the module containing the callable 92 | module: str 93 | 94 | #: The name of the callable object within the module 95 | value: str 96 | 97 | #: The directory in which the module is located; defaults to 98 | #: ``project_dir`` 99 | module_dir: Optional[str] 100 | 101 | def load(self, project_dir: str | Path) -> Callable: 102 | """ 103 | Loads the module and returns the callable 104 | 105 | :raises MethodError: if the object is not actually a callable 106 | """ 107 | if self.module_dir is not None: 108 | modpath = os.path.join(project_dir, self.module_dir) 109 | else: 110 | modpath = str(project_dir) 111 | log.debug("Prepending %s to sys.path", modpath) 112 | sys.path.insert(0, modpath) 113 | try: 114 | log.debug("Importing %s from %s", self.value, self.module) 115 | obj = import_module(self.module) 116 | finally: 117 | log.debug("Removing %s from sys.path", modpath) 118 | with suppress(ValueError): 119 | sys.path.remove(modpath) 120 | for attr in self.value.split("."): 121 | obj = getattr(obj, attr) 122 | if not callable(obj): 123 | raise MethodError( 124 | f"Custom method '{self.module}:{self.value}' did not resolve" 125 | " to a callable object" 126 | ) 127 | return cast(Callable, obj) 128 | 129 | 130 | @dataclass 131 | class CallableSpec(MethodSpec): 132 | """ 133 | A parsed method specification identifying a callable by the callable itself 134 | """ 135 | 136 | #: The callable 137 | func: Callable 138 | 139 | def load(self, _project_dir: str | Path) -> Callable: 140 | """Return the callable""" 141 | return self.func 142 | 143 | 144 | @dataclass 145 | class VersioningitMethod: 146 | """ 147 | A loaded `versioningit` method and the user-supplied parameters to pass to 148 | it 149 | """ 150 | 151 | #: The loaded method 152 | method: Callable 153 | 154 | #: User-supplied parameters obtained from the original configuration 155 | params: dict[str, Any] 156 | 157 | def __call__(self, **kwargs: Any) -> Any: 158 | """ 159 | Invokes the method with the given keyword arguments and the 160 | user-supplied parameters 161 | """ 162 | return self.method(params=self.params, **kwargs) 163 | -------------------------------------------------------------------------------- /src/versioningit/next_version.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from dataclasses import dataclass 3 | import re 4 | from typing import Any, Optional 5 | from packaging.version import Version 6 | from .errors import InvalidVersionError 7 | from .logging import warn_extra_fields 8 | 9 | 10 | @dataclass 11 | class BasicVersion: 12 | """A version consisting of just an optional epoch and a release segment""" 13 | 14 | #: The epoch (Zero equals a lack of an explicit epoch) 15 | epoch: int 16 | 17 | #: The integer values of the components of the release segment 18 | release: list[int] 19 | 20 | @classmethod 21 | def parse(cls, version: str) -> BasicVersion: 22 | """ 23 | Parse the initial epoch and release segment from a version string and 24 | discard any other trailing characters 25 | 26 | :raises InvalidVersionError: if ``version`` cannot be parsed 27 | """ 28 | m = re.match( 29 | r"v?(?:(?P[0-9]+)!)?(?P[0-9]+(?:\.[0-9]+)*)(?!!)", version 30 | ) 31 | if not m: 32 | raise InvalidVersionError(f"Cannot parse version {version!r}") 33 | sepoch = m["epoch"] 34 | if sepoch is None: 35 | epoch = 0 # type: ignore[unreachable] 36 | else: 37 | assert isinstance(sepoch, str) 38 | epoch = int(sepoch) 39 | release = m["release"] 40 | assert isinstance(release, str) 41 | return cls(epoch, list(map(int, release.split(".")))) 42 | 43 | def __str__(self) -> str: 44 | """Convert the `BasicVersion` to a string""" 45 | s = "" 46 | if self.epoch > 0: 47 | s += f"{self.epoch}!" 48 | s += ".".join(map(str, self.release)) 49 | return s 50 | 51 | 52 | def next_minor_version( 53 | *, 54 | version: str, 55 | branch: Optional[str], # noqa: U100 56 | params: dict[str, Any], 57 | ) -> str: 58 | """Implements the ``"minor"`` ``next-version`` method""" 59 | warn_extra_fields(params, "next-version") 60 | bv = BasicVersion.parse(version) 61 | bv.release = (bv.release + [0, 0])[:2] 62 | bv.release[1] += 1 63 | bv.release.append(0) 64 | return str(bv) 65 | 66 | 67 | def next_smallest_version( 68 | *, 69 | version: str, 70 | branch: Optional[str], # noqa: U100 71 | params: dict[str, Any], 72 | ) -> str: 73 | """Implements the ``"smallest"`` ``next-version`` method""" 74 | warn_extra_fields(params, "next-version") 75 | bv = BasicVersion.parse(version) 76 | bv.release[-1] += 1 77 | return str(bv) 78 | 79 | 80 | def null_next_version( 81 | *, 82 | version: str, 83 | branch: Optional[str], # noqa: U100 84 | params: dict[str, Any], 85 | ) -> str: 86 | """Implements the ``"null"`` ``next-version`` method""" 87 | warn_extra_fields(params, "next-version") 88 | return version 89 | 90 | 91 | def next_minor_release_version( 92 | *, 93 | version: str, 94 | branch: Optional[str], # noqa: U100 95 | params: dict[str, Any], 96 | ) -> str: 97 | """ 98 | Implements the ``"minor-release"`` ``next-version`` method. 99 | 100 | If ``version`` is a prerelease version, returns the base version. 101 | Otherwise, returns the next minor version after the base version. 102 | """ 103 | warn_extra_fields(params, "next-version") 104 | try: 105 | v = Version(version) 106 | except ValueError: 107 | raise InvalidVersionError(f"Cannot parse version {version!r}") 108 | if v.is_prerelease: 109 | return str(v.base_version) 110 | vs = list(v.release) + [0, 0] 111 | vs[1] += 1 112 | vs[2:] = [0] 113 | s = ".".join(map(str, vs)) 114 | if v.epoch: 115 | s = f"{v.epoch}!{s}" 116 | return s 117 | 118 | 119 | def next_smallest_release_version( 120 | *, 121 | version: str, 122 | branch: Optional[str], # noqa: U100 123 | params: dict[str, Any], 124 | ) -> str: 125 | """ 126 | Implements the ``"smallest-release"`` ``next-version`` method. 127 | 128 | If ``version`` is a prerelease version, returns the base version. 129 | Otherwise, returns the next smallest version after the base version. 130 | """ 131 | warn_extra_fields(params, "next-version") 132 | try: 133 | v = Version(version) 134 | except ValueError: 135 | raise InvalidVersionError(f"Cannot parse version {version!r}") 136 | if v.is_prerelease: 137 | return str(v.base_version) 138 | vs = list(v.release) 139 | vs[-1] += 1 140 | s = ".".join(map(str, vs)) 141 | if v.epoch: 142 | s = f"{v.epoch}!{s}" 143 | return s 144 | -------------------------------------------------------------------------------- /src/versioningit/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/src/versioningit/py.typed -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import logging 3 | import pytest 4 | 5 | 6 | def pytest_addoption(parser: pytest.Parser) -> None: 7 | parser.addoption( 8 | "--oldsetup", 9 | action="store_true", 10 | default=False, 11 | help="Run tests that require older setuptools", 12 | ) 13 | 14 | 15 | def pytest_collection_modifyitems( 16 | config: pytest.Config, items: list[pytest.Item] 17 | ) -> None: 18 | if not config.getoption("--oldsetup"): 19 | skipper = pytest.mark.skip(reason="Only run when --oldsetup is given") 20 | for item in items: 21 | if "oldsetup" in item.keywords: 22 | item.add_marker(skipper) 23 | 24 | 25 | @pytest.fixture(autouse=True) 26 | def capture_all_logs(caplog: pytest.LogCaptureFixture) -> None: 27 | caplog.set_level(logging.DEBUG, logger="versioningit") 28 | 29 | 30 | @pytest.fixture(autouse=True) 31 | def source_date_epoch(monkeypatch: pytest.MonkeyPatch) -> None: 32 | # 2038-01-19T03:14:07Z 33 | monkeypatch.setenv("SOURCE_DATE_EPOCH", "2147483647") 34 | -------------------------------------------------------------------------------- /test/data/config-error/bad-method.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit.vcs] 2 | method = true 3 | -------------------------------------------------------------------------------- /test/data/config-error/bad-method.txt: -------------------------------------------------------------------------------- 1 | versioningit: vcs.method must be a string or table 2 | -------------------------------------------------------------------------------- /test/data/config-error/bad-module-dir.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit.vcs] 2 | method = { module = "mymodule", value = "myfunc", module-dir = ["tools", "ving"] } 3 | -------------------------------------------------------------------------------- /test/data/config-error/bad-module-dir.txt: -------------------------------------------------------------------------------- 1 | versioningit: vcs.method.module-dir must be a string 2 | -------------------------------------------------------------------------------- /test/data/config-error/bad-module.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit.vcs] 2 | method = { module = ["mypackage", "mymodule"], value = "mine" } 3 | -------------------------------------------------------------------------------- /test/data/config-error/bad-module.txt: -------------------------------------------------------------------------------- 1 | versioningit: vcs.method.module is required and must be a string 2 | -------------------------------------------------------------------------------- /test/data/config-error/bad-section.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit] 2 | vcs = true 3 | -------------------------------------------------------------------------------- /test/data/config-error/bad-section.txt: -------------------------------------------------------------------------------- 1 | versioningit: vcs must be a string or table 2 | -------------------------------------------------------------------------------- /test/data/config-error/bad-value.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit.vcs] 2 | method = { module = "mymodule", value = ["myclass", "mystatic"] } 3 | -------------------------------------------------------------------------------- /test/data/config-error/bad-value.txt: -------------------------------------------------------------------------------- 1 | versioningit: vcs.method.value is required and must be a string 2 | -------------------------------------------------------------------------------- /test/data/config-error/bad-ving.toml: -------------------------------------------------------------------------------- 1 | [tool] 2 | versioningit = "git" 3 | -------------------------------------------------------------------------------- /test/data/config-error/bad-ving.txt: -------------------------------------------------------------------------------- 1 | versioningit config must be a table 2 | -------------------------------------------------------------------------------- /test/data/config-error/no-module.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit.vcs] 2 | method = { value = "mine" } 3 | -------------------------------------------------------------------------------- /test/data/config-error/no-module.txt: -------------------------------------------------------------------------------- 1 | versioningit: vcs.method.module is required and must be a string 2 | -------------------------------------------------------------------------------- /test/data/config-error/no-value.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit.vcs] 2 | method = { module = "mymodule" } 3 | -------------------------------------------------------------------------------- /test/data/config-error/no-value.txt: -------------------------------------------------------------------------------- 1 | versioningit: vcs.method.value is required and must be a string 2 | -------------------------------------------------------------------------------- /test/data/config-error/no-ving.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools >= 46.4.0", 4 | "wheel ~= 0.32" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /test/data/config-error/no-ving.txt: -------------------------------------------------------------------------------- 1 | versioningit not configured in {tomlfile} 2 | -------------------------------------------------------------------------------- /test/data/config-error/other-hatch.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling", "versioningit"] 3 | build-backend = "hatchling.build" 4 | 5 | [tool.hatch.version] 6 | source = "not-versioningit" 7 | -------------------------------------------------------------------------------- /test/data/config-error/other-hatch.txt: -------------------------------------------------------------------------------- 1 | versioningit not configured in {tomlfile} 2 | -------------------------------------------------------------------------------- /test/data/config/custom-methods.py: -------------------------------------------------------------------------------- 1 | from versioningit.config import Config, ConfigSection 2 | from versioningit.methods import CustomMethodSpec, EntryPointSpec 3 | 4 | cfg = Config( 5 | vcs=ConfigSection( 6 | method_spec=CustomMethodSpec( 7 | module="mypackage.mymodule", value="myvcs", module_dir=None 8 | ), 9 | params={"tag-dir": "tags"}, 10 | ), 11 | tag2version=ConfigSection( 12 | method_spec=CustomMethodSpec( 13 | module="mypackage.mytags", value="custom", module_dir="src" 14 | ), 15 | params={"style": "acme"}, 16 | ), 17 | next_version=ConfigSection( 18 | method_spec=EntryPointSpec(group="versioningit.next_version", name="minor"), 19 | params={}, 20 | ), 21 | format=ConfigSection( 22 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 23 | params={}, 24 | ), 25 | template_fields=ConfigSection( 26 | method_spec=EntryPointSpec(group="versioningit.template_fields", name="basic"), 27 | params={}, 28 | ), 29 | write=None, 30 | onbuild=None, 31 | ) 32 | -------------------------------------------------------------------------------- /test/data/config/custom-methods.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit.vcs] 2 | method = { module = "mypackage.mymodule", value = "myvcs" } 3 | tag-dir = "tags" 4 | 5 | [tool.versioningit.tag2version] 6 | method = { module = "mypackage.mytags", value = "custom", module-dir = "src" } 7 | style = "acme" 8 | -------------------------------------------------------------------------------- /test/data/config/custom-paramless.py: -------------------------------------------------------------------------------- 1 | from versioningit.config import Config, ConfigSection 2 | from versioningit.methods import CustomMethodSpec, EntryPointSpec 3 | 4 | cfg = Config( 5 | vcs=ConfigSection( 6 | method_spec=CustomMethodSpec( 7 | module="mypackage.mymodule", value="myvcs", module_dir=None 8 | ), 9 | params={}, 10 | ), 11 | tag2version=ConfigSection( 12 | method_spec=CustomMethodSpec( 13 | module="mypackage.mytags", value="custom", module_dir="src" 14 | ), 15 | params={}, 16 | ), 17 | next_version=ConfigSection( 18 | method_spec=EntryPointSpec(group="versioningit.next_version", name="minor"), 19 | params={}, 20 | ), 21 | format=ConfigSection( 22 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 23 | params={}, 24 | ), 25 | template_fields=ConfigSection( 26 | method_spec=EntryPointSpec(group="versioningit.template_fields", name="basic"), 27 | params={}, 28 | ), 29 | write=None, 30 | onbuild=None, 31 | ) 32 | -------------------------------------------------------------------------------- /test/data/config/custom-paramless.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit] 2 | vcs = { module = "mypackage.mymodule", value = "myvcs" } 3 | tag2version = { module = "mypackage.mytags", value = "custom", module-dir = "src" } 4 | -------------------------------------------------------------------------------- /test/data/config/empty.py: -------------------------------------------------------------------------------- 1 | from versioningit.config import Config, ConfigSection 2 | from versioningit.methods import EntryPointSpec 3 | 4 | cfg = Config( 5 | vcs=ConfigSection( 6 | method_spec=EntryPointSpec(group="versioningit.vcs", name="git"), 7 | params={}, 8 | ), 9 | tag2version=ConfigSection( 10 | method_spec=EntryPointSpec(group="versioningit.tag2version", name="basic"), 11 | params={}, 12 | ), 13 | next_version=ConfigSection( 14 | method_spec=EntryPointSpec(group="versioningit.next_version", name="minor"), 15 | params={}, 16 | ), 17 | format=ConfigSection( 18 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 19 | params={}, 20 | ), 21 | template_fields=ConfigSection( 22 | method_spec=EntryPointSpec(group="versioningit.template_fields", name="basic"), 23 | params={}, 24 | ), 25 | write=None, 26 | onbuild=None, 27 | ) 28 | -------------------------------------------------------------------------------- /test/data/config/empty.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit] 2 | -------------------------------------------------------------------------------- /test/data/config/hatch-and-std.py: -------------------------------------------------------------------------------- 1 | from versioningit.config import Config, ConfigSection 2 | from versioningit.methods import EntryPointSpec 3 | 4 | cfg = Config( 5 | vcs=ConfigSection( 6 | method_spec=EntryPointSpec(group="versioningit.vcs", name="git"), 7 | params={}, 8 | ), 9 | tag2version=ConfigSection( 10 | method_spec=EntryPointSpec(group="versioningit.tag2version", name="basic"), 11 | params={}, 12 | ), 13 | next_version=ConfigSection( 14 | method_spec=EntryPointSpec(group="versioningit.next_version", name="minor"), 15 | params={}, 16 | ), 17 | format=ConfigSection( 18 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 19 | params={ 20 | "distance": "{next_version}.dev{distance}+{vcs}{rev}", 21 | "dirty": "{version}+dirty", 22 | "distance-dirty": "{next_version}.dev{distance}+{vcs}{rev}.dirty", 23 | }, 24 | ), 25 | template_fields=ConfigSection( 26 | method_spec=EntryPointSpec(group="versioningit.template_fields", name="basic"), 27 | params={}, 28 | ), 29 | write=None, 30 | onbuild=None, 31 | ) 32 | -------------------------------------------------------------------------------- /test/data/config/hatch-and-std.toml: -------------------------------------------------------------------------------- 1 | [tool.hatch.version] 2 | source = "versioningit" 3 | 4 | [tool.hatch.version.format] 5 | distance = "{next_version}.dev{distance}+{vcs}{rev}" 6 | dirty = "{version}+dirty" 7 | distance-dirty = "{next_version}.dev{distance}+{vcs}{rev}.dirty" 8 | 9 | [tool.versioningit.format] 10 | distance = "{next_version}~{vcs}{rev}" 11 | dirty = "{version}~dirty" 12 | distance-dirty = "{next_version}~{vcs}{rev}.dirty" 13 | -------------------------------------------------------------------------------- /test/data/config/hatch.py: -------------------------------------------------------------------------------- 1 | from versioningit.config import Config, ConfigSection 2 | from versioningit.methods import EntryPointSpec 3 | 4 | cfg = Config( 5 | vcs=ConfigSection( 6 | method_spec=EntryPointSpec(group="versioningit.vcs", name="git"), 7 | params={}, 8 | ), 9 | tag2version=ConfigSection( 10 | method_spec=EntryPointSpec(group="versioningit.tag2version", name="basic"), 11 | params={}, 12 | ), 13 | next_version=ConfigSection( 14 | method_spec=EntryPointSpec(group="versioningit.next_version", name="minor"), 15 | params={}, 16 | ), 17 | format=ConfigSection( 18 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 19 | params={ 20 | "distance": "{next_version}.dev{distance}+{vcs}{rev}", 21 | "dirty": "{version}+dirty", 22 | "distance-dirty": "{next_version}.dev{distance}+{vcs}{rev}.dirty", 23 | }, 24 | ), 25 | template_fields=ConfigSection( 26 | method_spec=EntryPointSpec(group="versioningit.template_fields", name="basic"), 27 | params={}, 28 | ), 29 | write=None, 30 | onbuild=None, 31 | ) 32 | -------------------------------------------------------------------------------- /test/data/config/hatch.toml: -------------------------------------------------------------------------------- 1 | [tool.hatch.version] 2 | source = "versioningit" 3 | 4 | [tool.hatch.version.format] 5 | distance = "{next_version}.dev{distance}+{vcs}{rev}" 6 | dirty = "{version}+dirty" 7 | distance-dirty = "{next_version}.dev{distance}+{vcs}{rev}.dirty" 8 | -------------------------------------------------------------------------------- /test/data/config/other-hatch-and-std.py: -------------------------------------------------------------------------------- 1 | from versioningit.config import Config, ConfigSection 2 | from versioningit.methods import EntryPointSpec 3 | 4 | cfg = Config( 5 | vcs=ConfigSection( 6 | method_spec=EntryPointSpec(group="versioningit.vcs", name="git"), 7 | params={}, 8 | ), 9 | tag2version=ConfigSection( 10 | method_spec=EntryPointSpec(group="versioningit.tag2version", name="basic"), 11 | params={}, 12 | ), 13 | next_version=ConfigSection( 14 | method_spec=EntryPointSpec(group="versioningit.next_version", name="minor"), 15 | params={}, 16 | ), 17 | format=ConfigSection( 18 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 19 | params={ 20 | "distance": "{next_version}~{vcs}{rev}", 21 | "dirty": "{version}~dirty", 22 | "distance-dirty": "{next_version}~{vcs}{rev}.dirty", 23 | }, 24 | ), 25 | template_fields=ConfigSection( 26 | method_spec=EntryPointSpec(group="versioningit.template_fields", name="basic"), 27 | params={}, 28 | ), 29 | write=None, 30 | onbuild=None, 31 | ) 32 | -------------------------------------------------------------------------------- /test/data/config/other-hatch-and-std.toml: -------------------------------------------------------------------------------- 1 | [tool.hatch.version] 2 | source = "not-versioningit" 3 | 4 | [tool.hatch.version.format] 5 | distance = "{next_version}.dev{distance}+{vcs}{rev}" 6 | dirty = "{version}+dirty" 7 | distance-dirty = "{next_version}.dev{distance}+{vcs}{rev}.dirty" 8 | 9 | [tool.versioningit.format] 10 | distance = "{next_version}~{vcs}{rev}" 11 | dirty = "{version}~dirty" 12 | distance-dirty = "{next_version}~{vcs}{rev}.dirty" 13 | -------------------------------------------------------------------------------- /test/data/config/paramless.py: -------------------------------------------------------------------------------- 1 | from versioningit.config import Config, ConfigSection 2 | from versioningit.methods import EntryPointSpec 3 | 4 | cfg = Config( 5 | vcs=ConfigSection( 6 | method_spec=EntryPointSpec(group="versioningit.vcs", name="svn"), 7 | params={}, 8 | ), 9 | tag2version=ConfigSection( 10 | method_spec=EntryPointSpec(group="versioningit.tag2version", name="acme"), 11 | params={"style": "good"}, 12 | ), 13 | next_version=ConfigSection( 14 | method_spec=EntryPointSpec(group="versioningit.next_version", name="smallest"), 15 | params={}, 16 | ), 17 | format=ConfigSection( 18 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 19 | params={}, 20 | ), 21 | template_fields=ConfigSection( 22 | method_spec=EntryPointSpec(group="versioningit.template_fields", name="basic"), 23 | params={}, 24 | ), 25 | write=None, 26 | onbuild=None, 27 | ) 28 | -------------------------------------------------------------------------------- /test/data/config/paramless.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit] 2 | vcs = "svn" 3 | next-version = "smallest" 4 | 5 | [tool.versioningit.tag2version] 6 | method = "acme" 7 | style = "good" 8 | -------------------------------------------------------------------------------- /test/data/config/params.py: -------------------------------------------------------------------------------- 1 | from versioningit.config import Config, ConfigSection 2 | from versioningit.methods import EntryPointSpec 3 | 4 | cfg = Config( 5 | vcs=ConfigSection( 6 | method_spec=EntryPointSpec(group="versioningit.vcs", name="git"), 7 | params={ 8 | "match": ["v*", "package-*"], 9 | "exclude": ["*-alpha"], 10 | "default-tag": "package-0.0.0", 11 | }, 12 | ), 13 | tag2version=ConfigSection( 14 | method_spec=EntryPointSpec(group="versioningit.tag2version", name="basic"), 15 | params={"rmprefix": "package-"}, 16 | ), 17 | next_version=ConfigSection( 18 | method_spec=EntryPointSpec(group="versioningit.next_version", name="smallest"), 19 | params={}, 20 | ), 21 | format=ConfigSection( 22 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 23 | params={ 24 | "distance": "{base_version}.post{distance}+{vcs}{rev}", 25 | "dirty": "{base_version}+dirty.{build_date:%Y%m%d}", 26 | "distance-dirty": "{base_version}.post{distance}+{vcs}{rev}.dirty.{build_date:%Y%m%d}", 27 | }, 28 | ), 29 | template_fields=ConfigSection( 30 | method_spec=EntryPointSpec(group="versioningit.template_fields", name="basic"), 31 | params={ 32 | "version-tuple": { 33 | "pep440": True, 34 | "epoch": True, 35 | "split-on": r'\.', 36 | "double-quotes": False, 37 | }, 38 | }, 39 | ), 40 | write=ConfigSection( 41 | method_spec=EntryPointSpec(group="versioningit.write", name="basic"), 42 | params={ 43 | "file": "src/package/_version.py", 44 | "encoding": "utf-8", 45 | "template": "VERSION = {version!r}", 46 | }, 47 | ), 48 | onbuild=ConfigSection( 49 | method_spec=EntryPointSpec( 50 | group="versioningit.onbuild", name="replace-version" 51 | ), 52 | params={ 53 | "source-file": "src/package/__init__.py", 54 | "build-file": "package/__init__.py", 55 | "encoding": "iso-8859-1", 56 | "regex": "'NOT_SET'", 57 | "require-match": True, 58 | "replacement": "'{version}'", 59 | "append-line": "__version__ = '{version}'", 60 | }, 61 | ), 62 | ) 63 | -------------------------------------------------------------------------------- /test/data/config/params.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit.vcs] 2 | method = "git" 3 | match = ["v*", "package-*"] 4 | exclude = ["*-alpha"] 5 | default-tag = "package-0.0.0" 6 | 7 | [tool.versioningit.tag2version] 8 | method = "basic" 9 | rmprefix = "package-" 10 | 11 | [tool.versioningit.next-version] 12 | method = "smallest" 13 | 14 | [tool.versioningit.format] 15 | distance = "{base_version}.post{distance}+{vcs}{rev}" 16 | dirty = "{base_version}+dirty.{build_date:%Y%m%d}" 17 | distance-dirty = "{base_version}.post{distance}+{vcs}{rev}.dirty.{build_date:%Y%m%d}" 18 | 19 | [tool.versioningit.template-fields.version-tuple] 20 | pep440 = true 21 | epoch = true 22 | split-on = '\.' 23 | double-quotes = false 24 | 25 | [tool.versioningit.write] 26 | file = "src/package/_version.py" 27 | encoding = "utf-8" 28 | template = "VERSION = {version!r}" 29 | 30 | [tool.versioningit.onbuild] 31 | source-file = "src/package/__init__.py" 32 | build-file = "package/__init__.py" 33 | encoding = "iso-8859-1" 34 | regex = "'NOT_SET'" 35 | require-match = true 36 | replacement = "'{version}'" 37 | append-line = "__version__ = '{version}'" 38 | -------------------------------------------------------------------------------- /test/data/config/step-args.py: -------------------------------------------------------------------------------- 1 | from versioningit.config import Config, ConfigSection 2 | from versioningit.methods import EntryPointSpec 3 | 4 | cfg = Config( 5 | vcs=ConfigSection( 6 | method_spec=EntryPointSpec(group="versioningit.vcs", name="git"), 7 | params={ 8 | "match": ["v*", "package-*"], 9 | "exclude": ["*-alpha"], 10 | "default-tag": "package-0.0.0", 11 | "project_dir": "/usr/src/project", 12 | }, 13 | ), 14 | tag2version=ConfigSection( 15 | method_spec=EntryPointSpec(group="versioningit.tag2version", name="basic"), 16 | params={"rmprefix": "package-", "tag": "v1.2.3"}, 17 | ), 18 | next_version=ConfigSection( 19 | method_spec=EntryPointSpec(group="versioningit.next_version", name="smallest"), 20 | params={"version": "1.3.0", "branch": "master"}, 21 | ), 22 | format=ConfigSection( 23 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 24 | params={ 25 | "distance": "{base_version}.post{distance}+{vcs}{rev}", 26 | "dirty": "{base_version}+dirty.{build_date:%Y%m%d}", 27 | "distance-dirty": "{base_version}.post{distance}+{vcs}{rev}.dirty.{build_date:%Y%m%d}", 28 | "description": "This will be discarded", 29 | "version": "1.2.3", 30 | "next_version": "1.3.0", 31 | }, 32 | ), 33 | template_fields=ConfigSection( 34 | method_spec=EntryPointSpec(group="versioningit.template_fields", name="basic"), 35 | params={ 36 | "version-tuple": { "pep440": True, "epoch": True, "split-on": r'\.', "double-quotes": False }, 37 | "params": { "foo": "bar" }, 38 | "version": "1.2.3.post1", 39 | "description": "Cool", 40 | "base_version": "1.2.3", 41 | "next_version": "1.3.0", 42 | }, 43 | ), 44 | write=ConfigSection( 45 | method_spec=EntryPointSpec(group="versioningit.write", name="basic"), 46 | params={ 47 | "file": "src/package/_version.py", 48 | "encoding": "utf-8", 49 | "template": "VERSION = {version!r}", 50 | "project_dir": "/usr/src/project", 51 | "fields": {"version": "1.3.0", "version_tuple": "(1, 3, 0)"}, 52 | }, 53 | ), 54 | onbuild=ConfigSection( 55 | method_spec=EntryPointSpec( 56 | group="versioningit.onbuild", name="replace-version" 57 | ), 58 | params={ 59 | "source-file": "src/package/__init__.py", 60 | "build-file": "package/__init__.py", 61 | "build_dir": "/tmp/build", 62 | "is_source": True, 63 | "fields": {"version": "1.2.3", "version_tuple": "(1, 2, 3)"}, 64 | }, 65 | ), 66 | ) 67 | -------------------------------------------------------------------------------- /test/data/config/step-args.toml: -------------------------------------------------------------------------------- 1 | [tool.versioningit.vcs] 2 | method = "git" 3 | match = ["v*", "package-*"] 4 | exclude = ["*-alpha"] 5 | default-tag = "package-0.0.0" 6 | project_dir = "/usr/src/project" 7 | 8 | [tool.versioningit.tag2version] 9 | method = "basic" 10 | rmprefix = "package-" 11 | tag = "v1.2.3" 12 | 13 | [tool.versioningit.next-version] 14 | method = "smallest" 15 | version = "1.3.0" 16 | branch = "master" 17 | 18 | [tool.versioningit.format] 19 | distance = "{base_version}.post{distance}+{vcs}{rev}" 20 | dirty = "{base_version}+dirty.{build_date:%Y%m%d}" 21 | distance-dirty = "{base_version}.post{distance}+{vcs}{rev}.dirty.{build_date:%Y%m%d}" 22 | description = "This will be discarded" 23 | version = "1.2.3" 24 | next_version = "1.3.0" 25 | 26 | [tool.versioningit.template-fields] 27 | version-tuple = { pep440 = true, epoch = true, split-on = '\.', double-quotes = false } 28 | params = { foo = "bar" } 29 | version = "1.2.3.post1" 30 | description = "Cool" 31 | base_version = "1.2.3" 32 | next_version = "1.3.0" 33 | 34 | [tool.versioningit.write] 35 | file = "src/package/_version.py" 36 | encoding = "utf-8" 37 | template = "VERSION = {version!r}" 38 | project_dir = "/usr/src/project" 39 | fields = { version = "1.3.0", version_tuple = "(1, 3, 0)" } 40 | 41 | [tool.versioningit.onbuild] 42 | source-file = "src/package/__init__.py" 43 | build-file = "package/__init__.py" 44 | build_dir = "/tmp/build" 45 | is_source = true 46 | fields = { version = "1.2.3", version_tuple = "(1, 2, 3)" } 47 | -------------------------------------------------------------------------------- /test/data/hg-archival/distance.json: -------------------------------------------------------------------------------- 1 | { 2 | "repo": "74e7d040b48603d15c462406dc6fbb5fc8e31d3a", 3 | "node": "42ab202e999a827488efaf13ba0e9acc2ff04c86", 4 | "branch": "default", 5 | "latesttag": "v0.1.0", 6 | "latesttagdistance": "1", 7 | "changessincelatesttag": "1" 8 | } 9 | -------------------------------------------------------------------------------- /test/data/hg-archival/distance.txt: -------------------------------------------------------------------------------- 1 | repo: 74e7d040b48603d15c462406dc6fbb5fc8e31d3a 2 | node: 42ab202e999a827488efaf13ba0e9acc2ff04c86 3 | branch: default 4 | latesttag: v0.1.0 5 | latesttagdistance: 1 6 | changessincelatesttag: 1 7 | -------------------------------------------------------------------------------- /test/data/hg-archival/exact.json: -------------------------------------------------------------------------------- 1 | { 2 | "repo": "74e7d040b48603d15c462406dc6fbb5fc8e31d3a", 3 | "node": "9eaa2ff36144315dcaea2a1eff0a1a340f0bc30a", 4 | "branch": "default", 5 | "tag": "v0.1.0" 6 | } 7 | -------------------------------------------------------------------------------- /test/data/hg-archival/exact.txt: -------------------------------------------------------------------------------- 1 | repo: 74e7d040b48603d15c462406dc6fbb5fc8e31d3a 2 | node: 9eaa2ff36144315dcaea2a1eff0a1a340f0bc30a 3 | branch: default 4 | tag: v0.1.0 5 | -------------------------------------------------------------------------------- /test/data/hg-archival/multi-latesttag.json: -------------------------------------------------------------------------------- 1 | { 2 | "repo": "74e7d040b48603d15c462406dc6fbb5fc8e31d3a", 3 | "node": "638659737f7e9bef67498e9b4334a61c1997c417", 4 | "branch": "default", 5 | "latesttag": "v0.1.0", 6 | "latesttagdistance": "2", 7 | "changessincelatesttag": "2" 8 | } 9 | -------------------------------------------------------------------------------- /test/data/hg-archival/multi-latesttag.txt: -------------------------------------------------------------------------------- 1 | repo: 74e7d040b48603d15c462406dc6fbb5fc8e31d3a 2 | node: 638659737f7e9bef67498e9b4334a61c1997c417 3 | branch: default 4 | latesttag: v0.1.0 5 | latesttag: vextra 6 | latesttagdistance: 2 7 | changessincelatesttag: 2 8 | -------------------------------------------------------------------------------- /test/data/hg-archival/multi-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "repo": "74e7d040b48603d15c462406dc6fbb5fc8e31d3a", 3 | "node": "9eaa2ff36144315dcaea2a1eff0a1a340f0bc30a", 4 | "branch": "default", 5 | "tag": "v0.1.0" 6 | } 7 | -------------------------------------------------------------------------------- /test/data/hg-archival/multi-tag.txt: -------------------------------------------------------------------------------- 1 | repo: 74e7d040b48603d15c462406dc6fbb5fc8e31d3a 2 | node: 9eaa2ff36144315dcaea2a1eff0a1a340f0bc30a 3 | branch: default 4 | tag: v0.1.0 5 | tag: vextra 6 | -------------------------------------------------------------------------------- /test/data/hg-archival/null-latesttag.json: -------------------------------------------------------------------------------- 1 | { 2 | "repo": "a3c3e830f40fc94bbd8d43b50192b93a98967dd9", 3 | "node": "0234348fd175c227aefb8e51a55b79acc33d6f61", 4 | "branch": "default", 5 | "latesttag": "null", 6 | "latesttagdistance": "2", 7 | "changessincelatesttag": "2" 8 | } 9 | -------------------------------------------------------------------------------- /test/data/hg-archival/null-latesttag.txt: -------------------------------------------------------------------------------- 1 | repo: a3c3e830f40fc94bbd8d43b50192b93a98967dd9 2 | node: 0234348fd175c227aefb8e51a55b79acc33d6f61 3 | branch: default 4 | latesttag: null 5 | latesttagdistance: 2 6 | changessincelatesttag: 2 7 | -------------------------------------------------------------------------------- /test/data/metadata/desc-in-header.eml: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: mypackage 3 | Version: 0.1.0.post2+g4d891e7 4 | Summary: A test package 5 | Home-page: UNKNOWN 6 | Author: John Thorvald Wodder II 7 | Author-email: mypackage@varonathe.org 8 | License: MIT 9 | Description: This is a test package for testing versioningit_. 10 | 11 | .. _versioningit: https://github.com/jwodder/versioningit 12 | 13 | Platform: UNKNOWN 14 | Requires-Python: ~=3.6 15 | Description-Content-Type: text/x-rst 16 | -------------------------------------------------------------------------------- /test/data/metadata/desc-in-payload.eml: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: mypackage 3 | Version: 0.1.0.post2+g4d891e7 4 | Summary: A test package 5 | Home-page: UNKNOWN 6 | Author: John Thorvald Wodder II 7 | Author-email: mypackage@varonathe.org 8 | License: MIT 9 | Platform: UNKNOWN 10 | Requires-Python: ~=3.6 11 | Description-Content-Type: text/x-rst 12 | Requires-Dist: importlib-metadata ; python_version < "3.8" 13 | 14 | This is a test package for testing versioningit_. 15 | 16 | .. _versioningit: https://github.com/jwodder/versioningit 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/data/metadata/no-version-payload.eml: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: mypackage 3 | Summary: A test package 4 | Home-page: UNKNOWN 5 | Author: John Thorvald Wodder II 6 | Author-email: mypackage@varonathe.org 7 | License: MIT 8 | Platform: UNKNOWN 9 | Requires-Python: ~=3.6 10 | Description-Content-Type: text/x-rst 11 | Requires-Dist: importlib-metadata ; python_version < "3.8" 12 | 13 | This is a test package for testing versioningit_. 14 | 15 | .. _versioningit: https://github.com/jwodder/versioningit 16 | 17 | Version: 0.1.0.post2+g4d891e7 18 | 19 | -------------------------------------------------------------------------------- /test/data/metadata/no-version.eml: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: mypackage 3 | Summary: A test package 4 | Home-page: UNKNOWN 5 | Author: John Thorvald Wodder II 6 | Author-email: mypackage@varonathe.org 7 | License: MIT 8 | Description: This is a test package for testing versioningit_. 9 | 10 | .. _versioningit: https://github.com/jwodder/versioningit 11 | 12 | Platform: UNKNOWN 13 | Requires-Python: ~=3.6 14 | Description-Content-Type: text/x-rst 15 | -------------------------------------------------------------------------------- /test/data/metadata/version-after-desc.eml: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: mypackage 3 | Summary: A test package 4 | Home-page: UNKNOWN 5 | Author: John Thorvald Wodder II 6 | Author-email: mypackage@varonathe.org 7 | License: MIT 8 | Description: This is a test package for testing versioningit_. 9 | 10 | .. _versioningit: https://github.com/jwodder/versioningit 11 | 12 | Platform: UNKNOWN 13 | Requires-Python: ~=3.6 14 | Description-Content-Type: text/x-rst 15 | Version: 0.1.0.post2+g4d891e7 16 | -------------------------------------------------------------------------------- /test/data/metadata/version-in-desc.eml: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: mypackage 3 | Summary: A test package 4 | Home-page: UNKNOWN 5 | Author: John Thorvald Wodder II 6 | Author-email: mypackage@varonathe.org 7 | License: MIT 8 | Description: This is a test package for testing versioningit_. 9 | 10 | .. _versioningit: https://github.com/jwodder/versioningit 11 | 12 | Version: 0.1.0.post2+g4d891e7 13 | 14 | Platform: UNKNOWN 15 | Requires-Python: ~=3.6 16 | Description-Content-Type: text/x-rst 17 | -------------------------------------------------------------------------------- /test/data/mypackage-0.1.0.post2+gd338b00.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/mypackage-0.1.0.post2+gd338b00.tar.gz -------------------------------------------------------------------------------- /test/data/replace-version/append-newline.py: -------------------------------------------------------------------------------- 1 | """ A source file """ 2 | 3 | __version__ = "UNKNOWN" 4 | 5 | if __version__ == "UNKNOWN": 6 | __version_info__ = (0, 0, 0) 7 | else: 8 | __version_info__ = tuple(map(int, __version__.split("."))) 9 | VERSION = '1.2.3' 10 | -------------------------------------------------------------------------------- /test/data/replace-version/append-with-date.py: -------------------------------------------------------------------------------- 1 | """ A source file """ 2 | 3 | __version__ = "UNKNOWN" 4 | 5 | if __version__ == "UNKNOWN": 6 | __version_info__ = (0, 0, 0) 7 | else: 8 | __version_info__ = tuple(map(int, __version__.split("."))) 9 | VERSION = '1.2.3' 10 | BUILD_DATE = '2038-01-19T03:14:07Z' 11 | -------------------------------------------------------------------------------- /test/data/replace-version/append.py: -------------------------------------------------------------------------------- 1 | """ A source file """ 2 | 3 | __version__ = "UNKNOWN" 4 | 5 | if __version__ == "UNKNOWN": 6 | __version_info__ = (0, 0, 0) 7 | else: 8 | __version_info__ = tuple(map(int, __version__.split("."))) 9 | VERSION = '1.2.3' 10 | -------------------------------------------------------------------------------- /test/data/replace-version/base/comment.py: -------------------------------------------------------------------------------- 1 | # Replace me 2 | -------------------------------------------------------------------------------- /test/data/replace-version/base/empty.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/replace-version/base/empty.py -------------------------------------------------------------------------------- /test/data/replace-version/base/latin1.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/replace-version/base/latin1.txt -------------------------------------------------------------------------------- /test/data/replace-version/base/line-sep.py: -------------------------------------------------------------------------------- 1 | """ A source file """

__version__ = "UNKNOWN"

if __version__ == "UNKNOWN":
 __version_info__ = (0, 0, 0)
else:
 __version_info__ = tuple(map(int, __version__.split(".")))
 2 | -------------------------------------------------------------------------------- /test/data/replace-version/base/no-eof-nl.dat: -------------------------------------------------------------------------------- 1 | EOF -------------------------------------------------------------------------------- /test/data/replace-version/base/repeats.py: -------------------------------------------------------------------------------- 1 | __version__ = "UNKNOWN" 2 | 3 | __version__ = "not set" 4 | -------------------------------------------------------------------------------- /test/data/replace-version/base/source_file.py: -------------------------------------------------------------------------------- 1 | """ A source file """ 2 | 3 | __version__ = "UNKNOWN" 4 | 5 | if __version__ == "UNKNOWN": 6 | __version_info__ = (0, 0, 0) 7 | else: 8 | __version_info__ = tuple(map(int, __version__.split("."))) 9 | -------------------------------------------------------------------------------- /test/data/replace-version/base/wheel_file.py: -------------------------------------------------------------------------------- 1 | """ A wheel file """ 2 | 3 | __version__ = "UNKNOWN" 4 | 5 | if __version__ == "UNKNOWN": 6 | __version_info__ = (0, 0, 0) 7 | else: 8 | __version_info__ = tuple(map(int, __version__.split("."))) 9 | -------------------------------------------------------------------------------- /test/data/replace-version/latin1-edited.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/replace-version/latin1-edited.txt -------------------------------------------------------------------------------- /test/data/replace-version/line-sepped.py: -------------------------------------------------------------------------------- 1 | """ A source file """

__version__ = "1.2.3"

if __version__ == "UNKNOWN":
 __version_info__ = (0, 0, 0)
else:
 __version_info__ = tuple(map(int, __version__.split(".")))
 2 | -------------------------------------------------------------------------------- /test/data/replace-version/line2block.py: -------------------------------------------------------------------------------- 1 | """ A source file """ 2 | 3 | try: 4 | from importlib.metadata import version 5 | except ImportError: 6 | from importlib_metadata import version 7 | 8 | __version__ = version("mypackage") 9 | 10 | if __version__ == "UNKNOWN": 11 | __version_info__ = (0, 0, 0) 12 | else: 13 | __version_info__ = tuple(map(int, __version__.split("."))) 14 | -------------------------------------------------------------------------------- /test/data/replace-version/multi-matches.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.2.3" 2 | 3 | __version__ = "not set" 4 | -------------------------------------------------------------------------------- /test/data/replace-version/nl-append.txt: -------------------------------------------------------------------------------- 1 | EOF 2 | 1.2.3 3 | -------------------------------------------------------------------------------- /test/data/replace-version/nomatch.py: -------------------------------------------------------------------------------- 1 | """ A source file """ 2 | 3 | __version__ = "UNKNOWN" 4 | 5 | if __version__ == "UNKNOWN": 6 | __version_info__ = (0, 0, 0) 7 | else: 8 | __version_info__ = tuple(map(int, __version__.split("."))) 9 | -------------------------------------------------------------------------------- /test/data/replace-version/not-source.py: -------------------------------------------------------------------------------- 1 | """ A wheel file """ 2 | 3 | __version__ = "1.2.3" 4 | 5 | if __version__ == "UNKNOWN": 6 | __version_info__ = (0, 0, 0) 7 | else: 8 | __version_info__ = tuple(map(int, __version__.split("."))) 9 | -------------------------------------------------------------------------------- /test/data/replace-version/replace-nonl.py: -------------------------------------------------------------------------------- 1 | # 1.2.3 2 | -------------------------------------------------------------------------------- /test/data/replace-version/replacement.py: -------------------------------------------------------------------------------- 1 | """ A source file """ 2 | 3 | __version__ = importlib.metadata.version("mypackage") 4 | 5 | if __version__ == "UNKNOWN": 6 | __version_info__ = (0, 0, 0) 7 | else: 8 | __version_info__ = tuple(map(int, __version__.split("."))) 9 | -------------------------------------------------------------------------------- /test/data/replace-version/set-line.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.2.3" 2 | -------------------------------------------------------------------------------- /test/data/replace-version/source.py: -------------------------------------------------------------------------------- 1 | """ A source file """ 2 | 3 | __version__ = "1.2.3" 4 | 5 | if __version__ == "UNKNOWN": 6 | __version_info__ = (0, 0, 0) 7 | else: 8 | __version_info__ = tuple(map(int, __version__.split("."))) 9 | -------------------------------------------------------------------------------- /test/data/replace-version/still-empty.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/replace-version/still-empty.py -------------------------------------------------------------------------------- /test/data/replace-version/with-date.py: -------------------------------------------------------------------------------- 1 | """ A source file """ 2 | 3 | __version__ = "1.2.3" 4 | __build_date__ = "20380119T031407Z" 5 | 6 | if __version__ == "UNKNOWN": 7 | __version_info__ = (0, 0, 0) 8 | else: 9 | __version_info__ = tuple(map(int, __version__.split("."))) 10 | -------------------------------------------------------------------------------- /test/data/repos/archives/git-archive-distance.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g65c4f94", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/archives/git-archive-distance.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/archives/git-archive-distance.zip -------------------------------------------------------------------------------- /test/data/repos/archives/git-archive-exact.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "next_version": "0.3.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/archives/git-archive-exact.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/archives/git-archive-exact.zip -------------------------------------------------------------------------------- /test/data/repos/archives/hg-archive-default-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0.post2+hde9a117298ac", 3 | "next_version": "0.1.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/archives/hg-archive-default-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/archives/hg-archive-default-tag.zip -------------------------------------------------------------------------------- /test/data/repos/archives/hg-archive-distance.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+heaf0fda83586", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/archives/hg-archive-distance.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/archives/hg-archive-distance.zip -------------------------------------------------------------------------------- /test/data/repos/archives/hg-archive-exact.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/archives/hg-archive-exact.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/archives/hg-archive-exact.zip -------------------------------------------------------------------------------- /test/data/repos/git-errors/archive-no-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "NoTagError", 3 | "message": "`git describe --long --dirty --always '--exclude=v*'` could not find a tag" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git-errors/archive-no-tag.marks: -------------------------------------------------------------------------------- 1 | describe_exclude 2 | -------------------------------------------------------------------------------- /test/data/repos/git-errors/archive-no-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git-errors/archive-no-tag.zip -------------------------------------------------------------------------------- /test/data/repos/git-errors/hatch-no-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "NoTagError", 3 | "message": "`git describe --long --dirty --always --tags '--exclude=*'` could not find a tag" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git-errors/hatch-no-tag.marks: -------------------------------------------------------------------------------- 1 | describe_exclude 2 | -------------------------------------------------------------------------------- /test/data/repos/git-errors/hatch-no-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git-errors/hatch-no-tag.zip -------------------------------------------------------------------------------- /test/data/repos/git-errors/no-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "NoTagError", 3 | "message": "`git describe --long --dirty --always --tags '--exclude=*'` could not find a tag" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git-errors/no-tag.marks: -------------------------------------------------------------------------------- 1 | describe_exclude 2 | -------------------------------------------------------------------------------- /test/data/repos/git-errors/no-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git-errors/no-tag.zip -------------------------------------------------------------------------------- /test/data/repos/git-errors/template-fields-error.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "InvalidVersionError", 3 | "message": "'0.2.0~ge542767' is not a valid PEP 440 version" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git-errors/template-fields-error.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git-errors/template-fields-error.zip -------------------------------------------------------------------------------- /test/data/repos/git/added-no-commits-default-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0+d20380119", 3 | "next_version": "0.1.0", 4 | "logmsgs": [ 5 | { 6 | "level": "ERROR", 7 | "message": "`git describe --long --dirty --always --tags` command failed: fatal: bad revision 'HEAD'" 8 | }, 9 | { 10 | "level": "INFO", 11 | "message": "Falling back to default tag 'v0.0.0'" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /test/data/repos/git/added-no-commits-default-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/added-no-commits-default-tag.zip -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo-exclude.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g99b3417", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo-exclude.marks: -------------------------------------------------------------------------------- 1 | describe_exclude 2 | -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo-exclude.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/archive-repo-exclude.zip -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo-match.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g85cb6b8", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo-match.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/archive-repo-match.zip -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo-mixed-tags.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "next_version": "0.3.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo-mixed-tags.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/archive-repo-mixed-tags.zip -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo-mixed.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g0088e3a", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo-mixed.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/archive-repo-mixed.zip -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g65c4f94", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/archive-repo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/archive-repo.zip -------------------------------------------------------------------------------- /test/data/repos/git/custom-format-dirty.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0+dirty", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/custom-format-dirty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/custom-format-dirty.zip -------------------------------------------------------------------------------- /test/data/repos/git/custom-format-distance-dirty.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0.dev1+ge3cba8d.dirty", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/custom-format-distance-dirty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/custom-format-distance-dirty.zip -------------------------------------------------------------------------------- /test/data/repos/git/custom-format-distance.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0.dev1+g2fcec60", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/custom-format-distance.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/custom-format-distance.zip -------------------------------------------------------------------------------- /test/data/repos/git/custom-method-pkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post2+ge743690", 3 | "next_version": "0.2.0", 4 | "local_modules": [ 5 | "mypackage.mymethods", 6 | "mypackage" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /test/data/repos/git/custom-method-pkg.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/custom-method-pkg.zip -------------------------------------------------------------------------------- /test/data/repos/git/custom-method-src.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+gcf81ee4", 3 | "next_version": "0.2.0", 4 | "local_modules": [ 5 | "mypackage.mymethods", 6 | "mypackage" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /test/data/repos/git/custom-method-src.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/custom-method-src.zip -------------------------------------------------------------------------------- /test/data/repos/git/custom-method.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g62e8081", 3 | "next_version": "0.2.0", 4 | "local_modules": [ 5 | "setup" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/data/repos/git/custom-method.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/custom-method.zip -------------------------------------------------------------------------------- /test/data/repos/git/default-tag.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "author_date": "2023-11-03 11:35:06+00:00", 3 | "build_date": "2038-01-19 03:14:07+00:00", 4 | "committer_date": "2023-11-03 11:35:06+00:00", 5 | "distance": 2, 6 | "rev": "b4461bd", 7 | "revision": "b4461bd126befccd13d15f99302169044662b262", 8 | "vcs": "g", 9 | "vcs_name": "git" 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/git/default-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0.post2+gb4461bd", 3 | "next_version": "0.1.0", 4 | "logmsgs": [ 5 | { 6 | "level": "INFO", 7 | "message": "`git describe --long --dirty --always --tags` returned a hash instead of a tag; falling back to default tag 'v0.0.0'" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/git/default-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/default-tag.zip -------------------------------------------------------------------------------- /test/data/repos/git/default-version-bad.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.1.1m", 3 | "next_version": { 4 | "type": "NoTagError", 5 | "message": "`git describe --long --dirty --always --tags` could not find a tag" 6 | }, 7 | "logmsgs": [ 8 | { 9 | "level": "ERROR", 10 | "message": "NoTagError: `git describe --long --dirty --always --tags` could not find a tag" 11 | }, 12 | { 13 | "level": "INFO", 14 | "message": "Falling back to default-version" 15 | }, 16 | { 17 | "level": "WARNING", 18 | "message": "Final version '1.1.1m' is not PEP 440-compliant" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /test/data/repos/git/default-version-bad.marks: -------------------------------------------------------------------------------- 1 | oldsetup 2 | -------------------------------------------------------------------------------- /test/data/repos/git/default-version-bad.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/default-version-bad.zip -------------------------------------------------------------------------------- /test/data/repos/git/default-version-onbuild-write.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0+error", 3 | "next_version": { 4 | "type": "NoTagError", 5 | "message": "`git describe --long --dirty --always --tags '--match=v*'` could not find a tag" 6 | }, 7 | "logmsgs": [ 8 | { 9 | "level": "ERROR", 10 | "message": "NoTagError: `git describe --long --dirty --always --tags '--match=v*'` could not find a tag" 11 | }, 12 | { 13 | "level": "INFO", 14 | "message": "Falling back to default-version" 15 | } 16 | ], 17 | "files": [ 18 | { 19 | "sdist_path": "src/mypackage/_version.py", 20 | "wheel_path": "mypackage/_version.py", 21 | "contents": "__version__ = \"0.0.0+error\"\n__version_tuple__ = (0, 0, 0, \"+error\")\n" 22 | }, 23 | { 24 | "sdist_path": "src/mypackage/__init__.py", 25 | "wheel_path": "mypackage/__init__.py", 26 | "in_project": false, 27 | "contents": "\"\"\" A test package \"\"\"\n\n__version__ = \"0.0.0+error\"\n__version_tuple__ = (0, 0, 0, \"+error\")\n" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test/data/repos/git/default-version-onbuild-write.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/default-version-onbuild-write.zip -------------------------------------------------------------------------------- /test/data/repos/git/default-version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0+error", 3 | "next_version": { 4 | "type": "NoTagError", 5 | "message": "`git describe --long --dirty --always --tags` could not find a tag" 6 | }, 7 | "logmsgs": [ 8 | { 9 | "level": "ERROR", 10 | "message": "NoTagError: `git describe --long --dirty --always --tags` could not find a tag" 11 | }, 12 | { 13 | "level": "INFO", 14 | "message": "Falling back to default-version" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /test/data/repos/git/default-version.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/default-version.zip -------------------------------------------------------------------------------- /test/data/repos/git/detached-exact.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "author_date": "2023-11-03 11:35:05+00:00", 3 | "build_date": "2038-01-19 03:14:07+00:00", 4 | "committer_date": "2023-11-03 11:35:05+00:00", 5 | "distance": 0, 6 | "rev": "5f9a44c", 7 | "revision": "5f9a44c2cc0a95902d681c1187940acc901b6d79", 8 | "vcs": "g", 9 | "vcs_name": "git" 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/git/detached-exact.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/detached-exact.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/detached-exact.zip -------------------------------------------------------------------------------- /test/data/repos/git/dirty.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0+d20380119", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/dirty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/dirty.zip -------------------------------------------------------------------------------- /test/data/repos/git/distance-dirty.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "author_date": "2023-11-03 11:35:10+00:00", 3 | "build_date": "2038-01-19 03:14:07+00:00", 4 | "committer_date": "2023-11-03 11:35:10+00:00", 5 | "distance": 1, 6 | "rev": "037a0db", 7 | "revision": "037a0db350174e47eff82af4e41afe4e04d487cc", 8 | "vcs": "g", 9 | "vcs_name": "git" 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/git/distance-dirty.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g037a0db.d20380119", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/distance-dirty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/distance-dirty.zip -------------------------------------------------------------------------------- /test/data/repos/git/distance.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "author_date": "2023-11-03 11:35:06+00:00", 3 | "build_date": "2038-01-19 03:14:07+00:00", 4 | "committer_date": "2023-11-03 11:35:06+00:00", 5 | "distance": 1, 6 | "rev": "128c01a", 7 | "revision": "128c01a78849c4a5e98b6d3d7f90bf0aa08235e6", 8 | "vcs": "g", 9 | "vcs_name": "git" 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/git/distance.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g128c01a", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/distance.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/distance.zip -------------------------------------------------------------------------------- /test/data/repos/git/exact-annotated.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/exact-annotated.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/exact-annotated.zip -------------------------------------------------------------------------------- /test/data/repos/git/exact.exclude.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "author_date": "2023-11-03 11:35:08+00:00", 3 | "build_date": "2038-01-19 03:14:07+00:00", 4 | "committer_date": "2023-11-03 11:35:08+00:00", 5 | "distance": 1, 6 | "rev": "8219a50", 7 | "revision": "8219a50a5492ecd57c69e6494d4fbdd462f84da0", 8 | "vcs": "g", 9 | "vcs_name": "git" 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/git/exact.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "author_date": "2023-11-03 11:35:08+00:00", 3 | "build_date": "2038-01-19 03:14:07+00:00", 4 | "committer_date": "2023-11-03 11:35:08+00:00", 5 | "distance": 0, 6 | "rev": "8219a50", 7 | "revision": "8219a50a5492ecd57c69e6494d4fbdd462f84da0", 8 | "vcs": "g", 9 | "vcs_name": "git" 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/git/exact.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/exact.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/exact.zip -------------------------------------------------------------------------------- /test/data/repos/git/exclude.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "author_date": "2023-11-03 11:35:08+00:00", 3 | "build_date": "2038-01-19 03:14:07+00:00", 4 | "committer_date": "2023-11-03 11:35:08+00:00", 5 | "distance": 1, 6 | "rev": "be04b66", 7 | "revision": "be04b66983bf9a10f58273fcdc6ed11480fc59f2", 8 | "vcs": "g", 9 | "vcs_name": "git" 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/git/exclude.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+gbe04b66", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/exclude.marks: -------------------------------------------------------------------------------- 1 | describe_exclude 2 | -------------------------------------------------------------------------------- /test/data/repos/git/exclude.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/exclude.zip -------------------------------------------------------------------------------- /test/data/repos/git/match.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "author_date": "2023-11-03 11:35:12+00:00", 3 | "build_date": "2038-01-19 03:14:07+00:00", 4 | "committer_date": "2023-11-03 11:35:12+00:00", 5 | "distance": 1, 6 | "rev": "f1aa2c8", 7 | "revision": "f1aa2c874af61c3dce33dc2cd0d8faeb355948a4", 8 | "vcs": "g", 9 | "vcs_name": "git" 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/git/match.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+gf1aa2c8", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/match.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/match.zip -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-bases.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post2+g5a178f4", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/__init__.py", 7 | "wheel_path": "mypackage/__init__.py", 8 | "in_project": false, 9 | "contents": "\"\"\" A test package \"\"\"\n\n__version__ = \"0.1.0.post2+g5a178f4\"\n" 10 | }, 11 | { 12 | "sdist_path": "generated.txt", 13 | "wheel_path": null, 14 | "in_project": false, 15 | "contents": "This was generated by a custom sdist command.\n" 16 | }, 17 | { 18 | "sdist_path": null, 19 | "wheel_path": "generated.txt", 20 | "in_project": false, 21 | "contents": "This was generated by a custom build_py command.\n" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-bases.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/onbuild-bases.zip -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-cfg.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+gdc940b3", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/__init__.py", 7 | "wheel_path": "mypackage/__init__.py", 8 | "in_project": false, 9 | "contents": "\"\"\" A test package \"\"\"\n\n__version__ = \"0.1.0.post1+gdc940b3\"\n" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-cfg.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/onbuild-cfg.zip -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-nonidem.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post2+gfbd9284", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/__init__.py", 7 | "wheel_path": "mypackage/__init__.py", 8 | "in_project": false, 9 | "contents": "\"\"\" A test package \"\"\"\n\n__version__ = \"0.1.0.post2+gfbd9284\"\n# The above was set by versioningit!\n" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-nonidem.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/onbuild-nonidem.zip -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-write-norming.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0-r2", 3 | "pkg_version": "0.1.0.post2", 4 | "next_version": "0.2.0", 5 | "files": [ 6 | { 7 | "sdist_path": "src/mypackage/_version.py", 8 | "wheel_path": "mypackage/_version.py", 9 | "contents": "__raw_version__ = \"0.1.0-r2\"\n__version__ = \"0.1.0.post2\"\n" 10 | }, 11 | { 12 | "sdist_path": "src/mypackage/__init__.py", 13 | "wheel_path": "mypackage/__init__.py", 14 | "in_project": false, 15 | "contents": "\"\"\" A test package \"\"\"\n\n__raw_version__ = \"0.1.0-r2\"\n__version__ = \"0.1.0.post2\"\n" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-write-norming.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/onbuild-write-norming.zip -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-write.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post2+gd338b00", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/_version.py", 7 | "wheel_path": "mypackage/_version.py", 8 | "contents": "__version__ = \"0.1.0.post2+gd338b00\"\n__version_tuple__ = (0, 1, 0, \"post2\", \"+gd338b00\")\n__date__ = \"2023-11-03 11:34:48+00:00\"\n__branch__ = 'main'\n__build_date__ = \"20380119T031407Z\"\n__commit_date__ = \"2023-11-03 11:34:48+00:00\"\n__base_version__ = \"0.1.0\"\n__tag_distance__ = 2\n__next_version__ = \"0.2.0\"\n__rev__ = \"d338b00\"\n__revision__ = \"d338b0080552d4ff1af9a098c2a426effcee32f0\"\n__vcs__ = \"g\"\n__vcs_name__ = \"git\"\n" 9 | }, 10 | { 11 | "sdist_path": "src/mypackage/__init__.py", 12 | "wheel_path": "mypackage/__init__.py", 13 | "in_project": false, 14 | "contents": "\"\"\" A test package \"\"\"\n\n__version__ = \"0.1.0.post2+gd338b00\"\n__version_tuple__ = (0, 1, 0, \"post2\", \"+gd338b00\")\n__date__ = \"2023-11-03 11:34:48+00:00\"\n__branch__ = 'main'\n__build_date__ = \"20380119T031407Z\"\n__commit_date__ = \"2023-11-03 11:34:48+00:00\"\n__base_version__ = \"0.1.0\"\n__tag_distance__ = 2\n__next_version__ = \"0.2.0\"\n__rev__ = \"d338b00\"\n__revision__ = \"d338b0080552d4ff1af9a098c2a426effcee32f0\"\n__vcs__ = \"g\"\n__vcs_name__ = \"git\"\n" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /test/data/repos/git/onbuild-write.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/onbuild-write.zip -------------------------------------------------------------------------------- /test/data/repos/git/onbuild.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+gfc38540", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/__init__.py", 7 | "wheel_path": "mypackage/__init__.py", 8 | "in_project": false, 9 | "contents": "\"\"\" A test package \"\"\"\n\n__version__ = \"0.1.0.post1+gfc38540\"\n" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/data/repos/git/onbuild.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/onbuild.zip -------------------------------------------------------------------------------- /test/data/repos/git/vgit-toml.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0.dev1+gda20d46", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/git/vgit-toml.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/vgit-toml.zip -------------------------------------------------------------------------------- /test/data/repos/git/write-encoding.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g68356c9", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/_version.py", 7 | "wheel_path": "mypackage/_version.py", 8 | "encoding": "cp1252", 9 | "contents": "__version__ = \"\u20140.1.0.post1+g68356c9\u2014\"\n" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/data/repos/git/write-encoding.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/write-encoding.zip -------------------------------------------------------------------------------- /test/data/repos/git/write-py.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g03b6c17", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/_version.py", 7 | "wheel_path": "mypackage/_version.py", 8 | "contents": "__version__ = \"0.1.0.post1+g03b6c17\"\n" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/data/repos/git/write-py.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/write-py.zip -------------------------------------------------------------------------------- /test/data/repos/git/write-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g21d01f6", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/_version.py", 7 | "wheel_path": "mypackage/_version.py", 8 | "encoding": "utf-8", 9 | "contents": "__version__ = \"\u20140.1.0.post1+g21d01f6\u2014\"\n" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/data/repos/git/write-template.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/write-template.zip -------------------------------------------------------------------------------- /test/data/repos/git/write-txt.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g5c32daa", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/version.txt", 7 | "wheel_path": "mypackage/version.txt", 8 | "contents": "0.1.0.post1+g5c32daa\n" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/data/repos/git/write-txt.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/git/write-txt.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/both-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0.dev1+g940b8d0", 3 | "next_version": "0.2.0", 4 | "logmsgs": [ 5 | { 6 | "level": "WARNING", 7 | "message": "versioningit configuration found in both [tool.hatch.version] and [tool.versioningit]; only using the former" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/hatch/both-config.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/both-config.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/dirty.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0+d20380119", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hatch/dirty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/dirty.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/distance-dirty.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g4a5f0fd.d20380119", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hatch/distance-dirty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/distance-dirty.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/distance.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g5328e9c", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hatch/distance.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/distance.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/exact.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hatch/exact.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/exact.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/hatch-config-empty-std.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0.dev2+g27a421e", 3 | "next_version": "0.2.0", 4 | "logmsgs": [ 5 | { 6 | "level": "WARNING", 7 | "message": "versioningit configuration found in both [tool.hatch.version] and [tool.versioningit]; only using the former" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/hatch/hatch-config-empty-std.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/hatch-config-empty-std.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/hatch-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0.dev1+gc11638b", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hatch/hatch-config.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/hatch-config.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/no-std-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g8f422a6", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hatch/no-std-config.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/no-std-config.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/onbuild-both-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g43ee9fd.d20380119", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/__init__.py", 7 | "wheel_path": "mypackage/__init__.py", 8 | "in_project": false, 9 | "contents": "\"\"\" A test package \"\"\"\n\n__version__ = \"0.1.0.post1+g43ee9fd.d20380119\"\n" 10 | } 11 | ], 12 | "logmsgs": [ 13 | { 14 | "level": "WARNING", 15 | "message": "onbuild configuration in versioningit table detected. When using Hatch, onbuild must be configured via [tool.hatch.build.hooks.versioningit-onbuild]." 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test/data/repos/hatch/onbuild-both-config.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/onbuild-both-config.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/onbuild-fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post2+gc58dbb3", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/__init__.py", 7 | "wheel_path": "mypackage/__init__.py", 8 | "in_project": false, 9 | "contents": "\"\"\" A test package \"\"\"\n\n__version__ = \"0.1.0.post2+gc58dbb3\"\n__version_tuple__ = (0, 1, 0, \"post2\", \"gc58dbb3\")\n__date__ = \"2023-11-05 15:24:13+00:00\"\n__branch__ = 'main'\n__build_date__ = \"20380119T031407Z\"\n__commit_date__ = \"2023-11-05 15:24:13+00:00\"\n__base_version__ = \"0.1.0\"\n__tag_distance__ = 2\n__next_version__ = \"0.2.0\"\n__rev__ = \"c58dbb3\"\n__revision__ = \"c58dbb3e9e039ce24a2d058cadb5854ef80d69ed\"\n__vcs__ = \"g\"\n__vcs_name__ = \"git\"\n" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/data/repos/hatch/onbuild-fields.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/onbuild-fields.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/onbuild.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g0321f72", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/__init__.py", 7 | "wheel_path": "mypackage/__init__.py", 8 | "in_project": false, 9 | "contents": "\"\"\" A test package \"\"\"\n\n__version__ = \"0.1.0.post1+g0321f72\"\n" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/data/repos/hatch/onbuild.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/onbuild.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/std-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0.dev1+g257de64", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hatch/std-config.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/std-config.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/write-py.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g367679a", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/_version.py", 7 | "wheel_path": "mypackage/_version.py", 8 | "contents": "__version__ = \"0.1.0.post1+g367679a\"\n" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/data/repos/hatch/write-py.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/write-py.zip -------------------------------------------------------------------------------- /test/data/repos/hatch/write-txt.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+g20bd48c", 3 | "next_version": "0.2.0", 4 | "files": [ 5 | { 6 | "sdist_path": "src/mypackage/version.txt", 7 | "wheel_path": "mypackage/version.txt", 8 | "contents": "0.1.0.post1+g20bd48c\n" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/data/repos/hatch/write-txt.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hatch/write-txt.zip -------------------------------------------------------------------------------- /test/data/repos/hg-errors/hg-no-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "NoTagError", 3 | "message": "No latest tag in Mercurial repository (pattern = 're:^v')" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hg-errors/hg-no-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg-errors/hg-no-tag.zip -------------------------------------------------------------------------------- /test/data/repos/hg/added-no-commits-default-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0+d20380119", 3 | "next_version": "0.1.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hg/added-no-commits-default-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg/added-no-commits-default-tag.zip -------------------------------------------------------------------------------- /test/data/repos/hg/default-tag-fallback.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "build_date": "2038-01-19 03:14:07+00:00", 3 | "distance": 3, 4 | "rev": "5296453555c9", 5 | "revision": "5296453555c92c22bab469752758a549e297c922", 6 | "vcs": "h", 7 | "vcs_name": "hg" 8 | } 9 | -------------------------------------------------------------------------------- /test/data/repos/hg/default-tag-fallback.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0.post3+h5296453555c9", 3 | "next_version": "0.1.0", 4 | "logmsgs": [ 5 | { 6 | "level": "INFO", 7 | "message": "No latest tag (pattern = 're:^v'); falling back to default tag 'v0.0.0'" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/data/repos/hg/default-tag-fallback.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg/default-tag-fallback.zip -------------------------------------------------------------------------------- /test/data/repos/hg/default-tag.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "build_date": "2038-01-19 03:14:07+00:00", 3 | "distance": 2, 4 | "rev": "de9a117298ac", 5 | "revision": "de9a117298ac2f7266ccf581b4f039d60e7188ac", 6 | "vcs": "h", 7 | "vcs_name": "hg" 8 | } 9 | -------------------------------------------------------------------------------- /test/data/repos/hg/default-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0.post2+hde9a117298ac", 3 | "next_version": "0.1.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hg/default-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg/default-tag.zip -------------------------------------------------------------------------------- /test/data/repos/hg/dirty.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0+d20380119", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hg/dirty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg/dirty.zip -------------------------------------------------------------------------------- /test/data/repos/hg/distance-dirty.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "build_date": "2038-01-19 03:14:07+00:00", 3 | "distance": 1, 4 | "rev": "5b77082ddda1", 5 | "revision": "5b77082ddda1725cddece10607e227f3a2c859da", 6 | "vcs": "h", 7 | "vcs_name": "hg" 8 | } 9 | -------------------------------------------------------------------------------- /test/data/repos/hg/distance-dirty.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+h5b77082ddda1.d20380119", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hg/distance-dirty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg/distance-dirty.zip -------------------------------------------------------------------------------- /test/data/repos/hg/distance.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "build_date": "2038-01-19 03:14:07+00:00", 3 | "distance": 1, 4 | "rev": "eaf0fda83586", 5 | "revision": "eaf0fda83586198727abdcf8b2d208d5e5fa3e66", 6 | "vcs": "h", 7 | "vcs_name": "hg" 8 | } 9 | -------------------------------------------------------------------------------- /test/data/repos/hg/distance.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post1+heaf0fda83586", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hg/distance.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg/distance.zip -------------------------------------------------------------------------------- /test/data/repos/hg/exact.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "build_date": "2038-01-19 03:14:07+00:00", 3 | "distance": 0, 4 | "rev": "232938fa0aa0", 5 | "revision": "232938fa0aa0e4ba8ef067cc6a53a1a1681205d1", 6 | "vcs": "h", 7 | "vcs_name": "hg" 8 | } 9 | -------------------------------------------------------------------------------- /test/data/repos/hg/exact.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hg/exact.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg/exact.zip -------------------------------------------------------------------------------- /test/data/repos/hg/multi-tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post2+he632a842857c", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hg/multi-tag.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg/multi-tag.zip -------------------------------------------------------------------------------- /test/data/repos/hg/pattern.fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "build_date": "2038-01-19 03:14:07+00:00", 3 | "distance": 3, 4 | "rev": "3d5c59962577", 5 | "revision": "3d5c599625779eae6c617141c0eb1a2fc3d447b0", 6 | "vcs": "h", 7 | "vcs_name": "hg" 8 | } 9 | -------------------------------------------------------------------------------- /test/data/repos/hg/pattern.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0.post3+h3d5c59962577", 3 | "next_version": "0.2.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/data/repos/hg/pattern.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/hg/pattern.zip -------------------------------------------------------------------------------- /test/data/repos/no-git.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/no-git.zip -------------------------------------------------------------------------------- /test/data/repos/no-pyproject.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/no-pyproject.zip -------------------------------------------------------------------------------- /test/data/repos/no-versioningit.txt: -------------------------------------------------------------------------------- 1 | 0.0.0.post1+g1339629 2 | -------------------------------------------------------------------------------- /test/data/repos/no-versioningit.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/no-versioningit.zip -------------------------------------------------------------------------------- /test/data/repos/shallow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwodder/versioningit/2e37331898744a9c0ef00dc5d0250bce61e1c5d1/test/data/repos/shallow.zip -------------------------------------------------------------------------------- /test/test_config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from operator import attrgetter 3 | from pathlib import Path 4 | from typing import Any 5 | import pytest 6 | from versioningit.config import Config, ConfigSection 7 | from versioningit.errors import ConfigError, NotVersioningitError 8 | from versioningit.git import describe_git 9 | from versioningit.methods import CallableSpec, EntryPointSpec 10 | from versioningit.next_version import next_smallest_version 11 | 12 | DATA_DIR = Path(__file__).with_name("data") 13 | 14 | 15 | @pytest.mark.parametrize( 16 | "tomlfile", 17 | sorted((DATA_DIR / "config").glob("*.toml")), 18 | ids=attrgetter("stem"), 19 | ) 20 | def test_parse_toml_file(tomlfile: Path) -> None: 21 | cfg = Config.parse_toml_file(tomlfile) 22 | namespace: dict[str, Any] = {} 23 | exec(tomlfile.with_suffix(".py").read_text(encoding="utf-8"), namespace) 24 | assert cfg == namespace["cfg"] 25 | 26 | 27 | @pytest.mark.parametrize( 28 | "tomlfile", 29 | sorted((DATA_DIR / "config-error").glob("*.toml")), 30 | ids=attrgetter("stem"), 31 | ) 32 | def test_parse_bad_toml_file(tomlfile: Path) -> None: 33 | with pytest.raises((ConfigError, NotVersioningitError)) as excinfo: 34 | Config.parse_toml_file(tomlfile) 35 | assert str(excinfo.value) == tomlfile.with_suffix(".txt").read_text( 36 | encoding="utf-8" 37 | ).strip().format(tomlfile=tomlfile) 38 | 39 | 40 | def test_parse_obj_callable_methods() -> None: 41 | cfg = Config.parse_obj( 42 | { 43 | "vcs": describe_git, 44 | "next-version": {"method": next_smallest_version}, 45 | } 46 | ) 47 | assert cfg == Config( 48 | vcs=ConfigSection( 49 | method_spec=CallableSpec(describe_git), 50 | params={}, 51 | ), 52 | tag2version=ConfigSection( 53 | method_spec=EntryPointSpec(group="versioningit.tag2version", name="basic"), 54 | params={}, 55 | ), 56 | next_version=ConfigSection( 57 | method_spec=CallableSpec(next_smallest_version), 58 | params={}, 59 | ), 60 | format=ConfigSection( 61 | method_spec=EntryPointSpec(group="versioningit.format", name="basic"), 62 | params={}, 63 | ), 64 | template_fields=ConfigSection( 65 | method_spec=EntryPointSpec( 66 | group="versioningit.template_fields", name="basic" 67 | ), 68 | params={}, 69 | ), 70 | write=None, 71 | onbuild=None, 72 | ) 73 | -------------------------------------------------------------------------------- /test/test_get_version.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import shutil 3 | import pytest 4 | from versioningit.core import get_version 5 | from versioningit.errors import NotSdistError, NotVCSError 6 | 7 | DATA_DIR = Path(__file__).with_name("data") 8 | 9 | 10 | @pytest.mark.skipif(shutil.which("git") is None, reason="Git not installed") 11 | def test_get_version_no_git_fallback(tmp_path: Path) -> None: 12 | shutil.unpack_archive(DATA_DIR / "repos" / "no-git.zip", tmp_path) 13 | with pytest.raises(NotSdistError) as excinfo: 14 | get_version(project_dir=tmp_path, write=False, fallback=True) 15 | assert str(excinfo.value) == f"{tmp_path} does not contain a PKG-INFO file" 16 | 17 | 18 | @pytest.mark.skipif(shutil.which("git") is None, reason="Git not installed") 19 | def test_get_version_no_git_no_fallback(tmp_path: Path) -> None: 20 | shutil.unpack_archive(DATA_DIR / "repos" / "no-git.zip", tmp_path) 21 | with pytest.raises(NotVCSError) as excinfo: 22 | get_version(project_dir=tmp_path, write=False, fallback=False) 23 | assert str(excinfo.value) == f"{tmp_path} is not in a Git repository" 24 | -------------------------------------------------------------------------------- /test/test_logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | from versioningit.logging import parse_log_level, warn_bad_version, warn_extra_fields 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "name,level", 8 | [ 9 | ("CRITICAL", logging.CRITICAL), 10 | ("critical", logging.CRITICAL), 11 | ("cRiTiCaL", logging.CRITICAL), 12 | (str(logging.CRITICAL), logging.CRITICAL), 13 | ("ERROR", logging.ERROR), 14 | ("error", logging.ERROR), 15 | ("ErRoR", logging.ERROR), 16 | (str(logging.ERROR), logging.ERROR), 17 | ("WARNING", logging.WARNING), 18 | ("warning", logging.WARNING), 19 | ("WaRnInG", logging.WARNING), 20 | (str(logging.WARNING), logging.WARNING), 21 | ("INFO", logging.INFO), 22 | ("info", logging.INFO), 23 | ("iNfO", logging.INFO), 24 | (str(logging.INFO), logging.INFO), 25 | ("DEBUG", logging.DEBUG), 26 | ("debug", logging.DEBUG), 27 | ("dEbUg", logging.DEBUG), 28 | (str(logging.DEBUG), logging.DEBUG), 29 | ("NOTSET", logging.NOTSET), 30 | ("notset", logging.NOTSET), 31 | ("NoTsEt", logging.NOTSET), 32 | (str(logging.NOTSET), logging.NOTSET), 33 | ("42", 42), 34 | (" 42 ", 42), 35 | ], 36 | ) 37 | def test_parse_log_level(name: str, level: int) -> None: 38 | assert parse_log_level(name) == level 39 | 40 | 41 | @pytest.mark.parametrize("value", ["x", "logging.INFO", "VERBOSE"]) 42 | def test_parse_log_level_bad(value: str) -> None: 43 | with pytest.raises(ValueError) as excinfo: 44 | parse_log_level(value) 45 | assert str(excinfo.value) == f"Invalid log level: {value!r}" 46 | 47 | 48 | def test_warn_extra_fields_empty(caplog: pytest.LogCaptureFixture) -> None: 49 | warn_extra_fields({}, "test") 50 | assert caplog.record_tuples == [] 51 | 52 | 53 | def test_warn_extra_fields_some(caplog: pytest.LogCaptureFixture) -> None: 54 | warn_extra_fields( 55 | {"mispelled": 42, "extra": ["foo", "bar"]}, "test", ["misspelled"] 56 | ) 57 | assert caplog.record_tuples == [ 58 | ( 59 | "versioningit", 60 | logging.WARNING, 61 | "Ignoring unknown parameter 'mispelled' in versioningit's test" 62 | " (Did you mean: misspelled?)", 63 | ), 64 | ( 65 | "versioningit", 66 | logging.WARNING, 67 | "Ignoring unknown parameter 'extra' in versioningit's test", 68 | ), 69 | ] 70 | 71 | 72 | @pytest.mark.parametrize("v", ["0.1.0", "0.1.0a", "01.02.03", "v0.1.0"]) 73 | def test_warn_bad_version_good(caplog: pytest.LogCaptureFixture, v: str) -> None: 74 | warn_bad_version(v, "test") 75 | assert caplog.record_tuples == [] 76 | 77 | 78 | @pytest.mark.parametrize( 79 | "v", 80 | [ 81 | "", 82 | "0.1.", 83 | "1!", 84 | "0.1.0j", 85 | "0.1.0-extra", 86 | "rel-0.1.0", 87 | "1!v1.2.3", 88 | "1!2!3", 89 | ], 90 | ) 91 | def test_warn_bad_version_bad(caplog: pytest.LogCaptureFixture, v: str) -> None: 92 | warn_bad_version(v, "Test version") 93 | assert caplog.record_tuples == [ 94 | ( 95 | "versioningit", 96 | logging.WARNING, 97 | f"Test version {v!r} is not PEP 440-compliant", 98 | ) 99 | ] 100 | -------------------------------------------------------------------------------- /test/test_main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from pathlib import Path 4 | import subprocess 5 | import sys 6 | from typing import Any, Optional 7 | import pytest 8 | from pytest_mock import MockerFixture 9 | from versioningit.__main__ import main 10 | from versioningit.errors import Error 11 | 12 | 13 | def test_command( 14 | capsys: pytest.CaptureFixture[str], 15 | mocker: MockerFixture, 16 | monkeypatch: pytest.MonkeyPatch, 17 | ) -> None: 18 | monkeypatch.setattr(sys, "argv", ["versioningit"]) 19 | m = mocker.patch("versioningit.__main__.get_version", return_value="THE VERSION") 20 | spy = mocker.spy(logging, "basicConfig") 21 | main() 22 | m.assert_called_once_with(os.curdir, write=False, fallback=True) 23 | spy.assert_called_once_with( 24 | format="[%(levelname)-8s] %(name)s: %(message)s", 25 | level=logging.WARNING, 26 | ) 27 | out, err = capsys.readouterr() 28 | assert out == "THE VERSION\n" 29 | assert err == "" 30 | 31 | 32 | def test_command_arg( 33 | capsys: pytest.CaptureFixture[str], mocker: MockerFixture, tmp_path: Path 34 | ) -> None: 35 | m = mocker.patch("versioningit.__main__.get_version", return_value="THE VERSION") 36 | main([str(tmp_path)]) 37 | m.assert_called_once_with(str(tmp_path), write=False, fallback=True) 38 | out, err = capsys.readouterr() 39 | assert out == "THE VERSION\n" 40 | assert err == "" 41 | 42 | 43 | def test_command_write( 44 | capsys: pytest.CaptureFixture[str], mocker: MockerFixture 45 | ) -> None: 46 | m = mocker.patch("versioningit.__main__.get_version", return_value="THE VERSION") 47 | main(["--write"]) 48 | m.assert_called_once_with(os.curdir, write=True, fallback=True) 49 | out, err = capsys.readouterr() 50 | assert out == "THE VERSION\n" 51 | assert err == "" 52 | 53 | 54 | def test_command_next_version( 55 | capsys: pytest.CaptureFixture[str], mocker: MockerFixture 56 | ) -> None: 57 | m = mocker.patch( 58 | "versioningit.__main__.get_next_version", return_value="THE NEXT VERSION" 59 | ) 60 | main(["--next-version"]) 61 | m.assert_called_once_with(os.curdir) 62 | out, err = capsys.readouterr() 63 | assert out == "THE NEXT VERSION\n" 64 | assert err == "" 65 | 66 | 67 | def test_command_next_version_arg( 68 | capsys: pytest.CaptureFixture[str], mocker: MockerFixture, tmp_path: Path 69 | ) -> None: 70 | m = mocker.patch( 71 | "versioningit.__main__.get_next_version", return_value="THE NEXT VERSION" 72 | ) 73 | main(["-n", str(tmp_path)]) 74 | m.assert_called_once_with(str(tmp_path)) 75 | out, err = capsys.readouterr() 76 | assert out == "THE NEXT VERSION\n" 77 | assert err == "" 78 | 79 | 80 | @pytest.mark.parametrize( 81 | "arg,logenv,log_level", 82 | [ 83 | ("-v", None, logging.INFO), 84 | ("-vv", None, logging.DEBUG), 85 | ("-vvv", None, logging.DEBUG), 86 | (None, None, logging.WARNING), 87 | (None, "ERROR", logging.ERROR), 88 | (None, "WARNING", logging.WARNING), 89 | (None, "INFO", logging.INFO), 90 | (None, "DEBUG", logging.DEBUG), 91 | ("-v", "ERROR", logging.INFO), 92 | ("-v", "WARNING", logging.INFO), 93 | ("-v", "DEBUG", logging.DEBUG), 94 | ("-vv", "INFO", logging.DEBUG), 95 | ("-vv", "5", 5), 96 | ("-vvv", "DEBUG", logging.DEBUG), 97 | ], 98 | ) 99 | def test_command_verbose( 100 | capsys: pytest.CaptureFixture[str], 101 | mocker: MockerFixture, 102 | monkeypatch: pytest.MonkeyPatch, 103 | arg: Optional[str], 104 | logenv: Optional[str], 105 | log_level: int, 106 | ) -> None: 107 | if logenv is None: 108 | monkeypatch.delenv("VERSIONINGIT_LOG_LEVEL", raising=False) 109 | else: 110 | monkeypatch.setenv("VERSIONINGIT_LOG_LEVEL", logenv) 111 | m = mocker.patch("versioningit.__main__.get_version", return_value="THE VERSION") 112 | spy = mocker.spy(logging, "basicConfig") 113 | main([arg] if arg is not None else []) 114 | m.assert_called_once_with(os.curdir, write=False, fallback=True) 115 | spy.assert_called_once_with( 116 | format="[%(levelname)-8s] %(name)s: %(message)s", 117 | level=log_level, 118 | ) 119 | out, err = capsys.readouterr() 120 | assert out == "THE VERSION\n" 121 | assert err == "" 122 | 123 | 124 | def test_command_error( 125 | capsys: pytest.CaptureFixture[str], 126 | mocker: MockerFixture, 127 | monkeypatch: pytest.MonkeyPatch, 128 | ) -> None: 129 | monkeypatch.setattr(sys, "argv", ["versioningit"]) 130 | m = mocker.patch( 131 | "versioningit.__main__.get_version", side_effect=Error("Something broke") 132 | ) 133 | with pytest.raises(SystemExit) as excinfo: 134 | main() 135 | assert excinfo.value.args == (1,) 136 | m.assert_called_once_with(os.curdir, write=False, fallback=True) 137 | out, err = capsys.readouterr() 138 | assert out == "" 139 | assert err == "versioningit: Error: Something broke\n" 140 | 141 | 142 | @pytest.mark.parametrize( 143 | "argv,cmd", 144 | [ 145 | (["git", "get details"], "git 'get details'"), 146 | ([b"git", b"get details"], "git 'get details'"), 147 | (["git", "-C", Path("foo"), "gitify"], "git -C foo gitify"), 148 | ("git 'get details'", "git 'get details'"), 149 | (b"git 'get details'", "git 'get details'"), 150 | (Path("git-get-details"), "git-get-details"), 151 | ], 152 | ) 153 | def test_command_subprocess_error( 154 | caplog: pytest.LogCaptureFixture, 155 | capsys: pytest.CaptureFixture[str], 156 | mocker: MockerFixture, 157 | monkeypatch: pytest.MonkeyPatch, 158 | argv: Any, 159 | cmd: str, 160 | ) -> None: 161 | monkeypatch.setattr(sys, "argv", ["versioningit"]) 162 | m = mocker.patch( 163 | "versioningit.__main__.get_version", 164 | side_effect=subprocess.CalledProcessError( 165 | returncode=42, cmd=argv, output=b"", stderr=b"" 166 | ), 167 | ) 168 | with pytest.raises(SystemExit) as excinfo: 169 | main() 170 | assert excinfo.value.args == (42,) 171 | m.assert_called_once_with(os.curdir, write=False, fallback=True) 172 | out, err = capsys.readouterr() 173 | assert out == "" 174 | assert err == "" 175 | assert caplog.record_tuples == [ 176 | ("versioningit", logging.ERROR, f"{cmd}: command returned 42") 177 | ] 178 | -------------------------------------------------------------------------------- /test/test_methods/test_format.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from datetime import datetime, timezone 3 | from typing import Any 4 | import pytest 5 | from versioningit.basics import basic_format 6 | from versioningit.core import VCSDescription 7 | from versioningit.errors import ConfigError 8 | 9 | BUILD_DATE = datetime(2038, 1, 19, 3, 14, 7, tzinfo=timezone.utc) 10 | 11 | 12 | @pytest.mark.parametrize( 13 | "description,base_version,next_version,params,r", 14 | [ 15 | ( 16 | VCSDescription( 17 | tag="v0.1.0", 18 | state="distance", 19 | branch="main", 20 | fields={ 21 | "distance": 5, 22 | "vcs": "g", 23 | "rev": "abcdef0", 24 | "build_date": BUILD_DATE, 25 | }, 26 | ), 27 | "0.1.0", 28 | "0.2.0", 29 | {}, 30 | "0.1.0.post5+gabcdef0", 31 | ), 32 | ( 33 | VCSDescription( 34 | tag="v0.1.0", 35 | state="dirty", 36 | branch="main", 37 | fields={ 38 | "distance": 0, 39 | "vcs": "g", 40 | "rev": "abcdef0", 41 | "build_date": BUILD_DATE, 42 | }, 43 | ), 44 | "0.1.0", 45 | "0.2.0", 46 | {}, 47 | "0.1.0+d20380119", 48 | ), 49 | ( 50 | VCSDescription( 51 | tag="v0.1.0", 52 | state="distance-dirty", 53 | branch="main", 54 | fields={ 55 | "distance": 5, 56 | "vcs": "g", 57 | "rev": "abcdef0", 58 | "build_date": BUILD_DATE, 59 | }, 60 | ), 61 | "0.1.0", 62 | "0.2.0", 63 | {}, 64 | "0.1.0.post5+gabcdef0.d20380119", 65 | ), 66 | ( 67 | VCSDescription( 68 | tag="v0.1.0", 69 | state="distance", 70 | branch="main", 71 | fields={ 72 | "distance": 5, 73 | "vcs": "g", 74 | "rev": "abcdef0", 75 | "build_date": BUILD_DATE, 76 | }, 77 | ), 78 | "0.1.0", 79 | "0.2.0", 80 | {"distance": "{next_version}.dev{distance}+{vcs}{rev}"}, 81 | "0.2.0.dev5+gabcdef0", 82 | ), 83 | ( 84 | VCSDescription( 85 | tag="v0.1.0", 86 | state="distance", 87 | branch="feature/acme", 88 | fields={ 89 | "distance": 5, 90 | "vcs": "g", 91 | "rev": "abcdef0", 92 | "build_date": BUILD_DATE, 93 | }, 94 | ), 95 | "0.1.0", 96 | "0.2.0", 97 | {"distance": "{next_version}+{branch}.{rev}"}, 98 | "0.2.0+feature.acme.abcdef0", 99 | ), 100 | ( 101 | VCSDescription( 102 | tag="v0.1.0", 103 | state="distance", 104 | branch=None, 105 | fields={ 106 | "distance": 5, 107 | "vcs": "g", 108 | "rev": "abcdef0", 109 | "build_date": BUILD_DATE, 110 | }, 111 | ), 112 | "0.1.0", 113 | "0.2.0", 114 | {"distance": "{next_version}+{branch}.{rev}"}, 115 | "0.2.0+None.abcdef0", 116 | ), 117 | ( 118 | VCSDescription( 119 | tag="v0.1.0", 120 | state="weird", 121 | branch="main", 122 | fields={ 123 | "distance": 5, 124 | "vcs": "g", 125 | "rev": "abcdef0", 126 | "build_date": BUILD_DATE, 127 | }, 128 | ), 129 | "0.1.0", 130 | "0.2.0", 131 | {"weird": "{base_version}+{branch}.{build_date:%Y.%m.%d}"}, 132 | "0.1.0+main.2038.01.19", 133 | ), 134 | ], 135 | ) 136 | def test_basic_format( 137 | caplog: pytest.LogCaptureFixture, 138 | description: VCSDescription, 139 | base_version: str, 140 | next_version: str, 141 | params: dict[str, Any], 142 | r: str, 143 | ) -> None: 144 | assert ( 145 | basic_format( 146 | description=description, 147 | base_version=base_version, 148 | next_version=next_version, 149 | params=params, 150 | ) 151 | == r 152 | ) 153 | assert caplog.record_tuples == [] 154 | 155 | 156 | def test_basic_format_invalid_state(caplog: pytest.LogCaptureFixture) -> None: 157 | with pytest.raises(ConfigError) as excinfo: 158 | basic_format( 159 | description=VCSDescription( 160 | tag="v0.1.0", 161 | state="weird", 162 | branch="main", 163 | fields={ 164 | "distance": 5, 165 | "vcs": "g", 166 | "rev": "abcdef0", 167 | "build_date": BUILD_DATE, 168 | }, 169 | ), 170 | base_version="0.1.0", 171 | next_version="0.2.0", 172 | params={}, 173 | ) 174 | assert str(excinfo.value) == ( 175 | "No format string for 'weird' state found in versioningit's format table" 176 | ) 177 | assert caplog.record_tuples == [] 178 | 179 | 180 | def test_basic_format_invalid_param_style(caplog: pytest.LogCaptureFixture) -> None: 181 | with pytest.raises(ConfigError) as excinfo: 182 | basic_format( 183 | description=VCSDescription( 184 | tag="v0.1.0", 185 | state="dirty", 186 | branch="main", 187 | fields={ 188 | "distance": 5, 189 | "vcs": "g", 190 | "rev": "abcdef0", 191 | "build_date": BUILD_DATE, 192 | }, 193 | ), 194 | base_version="0.1.0", 195 | next_version="0.2.0", 196 | params={"dirty": 42}, 197 | ) 198 | assert str(excinfo.value) == "versioningit: format.* values must be strings" 199 | assert caplog.record_tuples == [] 200 | -------------------------------------------------------------------------------- /test/test_methods/test_hg.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from datetime import datetime, timezone 3 | import json 4 | import logging 5 | from operator import attrgetter 6 | from pathlib import Path 7 | import shutil 8 | import subprocess 9 | from typing import Any 10 | from pydantic import BaseModel 11 | import pytest 12 | from versioningit.core import VCSDescription 13 | from versioningit.errors import NoTagError, NotVCSError 14 | from versioningit.hg import HGRepo, describe_hg, parse_hg_archival 15 | 16 | needs_hg = pytest.mark.skipif( 17 | shutil.which("hg") is None, reason="Mercurial not installed" 18 | ) 19 | 20 | BUILD_DATE = datetime(2038, 1, 19, 3, 14, 7, tzinfo=timezone.utc) 21 | 22 | DATA_DIR = Path(__file__).parent.with_name("data") 23 | 24 | 25 | class HGFields(BaseModel): 26 | build_date: datetime 27 | distance: int 28 | rev: str 29 | revision: str 30 | vcs: str 31 | vcs_name: str 32 | 33 | 34 | @needs_hg 35 | @pytest.mark.parametrize( 36 | "repo,params,tag,state", 37 | [ 38 | ("exact", {}, "v0.1.0", "exact"), 39 | ("distance", {}, "v0.1.0", "distance"), 40 | ("distance-dirty", {}, "v0.1.0", "distance-dirty"), 41 | ("default-tag", {"default-tag": "v0.0.0"}, "v0.0.0", "distance"), 42 | ( 43 | "default-tag-fallback", 44 | {"default-tag": "v0.0.0", "pattern": "re:^v"}, 45 | "v0.0.0", 46 | "distance", 47 | ), 48 | ("pattern", {"pattern": r"re:^v"}, "v0.1.0", "distance"), 49 | ], 50 | ) 51 | def test_describe_hg( 52 | repo: str, params: dict[str, Any], tag: str, state: str, tmp_path: Path 53 | ) -> None: 54 | shutil.unpack_archive(DATA_DIR / "repos" / "hg" / f"{repo}.zip", tmp_path) 55 | with (DATA_DIR / "repos" / "hg" / f"{repo}.fields.json").open( 56 | encoding="utf-8" 57 | ) as fp: 58 | fields = HGFields.model_validate(json.load(fp)) 59 | description = VCSDescription( 60 | tag=tag, 61 | state=state, 62 | branch="default", 63 | fields=fields.model_dump(), 64 | ) 65 | desc = describe_hg(project_dir=tmp_path, params=params) 66 | assert desc == description 67 | assert desc.fields["build_date"].tzinfo is timezone.utc 68 | 69 | 70 | @pytest.mark.parametrize( 71 | "repo", 72 | [ 73 | pytest.param("hg/default-tag.zip", marks=needs_hg), 74 | "archives/hg-archive-default-tag.zip", 75 | ], 76 | ) 77 | def test_describe_hg_no_tag(repo: str, tmp_path: Path) -> None: 78 | shutil.unpack_archive(DATA_DIR / "repos" / repo, tmp_path) 79 | with pytest.raises(NoTagError) as excinfo: 80 | describe_hg(project_dir=tmp_path, params={}) 81 | assert str(excinfo.value) == "No latest tag in Mercurial repository" 82 | 83 | 84 | @needs_hg 85 | def test_describe_hg_no_repo(tmp_path: Path) -> None: 86 | with pytest.raises(NotVCSError) as excinfo: 87 | describe_hg(project_dir=tmp_path, params={}) 88 | assert str(excinfo.value) == f"{tmp_path} is not tracked by Mercurial" 89 | 90 | 91 | @needs_hg 92 | @pytest.mark.parametrize("params", [{}, {"default-tag": "0.0.0"}]) 93 | def test_describe_hg_no_commits(tmp_path: Path, params: dict[str, Any]) -> None: 94 | subprocess.run(["hg", "--cwd", str(tmp_path), "init"], check=True) 95 | with pytest.raises(NotVCSError) as excinfo: 96 | describe_hg(project_dir=tmp_path, params=params) 97 | assert str(excinfo.value) == f"{tmp_path} is not tracked by Mercurial" 98 | 99 | 100 | @needs_hg 101 | def test_describe_hg_added_no_commits(tmp_path: Path) -> None: 102 | shutil.unpack_archive( 103 | DATA_DIR / "repos" / "hg" / "added-no-commits-default-tag.zip", tmp_path 104 | ) 105 | with pytest.raises(NoTagError) as excinfo: 106 | describe_hg(project_dir=tmp_path, params={}) 107 | assert str(excinfo.value) == "No latest tag in Mercurial repository" 108 | 109 | 110 | @needs_hg 111 | def test_describe_hg_added_no_commits_default_tag( 112 | caplog: pytest.LogCaptureFixture, tmp_path: Path 113 | ) -> None: 114 | shutil.unpack_archive( 115 | DATA_DIR / "repos" / "hg" / "added-no-commits-default-tag.zip", tmp_path 116 | ) 117 | assert describe_hg( 118 | project_dir=tmp_path, params={"default-tag": "0.0.0"} 119 | ) == VCSDescription( 120 | tag="0.0.0", 121 | state="dirty", 122 | branch="default", 123 | fields={ 124 | "distance": 0, 125 | "rev": "0" * 12, 126 | "revision": "0" * 40, 127 | "build_date": BUILD_DATE, 128 | "vcs": "h", 129 | "vcs_name": "hg", 130 | }, 131 | ) 132 | assert ( 133 | "versioningit", 134 | logging.INFO, 135 | "No latest tag; falling back to default tag '0.0.0'", 136 | ) in caplog.record_tuples 137 | 138 | 139 | @needs_hg 140 | def test_ensure_is_repo_not_tracked(tmp_path: Path) -> None: 141 | shutil.unpack_archive(DATA_DIR / "repos" / "hg" / "exact.zip", tmp_path) 142 | (tmp_path / "subdir").mkdir() 143 | (tmp_path / "subdir" / "file.txt").touch() 144 | with pytest.raises(NotVCSError) as excinfo: 145 | HGRepo(tmp_path / "subdir").ensure_is_repo() 146 | assert str(excinfo.value) == f"{tmp_path / 'subdir'} is not tracked by Mercurial" 147 | 148 | 149 | @needs_hg 150 | def test_ensure_is_repo_dot_hg_dir(tmp_path: Path) -> None: 151 | subprocess.run(["hg", "--cwd", str(tmp_path), "init"], check=True) 152 | with pytest.raises(NotVCSError) as excinfo: 153 | HGRepo(tmp_path / ".hg").ensure_is_repo() 154 | assert str(excinfo.value) == f"{tmp_path / '.hg'} is not tracked by Mercurial" 155 | 156 | 157 | @pytest.mark.skipif( 158 | shutil.which("hg") is not None, reason="Mercurial must not be installed" 159 | ) 160 | def test_ensure_is_repo_hg_not_installed(tmp_path: Path) -> None: 161 | with pytest.raises(NotVCSError) as excinfo: 162 | HGRepo(tmp_path).ensure_is_repo() 163 | assert ( 164 | str(excinfo.value) 165 | == "hg not installed; assuming this isn't a Mercurial repository" 166 | ) 167 | 168 | 169 | @pytest.mark.parametrize( 170 | "archival_file", 171 | sorted((DATA_DIR / "hg-archival").glob("*.txt")), 172 | ids=attrgetter("stem"), 173 | ) 174 | def test_parse_hg_archival(archival_file: Path) -> None: 175 | assert parse_hg_archival(archival_file) == json.loads( 176 | archival_file.with_suffix(".json").read_text(encoding="utf-8") 177 | ) 178 | -------------------------------------------------------------------------------- /test/test_methods/test_next_version.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from versioningit.errors import InvalidVersionError 3 | from versioningit.next_version import ( 4 | BasicVersion, 5 | next_minor_release_version, 6 | next_minor_version, 7 | next_smallest_release_version, 8 | next_smallest_version, 9 | null_next_version, 10 | ) 11 | 12 | 13 | @pytest.mark.parametrize( 14 | "v,bv,s", 15 | [ 16 | ("1.2.3", BasicVersion(0, [1, 2, 3]), "1.2.3"), 17 | ("0!1.2.3", BasicVersion(0, [1, 2, 3]), "1.2.3"), 18 | ("1!1.2.3", BasicVersion(1, [1, 2, 3]), "1!1.2.3"), 19 | ("21.07.05", BasicVersion(0, [21, 7, 5]), "21.7.5"), 20 | ("1.2.3.0.0", BasicVersion(0, [1, 2, 3, 0, 0]), "1.2.3.0.0"), 21 | ("42", BasicVersion(0, [42]), "42"), 22 | ("1.2.3.post1", BasicVersion(0, [1, 2, 3]), "1.2.3"), 23 | ("1.2.3a0", BasicVersion(0, [1, 2, 3]), "1.2.3"), 24 | ("1.2.3.dev1", BasicVersion(0, [1, 2, 3]), "1.2.3"), 25 | ("v1.2.3", BasicVersion(0, [1, 2, 3]), "1.2.3"), 26 | ("1!2", BasicVersion(1, [2]), "1!2"), 27 | ], 28 | ) 29 | def test_basic_version(v: str, bv: BasicVersion, s: str) -> None: 30 | bv2 = BasicVersion.parse(v) 31 | assert bv2 == bv 32 | assert str(bv2) == s 33 | 34 | 35 | @pytest.mark.parametrize( 36 | "s", 37 | [ 38 | "", 39 | "rel1.2.3", 40 | "1!", 41 | "1!v1.2.3", 42 | "1!2!3", 43 | ], 44 | ) 45 | def test_bad_basic_version(s: str) -> None: 46 | with pytest.raises(InvalidVersionError): 47 | BasicVersion.parse(s) 48 | 49 | 50 | @pytest.mark.parametrize( 51 | "v1,v2", 52 | [ 53 | ("1.2.3.4", "1.3.0"), 54 | ("1.2", "1.3.0"), 55 | ("1", "1.1.0"), 56 | ("0", "0.1.0"), 57 | ("1.2.3a0", "1.3.0"), 58 | ("1.2.3.post1", "1.3.0"), 59 | ("1.2.3.dev1", "1.3.0"), 60 | ("1.2.3.0.0", "1.3.0"), 61 | ("0.5.0", "0.6.0"), 62 | ("0.5.1", "0.6.0"), 63 | ], 64 | ) 65 | def test_next_minor_version(v1: str, v2: str) -> None: 66 | assert next_minor_version(version=v1, branch="master", params={}) == v2 67 | 68 | 69 | @pytest.mark.parametrize( 70 | "v1,v2", 71 | [ 72 | ("1.2.3.4", "1.2.3.5"), 73 | ("1.2", "1.3"), 74 | ("1", "2"), 75 | ("0", "1"), 76 | ("1.2.3a0", "1.2.4"), 77 | ("1.2.3.post1", "1.2.4"), 78 | ("1.2.3.dev1", "1.2.4"), 79 | ("1.2.3.0.0", "1.2.3.0.1"), 80 | ("0.5.0", "0.5.1"), 81 | ("0.5.1", "0.5.2"), 82 | ], 83 | ) 84 | def test_next_smallest_version(v1: str, v2: str) -> None: 85 | assert next_smallest_version(version=v1, branch="master", params={}) == v2 86 | 87 | 88 | @pytest.mark.parametrize( 89 | "v", 90 | [ 91 | "1.2.3.4", 92 | "1.2", 93 | "1", 94 | "0", 95 | "1.2.3a0", 96 | "1.2.3.post1", 97 | "1.2.3.dev1", 98 | "1.2.3.0.0", 99 | "", 100 | "rel1.2.3", 101 | "1!", 102 | "1!v1.2.3", 103 | "1!2!3", 104 | "0.1.", 105 | "1!", 106 | "0.1.0j", 107 | "0.1.0-extra", 108 | ], 109 | ) 110 | def test_null_next_version(v: str) -> None: 111 | assert null_next_version(version=v, branch="master", params={}) == v 112 | 113 | 114 | @pytest.mark.parametrize( 115 | "v1,v2", 116 | [ 117 | ("1.2.3.4", "1.3.0"), 118 | ("1.2", "1.3.0"), 119 | ("1", "1.1.0"), 120 | ("0", "0.1.0"), 121 | ("1.2.3.0.0", "1.3.0"), 122 | ("0.5.0", "0.6.0"), 123 | ("0.5", "0.6.0"), 124 | ("0.5.0.0.0", "0.6.0"), 125 | ("0.5.1", "0.6.0"), 126 | ("0.5.0.post1", "0.6.0"), 127 | ("0.5.1.post1", "0.6.0"), 128 | ("0.5.0a1", "0.5.0"), 129 | ("0.5.1a1", "0.5.1"), 130 | ("0.5.0.dev1", "0.5.0"), 131 | ("0.5.1.dev1", "0.5.1"), 132 | ("1!0.5.0", "1!0.6.0"), 133 | ], 134 | ) 135 | def test_next_minor_release_version(v1: str, v2: str) -> None: 136 | assert next_minor_release_version(version=v1, branch="master", params={}) == v2 137 | 138 | 139 | @pytest.mark.parametrize( 140 | "v", 141 | [ 142 | "", 143 | "0.1.", 144 | "1!", 145 | "0.1.0j", 146 | "0.1.0-extra", 147 | "rel-0.1.0", 148 | "1!v1.2.3", 149 | "1!2!3", 150 | ], 151 | ) 152 | def test_next_minor_release_version_bad(v: str) -> None: 153 | with pytest.raises(InvalidVersionError) as excinfo: 154 | next_minor_release_version(version=v, branch="master", params={}) 155 | assert str(excinfo.value) == f"Cannot parse version {v!r}" 156 | 157 | 158 | @pytest.mark.parametrize( 159 | "v1,v2", 160 | [ 161 | ("1.2.3.4", "1.2.3.5"), 162 | ("1.2", "1.3"), 163 | ("1", "2"), 164 | ("0", "1"), 165 | ("1.2.3.0.0", "1.2.3.0.1"), 166 | ("0.5.0", "0.5.1"), 167 | ("0.5", "0.6"), 168 | ("0.5.0.0.0", "0.5.0.0.1"), 169 | ("0.5.1", "0.5.2"), 170 | ("0.5.0.post1", "0.5.1"), 171 | ("0.5.1.post1", "0.5.2"), 172 | ("0.5.0a1", "0.5.0"), 173 | ("0.5.1a1", "0.5.1"), 174 | ("0.5.0.dev1", "0.5.0"), 175 | ("0.5.1.dev1", "0.5.1"), 176 | ("1!0.5.0", "1!0.5.1"), 177 | ], 178 | ) 179 | def test_next_smallest_release_version(v1: str, v2: str) -> None: 180 | assert next_smallest_release_version(version=v1, branch="master", params={}) == v2 181 | 182 | 183 | @pytest.mark.parametrize( 184 | "v", 185 | [ 186 | "", 187 | "0.1.", 188 | "1!", 189 | "0.1.0j", 190 | "0.1.0-extra", 191 | "rel-0.1.0", 192 | "1!v1.2.3", 193 | "1!2!3", 194 | ], 195 | ) 196 | def test_next_smallest_release_version_bad(v: str) -> None: 197 | with pytest.raises(InvalidVersionError) as excinfo: 198 | next_smallest_release_version(version=v, branch="master", params={}) 199 | assert str(excinfo.value) == f"Cannot parse version {v!r}" 200 | -------------------------------------------------------------------------------- /test/test_methods/test_tag2version.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any 3 | import pytest 4 | from versioningit.basics import basic_tag2version 5 | from versioningit.errors import InvalidTagError 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "tag,params,version", 10 | [ 11 | ("v01.02.03", {}, "01.02.03"), 12 | ("01.02.03", {}, "01.02.03"), 13 | ("vbad", {}, "bad"), 14 | ("v", {}, ""), 15 | ("rel-1.2.3", {}, "rel-1.2.3"), 16 | ("rel-1.2.3", {"rmprefix": "rel-"}, "1.2.3"), 17 | ("1.2.3-final", {}, "1.2.3-final"), 18 | ("1.2.3-final", {"rmsuffix": "-final"}, "1.2.3"), 19 | ("rel-1.2.3-final", {"regex": r"\d+(\.\d+)+"}, "1.2.3"), 20 | ( 21 | "rel-1.2.3-final", 22 | {"regex": r"^rel-(?P\d+(\.\d+)+)-final$"}, 23 | "1.2.3", 24 | ), 25 | ( 26 | "rel-1.2.3-final", 27 | {"rmprefix": "rel-", "regex": r"^rel-(?P\d+(\.\d+)+)-final$"}, 28 | "1.2.3-final", 29 | ), 30 | ], 31 | ) 32 | def test_basic_tag2version(tag: str, params: dict[str, Any], version: str) -> None: 33 | assert basic_tag2version(tag=tag, params=params) == version 34 | 35 | 36 | def test_basic_tag2version_no_version_captured() -> None: 37 | with pytest.raises(InvalidTagError) as excinfo: 38 | basic_tag2version( 39 | tag="rel-final", params={"regex": r"^rel-(?P\d+(\.d+)+)?"} 40 | ) 41 | assert str(excinfo.value) == ( 42 | "'version' group in versioningit's tag2version.regex did" 43 | " not participate in match" 44 | ) 45 | 46 | 47 | def test_basic_tag2version_require_match() -> None: 48 | with pytest.raises(InvalidTagError) as excinfo: 49 | basic_tag2version( 50 | tag="rel-1.2.3-final", 51 | params={ 52 | "rmprefix": "rel-", 53 | "regex": r"^rel-(?P\d+(\.\d+)+)-final$", 54 | "require-match": True, 55 | }, 56 | ) 57 | assert str(excinfo.value) == "tag2version.regex did not match tag '1.2.3-final'" 58 | -------------------------------------------------------------------------------- /test/test_methods/test_write.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from datetime import datetime, timezone 3 | from pathlib import Path 4 | from typing import Any 5 | import pytest 6 | from versioningit.basics import basic_write 7 | from versioningit.errors import ConfigError 8 | 9 | 10 | @pytest.mark.parametrize( 11 | "filename,params,content", 12 | [ 13 | ("foo/bar.txt", {}, "1.2.3\n"), 14 | ("foo/bar.py", {}, '__version__ = "1.2.3"\n'), 15 | ("foo/bar", {}, "1.2.3\n"), 16 | ( 17 | "foo/bar.py", 18 | {"template": "__version__ = {version!r}"}, 19 | "__version__ = '1.2.3'\n", 20 | ), 21 | ("foo/bar.tex", {"template": r"$v = {version}$\bye"}, "$v = 1.2.3$\\bye\n"), 22 | ( 23 | "foo/bar.py", 24 | { 25 | "template": ( 26 | "__version__ = {version!r}\n" 27 | '__build_date__ = "{build_date:%Y-%m-%dT%H:%M:%SZ}"' 28 | ) 29 | }, 30 | "__version__ = '1.2.3'\n__build_date__ = \"2038-01-19T03:14:07Z\"\n", 31 | ), 32 | ], 33 | ) 34 | def test_basic_write( 35 | filename: str, params: dict[str, Any], content: str, tmp_path: Path 36 | ) -> None: 37 | basic_write( 38 | project_dir=tmp_path, 39 | template_fields={ 40 | "version": "1.2.3", 41 | "build_date": datetime(2038, 1, 19, 3, 14, 7, tzinfo=timezone.utc), 42 | }, 43 | params={"file": filename, **params}, 44 | ) 45 | assert (tmp_path / filename).read_text(encoding="utf-8") == content 46 | 47 | 48 | def test_basic_write_no_file(tmp_path: Path) -> None: 49 | with pytest.raises(ConfigError) as excinfo: 50 | basic_write( 51 | project_dir=tmp_path, template_fields={"version": "1.2.3"}, params={} 52 | ) 53 | assert str(excinfo.value) == "versioningit's write.file must be set to a string" 54 | 55 | 56 | def test_basic_write_bad_ext(tmp_path: Path) -> None: 57 | with pytest.raises(ConfigError) as excinfo: 58 | basic_write( 59 | project_dir=tmp_path, 60 | template_fields={"version": "1.2.3"}, 61 | params={"file": "foo/bar.tex"}, 62 | ) 63 | assert str(excinfo.value) == ( 64 | "versioningit: write.template not specified and file has unknown" 65 | " suffix '.tex'" 66 | ) 67 | assert list(tmp_path.iterdir()) == [] 68 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = lint,typing,py38,py39,py310,py311,py312,py313,pypy3,py-oldsetup 3 | skip_missing_interpreters = True 4 | isolated_build = True 5 | minversion = 3.3.0 6 | 7 | [testenv] 8 | deps = 9 | build>=0.7 # Must be a version that builds wheels from sdists 10 | editables 11 | hatchling 12 | setuptools>=42 13 | pip 14 | pydantic~=2.0 15 | pytest 16 | pytest-cov 17 | pytest-mock 18 | wheel 19 | commands = 20 | pytest {posargs:-v} test 21 | 22 | [testenv:py-oldsetup] 23 | deps = 24 | build>=0.7 # Must be a version that builds wheels from sdists 25 | hatchling 26 | setuptools<64 27 | pip 28 | pydantic~=2.0 29 | pytest 30 | pytest-cov 31 | pytest-mock 32 | wheel 33 | commands = 34 | pytest {posargs:-v} --oldsetup test 35 | 36 | [testenv:lint] 37 | skip_install = True 38 | deps = 39 | flake8 40 | flake8-bugbear 41 | flake8-builtins 42 | flake8-unused-arguments 43 | commands = 44 | flake8 src test 45 | 46 | [testenv:typing] 47 | deps = 48 | mypy 49 | types-setuptools 50 | {[testenv]deps} 51 | commands = 52 | mypy src test 53 | 54 | [pytest] 55 | addopts = 56 | --cov=versioningit 57 | # Explicitly setting the path to the coverage config file is necessary due 58 | # to some tests spawning subprocesses with changed working directories. 59 | --cov-config=tox.ini 60 | --no-cov-on-fail 61 | filterwarnings = 62 | error 63 | ignore:.*No source for code:coverage.exceptions.CoverageWarning 64 | markers = 65 | describe_exclude: Tests that use `git describe --exclude` (Added in Git 2.13.0) 66 | oldsetup: Tests to only run under pre-v64 setuptools 67 | norecursedirs = test/data 68 | 69 | [coverage:run] 70 | branch = True 71 | parallel = True 72 | 73 | [coverage:paths] 74 | source = 75 | src 76 | .tox/**/site-packages 77 | 78 | [coverage:report] 79 | precision = 2 80 | show_missing = True 81 | exclude_lines = 82 | pragma: no cover 83 | if TYPE_CHECKING: 84 | \.\.\. 85 | 86 | [flake8] 87 | doctests = True 88 | extend-exclude = build/,dist/,test/data,venv/ 89 | max-doc-length = 100 90 | max-line-length = 80 91 | unused-arguments-ignore-stub-functions = True 92 | extend-select = B901,B902,B950 93 | ignore = A003,A005,B005,E203,E262,E266,E501,E704,U101,W503 94 | 95 | [isort] 96 | atomic = True 97 | force_sort_within_sections = True 98 | honor_noqa = True 99 | lines_between_sections = 0 100 | profile = black 101 | reverse_relative = True 102 | sort_relative_in_force_sorted_sections = True 103 | src_paths = src 104 | 105 | [testenv:docs] 106 | basepython = python3 107 | deps = -rdocs/requirements.txt 108 | changedir = docs 109 | commands = sphinx-build -E -W -b html . _build/html 110 | --------------------------------------------------------------------------------