├── .coveragerc ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── dependabot.yml ├── lock.yml ├── no-response.yml └── workflows │ ├── automerge.yml │ ├── codeql-analysis.yml │ ├── dev-release.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── .vscode └── settings.json ├── CHANGELOG.rst ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── bandit.yaml ├── docs ├── Makefile ├── changelog.rst ├── ci.rst ├── command.rst ├── common_issues │ ├── all_dirty.rst │ ├── index.rst │ └── wrong_tag.rst ├── comparison.rst ├── conf.py ├── contributing.rst ├── index.rst ├── install.rst ├── make.bat ├── options │ ├── branch_formatter.rst │ ├── count_commits.rst │ ├── dev_template.rst │ ├── dirty_template.rst │ ├── enabled.rst │ ├── index.rst │ ├── sort_by.rst │ ├── starting_version.rst │ ├── tag_filter.rst │ ├── tag_formatter.rst │ ├── template.rst │ ├── version_callback.rst │ └── version_file.rst ├── robots.txt ├── runtime_version.rst ├── schemas │ ├── callback │ │ ├── index.rst │ │ └── version_callback.rst │ ├── file │ │ ├── dev_release_file.rst │ │ ├── index.rst │ │ └── version_file.rst │ ├── index.rst │ └── tag │ │ ├── dirty_version.rst │ │ ├── index.rst │ │ ├── post_release.rst │ │ └── tag_release.rst └── substitutions │ ├── branch.rst │ ├── ccount.rst │ ├── env.rst │ ├── full_sha.rst │ ├── index.rst │ ├── sha.rst │ ├── tag.rst │ └── timestamp.rst ├── pyproject.toml ├── pytest.ini ├── requirements-dev.txt ├── requirements-doc.txt ├── requirements-test.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── setuptools_git_versioning.py └── tests ├── __init__.py ├── conftest.py ├── lib ├── __init__.py └── util.py └── test_integration ├── __init__.py ├── conftest.py ├── test_branch_formatter.py ├── test_config.py ├── test_substitution.py ├── test_tag.py ├── test_tag_filter.py ├── test_tag_formatter.py ├── test_version_callback.py └── test_version_file.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source_pkgs = setuptools_git_versioning 4 | omit = 5 | tests/* 6 | parallel = true 7 | data_file = reports/.coverage 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/en/articles/about-code-owners 2 | # for details 3 | # 4 | # Maintainers are encouraged to use their best discretion in 5 | # setting reviewers on PRs manually, but this file should 6 | # offer a reasonable automatic best-guess 7 | 8 | # catch-all rule (this only gets matched if no rules below match) 9 | * @dolfinus 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dolfinus 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: github-actions 6 | directory: / 7 | schedule: 8 | interval: daily 9 | labels: 10 | - ci 11 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Lock Threads - https://github.com/dessant/lock-threads-app 2 | 3 | # Number of days of inactivity before a closed issue or pull request is locked 4 | daysUntilLock: 60 5 | 6 | # Skip issues and pull requests created before a given timestamp. Timestamp must 7 | # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable 8 | skipCreatedBefore: false 9 | 10 | # Issues and pull requests with these labels will be ignored. Set to `[]` to disable 11 | exemptLabels: [enhancement, help wanted] 12 | 13 | # Label to add before locking, such as `outdated`. Set to `false` to disable 14 | lockLabel: [outdated] 15 | 16 | # Comment to post before locking. Set to `false` to disable 17 | lockComment: false 18 | 19 | # Assign `resolved` as the reason for locking. Set to `false` to disable 20 | setLockReason: true 21 | 22 | # Limit to only `issues` or `pulls` 23 | # only: issues 24 | 25 | # Optionally, specify configuration settings just for `issues` or `pulls` 26 | # issues: 27 | # exemptLabels: 28 | # - help-wanted 29 | # lockLabel: outdated 30 | 31 | # pulls: 32 | # daysUntilLock: 30 33 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 30 5 | # Label requiring a response 6 | responseRequiredLabel: awaiting response 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because it has been awaiting a response for too long. 10 | When you have time to to work with the maintainers to resolve this issue, please post a new comment and it will be re-opened. 11 | If the issue has been locked for editing by the time you return to it, please open a new issue and reference this one. Thank you for taking the time to improve setuptools-git-versioning! 12 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: Automerge 2 | 3 | on: 4 | pull_request_target: 5 | 6 | jobs: 7 | automerge: 8 | name: Enable pull request automerge 9 | runs-on: ubuntu-latest 10 | if: github.event.pull_request.user.login == 'pre-commit-ci[bot]' || github.event.pull_request.user.login == 'dependabot[bot]' 11 | 12 | steps: 13 | - uses: alexwilson/enable-github-automerge-action@2.0.0 14 | with: 15 | github-token: ${{ secrets.PERSONAL_TOKEN }} 16 | merge-method: REBASE 17 | 18 | autoapprove: 19 | name: Automatically approve pull request 20 | needs: [automerge] 21 | runs-on: ubuntu-latest 22 | if: github.event.pull_request.user.login == 'pre-commit-ci[bot]' || github.event.pull_request.user.login == 'dependabot[bot]' 23 | 24 | steps: 25 | - uses: hmarr/auto-approve-action@v4 26 | with: 27 | github-token: ${{ secrets.PERSONAL_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | DEFAULT_PYTHON: '3.13' 17 | 18 | jobs: 19 | analyze: 20 | name: Analyze 21 | runs-on: ubuntu-latest 22 | permissions: 23 | actions: read 24 | contents: read 25 | security-events: write 26 | 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v4 30 | 31 | - name: Set up Python ${{ env.DEFAULT_PYTHON }} 32 | uses: actions/setup-python@v5 33 | with: 34 | python-version: ${{ env.DEFAULT_PYTHON }} 35 | 36 | - name: Cache pip 37 | uses: actions/cache@v4 38 | with: 39 | path: ~/.cache/pip 40 | key: ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-codeql-${{ hashFiles('requirements*.txt') }} 41 | restore-keys: | 42 | ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-codeql-${{ hashFiles('requirements*.txt') }} 43 | ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-codeql- 44 | ${{ runner.os }}-python 45 | ${{ runner.os }}- 46 | 47 | - name: Upgrade pip 48 | run: python -m pip install --upgrade pip setuptools wheel 49 | 50 | - name: Install dependencies 51 | run: | 52 | pip install -I -r requirements.txt -r requirements-dev.txt 53 | # Set the `CODEQL-PYTHON` environment variable to the Python executable 54 | # that includes the dependencies 55 | echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV 56 | 57 | - name: Run flake8 58 | run: flake8 --count --max-line-length=120 . 59 | 60 | # Initializes the CodeQL tools for scanning. 61 | - name: Initialize CodeQL 62 | uses: github/codeql-action/init@v3 63 | with: 64 | languages: python 65 | # If you wish to specify custom queries, you can do so here or in a config file. 66 | # By default, queries listed here will override any specified in a config file. 67 | # Prefix the list here with "+" to use these queries and those in the config file. 68 | 69 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 70 | # queries: security-extended,security-and-quality 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v3 74 | with: 75 | category: /language:python 76 | -------------------------------------------------------------------------------- /.github/workflows/dev-release.yml: -------------------------------------------------------------------------------- 1 | name: Dev release 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - dependabot/** 7 | - pre-commit-ci-update-config 8 | workflow_dispatch: 9 | 10 | env: 11 | DEFAULT_PYTHON: '3.13' 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | release: 19 | name: Release package 20 | runs-on: ubuntu-latest 21 | if: github.repository == 'dolfinus/setuptools-git-versioning' # prevent running on forks 22 | 23 | environment: 24 | name: test-pypi 25 | url: https://test.pypi.org/p/onetl 26 | permissions: 27 | id-token: write # to auth in Test PyPI 28 | 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v4 32 | with: 33 | fetch-depth: 0 34 | 35 | - name: Set up Python ${{ env.DEFAULT_PYTHON }} 36 | id: python 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: ${{ env.DEFAULT_PYTHON }} 40 | 41 | - name: Cache pip 42 | uses: actions/cache@v4 43 | with: 44 | path: ~/.cache/pip 45 | key: ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-release-${{ hashFiles('requirements*.txt') }} 46 | restore-keys: | 47 | ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-release-${{ hashFiles('requirements*.txt') }} 48 | ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-release- 49 | ${{ runner.os }}-python 50 | ${{ runner.os }}- 51 | 52 | - name: Upgrade pip 53 | run: python -m pip install --upgrade pip setuptools wheel 54 | 55 | - name: Install dependencies 56 | run: pip install -r requirements.txt 57 | 58 | - name: Patch setup.py with explicit version number 59 | # Fix for https://github.com/dolfinus/setuptools-git-versioning/issues/72#issuecomment-1312589464 60 | run: | 61 | version=$(python setup.py --version) 62 | sed -i -E "/from setuptools_git_versioning.*/d" setup.py 63 | sed -i -E "s/version=.*/version='${version}',/" setup.py 64 | 65 | - name: Build package 66 | run: python setup.py sdist bdist_wheel 67 | 68 | - name: Publish package 69 | uses: pypa/gh-action-pypi-publish@release/v1 70 | with: 71 | repository-url: https://test.pypi.org/legacy/ 72 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | env: 9 | DEFAULT_PYTHON: '3.13' 10 | 11 | jobs: 12 | release: 13 | name: Release package 14 | runs-on: ubuntu-latest 15 | if: github.repository == 'dolfinus/setuptools-git-versioning' # prevent running on forks 16 | 17 | environment: 18 | name: pypi 19 | url: https://pypi.org/p/onetl 20 | permissions: 21 | id-token: write # to auth in PyPI 22 | contents: write # to create Github release 23 | 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Set up Python ${{ env.DEFAULT_PYTHON }} 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: ${{ env.DEFAULT_PYTHON }} 34 | 35 | - name: Cache pip 36 | uses: actions/cache@v4 37 | with: 38 | path: ~/.cache/pip 39 | key: ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-release-${{ hashFiles('requirements*.txt') }} 40 | restore-keys: | 41 | ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-release-${{ hashFiles('requirements*.txt') }} 42 | ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-release- 43 | ${{ runner.os }}-python 44 | ${{ runner.os }}- 45 | 46 | - name: Upgrade pip 47 | run: python -m pip install --upgrade pip setuptools wheel 48 | 49 | - name: Install dependencies 50 | run: pip install -r requirements.txt -r requirements-doc.txt 51 | 52 | - name: Inject slug/short variables 53 | uses: rlespinasse/github-slug-action@v4.x 54 | 55 | - name: Patch setup.py with explicit version number 56 | # Fix for https://github.com/dolfinus/setuptools-git-versioning/issues/72#issuecomment-1312589464 57 | run: | 58 | version=$(echo $GITHUB_REF_SLUG | sed -E "s/v(.*)/\1/") 59 | sed -i -E "/from setuptools_git_versioning.*/d" setup.py 60 | sed -i -E "s/version=.*/version='${version}',/" setup.py 61 | 62 | - name: Build package 63 | run: python setup.py sdist bdist_wheel 64 | 65 | - name: Build release notes 66 | run: | 67 | mkdir reports/ 68 | pip install -e . 69 | version=$(echo $GITHUB_REF_SLUG | sed -E "s/v(.*)/\1/") 70 | # lines 1 and 3 are empty, line 2 contains version number, line 4 contains release date 71 | changelog generate-md -c ./docs/conf.py CHANGELOG.rst -v $version | sed "1,4d" > reports/release.md 72 | sed -i -E 's/:github-user:`(.*)`/@\1/g' reports/release.md 73 | sed -i -E 's/:issue:`(.*)`/#\1/g' reports/release.md 74 | sed -i -E 's/:pr:`(.*)`/#\1/g' reports/release.md 75 | 76 | - name: Publish package 77 | uses: pypa/gh-action-pypi-publish@release/v1 78 | 79 | - name: Create Github release 80 | id: create_release 81 | uses: softprops/action-gh-release@v2 82 | with: 83 | token: ${{ secrets.GITHUB_TOKEN }} 84 | draft: false 85 | prerelease: false 86 | body_path: reports/release.md 87 | files: | 88 | dist/* 89 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches-ignore: 5 | - dependabot/** 6 | - pre-commit-ci-update-config 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 14 | cancel-in-progress: true 15 | 16 | env: 17 | DEFAULT_PYTHON: '3.12' 18 | 19 | jobs: 20 | tests: 21 | name: Run ${{ matrix.mark}} tests (${{ matrix.python-version }} on ${{ matrix.os }}) 22 | runs-on: ${{ matrix.os }} 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | include: 27 | # run all the tests on latest Python version 28 | - os: ubuntu-latest 29 | mark: all 30 | python-version: '3.13' 31 | # run only minimal set of tests on other Python versions because it takes too much time 32 | - os: ubuntu-22.04 33 | mark: important 34 | python-version: '3.7' 35 | - os: ubuntu-latest 36 | mark: important 37 | python-version: 'pypy-3.7' 38 | - os: ubuntu-latest 39 | mark: important 40 | python-version: 'pypy-3.10' 41 | # for MacOS and Windows always run only minimal tests 42 | - os: macos-latest 43 | mark: important 44 | python-version: '3.13' 45 | - os: windows-latest 46 | mark: important 47 | python-version: '3.13' 48 | 49 | steps: 50 | - name: Checkout code 51 | uses: actions/checkout@v4 52 | with: 53 | fetch-depth: 0 54 | 55 | - name: Set up Python ${{ matrix.python-version }} 56 | uses: actions/setup-python@v5 57 | with: 58 | python-version: ${{ matrix.python-version }} 59 | 60 | - name: Cache pip 61 | uses: actions/cache@v4 62 | with: 63 | path: ~/.cache/pip 64 | key: ${{ runner.os }}-python-${{ matrix.python-version }}-tests-${{ hashFiles('requirements*.txt') }} 65 | restore-keys: | 66 | ${{ runner.os }}-python-${{ matrix.python-version }}-tests-${{ hashFiles('requirements*.txt') }} 67 | ${{ runner.os }}-python-${{ matrix.python-version }}-tests- 68 | ${{ runner.os }}-python 69 | ${{ runner.os }}- 70 | 71 | - name: Upgrade pip 72 | run: python -m pip install --upgrade pip setuptools wheel 73 | 74 | - name: Install dependencies 75 | run: pip install -I -r requirements.txt -r requirements-test.txt 76 | 77 | - name: Build package 78 | run: | 79 | git version 80 | git tag -l --sort=-creatordate --merged 81 | python setup.py --version 82 | python setup.py bdist_wheel sdist 83 | 84 | - name: Run tests 85 | run: | 86 | mkdir reports/ 87 | pip install -e . --no-build-isolation 88 | coverage run -m pytest -m ${{ matrix.mark }} 89 | 90 | - name: Upload coverage results 91 | uses: actions/upload-artifact@v4 92 | with: 93 | name: code-coverage-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.mark }} 94 | path: reports/.coverage* 95 | # https://github.com/actions/upload-artifact/issues/602 96 | include-hidden-files: true 97 | 98 | all_done: 99 | name: Tests done 100 | runs-on: ubuntu-latest 101 | needs: [tests] 102 | 103 | steps: 104 | - name: Checkout code 105 | uses: actions/checkout@v4 106 | 107 | - name: Set up Python ${{ env.DEFAULT_PYTHON }} 108 | uses: actions/setup-python@v5 109 | with: 110 | python-version: ${{ env.DEFAULT_PYTHON }} 111 | 112 | - name: Cache pip 113 | uses: actions/cache@v4 114 | with: 115 | path: ~/.cache/pip 116 | key: ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-tests-${{ hashFiles('requirements*.txt') }} 117 | restore-keys: | 118 | ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-tests-${{ hashFiles('requirements*.txt') }} 119 | ${{ runner.os }}-python-${{ env.DEFAULT_PYTHON }}-tests- 120 | ${{ runner.os }}-python 121 | ${{ runner.os }}- 122 | 123 | - name: Upgrade pip 124 | run: python -m pip install --upgrade pip setuptools wheel 125 | 126 | - name: Install dependencies 127 | run: pip install -I -r requirements.txt -r requirements-test.txt 128 | 129 | - name: Download all coverage reports 130 | uses: actions/download-artifact@v4 131 | with: 132 | path: reports 133 | 134 | - name: Move coverage reports to the root folder 135 | run: find reports -type f -exec mv '{}' reports \; 136 | 137 | - name: Combine coverage 138 | run: | 139 | coverage combine 140 | coverage xml -o reports/coverage.xml -i 141 | 142 | - name: Check coverage 143 | uses: codecov/codecov-action@v5 144 | with: 145 | token: ${{ secrets.CODECOV_TOKEN }} 146 | directory: ./reports 147 | fail_ci_if_error: true 148 | 149 | - name: All done 150 | run: echo 1 151 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *$py.class 2 | *.egg 3 | *.egg-info/ 4 | *.py[cod] 5 | .Python 6 | .eggs/ 7 | .idea/ 8 | .installed.cfg 9 | .vscode/* 10 | !.vscode/extensions.json 11 | !.vscode/launch.json 12 | !.vscode/settings.json 13 | !.vscode/tasks.json 14 | MANIFEST 15 | __pycache__/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | docs/_build/ 20 | downloads/ 21 | eggs/ 22 | lib/ 23 | !tests/lib/ 24 | lib64/ 25 | parts/ 26 | pip-wheel-metadata/ 27 | reports/ 28 | sdist/ 29 | share/python-wheels/ 30 | var/ 31 | venv/ 32 | wheels/ 33 | .python-version 34 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-ast 6 | - id: check-case-conflict 7 | - id: check-docstring-first 8 | - id: check-merge-conflict 9 | - id: check-vcs-permalinks 10 | - id: check-yaml 11 | - id: requirements-txt-fixer 12 | files: ^(requirements.*\.txt)$ 13 | - id: end-of-file-fixer 14 | - id: fix-byte-order-marker 15 | - id: fix-encoding-pragma 16 | args: [--remove] 17 | - id: name-tests-test 18 | files: ^tests\/test_(unit|integration)\/.*\.py$ 19 | args: [--django] 20 | - id: trailing-whitespace 21 | - id: detect-private-key 22 | 23 | - repo: https://github.com/Lucas-C/pre-commit-hooks 24 | rev: v1.5.5 25 | hooks: 26 | - id: remove-tabs 27 | 28 | - repo: https://github.com/codespell-project/codespell 29 | rev: v2.3.0 30 | hooks: 31 | - id: codespell 32 | args: [-w] 33 | 34 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks 35 | rev: v2.14.0 36 | hooks: 37 | - id: pretty-format-yaml 38 | args: [--autofix, --indent, '2', --preserve-quotes, --offset, '2'] 39 | 40 | - repo: https://github.com/pycqa/isort 41 | rev: 5.13.2 42 | hooks: 43 | - id: isort 44 | files: setuptools_git_versioning.py 45 | 46 | - repo: https://github.com/pre-commit/pygrep-hooks 47 | rev: v1.10.0 48 | hooks: 49 | - id: python-no-log-warn 50 | - id: python-no-eval 51 | - id: text-unicode-replacement-char 52 | 53 | - repo: https://github.com/asottile/pyupgrade 54 | rev: v3.19.1 55 | hooks: 56 | - id: pyupgrade 57 | args: [--py37-plus] 58 | 59 | - repo: https://github.com/psf/black 60 | rev: 24.10.0 61 | hooks: 62 | - id: black 63 | language_version: python3 64 | 65 | - repo: https://github.com/asottile/blacken-docs 66 | rev: 1.19.1 67 | hooks: 68 | - id: blacken-docs 69 | additional_dependencies: 70 | - black==24.10.0 71 | 72 | - repo: https://github.com/pre-commit/mirrors-mypy 73 | rev: v1.14.1 74 | hooks: 75 | - id: mypy 76 | additional_dependencies: [types-toml] 77 | 78 | - repo: https://github.com/pycqa/flake8 79 | rev: 7.1.1 80 | hooks: 81 | - id: flake8 82 | 83 | - repo: https://github.com/PyCQA/bandit 84 | rev: 1.8.0 85 | hooks: 86 | - id: bandit 87 | args: [-c, bandit.yaml] 88 | 89 | - repo: meta 90 | hooks: 91 | - id: check-hooks-apply 92 | - id: check-useless-excludes 93 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: '3.12' 7 | jobs: 8 | post_checkout: 9 | - git fetch --unshallow || true 10 | 11 | python: 12 | install: 13 | - requirements: requirements-doc.txt 14 | - requirements: requirements.txt 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "esbonio.sphinx.confDir": "${workspaceFolder}/docs" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========== 3 | 4 | 2.1 5 | --- 6 | 7 | .. changelog:: 8 | :version: 2.1.0 9 | :released: 10.01.2025 10 | 11 | .. change:: 12 | :tags: dependency, bugfix 13 | :tickets: 108 14 | 15 | Replace outdated ``toml`` package with modern ``tomli``. Thanks to :github-user:`graingert` 16 | 17 | .. change:: 18 | :tags: general, feature 19 | 20 | Test Python 3.13 compatibility 21 | 22 | 2.0 23 | --- 24 | 25 | .. changelog:: 26 | :version: 2.0.0 27 | :released: 08.04.2024 28 | 29 | .. change:: 30 | :tags: config, breaking 31 | 32 | Drop ``version_config`` keyword from ``setup.py``, which was deprecated since ``1.8.0``. 33 | 34 | .. change:: 35 | :tags: config, breaking 36 | 37 | Does not allow passing ``setuptools_git_versioning=False`` and ``setuptools_git_versioning=True`` to config. 38 | Always use ``setuptools_git_versioning={"enabled": True}``. 39 | 40 | .. change:: 41 | :tags: core, breaking 42 | 43 | Drop ``get_branch_tags`` function which was deprecated since ``1.8.0``. 44 | 45 | .. change:: 46 | :tags: core, breaking 47 | :tickets: 97,94 48 | 49 | ``version_file`` option now have precedence over any tags in the current branch. 50 | 51 | 1.13 52 | ---- 53 | 54 | .. changelog:: 55 | :version: 1.13.6 56 | :released: 30.01.2024 57 | 58 | .. change:: 59 | :tags: general, feature 60 | 61 | Test Python 3.12 compatibility 62 | 63 | .. change:: 64 | :tags: general, feature 65 | :tickets: 93 66 | 67 | Remove useless warning if ``tag_filter``, ``tag_formatter`` or ``branch_formatter`` is a regexp instead of function reference. 68 | 69 | .. changelog:: 70 | :version: 1.13.5 71 | :released: 08.08.2023 72 | 73 | .. change:: 74 | :tags: dependency, bugfix 75 | :pullreq: 84 76 | 77 | Import ``setuptools`` before importing ``distutils``. Thanks to :github-user:`tjni` 78 | 79 | .. changelog:: 80 | :version: 1.13.4 81 | :released: 31.07.2023 82 | 83 | .. change:: 84 | :tags: docs, feature 85 | :tickets: 83 86 | 87 | Update documentation to mention shallow clone issues. 88 | 89 | .. change:: 90 | :tags: general, feature 91 | 92 | Test PyPy 3.10 support 93 | 94 | .. change:: 95 | :tags: ci, feature 96 | 97 | Push release using ``Trusted publishers`` feature of PyPI.org 98 | 99 | .. changelog:: 100 | :version: 1.13.3 101 | :released: 14.03.2023 102 | 103 | .. change:: 104 | :tags: docs, bugfix 105 | :tickets: 78 106 | 107 | Move ``zip-safe`` option to ``tool.setuptools`` section of ``pyproject.toml``. 108 | 109 | Thanks to :github-user:`cclecle` 110 | 111 | .. changelog:: 112 | :version: 1.13.2 113 | :released: 26.02.2023 114 | 115 | .. change:: 116 | :tags: docs, feature 117 | :tickets: 77, 75 118 | 119 | Recommend users to use file-based schema instead of tag-based due some cases. 120 | 121 | .. change:: 122 | :tags: docs, feature 123 | :tickets: 17 124 | 125 | Add small example of ``.gitignore`` file to common issues section. Thanks to :github-user:`aram-eskandari` 126 | 127 | .. change:: 128 | :tags: docs, feature 129 | :tickets: 55 130 | 131 | Improve examples of fetching package version in runtime. 132 | 133 | .. changelog:: 134 | :version: 1.13.1 135 | :released: 13.11.2022 136 | 137 | .. change:: 138 | :tags: general, feature 139 | :tickets: 72, 49 140 | 141 | Build and publish sdist package again 142 | 143 | .. change:: 144 | :tags: general, bugfix 145 | :tickets: 72, 49 146 | 147 | Allow to install package from ``.tar.gz`` without ``--no-build-isolation`` flag 148 | 149 | .. change:: 150 | :tags: ci, feature 151 | 152 | Publish development releases to `Test PyPI `_ 153 | 154 | .. change:: 155 | :tags: ci, feature 156 | 157 | Use ``pypa/gh-action-pypi-publish`` Github action to publish releases to PyPI 158 | 159 | .. change:: 160 | :tags: ci, bugfix 161 | 162 | Remove local part of version because it is not allowed in PyPI 163 | 164 | .. change:: 165 | :tags: dependency, bugfix 166 | :tickets: 72 167 | 168 | Get rid of ``deprecated`` package dependency 169 | 170 | .. changelog:: 171 | :version: 1.13.0 172 | :released: 01.11.2022 173 | 174 | .. change:: 175 | :tags: dependency, feature 176 | 177 | For Python 3.11 use built-in ``tomllib`` instead of ``toml`` package 178 | 179 | .. change:: 180 | :tags: docs, feature 181 | :tickets: 55 182 | 183 | Add documentation about fetching package version in runtime 184 | 185 | .. change:: 186 | :tags: core, breaking 187 | 188 | Make all internal functions private 189 | 190 | .. change:: 191 | :tags: docs, feature 192 | 193 | Add description for some functions 194 | 195 | .. change:: 196 | :tags: core, feature 197 | :pullreq: 69 198 | :tickets: 68 199 | 200 | Add ``tag_filter`` option. Special thanks to :github-user:`vortechs2000` 201 | 202 | 1.12 203 | ---- 204 | 205 | .. changelog:: 206 | :version: 1.12.1 207 | :released: 24.10.2022 208 | 209 | .. change:: 210 | :tags: core, bug 211 | :tickets: 67 212 | 213 | Make version sanitization less strict, allow to automatically convert some cases, e.g. 214 | ``1.0.0+feature/abc`` to ``1.0.0+feature.abc`` 215 | 216 | .. changelog:: 217 | :version: 1.12.0 218 | :released: 13.10.2022 219 | 220 | .. change:: 221 | :tags: core, breaking 222 | 223 | Sanitize ``starting_version`` according :pep:`440` 224 | 225 | .. change:: 226 | :tags: core, breaking 227 | 228 | Do not remove leading non-numeric symbols from version number (except ``v``) 229 | 230 | 1.11 231 | ---- 232 | 233 | .. changelog:: 234 | :version: 1.11.0 235 | :released: 02.10.2022 236 | 237 | .. change:: 238 | :tags: core, feature 239 | :tickets: 58 240 | 241 | Allow ``setuptools-git-versioning`` script to infer version from ``setup.py`` if ``pyproject.toml`` is missing 242 | 243 | .. change:: 244 | :tags: core, breaking 245 | 246 | Raise error if ``pyproject.toml`` exists, but is not a file 247 | 248 | .. change:: 249 | :tags: core, feature 250 | 251 | Add ``cwd`` argument to most of functions, allowing to get versions of a specific repo without changing current directory 252 | 253 | .. change:: 254 | :tags: dev, feature 255 | 256 | Add info and debug messages to the module 257 | 258 | .. change:: 259 | :tags: docs, feature 260 | :tickets: 58 261 | 262 | Add documentation for ``setuptools-git-versioning`` script 263 | 264 | .. change:: 265 | :tags: tests, refactoring 266 | 267 | Use builtin type annotations (instead of type comments) in ``tests/lib/util.py``, 268 | use modern annotations syntax (``type | None`` instead of ``Optional[type]``) 269 | 270 | 1.10 271 | ---- 272 | 273 | .. changelog:: 274 | :version: 1.10.1 275 | :released: 03.09.2022 276 | 277 | .. change:: 278 | :tags: core, feature 279 | :tickets: 58 280 | 281 | Add ``setuptools-git-versioning`` script to infer version from ``pyproject.toml`` config 282 | 283 | .. change:: 284 | :tags: core, feature 285 | 286 | Check Python 3.11 support 287 | 288 | .. changelog:: 289 | :version: 1.10.0 290 | :released: 18.07.2022 291 | 292 | .. change:: 293 | :tags: core, breaking 294 | :tickets: 56 295 | 296 | :ref:`version-callback-option` option is used even if there are some tags in the current branch 297 | 298 | .. change:: 299 | :tags: config, feature 300 | 301 | Raise exception if both :ref:`version-callback-option` and :ref:`version-file-option` options are set 302 | 303 | .. change:: 304 | :tags: core, feature 305 | 306 | Remove all non-numeric symbols from version prefix, not just ``v`` 307 | 308 | 1.9 309 | ---- 310 | 311 | .. changelog:: 312 | :version: 1.9.2 313 | :released: 21.03.2022 314 | 315 | .. change:: 316 | :tags: general 317 | :tickets: 49 318 | 319 | Add ``setup_requires`` item to ``setup.py`` 320 | 321 | .. changelog:: 322 | :version: 1.9.1 323 | :released: 21.03.2022 324 | 325 | .. change:: 326 | :tags: general 327 | :tickets: 49 328 | 329 | Remove ``pyproject.toml`` file from ``.tag.gz`` package 330 | 331 | .. changelog:: 332 | :version: 1.9.0 333 | :released: 21.03.2022 334 | 335 | .. change:: 336 | :tags: general, breaking 337 | 338 | Drop Python 2.7, 3.5 and 3.6 support. Minimal supported Python version is now 3.7 339 | 340 | .. change:: 341 | :tags: core, feature 342 | :tickets: 49 343 | 344 | Do not fail on ``toml`` and ``packaging`` modules import while installing ``setuptools-git-versioning`` from ``tag.gz`` file 345 | 346 | .. change:: 347 | :tags: ci, bug 348 | 349 | Fix creating multiple releases for the same tag 350 | 351 | 1.8 352 | ---- 353 | 354 | .. changelog:: 355 | :version: 1.8.1 356 | :released: 10.01.2022 357 | 358 | .. change:: 359 | :tags: core, bug 360 | :tickets: 35 361 | 362 | Fix issue with empty ``pyproject.toml`` 363 | 364 | .. changelog:: 365 | :version: 1.8.0 366 | :released: 07.01.2022 367 | 368 | .. change:: 369 | :tags: general, breaking 370 | :pullreq: 37 371 | 372 | Drop Python 3.3 and 3.4 support 373 | 374 | .. change:: 375 | :tags: general, deprecated 376 | 377 | Python 2.7, 3.5 and 3.6 support is deprecated due to their end of life. 378 | 379 | .. change:: 380 | :tags: core, deprecated 381 | 382 | ``get_branch_tags`` function is renamed to ``get_tags``. 383 | 384 | It will be removed in ``2.0.0`` release. A warning message is added 385 | 386 | .. change:: 387 | :tags: config, deprecated 388 | 389 | ``version_config`` keyword in ``setup.py`` is renamed to ``setuptools_git_versioning``. 390 | 391 | It will be removed in ``2.0.0`` release. A warning message is added 392 | 393 | .. change:: 394 | :tags: config, deprecated 395 | 396 | Prefer using ``"enabled": True`` / ``"enabled": False`` option 397 | instead of pure boolean values (``True``, ``False``) for config. 398 | 399 | Old behavior is deprecated and will be removed in ``2.0`` version. A warning message is added 400 | 401 | .. change:: 402 | :tags: core, feature 403 | :pullreq: 37 404 | :tickets: 35 405 | 406 | Add support of reading config from ``pyproject.toml``. 407 | 408 | Thanks to :github-user:`Bloodmallet` 409 | 410 | .. change:: 411 | :tags: core, feature 412 | 413 | Allow to pass regexp to ``branch_formatter`` option 414 | 415 | .. change:: 416 | :tags: core, feature 417 | :tickets: 31 418 | 419 | Add ``tag_formatter`` option 420 | 421 | .. change:: 422 | :tags: core, feature 423 | 424 | Allow nested default values to be passed to ``env`` substitution 425 | 426 | .. change:: 427 | :tags: tests, feature 428 | 429 | Add integration tests 430 | 431 | .. change:: 432 | :tags: ci, feature 433 | 434 | Check test coverage and fail if it has been decreased 435 | 436 | .. change:: 437 | :tags: ci, feature 438 | 439 | Build docs using ReadTheDocs project 440 | 441 | .. change:: 442 | :tags: docs, feature 443 | 444 | Major docs improvement 445 | 446 | .. change:: 447 | :tags: docs, feature 448 | 449 | Added CHANGELOG.rst 450 | 451 | .. change:: 452 | :tags: docs 453 | 454 | Add ``miniver`` and ``versioningit`` to comparison table 455 | 456 | 1.7 457 | ---- 458 | 459 | .. changelog:: 460 | :version: 1.7.4 461 | :released: 31.10.2021 462 | 463 | .. change:: 464 | :tags: ci, feature 465 | 466 | Change release workflow action from ``actions/create-release@v1`` 467 | to ``softprops/action-gh-release@v1`` 468 | 469 | .. changelog:: 470 | :version: 1.7.3 471 | :released: 31.10.2021 472 | 473 | .. change:: 474 | :tags: ci, feature 475 | 476 | Change release workflow action from ``actions/create-release@v1`` 477 | to ``softprops/action-gh-release@v1`` 478 | 479 | .. changelog:: 480 | :version: 1.7.2 481 | :released: 28.10.2021 482 | 483 | .. change:: 484 | :tags: core, feature 485 | :tickets: 29 486 | 487 | String leading 'v' symbol from tag name 488 | 489 | .. changelog:: 490 | :version: 1.7.1 491 | :released: 28.10.2021 492 | 493 | .. change:: 494 | :tags: core, feature 495 | :tickets: 29 496 | 497 | String leading 'v' symbol from tag name 498 | 499 | .. changelog:: 500 | :version: 1.7.0 501 | :released: 21.09.2021 502 | 503 | .. change:: 504 | :tags: core, feature 505 | 506 | Add support of ``env`` variables substitution 507 | 508 | .. change:: 509 | :tags: core, feature 510 | 511 | Add support of ``timestamp`` substitution 512 | 513 | 514 | 1.6 515 | ---- 516 | 517 | .. changelog:: 518 | :version: 1.6.1 519 | :released: 16.09.2021 520 | 521 | .. change:: 522 | :tags: core, bug 523 | :tickets: 23 524 | 525 | Fix sorting for annotated tags 526 | 527 | .. changelog:: 528 | :version: 1.6.0 529 | :released: 15.09.2021 530 | 531 | .. change:: 532 | :tags: ci, bug 533 | 534 | Fix skipping duplicated runs 535 | 536 | .. change:: 537 | :tags: ci, feature 538 | 539 | Add automerge action for ``precommit-ci`` bot 540 | 541 | .. change:: 542 | :tags: ci, bug 543 | 544 | Fix tests workflow 545 | 546 | .. change:: 547 | :tags: core, bug 548 | :tickets: 22 549 | 550 | Sort tags by commit date instead of name 551 | 552 | .. change:: 553 | :tags: ci, bug 554 | 555 | Fix release pipeline 556 | 557 | 558 | 559 | 1.5 560 | ---- 561 | 562 | .. changelog:: 563 | :version: 1.5.0 564 | :released: 16.08.2021 565 | 566 | .. change:: 567 | :tags: docs 568 | :tickets: 15 569 | 570 | Add ``setuptools-scm`` and ``versioneer`` to comparison table 571 | 572 | .. change:: 573 | :tags: docs, feature 574 | :tickets: 17 575 | :pullreq: 16 576 | 577 | Add resolution for issue when all versions produced by CI pipeline are ``dirty`` 578 | 579 | .. change:: 580 | :tags: ci, feature 581 | 582 | Skip duplicated Github Actions runs 583 | 584 | .. change:: 585 | :tags: dev, feature 586 | 587 | Add ``pre-commit`` hooks and commit changes made by it 588 | 589 | 590 | 1.4 591 | ---- 592 | 593 | .. changelog:: 594 | :version: 1.4.0 595 | :released: 12.05.2021 596 | 597 | .. change:: 598 | :tags: ci, bug 599 | 600 | Use absolute paths in ``setup.py`` 601 | 602 | .. change:: 603 | :tags: dev, bug 604 | :pullreq: 13 605 | 606 | Add JetBrains config files to ``.gitignore``. 607 | 608 | Thanks to :github-user:`LeComptoirDesPharmacies` 609 | 610 | .. change:: 611 | :tags: core, feature 612 | :pullreq: 14 613 | 614 | Add ``branch_formatter`` option. 615 | 616 | Thanks to :github-user:`LeComptoirDesPharmacies` 617 | 618 | 619 | 1.3 620 | ---- 621 | 622 | .. changelog:: 623 | :version: 1.3.6 624 | :released: 12.03.2021 625 | 626 | .. change:: 627 | :tags: general, bug 628 | 629 | Update package short description 630 | 631 | .. change:: 632 | :tags: general 633 | 634 | Set license in ``setup.py`` file 635 | 636 | .. changelog:: 637 | :version: 1.3.5 638 | :released: 12.03.2021 639 | 640 | .. change:: 641 | :tags: docs, bug 642 | 643 | Fix comparison table typo 644 | 645 | .. change:: 646 | :tags: docs, feature 647 | 648 | Add license column into comparison table 649 | 650 | .. changelog:: 651 | :version: 1.3.4 652 | :released: 12.03.2021 653 | 654 | .. change:: 655 | :tags: docs, feature 656 | 657 | Add list of supported substitutions into comparison table 658 | 659 | .. change:: 660 | :tags: docs 661 | 662 | Add ``bad-setuptools-git-version`` and ``another-setuptools-git-version`` 663 | to comparison table 664 | 665 | .. changelog:: 666 | :version: 1.3.3 667 | :released: 12.03.2021 668 | 669 | .. change:: 670 | :tags: core, bug 671 | :pullreq: 11 672 | 673 | Replace forbidden chars in local version label. 674 | 675 | Thanks to :github-user:`ajasmin` 676 | 677 | .. changelog:: 678 | :version: 1.3.2 679 | :released: 12.03.2021 680 | 681 | .. change:: 682 | :tags: docs, bug 683 | 684 | Fix minor typos in documentation 685 | 686 | .. changelog:: 687 | :version: 1.3.1 688 | :released: 12.03.2021 689 | 690 | .. change:: 691 | :tags: docs, feature 692 | :tickets: 8 693 | 694 | Add Windows support column into comparison table 695 | 696 | .. change:: 697 | :tags: ci, bug 698 | 699 | Fix Github Actions 700 | 701 | .. change:: 702 | :tags: core, bug 703 | :tickets: 10 704 | 705 | Replace default suffix for dev and dirty versions from ``dev`` to ``post`` 706 | 707 | .. change:: 708 | :tags: docs, feature 709 | :tickets: 10 710 | 711 | Major documentation update 712 | 713 | .. changelog:: 714 | :version: 1.3.0 715 | :released: 01.03.2021 716 | 717 | .. change:: 718 | :tags: core, feature 719 | :tickets: 9 720 | 721 | Add ``full_sha`` substitution support 722 | 723 | 724 | 1.2 725 | ---- 726 | 727 | .. changelog:: 728 | :version: 1.2.10 729 | :released: 04.02.2021 730 | 731 | .. change:: 732 | :tags: ci, bug 733 | :pullreq: 7 734 | 735 | Fix release workflow 736 | 737 | .. change:: 738 | :tags: ci, feature 739 | :pullreq: 7 740 | 741 | Add some issue and PR automatization 742 | 743 | .. change:: 744 | :tags: core, bug 745 | :tickets: 8 746 | 747 | Fix Windows compatibility 748 | 749 | .. changelog:: 750 | :version: 1.2.9 751 | :released: 20.01.2021 752 | 753 | .. change:: 754 | :tags: ci, feature 755 | :pullreq: 7 756 | 757 | Use Github Actions instead of TravisCI 758 | 759 | .. changelog:: 760 | :version: 1.2.8 761 | :released: 29.11.2020 762 | 763 | .. change:: 764 | :tags: docs, bug 765 | :pullreq: 6 766 | 767 | Fixed typo in code examples. 768 | 769 | Thanks to :github-user:`Stedders` 770 | 771 | .. changelog:: 772 | :version: 1.2.7 773 | :released: 24.11.2020 774 | 775 | .. change:: 776 | :tags: core, bug 777 | :pullreq: 5 778 | 779 | Fix python error if no tag is found. 780 | 781 | Thanks to :github-user:`bmiklautz` 782 | 783 | .. changelog:: 784 | :version: 1.2.6 785 | :released: 07.10.2020 786 | 787 | .. change:: 788 | :tags: core, bug 789 | 790 | Fix version detection in case of missing .git folder 791 | 792 | .. changelog:: 793 | :version: 1.2.5 794 | :released: 30.09.2020 795 | 796 | .. change:: 797 | :tags: dependency, bug 798 | 799 | Fix Python 2.7 dependencies 800 | 801 | .. changelog:: 802 | :version: 1.2.4 803 | :released: 30.09.2020 804 | 805 | .. change:: 806 | :tags: dependency, bug 807 | 808 | Fix Python 2.7 dependencies 809 | 810 | .. changelog:: 811 | :version: 1.2.3 812 | :released: 16.09.2020 813 | 814 | .. change:: 815 | :tags: core, feature 816 | 817 | Add ``get_all_tags`` function 818 | 819 | .. change:: 820 | :tags: core, feature 821 | 822 | Add ``get_branch_tags`` function 823 | 824 | .. changelog:: 825 | :version: 1.2.2 826 | :released: 14.09.2020 827 | 828 | .. change:: 829 | :tags: core, bug 830 | 831 | Fix building version from VERSION file 832 | 833 | .. changelog:: 834 | :version: 1.2.1 835 | :released: 10.09.2020 836 | 837 | .. change:: 838 | :tags: core, feature 839 | 840 | Add ``count_commits_from_version_file`` option 841 | 842 | .. changelog:: 843 | :version: 1.2.0 844 | :released: 10.09.2020 845 | 846 | .. change:: 847 | :tags: core, feature 848 | 849 | Add ``version_file`` option 850 | 851 | 852 | 1.1 853 | ---- 854 | .. changelog:: 855 | :version: 1.1.14 856 | :released: 10.09.2020 857 | 858 | .. change:: 859 | :tags: core, feature 860 | 861 | Add ``version_callback`` option 862 | 863 | .. changelog:: 864 | :version: 1.1.13 865 | :released: 21.08.2020 866 | 867 | .. change:: 868 | :tags: ci, bug 869 | :tickets: 4 870 | 871 | Use ``six`` module for accessing ``collections.abc`` 872 | 873 | .. changelog:: 874 | :version: 1.1.12 875 | :released: 20.08.2020 876 | 877 | .. change:: 878 | :tags: ci, bug 879 | :tickets: 4 880 | 881 | Fix package name misspell 882 | 883 | .. changelog:: 884 | :version: 1.1.11 885 | :released: 18.08.2020 886 | 887 | .. change:: 888 | :tags: dependency, bug 889 | 890 | Remove ``flake8`` from ``requirements.txt`` 891 | 892 | .. changelog:: 893 | :version: 1.1.10 894 | :released: 18.08.2020 895 | 896 | .. change:: 897 | :tags: dependency, bug 898 | 899 | Make ``setuptools`` version check less strict 900 | 901 | .. changelog:: 902 | :version: 1.1.9 903 | :released: 17.08.2020 904 | 905 | .. change:: 906 | :tags: general, feature 907 | 908 | Test Python 3.9 support 909 | 910 | .. change:: 911 | :tags: ci, bug 912 | :tickets: 3 913 | 914 | Include ``requirements.txt`` into ``.tar.gz`` file 915 | 916 | .. changelog:: 917 | :version: 1.1.8 918 | :released: 14.08.2020 919 | 920 | .. change:: 921 | :tags: general, feature 922 | 923 | Add Python 3.3 and 3.4 support 924 | 925 | .. changelog:: 926 | :version: 1.1.7 927 | :released: 10.08.2020 928 | 929 | .. change:: 930 | :tags: ci, bug 931 | 932 | Fix TravisCI deploy 933 | 934 | .. changelog:: 935 | :version: 1.1.6 936 | 937 | .. change:: 938 | :tags: core, feature 939 | 940 | Add backward compatibility with ``git`` < 2.2 941 | 942 | .. change:: 943 | :tags: docs, feature 944 | 945 | Add supported python versions badge 946 | 947 | .. changelog:: 948 | :version: 1.1.5 949 | :released: 07.08.2020 950 | 951 | .. change:: 952 | :tags: core, bug 953 | :pullreq: 1 954 | 955 | Fix runtime error on Python 3.3 and 3.4. 956 | 957 | Thanks to :github-user:`WildCard65` 958 | 959 | .. changelog:: 960 | :version: 1.1.4 961 | :released: 07.08.2020 962 | 963 | .. change:: 964 | :tags: core, feature 965 | 966 | Add ``branch`` substitution support 967 | 968 | .. changelog:: 969 | :version: 1.1.3 970 | :released: 30.07.2020 971 | 972 | .. change:: 973 | :tags: core, feature 974 | 975 | Add ``starting_version`` option 976 | 977 | .. change:: 978 | :tags: ci, bug 979 | 980 | Fix TravisCI build 981 | 982 | .. changelog:: 983 | :version: 1.1.2 984 | :released: 29.07.2020 985 | 986 | .. change:: 987 | :tags: ci, bug 988 | 989 | Fix Python 2 version build 990 | 991 | .. change:: 992 | :tags: dependency, bug 993 | 994 | Fix ``requirements.txt`` 995 | 996 | .. changelog:: 997 | :version: 1.1.1 998 | 999 | .. change:: 1000 | :tags: general, feature 1001 | 1002 | Change package name to ``setuptools-git-versioning`` and publish it on PyPi.org 1003 | 1004 | .. changelog:: 1005 | :version: 1.1.0 1006 | 1007 | .. change:: 1008 | :tags: general 1009 | 1010 | Create fork of unmaintained repo `setuptools-git-ver `_ 1011 | 1012 | .. change:: 1013 | :tags: core, feature 1014 | 1015 | Added Python2 support. 1016 | 1017 | Typehints moved to comments section. 1018 | Python 3 syntax replaced with Python 2 compatible one 1019 | 1020 | .. change:: 1021 | :tags: core, feature 1022 | 1023 | Make internal functions public 1024 | 1025 | .. change:: 1026 | :tags: core, feature 1027 | 1028 | Add ``get_tags`` method 1029 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dolfinus/setuptools-git-versioning/c6e010d8f35a8613e90ed93d499d14e2516365f0/CONTRIBUTING.rst -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-present dolfinus 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE requirements.txt 2 | exclude pyproject.toml requirements-test.txt requirements-dev.txt requirements-doc.txt 3 | recursive-exclude * __pycache__ 4 | recursive-exclude * *.pyc 5 | recursive-exclude * *.pyo 6 | recursive-exclude * *.orig 7 | recursive-exclude docs * 8 | recursive-exclude tests * 9 | prune docs* 10 | prune tests* 11 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ************************* 2 | setuptools-git-versioning 3 | ************************* 4 | 5 | |status| |PyPI| |PyPI License| |PyPI Python Version| 6 | |ReadTheDocs| |Build| |Coverage| |pre-commit.ci| 7 | 8 | .. |status| image:: https://www.repostatus.org/badges/latest/active.svg 9 | :alt: Project Status: Active – The project has reached a stable, usable state and is being actively developed. 10 | :target: https://www.repostatus.org/#active 11 | .. |PyPI| image:: https://badge.fury.io/py/setuptools-git-versioning.svg 12 | :target: https://badge.fury.io/py/setuptools-git-versioning 13 | .. |PyPI License| image:: https://img.shields.io/pypi/l/setuptools-git-versioning.svg 14 | :target: https://github.com/dolfinus/setuptools-git-versioning/blob/master/LICENSE 15 | .. |PyPI Python Version| image:: https://img.shields.io/pypi/pyversions/setuptools-git-versioning.svg 16 | :target: https://badge.fury.io/py/setuptools-git-versioning 17 | .. |ReadTheDocs| image:: https://img.shields.io/readthedocs/setuptools-git-versioning.svg 18 | :target: https://setuptools-git-versioning.readthedocs.io 19 | .. |Build| image:: https://github.com/dolfinus/setuptools-git-versioning/workflows/Tests/badge.svg 20 | :target: https://github.com/dolfinus/setuptools-git-versioning/actions 21 | .. |Coverage| image:: https://codecov.io/gh/dolfinus/setuptools-git-versioning/branch/master/graph/badge.svg?token=GIMVHUTNW4 22 | :target: https://codecov.io/gh/dolfinus/setuptools-git-versioning 23 | .. |pre-commit.ci| image:: https://results.pre-commit.ci/badge/github/dolfinus/setuptools-git-versioning/master.svg 24 | :target: https://results.pre-commit.ci/latest/github/dolfinus/setuptools-git-versioning/master 25 | 26 | Use git repo data (latest tag, current commit hash, etc) for building a 27 | version number according :pep:`440`. 28 | 29 | **Features:** 30 | 31 | - Can be installed & configured through both ``setup.py`` and :pep:`518`'s ``pyproject.toml`` 32 | 33 | - Does not require to change source code of the project 34 | 35 | - Tag-, file-, and callback-based versioning schemas are supported 36 | 37 | - Templates for *tag*, *dev* and *dirty* versions are separated 38 | 39 | - Templates support a lot of substitutions including git and environment information 40 | 41 | - Well-documented 42 | 43 | See `comparison `_ 44 | between ``setuptools-git-versioning`` and other tools. 45 | 46 | **Limitations:** 47 | 48 | - Currently the only supported VCS is *Git* 49 | 50 | - Only Git v2 is supported 51 | 52 | - Only Setuptools build backend is supported (no Poetry & others) 53 | 54 | - Currently does not support automatic exporting of package version to a file for runtime use 55 | (but you can use ``setuptools-git-versioning > file`` redirect instead) 56 | 57 | .. documentation 58 | 59 | Documentation 60 | -------------- 61 | 62 | See https://setuptools-git-versioning.readthedocs.io/en/stable/ 63 | 64 | .. contribution 65 | 66 | Contribution Guide 67 | ------------------ 68 | 69 | See ./CONTRIBUTING.rst 70 | 71 | .. installation 72 | 73 | Install 74 | ------------ 75 | 76 | ``pyproject.toml`` 77 | ~~~~~~~~~~~~~~~~~~ 78 | 79 | Just add ``setuptools-git-versioning`` to ``build-sytem`` section of your ``pyproject.toml``, 80 | add a section ``tool.setuptools-git-versioning`` with config options, and mark the project 81 | ``version`` as dynamic. 82 | 83 | .. code:: toml 84 | 85 | [build-system] 86 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 87 | build-backend = "setuptools.build_meta" 88 | 89 | [tool.setuptools-git-versioning] 90 | enabled = true 91 | 92 | [project] 93 | dynamic = ["version"] 94 | 95 | And check the package version generated (see `command help `_): 96 | 97 | .. code:: bash 98 | 99 | $ python -m setuptools_git_versioning 100 | 0.0.1 101 | 102 | # or 103 | 104 | $ setuptools-git-versioning 105 | 0.0.1 106 | 107 | ``setup.py`` 108 | ~~~~~~~~~~~~ 109 | 110 | Just add ``setuptools-git-versioning`` to ``setup_requires`` argument of ``setuptools.setup`` function call, 111 | and then add new argument ``setuptools_git_versioning`` with config options: 112 | 113 | .. code:: python 114 | 115 | import setuptools 116 | 117 | setuptools.setup( 118 | ..., 119 | setuptools_git_versioning={ 120 | "enabled": True, 121 | }, 122 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 123 | ) 124 | 125 | And check the package version generated (see `command help `_): 126 | 127 | .. code:: bash 128 | 129 | $ python setup.py --version 130 | 0.0.1 131 | 132 | # or 133 | 134 | $ python -m setuptools_git_versioning 135 | 0.0.1 136 | 137 | # or 138 | 139 | $ setuptools-git-versioning 140 | 0.0.1 141 | -------------------------------------------------------------------------------- /bandit.yaml: -------------------------------------------------------------------------------- 1 | exclude_dirs: [tests, docs] 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /docs/ci.rst: -------------------------------------------------------------------------------- 1 | .. _ci: 2 | 3 | CI configuration 4 | ---------------- 5 | 6 | By default, CI workflows use shallow clone of the repo to speed up clone process. 7 | But this leads to cloning repo without any tags, and thus generating version number like ``0.0.1``. 8 | 9 | To avoid this, please use following settings: 10 | 11 | .. code-block:: yaml 12 | :caption: Github Actions 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | .. code-block:: yaml 21 | :caption: Gitlab CI 22 | 23 | variables: 24 | GIT_DEPTH: 0 25 | -------------------------------------------------------------------------------- /docs/command.rst: -------------------------------------------------------------------------------- 1 | .. _command: 2 | 3 | Console command 4 | --------------- 5 | 6 | Package contains script `setuptools-git-versioning` which can be used for calculating version number.\ 7 | 8 | See :ref:`_installation` instruction for creating the repo with your package. 9 | 10 | To get current package version in `mypackage` repo just execute: 11 | 12 | .. code:: bash 13 | 14 | $ cd /path/to/mypackage 15 | $ setuptools-git-versioning 16 | 0.0.1 17 | 18 | # or pass path to your repo explicitly 19 | 20 | $ setuptools-git-versioning /path/to/mypackage 21 | 0.0.1 22 | 23 | Version will be printed to ``stdout``. 24 | 25 | This script is a wrapper for ``setuptools_git_versioning`` module, you can just call it: 26 | 27 | .. code:: bash 28 | 29 | $ python -m setuptools_git_versioning /path/to/mypackage 30 | 0.0.1 31 | 32 | ``-v`` option enables verbose output which is useful for debugging, messages are printed to ``stderr``: 33 | 34 | .. code:: bash 35 | 36 | $ setuptools-git-versioning /path/to/mypackage -v 37 | 38 | INF0: No explicit config passed 39 | INF0: Searching for config files in '/path/to/mypackage' folder 40 | INF0: Trying 'setup.py' ... 41 | INF0: '/path/to/mypackage/pyproject.toml' does not exist 42 | INF0: Getting latest tag 43 | INF0: Latest tag: '1.0.0' 44 | INF0: Tag SHA-256: '8dc9881eacd373cb34c5d3f99a6ad9e2349a79c4' 45 | INF0: Parsing tag_formatter 'util:tag_formatter' of type 'str' 46 | INF0: Is dirty: False 47 | INF0: HEAD SHA-256: '8dc9881eacd373cb34c5d3f99a6ad9e2349a79c4' 48 | INF0: Commits count between HEAD and latest tag: 0 49 | INF0: HEAD is tagged: Truelog.INF0("'%s' does not exist", file_path) 50 | INF0: Current branch: 'master' 51 | INF0: Using template from 'template' option 52 | INF0: Version number after resolving substitutions: '1.0.0' 53 | INF0: Result: '1.0.0' 54 | 55 | 1.0.0 56 | 57 | 58 | ``-vV`` shows even more debug messages: 59 | 60 | .. code:: bash 61 | 62 | $ setuptools-git-versioning /path/to/mypackage -vV 63 | 64 | INF0: No explicit config passed 65 | INF0: Searching for config files in '/path/to/mypackage' folder 66 | INF0: Trying 'setup.py' ... 67 | DE8UG: Adding '/path/to/mypackage' folder to sys.path 68 | INF0: '/path/to/mypackage/pyproject.toml' does not exist 69 | INF0: Getting latest tag 70 | DE8UG: Sorting tags by 'creatordate' 71 | DE8UG: Executing 'git tag --sort=-creatordate --merged' at '/path/to/mypackage' 72 | INF0: Latest tag: '1.0.0' 73 | DE8UG: Executing 'git rev-list -n 1 "1.0.0"' at '/path/to/mypackage' 74 | INF0: Tag SHA-256: '8dc9881eacd373cb34c5d3f99a6ad9e2349a79c4' 75 | INF0: Parsing tag_formatter 'util:tag_formatter' of type 'str' 76 | DE8UG: Executing 'from my_module.util import tag_formatter' 77 | DE8UG: Tag after formatting: '1.0.0' 78 | DE8UG: Executing 'git status --short' at '/path/to/mypackage' 79 | INF0: Is dirty: False 80 | DE8UG: Executing 'git rev-list -n 1 "HEAD"' at '/path/to/mypackage' 81 | INF0: HEAD SHA-256: '8dc9881eacd373cb34c5d3f99a6ad9e2349a79c4' 82 | DE8UG: Executing 'git rev-list --count HEAD "^8dc9881eacd373cb34c5d3f99a6ad9e2349a79c4"' at '/path/to/mypackage' 83 | INF0: Commits count between HEAD and latest tag: 0 84 | INF0: HEAD is tagged: True 85 | DE8UG: Executing 'git rev-parse --abbrev-ref HEAD' at '/path/to/mypackage' 86 | INF0: Current branch: 'master' 87 | INF0: Using template from 'template' option 88 | DE8UG: Template: '{tag}' 89 | DE8UG: Args:() 90 | INF0: Version number after resolving substitutions: '1.0.0' 91 | INF0: Result: '1.0.0' 92 | 93 | 1.0.0 94 | 95 | 96 | Command help 97 | ~~~~~~~~~~~~~ 98 | 99 | .. argparse:: 100 | :module: setuptools_git_versioning 101 | :func: _parser 102 | :prog: setuptools-git-versioning 103 | -------------------------------------------------------------------------------- /docs/common_issues/all_dirty.rst: -------------------------------------------------------------------------------- 1 | .. _all-dirty-issue: 2 | 3 | Every version built by CI is ``dirty`` 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | This is usually caused by some files created by CI pipeline like build 7 | artifacts or test reports, e.g. ``dist/my_package.whl`` or 8 | ``reports/unit.xml``. If these files are not mentioned in ``.gitignore`` file 9 | they will be recognized by git as untracked. Because of that 10 | ``git status`` will report that you have **uncommitted (dirty) changes** in 11 | the index, so ``setuptools-git-versioning`` will detect current version 12 | as ``dirty``. 13 | 14 | You should such files to the ``.gitignore`` file. In most the cases adding these lines solves the issue: 15 | 16 | .. code:: gitignore 17 | 18 | build/ 19 | dist/ 20 | eggs/ 21 | *.egg* 22 | venv 23 | 24 | See `full example `_. 25 | -------------------------------------------------------------------------------- /docs/common_issues/index.rst: -------------------------------------------------------------------------------- 1 | Common issues 2 | -------------- 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | all_dirty 8 | wrong_tag 9 | -------------------------------------------------------------------------------- /docs/common_issues/wrong_tag.rst: -------------------------------------------------------------------------------- 1 | .. _wrong-tag-issue: 2 | 3 | Wrong tag ordering/latest tag number detection 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | This could be caused by some unusual way of creating tags in your repo. 7 | 8 | Annotated tags 9 | ^^^^^^^^^^^^^^ 10 | 11 | If you're using annotated tags in your git repo, please avoid creating them in non-chronological order. 12 | This can cause issues with detecting versions of existing commits. 13 | 14 | Multiple tags on commit 15 | ^^^^^^^^^^^^^^^^^^^^^^^ 16 | 17 | Please do not add multiple tags for the same commit. 18 | It is impossible to automatically determine which one is latest because of ambiguity - 19 | both tag and commit have a creation date, and it is not possible to sort tags by multiple columns. 20 | 21 | See also 22 | ^^^^^^^^ 23 | - :ref:`sort-by-option` option 24 | -------------------------------------------------------------------------------- /docs/comparison.rst: -------------------------------------------------------------------------------- 1 | Comparison with other tools 2 | =========================== 3 | 4 | Alternatives 5 | ------------ 6 | - `setuptools-scm `_ 7 | - `versioneer `_ 8 | - `versioningit `_ 9 | - `dunamai `_ 10 | - `miniver `_ 11 | - `setuptools-git-ver `_ (Base package) 12 | 13 | General information 14 | ------------------- 15 | 16 | +---------------------------+-----------------+----------------+--------------------+ 17 | | Package | Latest release | License | PEP 440 compatible | 18 | +===========================+=================+================+====================+ 19 | | setuptools-git-versioning | 2025 | MIT | yes | 20 | +---------------------------+-----------------+----------------+--------------------+ 21 | | setuptools-scm | 2023 | MIT | yes | 22 | +---------------------------+-----------------+----------------+--------------------+ 23 | | versioneer | 2023 | Public domain | yes | 24 | +---------------------------+-----------------+----------------+--------------------+ 25 | | versioningit | 2024 | MIT | yes | 26 | +---------------------------+-----------------+----------------+--------------------+ 27 | | dunamai | 2024 | MIT | yes | 28 | +---------------------------+-----------------+----------------+--------------------+ 29 | | miniver | 2021 | Public domain | no | 30 | +---------------------------+-----------------+----------------+--------------------+ 31 | | setuptools-git-ver | 2019 | MIT | no | 32 | +---------------------------+-----------------+----------------+--------------------+ 33 | 34 | VCS support 35 | ------------------- 36 | 37 | +---------------------------+-----+-----------+-------------------------------+ 38 | | Package | Git | Mercurial | Can be used in git submodules | 39 | +===========================+=====+===========+===============================+ 40 | | setuptools-git-versioning | yes | no | yes | 41 | +---------------------------+-----+-----------+-------------------------------+ 42 | | setuptools-scm | yes | yes | yes | 43 | +---------------------------+-----+-----------+-------------------------------+ 44 | | versioneer | yes | no | no | 45 | +---------------------------+-----+-----------+-------------------------------+ 46 | | versioningit | yes | yes | no | 47 | +---------------------------+-----+-----------+-------------------------------+ 48 | | dunamai | yes | yes | no | 49 | +---------------------------+-----+-----------+-------------------------------+ 50 | | miniver | yes | no | no | 51 | +---------------------------+-----+-----------+-------------------------------+ 52 | | setuptools-git-ver | yes | no | no | 53 | +---------------------------+-----+-----------+-------------------------------+ 54 | 55 | Development 56 | ------------ 57 | 58 | +----------------------------+---------------+-------+-----------+-------+------------+ 59 | | Package | Documentation | Tests | Changelog | CI/CD | pre-commit | 60 | +============================+===============+=======+===========+=======+============+ 61 | | setuptools-git-versioning | site | yes | yes | yes | yes | 62 | +----------------------------+---------------+-------+-----------+-------+------------+ 63 | | setuptools-scm | site | yes | yes | yes | yes | 64 | +----------------------------+---------------+-------+-----------+-------+------------+ 65 | | versioneer | repo | yes | yes | yes | no | 66 | +----------------------------+---------------+-------+-----------+-------+------------+ 67 | | versioningit | site | yes | yes | yes | yes | 68 | +----------------------------+---------------+-------+-----------+-------+------------+ 69 | | dunamai | site | yes | yes | yes | yes | 70 | +----------------------------+---------------+-------+-----------+-------+------------+ 71 | | miniver | readme | yes | yes | yes | no | 72 | +----------------------------+---------------+-------+-----------+-------+------------+ 73 | | setuptools-git-ver | readme | no | no | no | no | 74 | +----------------------------+---------------+-------+-----------+-------+------------+ 75 | 76 | Python version support 77 | ---------------------- 78 | 79 | +---------------------------+----------------+------------+--------------+ 80 | | Package | Python support | Type hints | PyPy support | 81 | +===========================+================+============+==============+ 82 | | setuptools-git-versioning | 3.7+ | yes | yes | 83 | +---------------------------+----------------+------------+--------------+ 84 | | setuptools-scm | 3.7+ | yes | unknown | 85 | +---------------------------+----------------+------------+--------------+ 86 | | versioneer | 3.7+ | no | unknown | 87 | +---------------------------+----------------+------------+--------------+ 88 | | versioningit | 3.6+ | yes | yes | 89 | +---------------------------+----------------+------------+--------------+ 90 | | dunamai | 3.5+ | yes | unknown | 91 | +---------------------------+----------------+------------+--------------+ 92 | | miniver | 3.5+ | no | unknown | 93 | +---------------------------+----------------+------------+--------------+ 94 | | setuptools-git-ver | 3.7+ | no | unknown | 95 | +---------------------------+----------------+------------+--------------+ 96 | 97 | OS support 98 | ----------- 99 | 100 | +---------------------------+-------+-------+---------+ 101 | | Package | Linux | MacOS | Windows | 102 | +===========================+=======+=======+=========+ 103 | | setuptools-git-versioning | yes | yes | yes | 104 | +---------------------------+-------+-------+---------+ 105 | | setuptools-scm | yes | yes | yes | 106 | +---------------------------+-------+-------+---------+ 107 | | versioneer | yes | yes | yes | 108 | +---------------------------+-------+-------+---------+ 109 | | versioningit | yes | yes | yes | 110 | +---------------------------+-------+-------+---------+ 111 | | dunamai | yes | yes | yes | 112 | +---------------------------+-------+-------+---------+ 113 | | miniver | yes | yes | yes | 114 | +---------------------------+-------+-------+---------+ 115 | | setuptools-git-ver | yes | yes | yes | 116 | +---------------------------+-------+-------+---------+ 117 | 118 | Configuration 119 | ------------------- 120 | 121 | +---------------------------+----------------+------------+------------+ 122 | | Package | pyproject.toml | setup.py | setup.cfg | 123 | +===========================+================+============+============+ 124 | | setuptools-git-versioning | yes | yes | no | 125 | +---------------------------+----------------+------------+------------+ 126 | | setuptools-scm | yes | deprecated | deprecated | 127 | +---------------------------+----------------+------------+------------+ 128 | | versioneer | no | deprecated | yes | 129 | +---------------------------+----------------+------------+------------+ 130 | | versioningit | yes | yes | no | 131 | +---------------------------+----------------+------------+------------+ 132 | | dunamai | yes | yes | no | 133 | +---------------------------+----------------+------------+------------+ 134 | | miniver | no | yes | no | 135 | +---------------------------+----------------+------------+------------+ 136 | | setuptools-git-ver | no | yes | no | 137 | +---------------------------+----------------+------------+------------+ 138 | 139 | :ref:`Substitutions ` 140 | ------------------------------------ 141 | 142 | +---------------------------+---------------+-----------+----------+ 143 | | Package | Commits count | Short SHA | Full SHA | 144 | +===========================+===============+===========+==========+ 145 | | setuptools-git-versioning | yes | yes | yes | 146 | +---------------------------+---------------+-----------+----------+ 147 | | setuptools-scm | yes | yes | no | 148 | +---------------------------+---------------+-----------+----------+ 149 | | versioneer | yes | yes | yes | 150 | +---------------------------+---------------+-----------+----------+ 151 | | versioningit | yes | yes | yes | 152 | +---------------------------+---------------+-----------+----------+ 153 | | dunamai | yes | yes | yes | 154 | +---------------------------+---------------+-----------+----------+ 155 | | miniver | yes | yes | no | 156 | +---------------------------+---------------+-----------+----------+ 157 | | setuptools-git-ver | yes | yes | no | 158 | +---------------------------+---------------+-----------+----------+ 159 | 160 | +---------------------------+--------+--------------------+ 161 | | Package | Branch | Branch name escape | 162 | +===========================+========+====================+ 163 | | setuptools-git-versioning | yes | yes | 164 | +---------------------------+--------+--------------------+ 165 | | setuptools-scm | no | no | 166 | +---------------------------+--------+--------------------+ 167 | | versioneer | no | no | 168 | +---------------------------+--------+--------------------+ 169 | | versioningit | yes | no | 170 | +---------------------------+--------+--------------------+ 171 | | dunamai | yes | yes | 172 | +---------------------------+--------+--------------------+ 173 | | miniver | no | no | 174 | +---------------------------+--------+--------------------+ 175 | | setuptools-git-ver | no | no | 176 | +---------------------------+--------+--------------------+ 177 | 178 | +---------------------------+---------------+------------------+-------------------+--------------+ 179 | | Package | Tag timestamp | Commit timestamp | Current timestamp | Env variable | 180 | +===========================+===============+==================+===================+==============+ 181 | | setuptools-git-versioning | no | no | yes | yes | 182 | +---------------------------+---------------+------------------+-------------------+--------------+ 183 | | setuptools-scm | no | no | no | no | 184 | +---------------------------+---------------+------------------+-------------------+--------------+ 185 | | versioneer | no | no | no | no | 186 | +---------------------------+---------------+------------------+-------------------+--------------+ 187 | | versioningit | yes | yes | yes | no | 188 | +---------------------------+---------------+------------------+-------------------+--------------+ 189 | | dunamai | no | yes | no | no | 190 | +---------------------------+---------------+------------------+-------------------+--------------+ 191 | | miniver | no | no | no | no | 192 | +---------------------------+---------------+------------------+-------------------+--------------+ 193 | | setuptools-git-ver | no | no | no | no | 194 | +---------------------------+---------------+------------------+-------------------+--------------+ 195 | 196 | :ref:`Tag-based versioning ` 197 | ----------------------------------------------- 198 | 199 | +---------------------------+-------------------------+---------------+-----------------+ 200 | | Package | Post (distance) version | Dirty version | Initial version | 201 | +===========================+=========================+===============+=================+ 202 | | setuptools-git-versioning | yes | yes | yes | 203 | +---------------------------+-------------------------+---------------+-----------------+ 204 | | setuptools-scm | yes | yes | no | 205 | +---------------------------+-------------------------+---------------+-----------------+ 206 | | versioneer | yes | no | no | 207 | +---------------------------+-------------------------+---------------+-----------------+ 208 | | versioningit | yes | yes | yes | 209 | +---------------------------+-------------------------+---------------+-----------------+ 210 | | dunamai | yes | yes | no | 211 | +---------------------------+-------------------------+---------------+-----------------+ 212 | | miniver | no | no | no | 213 | +---------------------------+-------------------------+---------------+-----------------+ 214 | | setuptools-git-ver | yes | yes | no | 215 | +---------------------------+-------------------------+---------------+-----------------+ 216 | 217 | 218 | +---------------------------+-------------------+-------------------+------------------------------------+ 219 | | Package | Remove tag prefix | Remove tag suffix | Select only tags matching template | 220 | +===========================+===================+===================+====================================+ 221 | | setuptools-git-versioning | yes | yes | yes | 222 | +---------------------------+-------------------+-------------------+------------------------------------+ 223 | | setuptools-scm | no | no | no | 224 | +---------------------------+-------------------+-------------------+------------------------------------+ 225 | | versioneer | no | no | no | 226 | +---------------------------+-------------------+-------------------+------------------------------------+ 227 | | versioningit | yes | yes | yes | 228 | +---------------------------+-------------------+-------------------+------------------------------------+ 229 | | dunamai | no | no | yes | 230 | +---------------------------+-------------------+-------------------+------------------------------------+ 231 | | miniver | no | no | no | 232 | +---------------------------+-------------------+-------------------+------------------------------------+ 233 | | setuptools-git-ver | no | no | no | 234 | +---------------------------+-------------------+-------------------+------------------------------------+ 235 | 236 | :ref:`File-based versioning ` 237 | ------------------------------------------------- 238 | 239 | +---------------------------+----------------+----------------------------------------+-------------------------+ 240 | | Package | Read from file | Write to file | Use file commit history | 241 | +===========================+================+========================================+=========================+ 242 | | setuptools-git-versioning | yes | no (but can be done using bash script) | yes | 243 | +---------------------------+----------------+----------------------------------------+-------------------------+ 244 | | setuptools-scm | no | yes | no | 245 | +---------------------------+----------------+----------------------------------------+-------------------------+ 246 | | versioneer | yes | yes | no | 247 | +---------------------------+----------------+----------------------------------------+-------------------------+ 248 | | versioningit | no | yes | no | 249 | +---------------------------+----------------+----------------------------------------+-------------------------+ 250 | | dunamai | no | no (but can be done using bash script) | no | 251 | +---------------------------+----------------+----------------------------------------+-------------------------+ 252 | | miniver | no | yes | no | 253 | +---------------------------+----------------+----------------------------------------+-------------------------+ 254 | | setuptools-git-ver | no | no | no | 255 | +---------------------------+----------------+----------------------------------------+-------------------------+ 256 | 257 | :ref:`Callback-based versioning ` 258 | --------------------------------------------------------- 259 | 260 | +---------------------------+---------------------------------------------+---------------------------------------+ 261 | | Package | Use callback function to get version number | Use module variable as version number | 262 | +===========================+=============================================+=======================================+ 263 | | setuptools-git-versioning | yes | yes | 264 | +---------------------------+---------------------------------------------+---------------------------------------+ 265 | | setuptools-scm | no | no | 266 | +---------------------------+---------------------------------------------+---------------------------------------+ 267 | | versioneer | no | no | 268 | +---------------------------+---------------------------------------------+---------------------------------------+ 269 | | versioningit | yes | no | 270 | +---------------------------+---------------------------------------------+---------------------------------------+ 271 | | dunamai | no | no | 272 | +---------------------------+---------------------------------------------+---------------------------------------+ 273 | | miniver | no | no | 274 | +---------------------------+---------------------------------------------+---------------------------------------+ 275 | | setuptools-git-ver | no | no | 276 | +---------------------------+---------------------------------------------+---------------------------------------+ 277 | 278 | Misc 279 | ---- 280 | 281 | +---------------------------+------------------------------+---------------------+ 282 | | Package | Reuse functions in your code | Supports extensions | 283 | +===========================+==============================+=====================+ 284 | | setuptools-git-versioning | yes | no | 285 | +---------------------------+------------------------------+---------------------+ 286 | | setuptools-scm | yes | no | 287 | +---------------------------+------------------------------+---------------------+ 288 | | versioneer | yes | no | 289 | +---------------------------+------------------------------+---------------------+ 290 | | versioningit | yes | yes | 291 | +---------------------------+------------------------------+---------------------+ 292 | | dunamai | yes | no | 293 | +---------------------------+------------------------------+---------------------+ 294 | | miniver | yes | no | 295 | +---------------------------+------------------------------+---------------------+ 296 | | setuptools-git-ver | no | no | 297 | +---------------------------+------------------------------+---------------------+ 298 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | 13 | import subprocess 14 | import sys 15 | from pathlib import Path 16 | 17 | from packaging.version import Version 18 | 19 | try: 20 | from setuptools_git_versioning import version_from_git 21 | 22 | ver = Version(version_from_git()) 23 | except (ImportError, NameError): 24 | sys.path.insert(0, str(Path(__file__).parent.parent.absolute())) 25 | ver = Version( 26 | subprocess.check_output( # nosec 27 | f"{sys.executable} ../setup.py --version", 28 | shell=True, 29 | ) 30 | .decode("utf-8") 31 | .strip() 32 | ) 33 | 34 | # -- Project information ----------------------------------------------------- 35 | 36 | project = "setuptools-git-versioning" 37 | copyright = "2020-2024, dolfinus" 38 | author = "dolfinus" 39 | 40 | # The short X.Y version 41 | version = ver.base_version 42 | # The full version, including alpha/beta/rc tags 43 | release = ver.public 44 | 45 | 46 | # -- General configuration --------------------------------------------------- 47 | 48 | # Add any Sphinx extension module names here, as strings. They can be 49 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 50 | # ones. 51 | extensions = [ 52 | "sphinx.ext.autodoc", 53 | "sphinx.ext.extlinks", 54 | "sphinx_autodoc_typehints", 55 | "changelog", 56 | "numpydoc", 57 | "sphinxarg.ext", 58 | "sphinx_tabs.tabs", 59 | "sphinx_last_updated_by_git", 60 | ] 61 | 62 | # Add any paths that contain templates here, relative to this directory. 63 | templates_path = ["_templates"] 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | # This pattern also affects html_static_path and html_extra_path. 68 | exclude_patterns = ["_build", "Thumds.db", ".DS_Store"] 69 | 70 | # -- Options for HTML output ------------------------------------------------- 71 | 72 | # The theme to use for HTML and HTML Help pages. See the documentation for 73 | # a list of builtin themes. 74 | # 75 | html_theme = "furo" 76 | html_title = f"setuptools-git-versioning {version}" 77 | html_extra_path = ["robots.txt"] 78 | 79 | # Add any paths that contain custom static files (such as style sheets) here, 80 | # relative to this directory. They are copied after the builtin static files, 81 | # so a file named 'default.css' will overwrite the builtin 'default.css'. 82 | # html_static_path = ['_static'] 83 | 84 | 85 | extlinks = { 86 | "github-user": ("https://github.com/%s", "@%s"), 87 | "issue": ("https://github.com/dolfinus/setuptools-git-versioning/issues/%s", "#%s"), 88 | "pr": ("https://github.com/dolfinus/setuptools-git-versioning/pull/%s", "#%s"), 89 | } 90 | 91 | changelog_sections = [ 92 | "general", 93 | "core", 94 | "dependency", 95 | "config", 96 | "docs", 97 | "ci", 98 | "tests", 99 | "dev", 100 | ] 101 | 102 | changelog_caption_class = "" 103 | 104 | changelog_inner_tag_sort = ["breaking", "deprecated", "feature", "bug", "refactor"] 105 | changelog_hide_sections_from_tags = True 106 | 107 | changelog_render_ticket = "https://github.com/dolfinus/setuptools-git-versioning/issues/%s" 108 | changelog_render_pullreq = {"default": "https://github.com/dolfinus/setuptools-git-versioning/pull/%s"} 109 | changelog_render_changeset = "https://github.com/dolfinus/setuptools-git-versioning/commit/%s" 110 | 111 | language = "en" 112 | 113 | default_role = "any" 114 | todo_include_todos = False 115 | 116 | numpydoc_show_class_members = False 117 | pygments_style = "sphinx" 118 | 119 | autoclass_content = "both" 120 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | :hide-toc: 2 | 3 | .. include:: ../README.rst 4 | :end-before: documentation 5 | 6 | .. toctree:: 7 | :hidden: 8 | 9 | comparison 10 | 11 | .. toctree:: 12 | :maxdepth: 3 13 | :caption: How to 14 | :name: howto 15 | :hidden: 16 | 17 | install 18 | command 19 | ci 20 | runtime_version 21 | schemas/index 22 | options/index 23 | substitutions/index 24 | common_issues/index 25 | 26 | .. toctree:: 27 | :maxdepth: 2 28 | :caption: Develop 29 | :name: develop 30 | :hidden: 31 | 32 | contributing 33 | changelog 34 | 35 | .. toctree:: 36 | :maxdepth: 1 37 | :caption: Project Links 38 | :hidden: 39 | 40 | Source Code 41 | CI/CD 42 | Code Coverage 43 | Issue Tracker 44 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | .. include:: ../README.rst 4 | :start-after: installation 5 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/options/branch_formatter.rst: -------------------------------------------------------------------------------- 1 | .. _branch-formatter-option: 2 | 3 | ``branch_formatter`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Callback to be used for formatting a branch name before template substitution 7 | 8 | .. note:: 9 | 10 | This option is used only with :ref:`tag-release` or :ref:`version-file` versioning schemas. 11 | 12 | Type 13 | ^^^^^^^^^^^^^^ 14 | 15 | ``str`` or ``callable`` 16 | 17 | 18 | Default value 19 | ^^^^^^^^^^^^^^ 20 | ``None`` 21 | 22 | Usage 23 | ^^^^^^ 24 | 25 | It is possible to use (see :ref:`dev-release-any-branch`) branch name in version number. 26 | 27 | But branches should have :pep:`440` compatible name, like: 28 | 29 | - ``alpha`` 30 | - ``beta`` 31 | - ``rc`` 32 | - ``preview`` 33 | - ``pre`` 34 | - ``post`` 35 | - ``dev`` 36 | 37 | In case of using branch names like ``feature/ABC-123`` or ``bugfix/ABC-123``, 38 | you'll get version number which ``pip`` cannot understand. 39 | 40 | To fix that you can define a callback which will receive current branch 41 | name and return a properly formatted one: 42 | 43 | .. tabs:: 44 | 45 | .. code-tab:: python ``my_module/util.py`` file 46 | 47 | import re 48 | 49 | 50 | def format_branch_name(name): 51 | # If branch has name like "bugfix/issue-1234-bug-title", take only "1234" part 52 | pattern = re.compile("^(bugfix|feature)\/issue-(?P[0-9]+)-\S+") 53 | 54 | match = pattern.search(name) 55 | if match: 56 | return match.group("branch") 57 | 58 | # function is called even if branch name is not used in a current template 59 | # just left properly named branches intact 60 | if name in ["master", "dev"]: 61 | return name 62 | 63 | # fail in case of wrong branch names like "bugfix/issue-unknown" 64 | raise ValueError(f"Wrong branch name: {name}") 65 | 66 | .. code-tab:: python ``setup.py`` file 67 | 68 | import setuptools 69 | from my_module.util import format_branch_name 70 | 71 | setuptools.setup( 72 | ..., 73 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 74 | setuptools_git_versioning={ 75 | "enabled": True, 76 | "dev_template": "{branch}.dev{ccount}", 77 | "dirty_template": "{branch}.dev{ccount}", 78 | "branch_formatter": format_branch_name, # <--- 79 | }, 80 | ) 81 | 82 | .. code-tab:: toml ``pyproject.toml`` file 83 | 84 | [build-system] 85 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 86 | # __legacy__ is required to have access to package 87 | # during build step 88 | build-backend = "setuptools.build_meta:__legacy__" 89 | 90 | [project] 91 | dynamic = ["version"] 92 | 93 | [tool.setuptools-git-versioning] 94 | enabled = true 95 | dev_template = "{branch}.dev{ccount}" 96 | dirty_template = "{branch}.dev{ccount}" 97 | branch_formatter = "my_module.util:format_branch_name" # <--- 98 | 99 | .. note:: 100 | 101 | Please pay attention to ``build-backend`` item in your config, it is important 102 | for ``setuptools-git-versioning`` to access your module source code. 103 | 104 | 105 | Possible values 106 | ^^^^^^^^^^^^^^^ 107 | 108 | - ``None`` 109 | 110 | Disables this feature 111 | 112 | - function/lambda (``setup.py`` only) 113 | - function full name in format ``"some.module:function_name"`` 114 | 115 | Function should have signature ``(str) -> str``. It accepts original branch name and returns formatted one 116 | 117 | .. warning:: 118 | 119 | Exception will be raised if module or function/lambda is missing or has invalid signature 120 | 121 | - regexp like ``".*(?P\d+).*"`` 122 | 123 | Regexp should have capture group named ``"branch"`` matching the expected branch name 124 | 125 | .. warning:: 126 | 127 | Exception will be raised if regexp is invalid or does not have expected capture group 128 | 129 | .. warning:: 130 | Exception will also be raised if branch name does not match regexp. 131 | So this regexp should be able to handle all possible branches in the repo 132 | -------------------------------------------------------------------------------- /docs/options/count_commits.rst: -------------------------------------------------------------------------------- 1 | .. _count-commits-option: 2 | 3 | ``count_commits_from_version_file`` 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Used by :ref:`dev-release-file` versioning schema. 7 | 8 | Enables feature of tracking commits history for file passed to :ref:`version-file-option` option 9 | 10 | .. note:: 11 | 12 | This option used only with :ref:`version-file-option` 13 | 14 | Type 15 | ^^^^^ 16 | ``bool`` 17 | 18 | Default value 19 | ^^^^^^^^^^^^^ 20 | ``False`` 21 | -------------------------------------------------------------------------------- /docs/options/dev_template.rst: -------------------------------------------------------------------------------- 1 | .. _dev-template-option: 2 | 3 | ``dev_template`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Version number template for :ref:`tag-release` or :ref:`version-file` versioning schemas. 7 | 8 | Used if there are no untracked files, and current commit is not tagged. 9 | 10 | .. note:: 11 | 12 | This option is completely ignored if :ref:`version-callback` schema is used, 13 | because git commit history is not fetched in such a case. 14 | 15 | Type 16 | ^^^^^ 17 | ``str`` 18 | 19 | Default value 20 | ^^^^^^^^^^^^^ 21 | ``"{tag}.post{ccount}+git.{sha}"`` 22 | 23 | 24 | Possible values 25 | ^^^^^^^^^^^^^^^ 26 | Any string which produces :pep:`440` compliant version number after substituting all necessary values. 27 | 28 | See :ref:`substitutions` 29 | -------------------------------------------------------------------------------- /docs/options/dirty_template.rst: -------------------------------------------------------------------------------- 1 | .. _dirty-template-option: 2 | 3 | ``dirty_template`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Version number template for :ref:`tag-release` or :ref:`version-file` versioning schemas. 7 | 8 | Used if untracked files exist or uncommitted changes have been made. 9 | 10 | .. note:: 11 | 12 | This option is completely ignored if :ref:`version-callback` schema is used, 13 | because git commit history is not fetched in such a case. 14 | 15 | Type 16 | ^^^^^ 17 | ``str`` 18 | 19 | Default value 20 | ^^^^^^^^^^^^^ 21 | ``"{tag}.post{ccount}+git.{sha}.dirty"`` 22 | 23 | 24 | Possible values 25 | ^^^^^^^^^^^^^^^ 26 | Any string which produces :pep:`440` compliant version number after substituting all necessary values. 27 | 28 | See :ref:`substitutions` 29 | -------------------------------------------------------------------------------- /docs/options/enabled.rst: -------------------------------------------------------------------------------- 1 | .. _enabled-option: 2 | 3 | ``enabled`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Enables ``setuptools-git-versioning`` for current project 7 | 8 | Type 9 | ^^^^^ 10 | ``bool`` 11 | 12 | Default value 13 | ^^^^^^^^^^^^^ 14 | 15 | Missing ``setuptools_git_versioning`` keyword in ``setup.py`` 16 | or missing ``setuptools-git-versioning`` section in ``pyproject.toml`` 17 | or empty dict (``{}``) 18 | is the same as ``"enabled": False``. 19 | 20 | **This is because we don't want to mess up existing projects which are not using this tool** 21 | 22 | If any other config value is set, default value is ``True``. 23 | -------------------------------------------------------------------------------- /docs/options/index.rst: -------------------------------------------------------------------------------- 1 | .. _config-options: 2 | 3 | Config options 4 | --------------- 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | enabled 10 | starting_version 11 | template 12 | dev_template 13 | dirty_template 14 | version_file 15 | count_commits 16 | version_callback 17 | sort_by 18 | branch_formatter 19 | tag_formatter 20 | tag_filter 21 | -------------------------------------------------------------------------------- /docs/options/sort_by.rst: -------------------------------------------------------------------------------- 1 | .. _sort-by-option: 2 | 3 | ``sort_by`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Format string passed to ``git tag --sort=`` command to sort the output. 7 | 8 | Used by :ref:`tag-release` versioning schema to get the latest tag in the current branch. 9 | 10 | .. note:: 11 | 12 | This option is completely ignored if :ref:`version-callback` schema is used, 13 | because git commit history is not fetched in such a case. 14 | 15 | Type 16 | ^^^^^^^^^^^^^^ 17 | 18 | ``str`` 19 | 20 | 21 | Default value 22 | ^^^^^^^^^^^^^^ 23 | ``"creatordate"`` 24 | 25 | 26 | Possible values 27 | ^^^^^^^^^^^^^^^^ 28 | - ``"creatordate"`` (either commit date or tag creation date) 29 | 30 | - ``"version:refname"`` (alphanumeric sort by tag name) 31 | 32 | .. warning:: 33 | 34 | This field can produce wrong version numbers in some cases, not recommended to use 35 | 36 | - ``"committerdate"`` (commit date of commit tag) 37 | 38 | .. warning:: 39 | 40 | This field is missing in case of annotated tags, not recommended to use 41 | 42 | - ``"taggerdate"`` (annotated tag creation date) 43 | 44 | .. warning:: 45 | 46 | This field is missing in case of commit tags, not recommended to use 47 | 48 | See also 49 | """""""" 50 | - `Git documentation `_ 51 | 52 | - `StackOverflow `_ 53 | -------------------------------------------------------------------------------- /docs/options/starting_version.rst: -------------------------------------------------------------------------------- 1 | .. _starting-version-option: 2 | 3 | ``starting_version`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | If you're using: 7 | 8 | - :ref:`tag-based-release` 9 | - :ref:`file-based-release` 10 | 11 | without any existing tag or version file respectively, you'll get some 12 | initial version, like ``0.0.1`` 13 | 14 | You can change this version by setting up ``starting_version`` option in your config file: 15 | 16 | .. tabs:: 17 | 18 | .. code-tab:: python ``setup.py`` file 19 | 20 | import setuptools 21 | 22 | setuptools.setup( 23 | ..., 24 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 25 | setuptools_git_versioning={ 26 | "enabled": True, 27 | "starting_version": "1.0.0", # <--- 28 | }, 29 | ) 30 | 31 | .. code-tab:: toml ``pyproject.toml`` file 32 | 33 | [build-system] 34 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 35 | build-backend = "setuptools.build_meta" 36 | 37 | [project] 38 | dynamic = ["version"] 39 | 40 | [tool.setuptools-git-versioning] 41 | enabled = true 42 | starting_version = "1.0.0" # <--- 43 | 44 | .. note:: 45 | 46 | This option is completely ignored if :ref:`version-callback` schema is used. 47 | 48 | Type 49 | ^^^^^^^^^^^^^^ 50 | 51 | ``str`` 52 | 53 | 54 | Default value 55 | ^^^^^^^^^^^^^^ 56 | 57 | ``"0.0.1"`` 58 | 59 | 60 | Possible values 61 | ^^^^^^^^^^^^^^^ 62 | 63 | Any :pep:`440` compliant version number 64 | -------------------------------------------------------------------------------- /docs/options/tag_filter.rst: -------------------------------------------------------------------------------- 1 | .. _tag_filter-option: 2 | 3 | ``tag_filter`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Callback to be used for filtering tag names before formatting and template 7 | substitution. 8 | 9 | .. note:: 10 | 11 | This option is completely ignored if :ref:`version-callback` schema is used, 12 | because git commit history is not fetched in such a case. 13 | 14 | Type 15 | ^^^^^ 16 | ``str`` or ``callable`` 17 | 18 | Default value 19 | ^^^^^^^^^^^^^ 20 | ``None`` 21 | 22 | Usage 23 | ^^^^^^ 24 | 25 | Set when multiple products are tagged in a single repo. 26 | 27 | If, for example, your repo has: 28 | 29 | - ``product_x/1.2.0`` 30 | - ``product_x/1.2.1`` 31 | - ``product_x/1.3.0`` 32 | - ``product_y/2.0.0`` 33 | - ``product_y/2.1.0`` 34 | 35 | and you only want versions from ``product_y``, simply set: 36 | 37 | .. code:: toml 38 | 39 | tag_filter = "product_y/(?P.*)" 40 | 41 | This will limit the tags considered to those that start with ``product_y``. 42 | 43 | You will likely still need to construct a :ref:`tag-formatter-option` that 44 | extract version number from a tag. To make thing easier, you can often 45 | use the same regexp/callback for both ``tag_filter`` and ``tag_formatter``. 46 | 47 | Possible values 48 | ^^^^^^^^^^^^^^^ 49 | - ``None`` 50 | 51 | Disables this feature 52 | 53 | - function full name in format ``"some.module:function_name"`` 54 | 55 | Function should have signature ``(str) -> bool``. It accepts original 56 | tag name and returns ``True`` if tag is accepted and ``False`` if not. 57 | You can return other value type, like ``str`` or ``None``, it will be 58 | treated as ``bool``. 59 | 60 | .. warning:: 61 | 62 | Exception will be raised if module or function/lambda is missing or has invalid signature 63 | 64 | - regexp like ``"tag-prefix/.*"`` or ``"tag-prefix/(?P.*)"`` 65 | 66 | .. note:: 67 | 68 | The ```` group isn't required for the filter, but makes it simpler to 69 | share with the ``tag_formatter`` option. 70 | 71 | .. warning:: 72 | 73 | Exception will be raised if regexp is invalid 74 | 75 | .. warning:: 76 | 77 | If regexp doesn't match any tag, the filter will return the empty list, and 78 | the default "0.0.1" version will be selected. 79 | -------------------------------------------------------------------------------- /docs/options/tag_formatter.rst: -------------------------------------------------------------------------------- 1 | .. _tag-formatter-option: 2 | 3 | ``tag_formatter`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Callback to be used for formatting a tag name before template substitution. 7 | 8 | .. note:: 9 | 10 | This option is completely ignored if :ref:`version-file` or :ref:`version-callback` schemas are used. 11 | Please set up your VERSION file or callback to return proper version in the first place. 12 | 13 | Type 14 | ^^^^^^^^^^^^^^ 15 | 16 | ``str`` or ``callable`` 17 | 18 | 19 | Default value 20 | ^^^^^^^^^^^^^^ 21 | ``None`` 22 | 23 | Usage 24 | ^^^^^^ 25 | 26 | It is possible to use (see :ref:`tag-release`) tag name in version number. 27 | 28 | But tags should have :pep:`440` compatible name, like: 29 | 30 | - ``1.2`` 31 | - ``1.2.3`` 32 | - ``1.2.3a4`` 33 | - ``1.2.3b4`` 34 | - ``1.2.3rc4`` 35 | - ``1.2.3.pre4`` 36 | - ``1.2.3.post4`` 37 | - ``1.2.3.dev4`` 38 | 39 | In case of using tag names like ``release/1.2.3`` or ``rc-1.2``, 40 | you'll get version number which ``pip`` cannot understand. 41 | 42 | To fix that you can define a callback which will receive current tag 43 | name and return a properly formatted one: 44 | 45 | .. tabs:: 46 | 47 | .. code-tab:: python ``my_module/util.py`` file 48 | 49 | import re 50 | 51 | 52 | def format_tag_name(name): 53 | # If tag has name like "release/1.2.3", take only "1.2.3" part 54 | pattern = re.compile(r"release\/(?P[^\d.]+)") 55 | 56 | match = pattern.search(name) 57 | if match: 58 | return match.group("tag") 59 | 60 | # just left properly named tags intact 61 | if name.startswith("v"): 62 | return name 63 | 64 | # fail in case of wrong tag names like "release/unknown" 65 | raise ValueError(f"Wrong tag name: {name}") 66 | 67 | .. code-tab:: python ``setup.py`` file 68 | 69 | import setuptools 70 | from my_module.util import format_tag_name 71 | 72 | setuptools.setup( 73 | ..., 74 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 75 | setuptools_git_versioning={ 76 | "enabled": True, 77 | "dev_template": "{tag}.dev{ccount}", 78 | "dirty_template": "{tag}.dev{ccount}", 79 | "tag_formatter": format_tag_name, # <--- 80 | }, 81 | ) 82 | 83 | .. code-tab:: toml ``pyproject.toml`` file 84 | 85 | [build-system] 86 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 87 | # __legacy__ is required to have access to package 88 | # during build step 89 | build-backend = "setuptools.build_meta:__legacy__" 90 | 91 | [project] 92 | dynamic = ["version"] 93 | 94 | [tool.setuptools-git-versioning] 95 | enabled = true 96 | dev_template = "{tag}.dev{ccount}" 97 | dirty_template = "{tag}.dev{ccount}" 98 | tag_formatter = "my_module.util:format_tag_name" # <--- 99 | 100 | .. note:: 101 | 102 | Please pay attention to ``build-backend`` item in your config, it is important 103 | for ``setuptools-git-versioning`` to access your module source code. 104 | 105 | 106 | Possible values 107 | ^^^^^^^^^^^^^^^ 108 | 109 | - ``None`` 110 | 111 | Disables this feature 112 | 113 | - function/lambda (``setup.py`` only) 114 | - function full name in format ``"some.module:function_name"`` 115 | 116 | Function should have signature ``(str) -> str``. It accepts original tag name and returns formatted one 117 | 118 | .. warning:: 119 | 120 | Exception will be raised if module or function/lambda is missing or has invalid signature 121 | 122 | - regexp like ``".*(?P\d+).*"`` 123 | 124 | Regexp should have capture group named ``"tag"`` matching the expected tag name 125 | 126 | .. warning:: 127 | 128 | Exception will be raised if regexp is invalid or does not have expected capture group 129 | 130 | .. warning:: 131 | 132 | Exception will also be raised if tag name does not match regexp. 133 | So this regexp should be able to handle all possible tags in the repo 134 | -------------------------------------------------------------------------------- /docs/options/template.rst: -------------------------------------------------------------------------------- 1 | .. _template-option: 2 | 3 | ``template`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Version number template for :ref:`tag-release` versioning schema. 7 | 8 | Used if no untracked files and current commit is tagged. 9 | 10 | .. note:: 11 | 12 | This option is completely ignored if :ref:`version-callback` schema is used, 13 | because git commit history is not fetched in such a case. 14 | 15 | Type 16 | ^^^^^ 17 | ``str`` 18 | 19 | Default value 20 | ^^^^^^^^^^^^^ 21 | ``"{tag}"`` 22 | 23 | Possible values 24 | ^^^^^^^^^^^^^^^ 25 | Any string which produces :pep:`440` compliant version number after substituting all necessary values. 26 | 27 | See :ref:`substitutions` 28 | -------------------------------------------------------------------------------- /docs/options/version_callback.rst: -------------------------------------------------------------------------------- 1 | .. _version-callback-option: 2 | 3 | ``version_callback`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Callback to be used for getting a version number. 7 | 8 | Used by :ref:`version-callback` versioning schema. 9 | 10 | .. note:: 11 | 12 | This option conflicts with :ref:`version-file-option`, only one of them can be set. 13 | 14 | Type 15 | ^^^^^^^^^^^^^^ 16 | 17 | ``str`` or ``callable`` 18 | 19 | 20 | Default value 21 | ^^^^^^^^^^^^^^ 22 | ``None`` 23 | 24 | 25 | Possible values 26 | ^^^^^^^^^^^^^^^ 27 | 28 | - ``None`` 29 | 30 | Disables this feature 31 | 32 | - function/lambda (``setup.py`` only) 33 | - function full name in format ``"some.module:function_name"`` 34 | 35 | Function should have signature ``() -> str``. It should return version number 36 | 37 | - variable (``setup.py`` only) 38 | - variable full name in format ``"some.module:veriable_name"`` 39 | 40 | Variable should contain ``str`` value with version number 41 | 42 | .. note:: 43 | 44 | If your config file is ``pyproject.toml`` you should add this to allow ``setuptools-git-versioning`` to access your module source code: 45 | 46 | .. code:: toml 47 | 48 | [build-system] 49 | ... 50 | build-backend = "setuptools.build_meta:__legacy__" 51 | ... 52 | 53 | .. warning:: 54 | 55 | If function return value or variable content is not :pep:`440` compatible version, the exception will be raised 56 | 57 | .. warning:: 58 | 59 | Exception will also be raised if module or function/lambda/variable is missing 60 | -------------------------------------------------------------------------------- /docs/options/version_file.rst: -------------------------------------------------------------------------------- 1 | .. _version-file-option: 2 | 3 | ``version_file`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Version file path template for :ref:`version-file` versioning schema. 7 | 8 | .. note:: 9 | 10 | This option conflicts with :ref:`version-callback-option`, only one of them can be set. 11 | 12 | Type 13 | ^^^^^ 14 | ``str`` 15 | 16 | Default value 17 | ^^^^^^^^^^^^^ 18 | ``None`` 19 | -------------------------------------------------------------------------------- /docs/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: /*/stable/ 3 | Allow: /en/stable/ # Fallback for bots that don't understand wildcards 4 | Disallow: / 5 | Sitemap: https://setuptools-git-versioning.readthedocs.io/sitemap.xml 6 | -------------------------------------------------------------------------------- /docs/runtime_version.rst: -------------------------------------------------------------------------------- 1 | .. _runtime-version: 2 | 3 | Retrieving package version at runtime 4 | ------------------------------------- 5 | 6 | Using ``version_file`` option (recommended) 7 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | 9 | In case of using :ref:`version_file ` option you can directly read the ``VERSION`` file content, 10 | and use at as version number. 11 | 12 | To resolve version number in runtime, you should move ``VERSION`` file to your module subfolder: 13 | 14 | .. tabs:: 15 | 16 | .. tab:: ``setup.py`` 17 | 18 | Create ``MANIFEST.in`` file in the project root: 19 | 20 | include my_module/VERSION 21 | 22 | Then make few changes in ``setup.py``: 23 | 24 | .. code:: python 25 | 26 | from path import Path 27 | 28 | root_path = Path(__file__).parent 29 | version_file = root_path / "my_module" / "VERSION" 30 | 31 | setuptools.setup( 32 | ..., 33 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 34 | setuptools_git_versioning={ 35 | "enabled": True, 36 | "version_file": version_file, 37 | }, 38 | # read MANIFEST.in and include files mentioned here to the package 39 | include_package_data=True, 40 | # this package will read some included files in runtime, avoid installing it as .zip 41 | zip_safe=False, 42 | ) 43 | 44 | .. code-tab:: toml ``pyproject.toml`` 45 | 46 | [build-system] 47 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 48 | build-backend = "setuptools.build_meta" 49 | 50 | [project] 51 | dynamic = ["version"] 52 | 53 | [tool.setuptools.package-data] 54 | # include VERSION file to a package 55 | my_module = ["VERSION"] 56 | 57 | [tool.setuptools] 58 | # this package will read some included files in runtime, avoid installing it as .zip 59 | zip-safe = false 60 | 61 | [tool.setuptools-git-versioning] 62 | enabled = true 63 | # change the file path 64 | version_file = "my_module/VERSION" 65 | 66 | And then read this file: 67 | 68 | .. code:: python 69 | 70 | # content of my_module/__init__.py 71 | 72 | from pathlib import Path 73 | 74 | # you can use os.path and open() as well 75 | __version__ = Path(__file__).parent.joinpath("VERSION").read_text() 76 | 77 | 78 | Using ``version_callback`` option 79 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 80 | 81 | In case of using :ref:`version_callback ` option you can directly call this callback inside a module: 82 | 83 | .. code:: python 84 | 85 | # content of my_module/__init__.py 86 | 87 | from my_module.version import get_version 88 | 89 | __version__ = get_version() 90 | 91 | 92 | Using ``importlib`` 93 | ~~~~~~~~~~~~~~~~~~~ 94 | 95 | If you have opted not to hardcode the version number inside the package, 96 | you can retrieve it at runtime from :pep:`0566` metadata using 97 | ``importlib.metadata`` from the standard library (added in Python 3.8) 98 | or the `importlib_metadata`_ backport: 99 | 100 | .. code:: python 101 | 102 | from importlib.metadata import version, PackageNotFoundError 103 | 104 | try: 105 | __version__ = version("package-name") 106 | except PackageNotFoundError: 107 | # package is not installed 108 | pass 109 | 110 | .. _importlib_metadata: https://pypi.org/project/importlib-metadata/ 111 | 112 | Using ``pkg_resources`` 113 | ~~~~~~~~~~~~~~~~~~~~~~~ 114 | 115 | In some cases ``importlib`` cannot properly detect package version, 116 | for example it was compiled into executable file, so it uses some 117 | custom import mechanism. 118 | 119 | Instead, you can use ``pkg_resources`` which is included in ``setuptools`` 120 | (but has a significant runtime cost): 121 | 122 | .. code:: python 123 | 124 | from pkg_resources import get_distribution, DistributionNotFound 125 | 126 | try: 127 | __version__ = get_distribution("package-name").version 128 | except DistributionNotFound: 129 | # package is not installed 130 | pass 131 | 132 | However, this does place a runtime dependency on setuptools, 133 | and can add up to a few 100ms overhead for the package import time. 134 | 135 | Calling internals of ``setuptools_git_versioning`` module 136 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 137 | 138 | .. warning:: 139 | 140 | This way is STRONGLY DISCOURAGED. Functions in the module 141 | are not a part of public API, and could be changed in the future without 142 | maintaining backward compatibility. 143 | 144 | .. warning:: 145 | 146 | Use this ONLY in CI/CD tools. 147 | 148 | NEVER use ``setuptools_git_versioning`` inside your package, because ``.git`` 149 | folder is not being included into it, and target OS can lack of ``git`` executable. 150 | 151 | ``.git`` folder and ``git`` executable presence is crucial 152 | for ``setuptools-git-versioning`` to work properly. 153 | 154 | .. code:: python 155 | 156 | from setuptools_git_versioning import get_version 157 | 158 | # uses setup.py or pyproject.toml as config source 159 | version = get_version() 160 | 161 | from setuptools_git_versioning import get_tag, get_all_tags 162 | 163 | # calls `git` executable to get latest tag merged into HEAD history tree 164 | latest_tag = get_tag() 165 | 166 | # calls `git` executable to get all the tags in the repo 167 | all_tags = get_all_tags() 168 | -------------------------------------------------------------------------------- /docs/schemas/callback/index.rst: -------------------------------------------------------------------------------- 1 | .. _callback-based-release: 2 | 3 | Callback-based release (advanced) 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | version_callback 10 | -------------------------------------------------------------------------------- /docs/schemas/callback/version_callback.rst: -------------------------------------------------------------------------------- 1 | .. _version-callback: 2 | 3 | Execute some callback function to get current version 4 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 5 | 6 | For example, current repo state is: 7 | 8 | .. code:: bash 9 | 10 | commit 233f6d72 Dev branch commit (HEAD, dev) 11 | | 12 | | commit 86269212 Release commit (v1.0.0, master) 13 | | | 14 | | commit e7bdbe51 Another commit 15 | | / 16 | ... 17 | | 18 | commit 273c47eb Long long ago 19 | | 20 | ... 21 | 22 | By default, when you try to get current version, you'll receive some 23 | initial value (see :ref:`starting-version-option` option), 24 | because there are no tags in the ``dev`` branch. 25 | 26 | If you want to get synchronized version numbers in both ``master`` and ``dev`` branches, 27 | you can create a function in some file (for example, in the 28 | ``my_module/version.py`` file): 29 | 30 | .. code:: python 31 | 32 | def get_version(): 33 | return "1.0.0" 34 | 35 | Then place it in both the branches and update your config: 36 | 37 | .. tabs:: 38 | 39 | .. code-tab:: python ``setup.py`` file 40 | 41 | from my_module.version import get_version 42 | 43 | setuptools.setup( 44 | ..., 45 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 46 | setuptools_git_versioning={ 47 | "enabled": True, 48 | "version_callback": get_version, # <--- 49 | }, 50 | ) 51 | 52 | .. code-tab:: toml ``pyproject.toml`` file 53 | 54 | [build-system] 55 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 56 | # __legacy__ is required to have access to package 57 | # during build step 58 | build-backend = "setuptools.build_meta:__legacy__" 59 | 60 | [project] 61 | dynamic = ["version"] 62 | 63 | [tool.setuptools-git-versioning] 64 | enabled = true 65 | version_callback = "my_module.version:get_version" # <--- 66 | 67 | When you'll try to get current version in **any** branch, the result 68 | of executing this function will be returned instead of latest tag 69 | number. 70 | 71 | If a value of this option is not a function but just str, it also could be used: 72 | 73 | .. tabs:: 74 | 75 | .. code-tab:: python ``my_module/__init__.py`` file 76 | 77 | __version__ = "1.0.0" 78 | 79 | .. code-tab:: python ``setup.py`` file 80 | 81 | import setuptools 82 | 83 | import my_module 84 | 85 | setuptools.setup( 86 | ..., 87 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 88 | setuptools_git_versioning={ 89 | "enabled": True, 90 | "version_callback": my_module.__version__, # <--- 91 | }, 92 | ) 93 | 94 | .. code-tab:: toml ``pyproject.toml`` file 95 | 96 | [build-system] 97 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 98 | # __legacy__ is required to have access to package 99 | # during build step 100 | build-backend = "setuptools.build_meta:__legacy__" 101 | 102 | [project] 103 | dynamic = ["version"] 104 | 105 | [tool.setuptools-git-versioning] 106 | enabled = true 107 | version_callback = "my_module:__version__" # <--- 108 | 109 | **Please take into account that any tag in the branch is completely ignored if version_callback 110 | is set**. 111 | You should explicitly call ``setuptools_git_versioning.version_from_git`` function in the callback. 112 | 113 | .. note:: 114 | 115 | Callback result is returned *as is*, so it should be a :pep:`440` compatible version number 116 | 117 | See also 118 | """""""" 119 | - :ref:`version-callback-option` option 120 | - :ref:`runtime-version` 121 | -------------------------------------------------------------------------------- /docs/schemas/file/dev_release_file.rst: -------------------------------------------------------------------------------- 1 | .. _dev-release-file: 2 | 3 | Development releases (prereleases) from ``dev`` branch 4 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 5 | 6 | For example, current repo state is: 7 | 8 | .. code:: bash 9 | 10 | commit 233f6d72 Dev branch commit (HEAD, dev) 11 | | 12 | | commit 86269212 Release commit (v1.0.0, master) 13 | | | 14 | | commit e7bdbe51 Another commit 15 | | / 16 | ... 17 | | 18 | commit 273c47eb Long long ago 19 | | 20 | ... 21 | 22 | If you want to create development releases (prereleases) for the next planned version ``1.1.0``, 23 | so every commit to ``dev`` branch should produce version number like ``1.1.0.dev123`` (just plain increment) 24 | or even ``1.1.0.dev123+git.sha`` (to describe which commit was used for this exact version). 25 | 26 | In such a case you need to create a text file (for example, ``VERSION`` or ``VERSION.txt``) 27 | with your **next release number** (e.g. ``1.1.0``): 28 | 29 | .. code:: txt 30 | 31 | 1.1.0 32 | 33 | Then update your config file: 34 | 35 | .. tabs:: 36 | 37 | .. code-tab:: python ``setup.py`` 38 | 39 | import setuptools 40 | from pathlib import Path 41 | 42 | root_path = Path(__file__).parent 43 | version_file = root_path / "VERSION.txt" 44 | 45 | setuptools.setup( 46 | ..., 47 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 48 | setuptools_git_versioning={ 49 | "enabled": True, 50 | "version_file": version_file, 51 | "count_commits_from_version_file": True, # <--- enable commits tracking 52 | "dev_template": "{tag}.dev{ccount}", # suffix for versions will be .dev 53 | "dirty_template": "{tag}.dev{ccount}", # same thing here 54 | }, 55 | ) 56 | 57 | .. code-tab:: toml ``pyproject.toml`` 58 | 59 | [build-system] 60 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 61 | build-backend = "setuptools.build_meta" 62 | 63 | [project] 64 | dynamic = ["version"] 65 | 66 | [tool.setuptools-git-versioning] 67 | enabled = true 68 | version_file = "VERSION" 69 | count_commits_from_version_file = true # <--- enable commits tracking 70 | dev_template = "{tag}.dev{ccount}" # suffix for versions will be .dev 71 | dirty_template = "{tag}.dev{ccount}" # same thing here 72 | 73 | In case of next release version ``1.1.0``, the third commit to ``dev`` branch will produce 74 | version number ``1.1.0.dev3``, and so on. 75 | 76 | Release process 77 | """"""""""""""" 78 | 79 | - Merge ``dev`` branch into ``master`` branch. 80 | - Tag commit in the ``master`` branch, and publis a repease from code on this branch. 81 | - Checkout back to ``dev`` branch 82 | - Save next release version (e.g. ``1.2.0``) in ``VERSION`` or ``VERSION.txt`` file in the ``dev`` branch. 83 | - Next commits to a ``dev`` branch will lead to returning this next release version plus dev suffix, like ``1.2.0.dev1`` or so. 84 | - ``N`` in ``.devN`` suffix is a number of commits since the last change of a certain file. 85 | 86 | .. warning:: 87 | 88 | Every change of this file in the ``dev`` branch will lead to this ``N`` suffix to be reset to ``0``. Update this file only in the case when you're setting up the next release version! 89 | 90 | .. _dev-release-any-branch: 91 | 92 | Development releases from any branch 93 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 94 | 95 | Just like example above, but you want to make development releases 96 | (prereleases) with a branch name (``feature`` / ``bugfix`` / ``preview`` / ``beta`` / etc) 97 | present in the version number. 98 | 99 | For example, if the branch name is something like ``alpha``, ``beta``, 100 | ``preview`` or ``rc``, you can add ``{branch}`` substitution to template in your config file: 101 | 102 | .. tabs:: 103 | 104 | .. code-tab:: python ``setup.py`` 105 | 106 | import setuptools 107 | from pathlib import Path # same thing here 108 | 109 | root_path = Path(__file__).parent 110 | version_file = root_path / "VERSION.txt" 111 | 112 | setuptools.setup( 113 | ..., 114 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 115 | setuptools_git_versioning={ 116 | "enabled": True, 117 | "version_file": version_file, 118 | "count_commits_from_version_file": True, 119 | "dev_template": "{tag}.{branch}{ccount}", # <--- note {branch} here 120 | "dirty_template": "{tag}.{branch}{ccount}", 121 | }, 122 | ) 123 | 124 | .. code-tab:: toml ``pyproject.toml`` 125 | 126 | [build-system] 127 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 128 | build-backend = "setuptools.build_meta" 129 | 130 | [project] 131 | dynamic = ["version"] 132 | 133 | [tool.setuptools-git-versioning] 134 | enabled = true 135 | version_file = "VERSION" 136 | count_commits_from_version_file = true 137 | dev_template = "{tag}.{branch}{ccount}" # <--- note {branch} here 138 | dirty_template = "{tag}.{branch}{ccount}" 139 | 140 | Fourth commit to ``alpha`` branch with next release number ``1.2.3`` 141 | will generate a version number like ``1.2.3a4``. 142 | 143 | Fifth commit to ``beta`` branch with next release number ``1.2.3`` 144 | will generate a version number like ``1.2.3b5``. 145 | 146 | .. _dev-release-ignore-file: 147 | 148 | Development releases using only branch name 149 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 150 | 151 | It is also possible to use branch names like ``1.0-alpha`` or ``1.1.beta``: 152 | 153 | .. tabs:: 154 | 155 | .. code-tab:: python ``setup.py`` 156 | 157 | import setuptools 158 | from pathlib import Path 159 | 160 | root_path = Path(__file__).parent 161 | version_file = root_path / "VERSION.txt" 162 | 163 | setuptools.setup( 164 | ..., 165 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 166 | setuptools_git_versioning={ 167 | "enabled": True, 168 | "count_commits_from_version_file": True, 169 | "dev_template": "{branch}{ccount}", # <--- without {tag} 170 | "dirty_template": "{branch}{ccount}", 171 | "version_file": version_file, 172 | }, 173 | ) 174 | 175 | .. code-tab:: toml ``pyproject.toml`` 176 | 177 | [build-system] 178 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 179 | build-backend = "setuptools.build_meta" 180 | 181 | [project] 182 | dynamic = ["version"] 183 | 184 | [tool.setuptools-git-versioning] 185 | enabled = true 186 | version_file = "VERSION" 187 | count_commits_from_version_file = true 188 | dev_template = "{branch}{ccount}" # <--- without {tag} 189 | dirty_template = "{branch}{ccount}" 190 | 191 | Second commit to ``1.0-alpha`` branch 192 | will generate a version number like ``1.0a2``. 193 | 194 | Third commit to ``1.2.beta`` branch 195 | will generate a version number like ``1.2b3``. 196 | 197 | If branch name is not :pep:`440` compliant, use :ref:`branch-formatter-option` option 198 | 199 | .. note:: 200 | 201 | Although ``VERSION`` file content is not used in this particular example, you still need to update it 202 | while changing your next release version. 203 | 204 | Otherwise this tool will not be able to properly calculate version number. 205 | The commits history is used for this calcucation, 206 | so no file changes means that ``ccount`` will not be reset to ``0``. 207 | 208 | See also 209 | """""""" 210 | - :ref:`version-file-option` option 211 | - :ref:`count-commits-option` option 212 | - :ref:`dev-template-option` option 213 | - :ref:`dirty-template-option` option 214 | - :ref:`branch-formatter-option` option 215 | -------------------------------------------------------------------------------- /docs/schemas/file/index.rst: -------------------------------------------------------------------------------- 1 | .. _file-based-release: 2 | 3 | File-based release (recommended) 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | By default, ``setuptools-git-versioning`` can be used only within: 7 | 8 | * git repo, which means a ``.git`` subfolder should exist in the repo root folder 9 | * branch with at least one tag 10 | 11 | Otherwise it will be impossible to get project version based on the git repo commits, 12 | and ``setuptools-git-versioning`` will return version number ``0.0.1`` (or other value set up by :ref:`starting-version-option`). 13 | 14 | But one or all of these requirements cannot be satisfied in the following cases: 15 | 16 | * Downloading source tarball without ``.git`` folder (:issue:`77`). 17 | * Shallow repo clone without tags (:issue:`75`). 18 | * Getting version number from a branch which does not contain any tags (`Git-flow `_ and its derivatives) 19 | 20 | To avoid getting meaningless version number prefer using versioning schema described below. 21 | 22 | .. toctree:: 23 | :maxdepth: 1 24 | 25 | version_file 26 | dev_release_file 27 | -------------------------------------------------------------------------------- /docs/schemas/file/version_file.rst: -------------------------------------------------------------------------------- 1 | .. _version-file: 2 | 3 | Read some file content as current version 4 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 5 | 6 | For example, current repo state is: 7 | 8 | .. code:: bash 9 | 10 | commit 233f6d72 Dev branch commit (HEAD, dev) 11 | | 12 | | commit 86269212 Release commit (v1.0.0, master) 13 | | | 14 | | commit e7bdbe51 Another commit 15 | | / 16 | ... 17 | | 18 | commit 273c47eb Long long ago 19 | | 20 | ... 21 | 22 | By default, when you try to get current version, you'll receive some 23 | initial value (see :ref:`starting-version-option` option). 24 | 25 | But if you want to get synchronized version numbers in 26 | both on the branches, you can create a text file (for example, ``VERSION`` or ``VERSION.txt``) 27 | and save here current version number: 28 | 29 | .. code:: txt 30 | 31 | 1.0.0 32 | 33 | Then place it in both the branches and update your config file: 34 | 35 | .. tabs:: 36 | 37 | .. code-tab:: python ``setup.py`` 38 | 39 | import setuptools 40 | from pathlib import Path 41 | 42 | root_path = Path(__file__).parent 43 | version_file = root_path / "VERSION" 44 | 45 | setuptools.setup( 46 | ..., 47 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 48 | setuptools_git_versioning={ 49 | "enabled": True, 50 | "version_file": version_file, # <--- 51 | }, 52 | ) 53 | 54 | .. code-tab:: toml ``pyproject.toml`` 55 | 56 | [build-system] 57 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 58 | build-backend = "setuptools.build_meta" 59 | 60 | [tool.setuptools-git-versioning] 61 | enabled = true 62 | version_file = "VERSION" # <--- 63 | 64 | When you'll try to get current version in non-master branch, the content 65 | of this file (``1.0.0``) will be returned instead default version number. 66 | 67 | **Please take into account that any tags in the repo are ignored if this option is being used.** 68 | 69 | See also 70 | """"""""" 71 | - :ref:`version-callback` 72 | - :ref:`version-file-option` option 73 | - :ref:`runtime-version` 74 | -------------------------------------------------------------------------------- /docs/schemas/index.rst: -------------------------------------------------------------------------------- 1 | .. _versioning-schemas: 2 | 3 | Versioning schemas 4 | ------------------ 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Versioning schemas 9 | :name: schemas 10 | 11 | tag/index 12 | file/index 13 | callback/index 14 | -------------------------------------------------------------------------------- /docs/schemas/tag/dirty_version.rst: -------------------------------------------------------------------------------- 1 | .. _dirty-version: 2 | 3 | *Dirty* version 4 | ^^^^^^^^^^^^^^^^^^ 5 | 6 | For example, current repo state is: 7 | 8 | .. code:: bash 9 | 10 | Unstashed changes (HEAD) 11 | | 12 | commit 64e68cd4 Current commit (master) 13 | | 14 | commit 86269212 Release commit (v1.0.0) 15 | | 16 | commit e7bdbe51 Another commit 17 | | 18 | ... 19 | | 20 | commit 273c47eb Long long ago 21 | | 22 | ... 23 | 24 | And you want to generate post versions for every commit after release tag 25 | 26 | Just :ref:`install setuptools-git-versioning ` 27 | and then your package version will be ``1.0.0.post1+git.64e68cd4.dirty``. 28 | 29 | Version number template 30 | """""""""""""""""""""""" 31 | 32 | Sometimes you want see just ``1.0.0.post1+dirty`` value or even ``1.0.0+dirty``. 33 | 34 | To get version in such a format you can set a template in the config file: 35 | 36 | .. tabs:: 37 | 38 | .. code-tab:: python ``setup.py`` 39 | 40 | import setuptools 41 | 42 | setuptools.setup( 43 | ..., 44 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 45 | setuptools_git_versioning={ 46 | "enabled": True, 47 | "dirty_template": "{tag}.post{ccount}+dirty", # <--- 48 | }, 49 | ) 50 | 51 | .. code-tab:: toml ``pyproject.toml`` 52 | 53 | [build-system] 54 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 55 | build-backend = "setuptools.build_meta" 56 | 57 | [tool.setuptools-git-versioning] 58 | enabled = true 59 | dirty_template = "{tag}.post{ccount}+dirty" # <--- 60 | 61 | 62 | See also 63 | """""""" 64 | - :ref:`dirty-template-option` option 65 | - :ref:`sort-by-option` option 66 | - :ref:`substitutions` 67 | - :ref:`all-dirty-issue` issue 68 | -------------------------------------------------------------------------------- /docs/schemas/tag/index.rst: -------------------------------------------------------------------------------- 1 | .. _tag-based-release: 2 | 3 | Tag-based release (not recommended) 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | .. note:: 7 | 8 | This schema has several drawbacks. Use :ref:`file-based schema ` instead. 9 | 10 | .. toctree:: 11 | :maxdepth: 1 12 | 13 | tag_release 14 | post_release 15 | dirty_version 16 | -------------------------------------------------------------------------------- /docs/schemas/tag/post_release.rst: -------------------------------------------------------------------------------- 1 | .. _post-release: 2 | 3 | Post release 4 | ^^^^^^^^^^^^ 5 | 6 | For example, current repo state is: 7 | 8 | .. code:: bash 9 | 10 | commit 64e68cd4 Current commit (HEAD, master) 11 | | 12 | commit 86269212 Release commit (v1.0.0) 13 | | 14 | commit e7bdbe51 Another commit 15 | | 16 | ... 17 | | 18 | commit 273c47eb Long long ago 19 | | 20 | ... 21 | 22 | And you want to generate post versions for every commit after release tag. 23 | 24 | Just :ref:`install setuptools-git-versioning ` 25 | and then your package version will be ``1.0.0.post1+git.64e68cd4``. 26 | 27 | Version number template 28 | """""""""""""""""""""""" 29 | 30 | Sometimes you want see just ``1.0.0.post1`` value or even ``1.0.0``. 31 | 32 | To get version in such a format you can set a template in the config file: 33 | 34 | .. tabs:: 35 | 36 | .. code-tab:: python ``setup.py`` 37 | 38 | import setuptools 39 | 40 | setuptools.setup( 41 | ..., 42 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 43 | setuptools_git_versioning={ 44 | "enabled": True, 45 | "dev_template": "{tag}.post{ccount}", # <--- 46 | }, 47 | ) 48 | 49 | .. code-tab:: toml ``pyproject.toml`` 50 | 51 | [build-system] 52 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 53 | build-backend = "setuptools.build_meta" 54 | 55 | [tool.setuptools-git-versioning] 56 | enabled = true 57 | dev_template = "{tag}.post{ccount}" # <--- 58 | 59 | See also 60 | """""""" 61 | - :ref:`dev-template-option` option 62 | - :ref:`sort-by-option` option 63 | - :ref:`substitutions` 64 | -------------------------------------------------------------------------------- /docs/schemas/tag/tag_release.rst: -------------------------------------------------------------------------------- 1 | .. _tag-release: 2 | 3 | Release is a git tag 4 | ^^^^^^^^^^^^^^^^^^^^ 5 | 6 | For example, current repo state is: 7 | 8 | .. code:: bash 9 | 10 | commit 86269212 Release commit (HEAD, master) 11 | | 12 | commit e7bdbe51 Another commit 13 | | 14 | ... 15 | | 16 | commit 273c47eb Long long ago 17 | | 18 | ... 19 | 20 | And you want to use git tag as a release number instead of duplicating it in 21 | ``setup.py`` or other file. 22 | 23 | Just :ref:`install setuptools-git-versioning ` 24 | and then tag the commit with a proper release version (e.g. ``1.0.0``): 25 | 26 | .. code:: bash 27 | 28 | commit 86269212 Release commit (v1.0.0, HEAD, master) 29 | | 30 | commit e7bdbe51 Another commit 31 | | 32 | ... 33 | | 34 | commit 273c47eb Long long ago 35 | | 36 | ... 37 | 38 | Your package version is now ``1.0.0``. 39 | 40 | If tag number had ``v`` prefix, like ``v1.0.0``, it will be trimmed. 41 | 42 | 43 | Version number template 44 | """""""""""""""""""""""" 45 | 46 | By default, when you try to get current version, you'll receive version == current tag, e.g. ``1.0.0``. 47 | 48 | You can change this template in the config file: 49 | 50 | .. tabs:: 51 | 52 | .. code-tab:: python ``setup.py`` 53 | 54 | import setuptools 55 | 56 | setuptools.setup( 57 | ..., 58 | setup_requires=["setuptools-git-versioning>=2.0,<3"], 59 | setuptools_git_versioning={ 60 | "template": "2022.{tag}", # <--- 61 | }, 62 | ) 63 | 64 | .. code-tab:: toml ``pyproject.toml`` 65 | 66 | [build-system] 67 | requires = [ "setuptools>=41", "wheel", "setuptools-git-versioning>=2.0,<3", ] 68 | build-backend = "setuptools.build_meta" 69 | 70 | [tool.setuptools-git-versioning] 71 | template = "2022.{tag}" # <--- 72 | 73 | In this case, for tag ``3.4`` version number will be ``2022.3.4`` 74 | 75 | .. note:: 76 | 77 | If tag name is not :pep:`440` compliant, like ``"release/1.2.3"``, 78 | use :ref:`tag-formatter-option` option 79 | 80 | 81 | See also 82 | """""""" 83 | - :ref:`template-option` option 84 | - :ref:`sort-by-option` option 85 | - :ref:`tag-formatter-option` option 86 | - :ref:`substitutions` 87 | - :ref:`wrong-tag-issue` issue 88 | -------------------------------------------------------------------------------- /docs/substitutions/branch.rst: -------------------------------------------------------------------------------- 1 | .. _branch-substitution: 2 | 3 | ``branch`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Substituted by current branch name. 7 | 8 | .. note:: 9 | 10 | It is not possible to determine branch name in case of :ref:`tag-release` - 11 | tag is just custom commit name, and commit can present in several branches. 12 | 13 | Example 14 | ^^^^^^^ 15 | ``"{branch}"`` 16 | 17 | Options 18 | ^^^^^^^ 19 | No 20 | -------------------------------------------------------------------------------- /docs/substitutions/ccount.rst: -------------------------------------------------------------------------------- 1 | .. _ccount-substitution: 2 | 3 | ``ccount`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Substituted by current number of commits since tag (in case of using :ref:`tag-release`) 7 | or since last commit to a version file (in case of using :ref:`version-file` with :ref:`count-commits-option` option). 8 | 9 | Example 10 | ^^^^^^^ 11 | ``"{ccount}"`` 12 | 13 | Options 14 | ^^^^^^^ 15 | No 16 | -------------------------------------------------------------------------------- /docs/substitutions/env.rst: -------------------------------------------------------------------------------- 1 | .. _env-substitution: 2 | 3 | ``env`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Substituted by environment variable. 7 | 8 | Example 9 | ^^^^^^^ 10 | - ``"{env:MYVAR}"`` 11 | - ``"{env:MYVAR:default value}"`` 12 | - ``"{env:MYVAR:{ccount}}"`` 13 | - ``"{env:MYVAR:IGNORE}"`` 14 | 15 | Options 16 | ^^^^^^^ 17 | You can pass 2 positional options to this substitution: 18 | 19 | - Env variable name (case sensitive) 20 | - Default value (optional) 21 | 22 | Used if environment variable is not set. 23 | 24 | - no value or empty string means that variable will be replaced with string value ``UNKNOWN`` 25 | - ``some value`` - any plain text 26 | - ``{ccount}`` - any other substitution is supported 27 | - ``{env:MISSINGVAR:{ccount}}`` - nested substitutions are allowed too 28 | - ``IGNORE`` - return empty string 29 | -------------------------------------------------------------------------------- /docs/substitutions/full_sha.rst: -------------------------------------------------------------------------------- 1 | .. _full-sha-substitution: 2 | 3 | ``full_sha`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Substituted by the entire SHA-256 hash of current commit. 7 | 8 | Example 9 | ^^^^^^^ 10 | ``"{full_sha}"`` 11 | 12 | Options 13 | ^^^^^^^ 14 | No 15 | -------------------------------------------------------------------------------- /docs/substitutions/index.rst: -------------------------------------------------------------------------------- 1 | .. _substitutions: 2 | 3 | Template substitutions 4 | ---------------------- 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | tag 10 | ccount 11 | sha 12 | full_sha 13 | branch 14 | env 15 | timestamp 16 | -------------------------------------------------------------------------------- /docs/substitutions/sha.rst: -------------------------------------------------------------------------------- 1 | .. _sha-substitution: 2 | 3 | ``sha`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Substituted by first 8 symbols of SHA-256 hash of current commit. 7 | 8 | Example 9 | ^^^^^^^ 10 | ``"{sha}"`` 11 | 12 | Options 13 | ^^^^^^^ 14 | No 15 | -------------------------------------------------------------------------------- /docs/substitutions/tag.rst: -------------------------------------------------------------------------------- 1 | .. _tag-substitution: 2 | 3 | ``tag`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Substituted by current tag (in case of using :ref:`tag-release`) 7 | or version file content (in case of using :ref:`version-file`). 8 | 9 | Example 10 | ^^^^^^^ 11 | ``"{tag}"`` 12 | 13 | Options 14 | ^^^^^^^ 15 | No 16 | -------------------------------------------------------------------------------- /docs/substitutions/timestamp.rst: -------------------------------------------------------------------------------- 1 | .. _timestamp-substitution: 2 | 3 | ``timestamp`` 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Substituted by current timestamp with formatting it. 7 | 8 | Example 9 | ^^^^^^^ 10 | - ``"{timestamp}"`` 11 | - ``"{timestamp:%s}"`` 12 | - ``"{timestamp:%Y-%m-%dT%H-%M-%S}"`` 13 | 14 | Options 15 | ^^^^^^^ 16 | You can pass 1 positional option to this substitution: 17 | 18 | - Format (optional) 19 | 20 | Could be any format supported by `stftime `_. 21 | 22 | - no format means ``%s`` (unix timestamp, e.g. ``1641342388``) 23 | - ``%Y-%m-%dT%H-%M-%S`` means current datetime like ``"2021-12-31T11-22-33"`` 24 | 25 | 26 | .. note:: 27 | 28 | ``setuptools`` is removing all leading zeros (e.g. ``00`` in ``1.001.002``) 29 | to produce :pep:`440` compliant version number. 30 | 31 | If you're using datetime as your 32 | version number, you can get unexpected results like ``"2021.1.1"`` instead of 33 | ``"2021.01.01"`` because of such removal. 34 | 35 | So it is recommended to use timestamp either in local version part (``git.sha`` in ``1.2.3+git.sha``) 36 | or to use just year number in public version (``1.2.3``). 37 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.isort] 2 | profile = "black" 3 | multi_line_output = 3 4 | 5 | [tool.black] 6 | line-length = 120 7 | target-version = ['py37', 'py38', 'py39', 'py310'] 8 | include = '\.pyi?$' 9 | exclude = '''(\.eggs|\.git|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist)''' 10 | 11 | [tool.mypy] 12 | follow_imports = "silent" 13 | ignore_missing_imports = true 14 | pretty = true 15 | show_error_codes = true 16 | strict_optional = true 17 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | all: mark all tests 4 | important: mark only important tests (e.g. functions which depend on OS and Python version) 5 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | flake8>=3.8 2 | pre-commit 3 | -------------------------------------------------------------------------------- /requirements-doc.txt: -------------------------------------------------------------------------------- 1 | changelog 2 | furo 3 | numpydoc 4 | sphinx 5 | sphinx-argparse 6 | sphinx-autodoc-typehints 7 | sphinx-last-updated-by-git 8 | sphinx-tabs 9 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | build 2 | coverage 3 | pytest 4 | pytest-rerunfailures 5 | toml 6 | wheel 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | packaging 2 | setuptools 3 | tomli>=2.0.1; python_version<"3.11" 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = 4 | # No need to traverse our git directory 5 | .git, 6 | # There's no value in checking cache directories 7 | __pycache__, 8 | # The conf file is mostly autogenerated, ignore it 9 | docs/source/conf.py, 10 | # The old directory contains Flake8 2.0 11 | old, 12 | # This contains our built documentation 13 | build, 14 | # This contains builds of flake8 that we don't want to check 15 | dist 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | from setuptools import find_packages, setup 6 | 7 | from setuptools_git_versioning import version_from_git 8 | 9 | 10 | def parse_requirements(file: Path) -> list[str]: 11 | lines = file.read_text().splitlines() 12 | return [line.rstrip() for line in lines if line and not line.startswith("#")] 13 | 14 | 15 | here = Path(__file__).parent.resolve() 16 | requirements = parse_requirements(here / "requirements.txt") 17 | long_description = here.joinpath("README.rst").read_text() 18 | 19 | setup( 20 | name="setuptools-git-versioning", 21 | # +local version is not allowed in PyPI 22 | # https://github.com/pypa/pypi-legacy/issues/731#issuecomment-345461596 23 | version=version_from_git(root=here, dev_template="{tag}.post{ccount}"), 24 | author="dolfinus", 25 | author_email="martinov.m.s.8@gmail.com", 26 | description="Use git repo data for building a version number according PEP-440", 27 | license="MIT", 28 | long_description=long_description, 29 | long_description_content_type="text/x-rst", 30 | url="https://setuptools-git-versioning.readthedocs.io", 31 | keywords=["setuptools", "git", "versioning", "pep-440"], 32 | packages=find_packages(exclude=["docs", "tests", "docs.*", "tests.*"]), 33 | classifiers=[ 34 | "Framework :: Setuptools Plugin", 35 | "Intended Audience :: Developers", 36 | "Programming Language :: Python :: 3 :: Only", 37 | "Programming Language :: Python :: 3.7", 38 | "Programming Language :: Python :: 3.8", 39 | "Programming Language :: Python :: 3.9", 40 | "Programming Language :: Python :: 3.10", 41 | "Programming Language :: Python :: 3.11", 42 | "Programming Language :: Python :: 3.12", 43 | "Programming Language :: Python :: 3.13", 44 | "License :: OSI Approved :: MIT License", 45 | "Operating System :: OS Independent", 46 | ], 47 | project_urls={ 48 | "Documentation": "https://setuptools-git-versioning.readthedocs.io", 49 | "Source": "https://github.com/dolfinus/setuptools-git-versioning", 50 | "CI/CD": "https://github.com/dolfinus/setuptools-git-versioning/actions", 51 | "Coverage": "https://app.codecov.io/gh/dolfinus/setuptools-git-versioning", 52 | "Tracker": "https://github.com/dolfinus/setuptools-git-versioning/issues", 53 | }, 54 | python_requires=">=3.7", 55 | py_modules=["setuptools_git_versioning"], 56 | install_requires=requirements, 57 | setup_requires=requirements, 58 | entry_points={ 59 | "distutils.setup_keywords": [ 60 | "setuptools_git_versioning = setuptools_git_versioning:parse_config", 61 | ], 62 | "setuptools.finalize_distribution_options": [ 63 | "setuptools_git_versioning = setuptools_git_versioning:infer_version", 64 | ], 65 | "console_scripts": ["setuptools-git-versioning=setuptools_git_versioning:__main__"], 66 | }, 67 | include_package_data=True, 68 | zip_safe=False, 69 | ) 70 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dolfinus/setuptools-git-versioning/c6e010d8f35a8613e90ed93d499d14e2516365f0/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import textwrap 4 | 5 | import pytest 6 | 7 | from tests.lib.util import create_file, execute, rand_str 8 | 9 | root = os.path.dirname(os.path.dirname(__file__)) 10 | 11 | 12 | @pytest.fixture 13 | def repo_dir(tmpdir): 14 | repo_dir = str(tmpdir.mkdir(rand_str())) 15 | # collect coverage data 16 | with open(os.path.join(root, ".coveragerc")) as f: 17 | create_file(repo_dir, ".coveragerc", f.read(), add=False, commit=False) 18 | os.mkdir(os.path.join(repo_dir, "reports")) 19 | 20 | yield repo_dir 21 | 22 | if os.environ.get("CI", "false").lower() in ["1", "true"]: 23 | # move collect coverage data to reports directory 24 | for root_path, _dirs, files in os.walk(os.path.join(repo_dir, "reports")): 25 | for file in files: 26 | shutil.move(os.path.join(root_path, file), os.path.join(root, "reports", file)) 27 | 28 | 29 | @pytest.fixture 30 | def repo(repo_dir): 31 | execute(repo_dir, "git init -b master") 32 | execute(repo_dir, "git config --local user.email 'tests@example.com'") 33 | execute(repo_dir, "git config --local user.name 'Tests runner'") 34 | execute(repo_dir, "git add .coveragerc") 35 | create_file( 36 | repo_dir, 37 | ".gitignore", 38 | textwrap.dedent( 39 | """ 40 | .eggs 41 | *.egg 42 | *.egg-info/ 43 | build 44 | dist 45 | *.py[oc] 46 | reports/ 47 | """ 48 | ), 49 | ) 50 | create_file(repo_dir, "__init__.py", "") 51 | 52 | return repo_dir 53 | -------------------------------------------------------------------------------- /tests/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dolfinus/setuptools-git-versioning/c6e010d8f35a8613e90ed93d499d14e2516365f0/tests/lib/__init__.py -------------------------------------------------------------------------------- /tests/lib/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import os 5 | import subprocess 6 | import sys 7 | import textwrap 8 | from datetime import datetime 9 | from pathlib import Path 10 | from secrets import token_hex 11 | from typing import Any, Callable 12 | 13 | import toml 14 | 15 | log = logging.getLogger(__name__) 16 | root = Path(__file__).parent.parent 17 | 18 | 19 | def rand_str() -> str: 20 | return token_hex() 21 | 22 | 23 | def rand_full_sha() -> str: 24 | return token_hex(40) 25 | 26 | 27 | def rand_sha() -> str: 28 | return token_hex(8) 29 | 30 | 31 | def execute(cwd: str | os.PathLike, cmd: str, **kwargs) -> str: 32 | log.info(f"Executing '{cmd}' at '{cwd}'") 33 | 34 | if "env" in kwargs: 35 | kwargs["env"]["PATH"] = os.environ["PATH"] 36 | pythonpath = os.getenv("PYTHONPATH", None) 37 | if pythonpath: 38 | kwargs["env"]["PYTHONPATH"] = pythonpath 39 | 40 | return subprocess.check_output(cmd, cwd=cwd, shell=True, universal_newlines=True, **kwargs) # nosec 41 | 42 | 43 | def get_full_sha(cwd: str | os.PathLike, **kwargs) -> str: 44 | return execute(cwd, "git rev-list -n 1 HEAD", **kwargs).strip() 45 | 46 | 47 | def get_sha(cwd: str | os.PathLike, **kwargs) -> str: 48 | return get_full_sha(cwd, **kwargs)[:8] 49 | 50 | 51 | def create_commit( 52 | cwd: str | os.PathLike, 53 | message: str, 54 | dt: datetime | None = None, 55 | **kwargs, 56 | ) -> str: 57 | options = "" 58 | 59 | if dt is not None: 60 | # Store committer date in case it was set somewhere else 61 | original_committer_date = os.environ.get("GIT_COMMITTER_DATE", None) 62 | 63 | options += f"--date {dt.isoformat()}" 64 | # The committer date is what is used to determine sort order for tags, etc 65 | os.environ["GIT_COMMITTER_DATE"] = dt.isoformat() 66 | 67 | try: 68 | return_value = execute(cwd, f'git commit -m "{message}" {options}', **kwargs) 69 | finally: 70 | # Return committer date env var to prior value if set 71 | if dt is not None: 72 | if original_committer_date is None: 73 | # unset the var 74 | del os.environ["GIT_COMMITTER_DATE"] 75 | else: 76 | # restore previous value 77 | os.environ["GIT_COMMITTER_DATE"] = original_committer_date 78 | 79 | return return_value 80 | 81 | 82 | def create_tag( 83 | cwd: str | os.PathLike, 84 | tag: str, 85 | message: str | None = None, 86 | commit: str | None = None, 87 | **kwargs, 88 | ) -> str: 89 | options = "" 90 | if message: 91 | options += f' -a -m "{message}"' 92 | 93 | if not commit: 94 | commit = "" 95 | 96 | return execute(cwd, f"git tag {options} {tag} {commit}", **kwargs) 97 | 98 | 99 | def checkout_branch(cwd: str | os.PathLike, branch: str, new: bool = True, **kwargs) -> str: 100 | options = "" 101 | if new: 102 | options += " -b" 103 | return execute(cwd, f"git checkout {options} {branch}", **kwargs) 104 | 105 | 106 | def create_folder( 107 | cwd: str | os.PathLike, 108 | name: str | None = None, 109 | add: bool = True, 110 | commit: bool = True, 111 | **kwargs, 112 | ) -> str | None: 113 | result = None 114 | if not name: 115 | name = rand_str() 116 | 117 | path = Path(cwd).joinpath(name) 118 | 119 | # create dir with some random file 120 | path.mkdir(parents=True, exist_ok=True) 121 | path.joinpath(rand_str()).touch() 122 | 123 | if add: 124 | execute(cwd, f"git add {name}") 125 | log.info(execute(cwd, "git status")) 126 | log.info(execute(cwd, "git diff")) 127 | 128 | if commit: 129 | create_commit(cwd, f"Add {name}") 130 | result = get_sha(cwd) 131 | 132 | return result 133 | 134 | 135 | def create_file( 136 | cwd: str | os.PathLike, 137 | name: str | None = None, 138 | content: str | None = None, 139 | add: bool = True, 140 | commit: bool = True, 141 | **kwargs, 142 | ) -> str | None: 143 | result = None 144 | 145 | if not name: 146 | name = rand_str() 147 | if content is None: 148 | content = rand_str() 149 | 150 | log.info(content) 151 | Path(cwd).joinpath(name).write_text(content) 152 | 153 | if add: 154 | execute(cwd, f"git add {name}") 155 | log.info(execute(cwd, "git status")) 156 | log.info(execute(cwd, "git diff")) 157 | 158 | if commit: 159 | create_commit(cwd, f"Add {name}") 160 | result = get_sha(cwd) 161 | 162 | return result 163 | 164 | 165 | def create_pyproject_toml( 166 | cwd: str | os.PathLike, 167 | config: dict | None = None, 168 | commit: bool = True, 169 | **kwargs, 170 | ) -> str | None: 171 | # well, using pyproject.toml+setup.cfg is more classic 172 | # but it is not easy to check code coverage in such a case 173 | # so we're using pyproject.toml+setup.py 174 | create_file( 175 | cwd, 176 | "setup.py", 177 | textwrap.dedent( 178 | """ 179 | from coverage.control import Coverage 180 | 181 | coverage = Coverage() 182 | coverage.start() 183 | 184 | try: 185 | import setuptools 186 | 187 | setuptools.setup( 188 | name="mypkg", 189 | ) 190 | finally: 191 | coverage.stop() 192 | coverage.save() 193 | """ 194 | ), 195 | commit=False, 196 | **kwargs, 197 | ) 198 | 199 | cfg: dict[str, Any] = {} 200 | cfg["build-system"] = { 201 | "requires": [ 202 | "setuptools>=41", 203 | "wheel", 204 | "setuptools-git-versioning", 205 | "coverage", 206 | ], 207 | # with default "setuptools.build_meta" it is not possible to build package 208 | # which uses its own source code to get version number, 209 | # e.g. `version_callback` or `branch_formatter` 210 | # mote details: https://github.com/pypa/setuptools/issues/1642#issuecomment-457673563 211 | "build-backend": "setuptools.build_meta:__legacy__", 212 | } 213 | 214 | if config is None: 215 | config = {"enabled": True} 216 | 217 | if config != NotImplemented: 218 | cfg["tool"] = {"setuptools-git-versioning": config} 219 | 220 | return create_file(cwd, "pyproject.toml", toml.dumps(cfg), commit=commit, **kwargs) 221 | 222 | 223 | def create_setup_py( 224 | cwd: str | os.PathLike, 225 | config: dict | None = None, 226 | **kwargs, 227 | ) -> str | None: 228 | if config is None: 229 | config = {"enabled": True} 230 | 231 | if config == NotImplemented: 232 | cfg = "" 233 | else: 234 | cfg = f"setuptools_git_versioning={config}," 235 | 236 | return create_file( 237 | cwd, 238 | "setup.py", 239 | textwrap.dedent( 240 | f""" 241 | from coverage.control import Coverage 242 | 243 | coverage = Coverage() 244 | coverage.start() 245 | 246 | try: 247 | import setuptools 248 | 249 | setuptools.setup( 250 | name="mypkg", 251 | {cfg} 252 | setup_requires=[ 253 | "setuptools>=41", 254 | "wheel", 255 | "coverage", 256 | "setuptools-git-versioning", 257 | ] 258 | ) 259 | finally: 260 | coverage.stop() 261 | coverage.save() 262 | """ 263 | ), 264 | **kwargs, 265 | ) 266 | 267 | 268 | def typed_config( 269 | repo: str | os.PathLike, 270 | config_creator: Callable, 271 | config_type: str, 272 | template: str | None = None, 273 | template_name: str | None = None, 274 | config: dict | None = None, 275 | ) -> None: 276 | if config_type == "tag": 277 | cfg = {} 278 | else: 279 | cfg = {"version_file": "VERSION.txt", "count_commits_from_version_file": True} 280 | 281 | if template_name is None: 282 | if config_type == "tag": 283 | template_name = "template" 284 | else: 285 | template_name = "dev_template" 286 | 287 | if template: 288 | cfg[template_name] = template 289 | 290 | if config: 291 | cfg.update(config) 292 | 293 | config_creator(repo, cfg) 294 | 295 | if config_type == "tag": 296 | create_tag(repo, "1.2.3") 297 | else: 298 | create_file(repo, "VERSION.txt", "1.2.3") 299 | 300 | 301 | def get_version_setup_py(cwd: str | os.PathLike, **kwargs) -> str: 302 | return execute(cwd, f"{sys.executable} setup.py --version", **kwargs).strip() 303 | 304 | 305 | def get_version_module(cwd: str | os.PathLike, args: list[str] | None = None, **kwargs) -> str: 306 | args_str = " ".join(args or []) 307 | 308 | return execute( 309 | cwd, 310 | f"{sys.executable} -m coverage run -m setuptools_git_versioning {args_str} -vv", 311 | **kwargs, 312 | ).strip() 313 | 314 | 315 | def get_version_script(cwd: str | os.PathLike, args: list[str] | None = None, **kwargs) -> str: 316 | args_str = " ".join(args or []) 317 | return execute(cwd, f"setuptools-git-versioning {args_str} -vv", **kwargs).strip() 318 | 319 | 320 | def get_version(cwd: str | os.PathLike, isolated: bool = False, **kwargs) -> str: 321 | cmd = f"{sys.executable} -m build -s" 322 | if not isolated: 323 | cmd += " --no-isolation" 324 | execute(cwd, cmd, **kwargs) 325 | 326 | content = Path(cwd).joinpath("mypkg.egg-info/PKG-INFO").read_text().splitlines() 327 | 328 | for line in content: 329 | if line.startswith("Version: "): 330 | return line.replace("Version: ", "").strip() 331 | 332 | raise RuntimeError("Cannot get package version") 333 | -------------------------------------------------------------------------------- /tests/test_integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dolfinus/setuptools-git-versioning/c6e010d8f35a8613e90ed93d499d14e2516365f0/tests/test_integration/__init__.py -------------------------------------------------------------------------------- /tests/test_integration/conftest.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | import pytest 4 | 5 | from tests.lib.util import create_pyproject_toml, create_setup_py, typed_config 6 | 7 | 8 | @pytest.fixture(params=[create_setup_py, create_pyproject_toml]) 9 | def create_config(request): 10 | return request.param 11 | 12 | 13 | @pytest.fixture(params=["tag", "version_file"]) 14 | def template_config(request): 15 | return partial(typed_config, config_type=request.param) 16 | -------------------------------------------------------------------------------- /tests/test_integration/test_branch_formatter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import textwrap 4 | 5 | import pytest 6 | 7 | from tests.lib.util import ( 8 | checkout_branch, 9 | create_file, 10 | get_version, 11 | get_version_module, 12 | get_version_script, 13 | ) 14 | 15 | pytestmark = pytest.mark.all 16 | 17 | 18 | @pytest.mark.parametrize( 19 | "branch, suffix", 20 | [ 21 | ("1234", "1234"), 22 | ("feature/issue-1234-add-a-great-feature", "1234"), 23 | ("unknown", ""), 24 | ], 25 | ) 26 | def test_branch_formatter_external(repo, template_config, create_config, branch, suffix): 27 | checkout_branch(repo, branch) 28 | 29 | create_file( 30 | repo, 31 | "util.py", 32 | textwrap.dedent( 33 | """ 34 | import re 35 | 36 | def branch_formatter(branch): 37 | return re.sub("[^\\d]+", "", branch) 38 | """ 39 | ), 40 | ) 41 | 42 | template_config( 43 | repo, 44 | create_config, 45 | template="{tag}{branch}{ccount}", 46 | config={ 47 | "branch_formatter": "util:branch_formatter", 48 | }, 49 | ) 50 | 51 | assert get_version(repo) == f"1.2.3{suffix}0" 52 | assert get_version_script(repo) == f"1.2.3{suffix}0" 53 | assert get_version_module(repo) == f"1.2.3{suffix}0" 54 | 55 | # path to the repo can be passed as positional argument 56 | assert get_version_script(os.getcwd(), args=[repo]) == f"1.2.3{suffix}0" 57 | assert get_version_module(os.getcwd(), args=[repo]) == f"1.2.3{suffix}0" 58 | 59 | 60 | @pytest.mark.parametrize("create_util", [True, False]) 61 | def test_branch_formatter_external_missing(repo, template_config, create_config, create_util): 62 | if create_util: 63 | create_file( 64 | repo, 65 | "util.py", 66 | textwrap.dedent( 67 | """ 68 | import re 69 | 70 | def branch_formatter(branch): 71 | return re.sub("[^\\d]+", "", branch) 72 | """ 73 | ), 74 | ) 75 | 76 | template_config( 77 | repo, 78 | create_config, 79 | config={ 80 | "branch_formatter": "util:wtf", 81 | }, 82 | ) 83 | 84 | with pytest.raises(subprocess.CalledProcessError): 85 | get_version(repo) 86 | 87 | 88 | def test_branch_formatter_external_not_callable(repo, template_config, create_config): 89 | create_file( 90 | repo, 91 | "util.py", 92 | textwrap.dedent( 93 | """ 94 | import re 95 | 96 | branch_formatter = re.compile("[^\\d]+") 97 | """ 98 | ), 99 | ) 100 | 101 | template_config( 102 | repo, 103 | create_config, 104 | config={ 105 | "branch_formatter": "util:branch_formatter", 106 | }, 107 | ) 108 | 109 | with pytest.raises(subprocess.CalledProcessError): 110 | get_version(repo) 111 | 112 | 113 | def test_branch_formatter_external_setup_py_direct_import(repo, template_config): 114 | branch = "feature/issue-1234-add-a-great-feature" 115 | suffix = ".1234" 116 | 117 | checkout_branch(repo, branch) 118 | 119 | def config_creator(root, cfg): 120 | return create_file( 121 | root, 122 | "setup.py", 123 | textwrap.dedent( 124 | f""" 125 | from coverage.control import Coverage 126 | 127 | coverage = Coverage() 128 | coverage.start() 129 | 130 | try: 131 | import re 132 | import setuptools 133 | import pickle 134 | 135 | def branch_formatter(branch): 136 | return re.sub("[^\\d]+", "", branch) 137 | 138 | version_config = {cfg} 139 | version_config["branch_formatter"] = branch_formatter 140 | 141 | setuptools.setup( 142 | name="mypkg", 143 | setuptools_git_versioning=version_config, 144 | setup_requires=[ 145 | "setuptools>=41", 146 | "wheel", 147 | "setuptools-git-versioning", 148 | ] 149 | ) 150 | finally: 151 | coverage.stop() 152 | coverage.save() 153 | """ 154 | ), 155 | ) 156 | 157 | template_config(repo, config_creator, template="{tag}.{branch}{ccount}") 158 | 159 | assert get_version(repo) == f"1.2.3{suffix}0" 160 | assert get_version_script(repo) == f"1.2.3{suffix}0" 161 | assert get_version_module(repo) == f"1.2.3{suffix}0" 162 | 163 | # path to the repo can be passed as positional argument 164 | assert get_version_script(os.getcwd(), args=[repo]) == f"1.2.3{suffix}0" 165 | assert get_version_module(os.getcwd(), args=[repo]) == f"1.2.3{suffix}0" 166 | 167 | 168 | @pytest.mark.parametrize( 169 | "branch, suffix", 170 | [ 171 | ("1234", "1234"), 172 | ("feature/issue-1234-add-a-great-feature", "1234"), 173 | ], 174 | ) 175 | def test_branch_formatter_regexp(repo, template_config, create_config, branch, suffix): 176 | checkout_branch(repo, branch) 177 | 178 | template_config( 179 | repo, 180 | create_config, 181 | template="{tag}{branch}{ccount}", 182 | config={ 183 | "branch_formatter": r".*?(?P[\d]+).*", 184 | }, 185 | ) 186 | 187 | assert get_version(repo) == f"1.2.3{suffix}0" 188 | 189 | 190 | def test_branch_formatter_regexp_not_match(repo, template_config, create_config): 191 | checkout_branch(repo, "unknown") 192 | 193 | template_config( 194 | repo, 195 | create_config, 196 | template="{tag}{branch}{ccount}", 197 | config={ 198 | "branch_formatter": r".*?(?P[\d]+).*", 199 | }, 200 | ) 201 | 202 | with pytest.raises(subprocess.CalledProcessError): 203 | get_version(repo) 204 | 205 | 206 | @pytest.mark.parametrize("regexp", [r".*?([\d]+).*", r".*?(?P[\d]+).*"]) 207 | def test_branch_formatter_regexp_no_capture_group(repo, template_config, create_config, regexp): 208 | template_config( 209 | repo, 210 | create_config, 211 | config={ 212 | "branch_formatter": regexp, 213 | }, 214 | ) 215 | 216 | with pytest.raises(subprocess.CalledProcessError): 217 | get_version(repo) 218 | 219 | 220 | def test_branch_formatter_regexp_wrong_format(repo, template_config, create_config): 221 | template_config( 222 | repo, 223 | create_config, 224 | config={ 225 | "branch_formatter": "(", 226 | }, 227 | ) 228 | 229 | with pytest.raises(subprocess.CalledProcessError): 230 | get_version(repo) 231 | 232 | 233 | @pytest.mark.parametrize( 234 | "version_callback", 235 | ["version:get_version", "version:__version__"], 236 | ) 237 | def test_branch_formatter_ignored_if_version_callback_set(repo, create_config, version_callback): 238 | create_file( 239 | repo, 240 | "version.py", 241 | textwrap.dedent( 242 | """ 243 | def get_version(): 244 | return "1.0.0" 245 | 246 | __version__ = "1.0.0" 247 | """ 248 | ), 249 | ) 250 | create_config( 251 | repo, 252 | { 253 | "version_callback": version_callback, 254 | "branch_formatter": "(", 255 | }, 256 | ) 257 | 258 | assert get_version(repo) == "1.0.0" 259 | -------------------------------------------------------------------------------- /tests/test_integration/test_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import textwrap 4 | 5 | import pytest 6 | import toml 7 | 8 | from tests.lib.util import ( 9 | create_file, 10 | create_folder, 11 | create_pyproject_toml, 12 | create_setup_py, 13 | get_version, 14 | get_version_module, 15 | get_version_script, 16 | get_version_setup_py, 17 | ) 18 | 19 | pytestmark = [pytest.mark.all, pytest.mark.important] 20 | 21 | 22 | def test_config_not_set(repo, create_config): 23 | # legacy builder for pyproject.toml 24 | create_config(repo, NotImplemented) 25 | 26 | assert get_version(repo) == "0.0.0" 27 | 28 | 29 | def test_config_not_used(repo): 30 | # modern builder for pyproject.toml 31 | create_file( 32 | repo, 33 | "setup.py", 34 | textwrap.dedent( 35 | """ 36 | from coverage.control import Coverage 37 | 38 | coverage = Coverage() 39 | coverage.start() 40 | 41 | try: 42 | import setuptools 43 | 44 | setuptools.setup( 45 | name="mypkg", 46 | ) 47 | finally: 48 | coverage.stop() 49 | coverage.save() 50 | """ 51 | ), 52 | ) 53 | assert get_version_setup_py(repo) == "0.0.0" 54 | 55 | cfg = { 56 | "build-system": { 57 | "requires": [ 58 | "setuptools>=41", 59 | "wheel", 60 | "coverage", 61 | ], 62 | "build-backend": "setuptools.build_meta", 63 | } 64 | } 65 | 66 | create_file( 67 | repo, 68 | "pyproject.toml", 69 | toml.dumps(cfg), 70 | ) 71 | 72 | assert get_version(repo, isolated=False) == "0.0.0" 73 | assert get_version(repo, isolated=True) == "0.0.0" 74 | 75 | # python -m setuptools_git_versioning 76 | # and 77 | # setuptools-git-versioning 78 | # requires pyproject.toml or setup.py to exist with `enabled: True`` option 79 | with pytest.raises(subprocess.CalledProcessError): 80 | get_version_module(repo) 81 | 82 | with pytest.raises(subprocess.CalledProcessError): 83 | get_version_script(repo) 84 | 85 | 86 | def test_config_enabled_true(repo, create_config): 87 | create_config(repo, {"enabled": True}) 88 | 89 | assert get_version(repo) == "0.0.1" 90 | assert get_version_script(repo) == "0.0.1" 91 | assert get_version_module(repo) == "0.0.1" 92 | 93 | # path to the repo can be passed as positional argument 94 | assert get_version_script(os.getcwd(), args=[repo]) == "0.0.1" 95 | assert get_version_module(os.getcwd(), args=[repo]) == "0.0.1" 96 | 97 | 98 | @pytest.mark.parametrize( 99 | "value", 100 | [ 101 | True, 102 | False, 103 | [("A", "B")], 104 | ], 105 | ) 106 | def test_config_wrong_format(repo, create_config, value): 107 | create_config(repo, value) 108 | 109 | with pytest.raises(subprocess.CalledProcessError): 110 | get_version(repo) 111 | 112 | with pytest.raises(subprocess.CalledProcessError): 113 | get_version_module(repo) 114 | 115 | with pytest.raises(subprocess.CalledProcessError): 116 | get_version_script(repo) 117 | 118 | 119 | def test_config_pyproject_toml_is_used_then_setup_py_is_empty(repo): 120 | create_pyproject_toml(repo, {"starting_version": "2.3.4"}, add=False, commit=False) 121 | create_setup_py(repo, NotImplemented, add=False, commit=False) 122 | 123 | assert get_version(repo) == "2.3.4" 124 | assert get_version_script(repo) == "2.3.4" 125 | assert get_version_module(repo) == "2.3.4" 126 | 127 | 128 | def test_config_pyproject_toml_is_used_then_setup_py_does_not_exist(repo): 129 | create_pyproject_toml(repo, {"starting_version": "2.3.4"}, add=False, commit=False) 130 | create_file( 131 | repo, 132 | "setup.cfg", 133 | textwrap.dedent( 134 | """ 135 | [metadata] 136 | name = mypkg 137 | """ 138 | ), 139 | ) 140 | 141 | assert get_version(repo) == "2.3.4" 142 | assert get_version_script(repo) == "2.3.4" 143 | assert get_version_module(repo) == "2.3.4" 144 | 145 | 146 | def test_config_setup_py_is_used_then_pyproject_toml_is_empty(repo): 147 | create_pyproject_toml(repo, NotImplemented, add=False, commit=False) 148 | create_setup_py(repo, {"starting_version": "2.3.4"}, add=False, commit=False) 149 | 150 | assert get_version(repo) == "2.3.4" 151 | assert get_version_setup_py(repo) == "2.3.4" 152 | assert get_version_module(repo) == "2.3.4" 153 | assert get_version_script(repo) == "2.3.4" 154 | 155 | 156 | def test_config_setup_py_is_used_then_pyproject_toml_does_not_exist(repo): 157 | create_setup_py(repo, {"starting_version": "2.3.4"}, add=False, commit=False) 158 | 159 | assert get_version(repo) == "2.3.4" 160 | assert get_version_setup_py(repo) == "2.3.4" 161 | assert get_version_module(repo) == "2.3.4" 162 | assert get_version_script(repo) == "2.3.4" 163 | 164 | 165 | def test_config_both_setup_py_and_pyproject_toml_are_present(repo): 166 | create_pyproject_toml(repo) 167 | create_setup_py(repo) 168 | 169 | with pytest.raises(subprocess.CalledProcessError): 170 | get_version(repo) 171 | 172 | with pytest.raises(subprocess.CalledProcessError): 173 | get_version_setup_py(repo) 174 | 175 | with pytest.raises(subprocess.CalledProcessError): 176 | get_version_script(repo) 177 | 178 | with pytest.raises(subprocess.CalledProcessError): 179 | get_version_module(repo) 180 | 181 | 182 | def test_config_setup_py_is_folder(repo): 183 | create_folder(repo, "setup.py") 184 | 185 | with pytest.raises(subprocess.CalledProcessError): 186 | get_version(repo) 187 | 188 | with pytest.raises(subprocess.CalledProcessError): 189 | get_version_module(repo) 190 | 191 | with pytest.raises(subprocess.CalledProcessError): 192 | get_version_script(repo) 193 | 194 | 195 | def test_config_pyproject_toml_is_folder(repo): 196 | create_folder(repo, "pyproject.toml") 197 | 198 | with pytest.raises(subprocess.CalledProcessError): 199 | get_version(repo) 200 | 201 | with pytest.raises(subprocess.CalledProcessError): 202 | get_version_module(repo) 203 | 204 | with pytest.raises(subprocess.CalledProcessError): 205 | get_version_script(repo) 206 | -------------------------------------------------------------------------------- /tests/test_integration/test_substitution.py: -------------------------------------------------------------------------------- 1 | import re 2 | import subprocess 3 | from datetime import datetime 4 | 5 | import pytest 6 | 7 | from tests.lib.util import ( 8 | checkout_branch, 9 | create_file, 10 | create_setup_py, 11 | create_tag, 12 | get_version_setup_py, 13 | ) 14 | 15 | pytestmark = pytest.mark.all 16 | 17 | 18 | @pytest.mark.parametrize( 19 | "template", 20 | [ 21 | "{tag}", 22 | "{tag}+a{tag}", 23 | ], 24 | ) 25 | def test_substitution_tag(repo, template): 26 | create_setup_py(repo, {"template": template}) 27 | create_tag(repo, "1.2.3") 28 | 29 | assert get_version_setup_py(repo) == template.format(tag="1.2.3") 30 | 31 | 32 | @pytest.mark.parametrize( 33 | "dev_template", 34 | [ 35 | "{tag}.{ccount}", 36 | "{tag}.{ccount}+a{ccount}", 37 | ], 38 | ) 39 | def test_substitution_ccount(repo, dev_template): 40 | create_setup_py(repo, {"dev_template": dev_template}) 41 | create_tag(repo, "1.2.3") 42 | create_file(repo) 43 | 44 | assert get_version_setup_py(repo) == dev_template.format(tag="1.2.3", ccount=1) 45 | 46 | 47 | @pytest.mark.parametrize( 48 | "branch, suffix", 49 | [ 50 | ("alpha", "a"), 51 | ("beta", "b"), 52 | ("dev", ".dev"), 53 | ("pre", "rc"), 54 | ("preview", "rc"), 55 | ("post", ".post"), 56 | ], 57 | ) 58 | @pytest.mark.parametrize( 59 | "template, real_template", 60 | [ 61 | ("{tag}{branch}", "{tag}{suffix}0"), 62 | ("{tag}{branch}+a{branch}", "{tag}{suffix}0+a{branch}"), 63 | ], 64 | ) 65 | def test_substitution_branch(repo, template, real_template, branch, suffix): 66 | checkout_branch(repo, branch) 67 | create_setup_py(repo, {"template": template}) 68 | create_tag(repo, "1.2.3") 69 | 70 | assert get_version_setup_py(repo) == real_template.format(tag="1.2.3", branch=branch, suffix=suffix) 71 | 72 | 73 | @pytest.mark.parametrize( 74 | "dev_template, pipeline_id, suffix", 75 | [ 76 | # leading zeros are removed by setuptools 77 | ("{tag}.post{env:PIPELINE_ID}", "234", "234"), 78 | ("{tag}.post{env:PIPELINE_ID}", "0234", "234"), 79 | ("{tag}.post{env:PIPELINE_ID:123}", "234", "234"), 80 | ("{tag}.post{env:PIPELINE_ID:123}", None, "123"), 81 | ("{tag}.post{env:PIPELINE_ID:IGNORE}", "234", "234"), 82 | ("{tag}.post{env:PIPELINE_ID:IGNORE}", None, "0"), 83 | ("{tag}.post{env:PIPELINE_ID:}", "234", "234"), 84 | ("{tag}.post{env:PIPELINE_ID:{ccount}}", "234", "234"), 85 | ("{tag}.post{env:PIPELINE_ID:{ccount}}", None, "1"), 86 | ("{tag}.post{env:PIPELINE_ID:{timestamp:%Y}}", "234", "234"), 87 | ("{tag}.post{env:PIPELINE_ID:{timestamp:%Y}}", None, datetime.now().year), 88 | ("{tag}.post{env:PIPELINE_ID:{env:ANOTHER_ENV}}", "234", "234"), 89 | ("{tag}.post{env:PIPELINE_ID:{env:ANOTHER_ENV}}", None, "345"), 90 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV}}", "234", "234"), 91 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV:IGNORE}}", "234", "234"), 92 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV:IGNORE}}", None, "0"), 93 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV:}}", "234", "234"), 94 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV:567}}", "234", "234"), 95 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV:567}}", None, "567"), 96 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV:{ccount}}}", "234", "234"), 97 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV:{ccount}}}", None, "1"), 98 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV:{timestamp:%Y}}}", "234", "234"), 99 | ("{tag}.post{env:PIPELINE_ID:{env:MISSING_ENV:{timestamp:%Y}}}", None, datetime.now().year), 100 | ("{tag}.post{env:PIPELINE_ID}+abc{env:ANOTHER_ENV}", "234", "234+abc345"), 101 | ("{tag}.post{env:PIPELINE_ID}+abc{env:MISSING_ENV}", "234", "234+abcunknown"), 102 | ("{tag}.post{env:PIPELINE_ID}+abc{env:MISSING_ENV:5.6.7}", "234", "234+abc5.6.7"), 103 | ("{tag}.post{env:PIPELINE_ID}+abc{env:MISSING_ENV:B-C-D}", "234", "234+abcb.c.d"), 104 | ("{tag}.post{env:PIPELINE_ID}+abc{env:MISSING_ENV:IGNORE}d", "234", "234+abcd"), 105 | ("{tag}.post{env:PIPELINE_ID}+abc{env:MISSING_ENV:}d", "234", "234+abcunknownd"), 106 | ("{tag}.post{env:PIPELINE_ID}+abc{env:MISSING_ENV:.}d", "234", "234+abc.d"), 107 | ("{tag}.post{env:PIPELINE_ID}+abc{env:MISSING_ENV:{ccount}}", "234", "234+abc1"), 108 | ( 109 | "{tag}.post{env:PIPELINE_ID}+abc{env:MISSING_ENV:{timestamp:%Y}}", 110 | "234", 111 | "234+abc" + str(datetime.now().year), 112 | ), 113 | # empty env variable name 114 | ("{tag}.post{env:PIPELINE_ID}+abc{env: }d", "234", "234+abcunknownd"), 115 | ], 116 | ) 117 | def test_substitution_env(repo, dev_template, pipeline_id, suffix): 118 | create_setup_py(repo, {"dev_template": dev_template}) 119 | create_tag(repo, "1.2.3") 120 | create_file(repo) 121 | 122 | env = {"ANOTHER_ENV": "345"} 123 | if pipeline_id is not None: 124 | env["PIPELINE_ID"] = pipeline_id 125 | 126 | assert get_version_setup_py(repo, env=env) == f"1.2.3.post{suffix}" 127 | 128 | 129 | @pytest.mark.flaky(reruns=3) # running subprocess takes some time, so we can get previous second 130 | @pytest.mark.parametrize( 131 | "template, fmt, callback", 132 | [ 133 | ("{tag}.post{timestamp}", "{tag}.post{}", lambda dt: (int(dt.strftime("%s")) // 100,)), 134 | ("{tag}.post{timestamp:}", "{tag}.post{}", lambda dt: (int(dt.strftime("%s")) // 100,)), 135 | ("{tag}.post{timestamp:%s}", "{tag}.post{}", lambda dt: (int(dt.strftime("%s")) // 100,)), 136 | ( 137 | "{timestamp:%Y}.{timestamp:%m}.{timestamp:%d}+{timestamp:%H%M%S}", 138 | "{}.{}.{}+{}", 139 | lambda dt: (dt.year, dt.month, dt.day, dt.strftime("%H%M")), 140 | ), 141 | ( 142 | "{tag}.post{ccount}+{timestamp:%Y-%m-%dT%H-%M-%S}", 143 | "{tag}.post{ccount}+{}", 144 | lambda dt: (dt.strftime("%Y.%m.%dt%H.%M"),), 145 | ), 146 | # unknown substitution 147 | ("{tag}+git.{timestamp:%i}", "{tag}+git.i", lambda x: []), 148 | # pure string 149 | ("{tag}+git.{timestamp:abc}", "{tag}+git.abc", lambda x: []), 150 | ], 151 | ) 152 | def test_substitution_timestamp(repo, template, fmt, callback): 153 | create_setup_py(repo, {"template": template}) 154 | create_tag(repo, "1.2.3") 155 | 156 | value = fmt.format(tag="1.2.3", ccount=0, *callback(datetime.now())) 157 | pattern = re.compile(r"([^\d\w])0+(\d+[^\d\w]|\d+$)") 158 | while True: 159 | # leading zeros are removed even in local part of version 160 | new_value = pattern.sub(r"\1\2", value) 161 | if new_value == value: 162 | break 163 | value = new_value 164 | 165 | assert new_value in get_version_setup_py(repo) 166 | 167 | 168 | @pytest.mark.parametrize( 169 | "template", 170 | [ 171 | "{tag}+a{env}", 172 | "{tag}+a{env:}", 173 | "{tag}.post{env:{}}", 174 | "{tag}+a{env:MISSING_ENV:{}", 175 | "{tag}+a{env:MISSING_ENV:{{}}", 176 | "{tag}+a{env:MISSING_ENV:}}", 177 | "{tag}+a{env:MISSING_ENV:{}}}", 178 | "{tag}+a{timestamp:A:B}", 179 | "{tag}+a{timestamp:{%Y}", 180 | ], 181 | ) 182 | def test_substitution_wrong_format(repo, template): 183 | create_setup_py(repo, {"template": template}) 184 | create_tag(repo, "1.2.3") 185 | 186 | with pytest.raises(subprocess.CalledProcessError): 187 | get_version_setup_py(repo) 188 | -------------------------------------------------------------------------------- /tests/test_integration/test_tag.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import time 3 | from datetime import datetime, timedelta 4 | 5 | import pytest 6 | 7 | from tests.lib.util import ( 8 | checkout_branch, 9 | create_commit, 10 | create_file, 11 | create_tag, 12 | get_full_sha, 13 | get_sha, 14 | get_version, 15 | ) 16 | 17 | pytestmark = pytest.mark.all 18 | 19 | 20 | @pytest.mark.important 21 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 22 | @pytest.mark.parametrize( 23 | "template, subst", 24 | [ 25 | (None, "1.0.0"), 26 | ("{tag}.post{ccount}+git.{full_sha}", "1.0.0.post0+git.{full_sha}"), 27 | ("{tag}.post{ccount}+git.{sha}", "1.0.0.post0+git.{sha}"), 28 | ("{tag}.post{ccount}", "1.0.0.post0"), 29 | ("{tag}", "1.0.0"), 30 | ], 31 | ) 32 | def test_tag(repo, create_config, template, subst): 33 | if template: 34 | create_config(repo, {"template": template}) 35 | else: 36 | create_config(repo) 37 | 38 | create_tag(repo, "1.0.0") 39 | 40 | full_sha = get_full_sha(repo) 41 | sha = get_sha(repo) 42 | assert get_version(repo) == subst.format(sha=sha, full_sha=full_sha) 43 | 44 | 45 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 46 | @pytest.mark.parametrize( 47 | "template, subst", 48 | [ 49 | (None, "1.0.0.post1+git.{sha}"), 50 | ("{tag}.post{ccount}+git.{full_sha}", "1.0.0.post1+git.{full_sha}"), 51 | ("{tag}.post{ccount}+git.{sha}", "1.0.0.post1+git.{sha}"), 52 | ("{tag}.post{ccount}", "1.0.0.post1"), 53 | ("{tag}", "1.0.0"), 54 | ], 55 | ) 56 | def test_tag_dev(repo, create_config, template, subst): 57 | if template: 58 | create_config(repo, {"dev_template": template}) 59 | else: 60 | create_config(repo) 61 | 62 | create_tag(repo, "1.0.0") 63 | create_file(repo) 64 | 65 | full_sha = get_full_sha(repo) 66 | sha = get_sha(repo) 67 | assert get_version(repo) == subst.format(sha=sha, full_sha=full_sha) 68 | 69 | 70 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 71 | @pytest.mark.parametrize( 72 | "template, subst", 73 | [ 74 | (None, "1.0.0.post0+git.{sha}.dirty"), 75 | ("{tag}.post{ccount}+git.{full_sha}.dirty", "1.0.0.post0+git.{full_sha}.dirty"), 76 | ("{tag}.post{ccount}+git.{sha}.dirty", "1.0.0.post0+git.{sha}.dirty"), 77 | ("{tag}.post{ccount}+dirty", "1.0.0.post0+dirty"), 78 | ("{tag}+dirty", "1.0.0+dirty"), 79 | ], 80 | ) 81 | @pytest.mark.parametrize("add", [True, False]) 82 | def test_tag_dirty(repo, create_config, add, template, subst): 83 | if template: 84 | create_config(repo, {"dirty_template": template}) 85 | else: 86 | create_config(repo) 87 | 88 | create_tag(repo, "1.0.0") 89 | create_file(repo, add=add, commit=False) 90 | 91 | full_sha = get_full_sha(repo) 92 | sha = get_sha(repo) 93 | assert get_version(repo) == subst.format(sha=sha, full_sha=full_sha) 94 | 95 | 96 | @pytest.mark.parametrize("starting_version, version", [(None, "0.0.1"), ("1.2.3", "1.2.3")]) 97 | def test_tag_missing(repo, create_config, starting_version, version): 98 | if starting_version: 99 | create_config(repo, {"starting_version": starting_version}) 100 | else: 101 | create_config(repo) 102 | 103 | assert get_version(repo) == version 104 | 105 | 106 | @pytest.mark.parametrize( 107 | "tag, version", 108 | [ 109 | ("1.0.0", "1.0.0"), 110 | ("v1.2.3", "1.2.3"), 111 | ("1.2.3dev1", "1.2.3.dev1"), 112 | ("1.2.3.dev1", "1.2.3.dev1"), 113 | ("1.2.3-dev1", "1.2.3.dev1"), 114 | ("1.2.3+local", "1.2.3+local"), 115 | ("1.2.3+local-abc", "1.2.3+local.abc"), 116 | ("1.2.3+local_abc", "1.2.3+local.abc"), 117 | ("1.2.3+local/abc", "1.2.3+local.abc"), 118 | ("1.2.3+local/abc/-", "1.2.3+local.abc"), 119 | ], 120 | ) 121 | def test_tag_sanitization(repo, create_config, tag, version): 122 | create_config(repo) 123 | create_tag(repo, tag) 124 | 125 | assert get_version(repo) == version 126 | 127 | 128 | @pytest.mark.parametrize( 129 | "tag", 130 | [ 131 | "alpha1.0.0", 132 | "1.0.0abc", 133 | "1.0.0.abc", 134 | "1.0.0-abc", 135 | "1.0.0_abc", 136 | ], 137 | ) 138 | def test_tag_wrong_version_number(repo, tag, create_config): 139 | create_config(repo) 140 | create_tag(repo, tag) 141 | 142 | with pytest.raises(subprocess.CalledProcessError): 143 | get_version(repo) 144 | 145 | 146 | @pytest.mark.parametrize( 147 | "starting_version", 148 | [ 149 | "alpha1.0.0", 150 | "1.0.0abc", 151 | "1.0.0.abc", 152 | "1.0.0-abc", 153 | "1.0.0_abc", 154 | ], 155 | ) 156 | def test_tag_wrong_starting_version(repo, create_config, starting_version): 157 | create_config(repo, {"starting_version": starting_version}) 158 | 159 | with pytest.raises(subprocess.CalledProcessError): 160 | get_version(repo) 161 | 162 | 163 | def test_tag_not_a_repo(repo_dir, create_config): 164 | create_config(repo_dir, add=False, commit=False) 165 | 166 | assert get_version(repo_dir) == "0.0.1" 167 | 168 | 169 | def test_tag_non_linear_history(repo, create_config): 170 | checkout_branch(repo, "dev") 171 | create_file(repo, commit=False) 172 | create_config(repo) 173 | 174 | checkout_branch(repo, "master", new=False) 175 | create_file(repo, commit=False) 176 | create_config(repo) 177 | create_tag(repo, "1.0.0") 178 | 179 | checkout_branch(repo, "dev", new=False) 180 | create_file(repo) 181 | assert get_version(repo) == "0.0.1" 182 | 183 | 184 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 185 | def test_tag_linear_history(repo, create_config): 186 | create_tag(repo, "1.0.0") 187 | checkout_branch(repo, "dev") 188 | create_config(repo) 189 | 190 | sha = get_sha(repo) 191 | assert get_version(repo) == f"1.0.0.post1+git.{sha}" 192 | 193 | 194 | @pytest.mark.parametrize( 195 | "starting_version", 196 | [ 197 | "1.0.0", 198 | "1.2.3", 199 | ], 200 | ) 201 | def test_tag_missing_with_starting_version(repo, create_config, starting_version): 202 | create_config(repo, {"starting_version": starting_version}) 203 | 204 | assert get_version(repo) == starting_version 205 | 206 | 207 | @pytest.mark.parametrize("message", ["", "Some message"]) 208 | def test_tag_sort_by_version(repo, create_config, message): 209 | sort_by = "version:refname" 210 | create_config(repo, {"sort_by": sort_by}) 211 | 212 | tags_to_commit = [ 213 | "1.1.0", 214 | "1.1.10", 215 | "1.1.1", 216 | ] 217 | 218 | for i, tag in enumerate(tags_to_commit): 219 | create_file(repo, commit=False) 220 | dt = datetime.now() - timedelta(days=len(tags_to_commit) - i) 221 | create_commit(repo, "Some commit", dt=dt) 222 | create_tag(repo, tag, message=message, commit=get_sha(repo)) 223 | 224 | # the result is not stable because latest tag (by name) 225 | # has nothing in common with commit creation time 226 | # so it could mess everything up: https://github.com/dolfinus/setuptools-git-versioning/issues/22 227 | # that's why this value is not default 228 | assert "1.1.10" in get_version(repo) 229 | 230 | 231 | @pytest.mark.parametrize("message", ["", "Some message"]) 232 | def test_tag_sort_by_commit_date(repo, create_config, message): 233 | sort_by = "committerdate" 234 | create_config(repo, {"sort_by": sort_by}) 235 | 236 | commits = {} 237 | tags_to_commit = [ 238 | "1.1.10", 239 | "1.1.0", 240 | "1.1.1", 241 | ] 242 | 243 | for i, tag in enumerate(tags_to_commit): 244 | create_file(repo, commit=False) 245 | dt = datetime.now() - timedelta(days=len(tags_to_commit) - i) 246 | create_commit(repo, "Some commit", dt=dt) 247 | commits[tag] = get_sha(repo) 248 | 249 | tags_to_create = [ 250 | "1.1.0", 251 | "1.1.1", 252 | "1.1.10", 253 | ] 254 | 255 | for tag in tags_to_create: 256 | create_tag(repo, tag, message=message, commit=commits[tag]) 257 | 258 | if not message: 259 | assert get_version(repo) == "1.1.1" 260 | else: 261 | assert get_version(repo).startswith("1.1") 262 | # the result is totally random because annotaged tags have no such field at all 263 | # https://github.com/dolfinus/setuptools-git-versioning/issues/23 264 | 265 | 266 | @pytest.mark.parametrize("message", ["", "Some message"]) 267 | def test_tag_sort_by_tag_date(repo, create_config, message): 268 | sort_by = "taggerdate" 269 | create_config(repo, {"sort_by": sort_by}) 270 | 271 | commits = {} 272 | tags_to_commit = [ 273 | "1.1.10", 274 | "1.1.0", 275 | "1.1.1", 276 | ] 277 | 278 | for i, tag in enumerate(tags_to_commit): 279 | create_file(repo, commit=False) 280 | dt = datetime.now() - timedelta(days=len(tags_to_commit) - i) 281 | create_commit(repo, "Some commit", dt=dt) 282 | commits[tag] = get_sha(repo) 283 | 284 | tags_to_create = [ 285 | "1.1.0", 286 | "1.1.1", 287 | "1.1.10", 288 | ] 289 | 290 | for tag in tags_to_create: 291 | create_tag(repo, tag, message=message, commit=commits[tag]) 292 | time.sleep(1) 293 | 294 | if message: 295 | # the result is not stable because latest tag (by creation time) 296 | # has nothing in common with commit creation time 297 | assert "1.1.10" in get_version(repo) 298 | else: 299 | assert get_version(repo).startswith("1.1") 300 | # the result is totally random because annotaged tags have no such field at all 301 | # https://github.com/dolfinus/setuptools-git-versioning/issues/23 302 | 303 | 304 | @pytest.mark.parametrize("sort_by", [None, "creatordate"]) 305 | @pytest.mark.parametrize("message", ["", "Some message"]) 306 | def test_tag_sort_by_create_date(repo, create_config, message, sort_by): 307 | if sort_by: 308 | create_config(repo, {"sort_by": sort_by}) 309 | else: 310 | create_config(repo) 311 | 312 | commits = {} 313 | tags_to_commit = [ 314 | "1.1.0", 315 | "1.1.10", 316 | "1.1.1", 317 | ] 318 | 319 | for i, tag in enumerate(tags_to_commit): 320 | create_file(repo, commit=False) 321 | dt = datetime.now() - timedelta(days=len(tags_to_commit) - i) 322 | create_commit(repo, "Some commit", dt=dt) 323 | commits[tag] = get_sha(repo) 324 | 325 | tags_to_create = [ 326 | "1.1.10", 327 | "1.1.1", 328 | "1.1.0", 329 | ] 330 | 331 | for tag in tags_to_create: 332 | create_tag(repo, tag, message=message, commit=commits[tag]) 333 | 334 | if message: 335 | # the result is not stable because latest tag (by creation time) 336 | # has nothing in common with commit creation time 337 | # so it is not recommended to create annotated tags in the past 338 | assert "1.1.0" in get_version(repo) 339 | else: 340 | # but at least creatordate field is present in both tag types 341 | # https://stackoverflow.com/questions/67206124 342 | # so this is a default value 343 | assert get_version(repo) == "1.1.1" 344 | -------------------------------------------------------------------------------- /tests/test_integration/test_tag_filter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import textwrap 4 | from datetime import datetime, timedelta 5 | 6 | import pytest 7 | 8 | from tests.lib.util import ( 9 | create_commit, 10 | create_file, 11 | create_tag, 12 | get_sha, 13 | get_version, 14 | get_version_module, 15 | get_version_script, 16 | ) 17 | 18 | pytestmark = pytest.mark.all 19 | 20 | 21 | @pytest.mark.important 22 | @pytest.mark.parametrize( 23 | "tag_filter, version", 24 | [ 25 | ("product_x/(?P.*)", "1.1.0"), 26 | ("product_y/(?P.*)", "1.1.10"), 27 | ("product_z/foo/(?P.*)", "1.1.1"), 28 | ], 29 | ) 30 | def test_tag_filter(repo, create_config, tag_filter, version): 31 | create_config(repo, {"tag_filter": tag_filter, "tag_formatter": tag_filter}) 32 | 33 | tags_to_commit = [ 34 | "product_x/1.0.0", 35 | "product_x/1.0.2", 36 | "product_x/1.1.0", 37 | "product_y/1.1.10", 38 | "product_z/foo/1.1.1", 39 | ] 40 | 41 | for i, tag in enumerate(tags_to_commit): 42 | create_file(repo, commit=False) 43 | dt = datetime.now() - timedelta(days=10) + timedelta(days=i) 44 | create_commit(repo, "Some commit", dt=dt) 45 | create_tag(repo, tag, message="", commit=get_sha(repo)) 46 | 47 | assert get_version(repo).startswith(version) 48 | 49 | 50 | @pytest.mark.parametrize( 51 | "tag, version, filter_regex", 52 | [ 53 | ("1.0.0", "1.0.0", r"(?P[\d.]+)"), 54 | ("release/1.0.0", "0.0.1", r"(?P[\d.]+)"), 55 | ("unknown", "0.0.1", r"(?P[\d.]+)"), 56 | ("foo/bar/1.0.3-123", "1.0.3.123", r"foo/bar/(?P.*)"), 57 | ], 58 | ) 59 | def test_tag_filter_external(repo, create_config, tag, version, filter_regex): 60 | create_file( 61 | repo, 62 | "util.py", 63 | textwrap.dedent( 64 | rf""" 65 | from __future__ import annotations # For `str | None` type syntax 66 | 67 | import re 68 | 69 | def tag_filter(tag: str) -> str | None: 70 | m = re.match(r"{filter_regex}", tag) 71 | 72 | if m: 73 | return m.group('tag') 74 | return None 75 | """ 76 | ), 77 | ) 78 | 79 | create_config( 80 | repo, 81 | { 82 | "tag_filter": "util:tag_filter", 83 | "tag_formatter": "util:tag_filter", 84 | }, 85 | ) 86 | create_tag(repo, tag) 87 | 88 | assert get_version(repo) == version 89 | assert get_version_script(repo) == version 90 | assert get_version_module(repo) == version 91 | 92 | # path to the repo can be passed as positional argument 93 | assert get_version_script(os.getcwd(), args=[repo]) == version 94 | assert get_version_module(os.getcwd(), args=[repo]) == version 95 | 96 | 97 | def test_tag_filter_without_tag_formatter(repo, create_config): 98 | create_file( 99 | repo, 100 | "util.py", 101 | textwrap.dedent( 102 | r""" 103 | from __future__ import annotations # For `str | None` type syntax 104 | 105 | import re 106 | 107 | def tag_filter(tag: str) -> str | None: 108 | if re.match(r"foo/bar/.*", tag): 109 | return tag 110 | return None 111 | """ 112 | ), 113 | ) 114 | 115 | create_config( 116 | repo, 117 | { 118 | "tag_filter": "util:tag_filter", 119 | }, 120 | ) 121 | create_tag(repo, "foo/bar/1.2.0") 122 | 123 | # foo/bar/1.2.0 has passed the filter, but is not a valid version number 124 | with pytest.raises(subprocess.CalledProcessError): 125 | get_version(repo) 126 | 127 | 128 | def test_tag_filter_invalid_regex(repo, create_config): 129 | create_config( 130 | repo, 131 | {"tag_filter": "abc(.*"}, 132 | ) 133 | create_tag(repo, "1.0.0") 134 | 135 | with pytest.raises(subprocess.CalledProcessError): 136 | get_version(repo) 137 | -------------------------------------------------------------------------------- /tests/test_integration/test_tag_formatter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import textwrap 4 | 5 | import pytest 6 | 7 | from tests.lib.util import ( 8 | create_file, 9 | create_tag, 10 | get_version, 11 | get_version_module, 12 | get_version_script, 13 | ) 14 | 15 | pytestmark = pytest.mark.all 16 | 17 | 18 | @pytest.mark.parametrize( 19 | "tag, version", 20 | [ 21 | ("1.0.0", "1.0.0"), 22 | ("release/1.0.0", "1.0.0"), 23 | ("unknown", "0.0.0"), 24 | ], 25 | ) 26 | def test_tag_formatter_external(repo, create_config, tag, version): 27 | create_file( 28 | repo, 29 | "util.py", 30 | textwrap.dedent( 31 | r""" 32 | import re 33 | 34 | def tag_formatter(tag): 35 | return re.sub(r"[^\d.]+", "", tag) or "0.0.0" 36 | """ 37 | ), 38 | ) 39 | 40 | create_config( 41 | repo, 42 | { 43 | "tag_formatter": "util:tag_formatter", 44 | }, 45 | ) 46 | create_tag(repo, tag) 47 | 48 | assert get_version(repo) == version 49 | assert get_version_script(repo) == version 50 | assert get_version_module(repo) == version 51 | 52 | # path to the repo can be passed as positional argument 53 | assert get_version_script(os.getcwd(), args=[repo]) == version 54 | assert get_version_module(os.getcwd(), args=[repo]) == version 55 | 56 | 57 | @pytest.mark.parametrize("create_util", [True, False]) 58 | def test_tag_formatter_external_missing(repo, create_config, create_util): 59 | if create_util: 60 | create_file( 61 | repo, 62 | "util.py", 63 | textwrap.dedent( 64 | r""" 65 | import re 66 | 67 | def tag_formatter(tag): 68 | return re.sub(r"[^\d.]+", "", tag) 69 | """ 70 | ), 71 | ) 72 | 73 | create_config( 74 | repo, 75 | { 76 | "tag_formatter": "util:wtf", 77 | }, 78 | ) 79 | create_tag(repo, "1.0.0") 80 | 81 | with pytest.raises(subprocess.CalledProcessError): 82 | get_version(repo) 83 | 84 | 85 | def test_tag_formatter_external_not_callable(repo, create_config): 86 | create_file( 87 | repo, 88 | "util.py", 89 | textwrap.dedent( 90 | r""" 91 | import re 92 | 93 | tag_formatter = re.compile(r"[^\d.]+") 94 | """ 95 | ), 96 | ) 97 | 98 | create_config( 99 | repo, 100 | { 101 | "tag_formatter": "util:tag_formatter", 102 | }, 103 | ) 104 | create_tag(repo, "1.0.0") 105 | 106 | with pytest.raises(subprocess.CalledProcessError): 107 | get_version(repo) 108 | 109 | 110 | def test_tag_formatter_external_setup_py_direct_import(repo): 111 | create_file( 112 | repo, 113 | "setup.py", 114 | textwrap.dedent( 115 | r""" 116 | from coverage.control import Coverage 117 | 118 | coverage = Coverage() 119 | coverage.start() 120 | 121 | try: 122 | import re 123 | import setuptools 124 | 125 | def tag_formatter(tag): 126 | return re.sub(r"[^\d.]+", "", tag) 127 | 128 | setuptools.setup( 129 | name="mypkg", 130 | setuptools_git_versioning={ 131 | "tag_formatter": tag_formatter 132 | }, 133 | setup_requires=[ 134 | "setuptools>=41", 135 | "wheel", 136 | "setuptools-git-versioning", 137 | ] 138 | ) 139 | finally: 140 | coverage.stop() 141 | coverage.save() 142 | """ 143 | ), 144 | ) 145 | 146 | create_tag(repo, "release/1.0.0") 147 | 148 | assert get_version(repo) == "1.0.0" 149 | assert get_version(repo) == "1.0.0" 150 | assert get_version_script(repo) == "1.0.0" 151 | assert get_version_module(repo) == "1.0.0" 152 | 153 | # path to the repo can be passed as positional argument 154 | assert get_version_script(os.getcwd(), args=[repo]) == "1.0.0" 155 | assert get_version_module(os.getcwd(), args=[repo]) == "1.0.0" 156 | 157 | 158 | @pytest.mark.parametrize( 159 | "tag, version", 160 | [ 161 | ("1.0.0", "1.0.0"), 162 | ("release/1.0.0", "1.0.0"), 163 | ], 164 | ) 165 | def test_tag_formatter_regexp(repo, create_config, tag, version): 166 | create_config( 167 | repo, 168 | { 169 | "tag_formatter": r".*?(?P[\d.]+).*", 170 | }, 171 | ) 172 | create_tag(repo, tag) 173 | 174 | assert get_version(repo) == version 175 | 176 | 177 | def test_tag_formatter_regexp_not_match(repo, create_config): 178 | create_config( 179 | repo, 180 | { 181 | "tag_formatter": r".*?(?P[\d.]+).*", 182 | }, 183 | ) 184 | 185 | create_tag(repo, "unknown") 186 | 187 | with pytest.raises(subprocess.CalledProcessError): 188 | get_version(repo) 189 | 190 | 191 | @pytest.mark.parametrize("regexp", [r".*?([\d.]+).*", r".*?(?P[\d.]+).*"]) 192 | def test_tag_formatter_regexp_no_capture_group(repo, create_config, regexp): 193 | create_config( 194 | repo, 195 | { 196 | "tag_formatter": regexp, 197 | }, 198 | ) 199 | 200 | create_tag(repo, "1.0.0") 201 | 202 | with pytest.raises(subprocess.CalledProcessError): 203 | get_version(repo) 204 | 205 | 206 | def test_tag_formatter_regexp_wrong_format(repo, create_config): 207 | create_config( 208 | repo, 209 | { 210 | "tag_formatter": "(", 211 | }, 212 | ) 213 | 214 | create_tag(repo, "1.0.0") 215 | 216 | with pytest.raises(subprocess.CalledProcessError): 217 | get_version(repo) 218 | 219 | 220 | def test_tag_formatter_no_tag(repo, create_config): 221 | create_config( 222 | repo, 223 | { 224 | "tag_formatter": "(", 225 | }, 226 | ) 227 | 228 | assert get_version(repo) == "0.0.1" 229 | 230 | 231 | @pytest.mark.parametrize( 232 | "count_commits", 233 | [True, False], 234 | ) 235 | def test_tag_formatter_ignored_if_version_file_set(repo, create_config, count_commits): 236 | create_config( 237 | repo, 238 | { 239 | "version_file": "VERSION.txt", 240 | "count_commits_from_version_file": count_commits, 241 | "dev_template": "{tag}", 242 | "tag_formatter": "(", 243 | }, 244 | ) 245 | create_file(repo, "VERSION.txt", "1.0.0") 246 | 247 | assert get_version(repo) == "1.0.0" 248 | 249 | 250 | @pytest.mark.parametrize( 251 | "version_callback", 252 | ["version:get_version", "version:__version__"], 253 | ) 254 | def test_tag_formatter_ignored_if_version_callback_set(repo, create_config, version_callback): 255 | create_file( 256 | repo, 257 | "version.py", 258 | textwrap.dedent( 259 | """ 260 | def get_version(): 261 | return "1.0.0" 262 | 263 | __version__ = "1.0.0" 264 | """ 265 | ), 266 | ) 267 | create_config( 268 | repo, 269 | { 270 | "version_callback": version_callback, 271 | "tag_formatter": "(", 272 | }, 273 | ) 274 | 275 | assert get_version(repo) == "1.0.0" 276 | -------------------------------------------------------------------------------- /tests/test_integration/test_version_callback.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | import pytest 5 | 6 | from tests.lib.util import ( 7 | create_file, 8 | create_tag, 9 | get_version, 10 | get_version_module, 11 | get_version_script, 12 | get_version_setup_py, 13 | ) 14 | 15 | pytestmark = pytest.mark.all 16 | 17 | 18 | VERSION_PY = """def get_version(): 19 | return "{version}" 20 | 21 | __version__ = "{version}" 22 | """ 23 | 24 | 25 | SETUP_PY_CALLABLE = """from coverage.control import Coverage 26 | 27 | coverage = Coverage() 28 | coverage.start() 29 | 30 | try: 31 | import setuptools 32 | from version import get_version 33 | 34 | setuptools.setup( 35 | setuptools_git_versioning={ 36 | "version_callback": get_version 37 | }, 38 | setup_requires=[ 39 | "setuptools>=41", 40 | "wheel", 41 | "setuptools-git-versioning", 42 | ] 43 | ) 44 | finally: 45 | coverage.stop() 46 | coverage.save() 47 | """ 48 | 49 | 50 | SETUP_PY_STR = """from coverage.control import Coverage 51 | 52 | coverage = Coverage() 53 | coverage.start() 54 | 55 | try: 56 | import setuptools 57 | 58 | from version import __version__ 59 | 60 | setuptools.setup( 61 | setuptools_git_versioning={ 62 | "version_callback": __version__ 63 | }, 64 | setup_requires=[ 65 | "setuptools>=41", 66 | "wheel", 67 | "setuptools-git-versioning", 68 | ] 69 | ) 70 | finally: 71 | coverage.stop() 72 | coverage.save() 73 | """ 74 | 75 | 76 | @pytest.mark.parametrize( 77 | "version_callback", 78 | [ 79 | "version:get_version", 80 | "version:__version__", 81 | ], 82 | ids=["callable", "str"], 83 | ) 84 | def test_version_callback(repo, version_callback, create_config): 85 | create_file(repo, "version.py", VERSION_PY.format(version="1.0.0"), commit=False) 86 | 87 | create_config(repo, {"version_callback": version_callback}) 88 | 89 | assert get_version(repo) == "1.0.0" 90 | assert get_version_script(repo) == "1.0.0" 91 | assert get_version_module(repo) == "1.0.0" 92 | 93 | # path to the repo can be passed as positional argument 94 | assert get_version_script(os.getcwd(), args=[repo]) == "1.0.0" 95 | assert get_version_module(os.getcwd(), args=[repo]) == "1.0.0" 96 | 97 | # git status does not influence callback result 98 | create_file(repo) 99 | assert get_version(repo) == "1.0.0" 100 | 101 | create_file(repo) 102 | assert get_version(repo) == "1.0.0" 103 | 104 | 105 | @pytest.mark.parametrize( 106 | "setup_py", 107 | [ 108 | SETUP_PY_CALLABLE, 109 | SETUP_PY_STR, 110 | ], 111 | ids=["callable", "str"], 112 | ) 113 | def test_version_callback_setup_py_direct_import(repo, setup_py): 114 | create_file(repo, "version.py", VERSION_PY.format(version="1.0.0"), commit=False) 115 | create_file( 116 | repo, 117 | "setup.py", 118 | setup_py, 119 | ) 120 | 121 | assert get_version_setup_py(repo) == "1.0.0" 122 | assert get_version_script(repo) == "1.0.0" 123 | assert get_version_module(repo) == "1.0.0" 124 | 125 | # path to the repo can be passed as positional argument 126 | assert get_version_script(os.getcwd(), args=[repo]) == "1.0.0" 127 | assert get_version_module(os.getcwd(), args=[repo]) == "1.0.0" 128 | 129 | # git status does not influence callback result 130 | create_file(repo) 131 | assert get_version_setup_py(repo) == "1.0.0" 132 | 133 | create_file(repo) 134 | assert get_version_setup_py(repo) == "1.0.0" 135 | 136 | 137 | @pytest.mark.parametrize("create_version_py", [True, False]) 138 | def test_version_callback_missing(repo, create_version_py, create_config): 139 | version_callback = "version:wtf" 140 | 141 | if create_version_py: 142 | create_file(repo, "version.py", "", commit=False) 143 | 144 | create_config(repo, {"version_callback": version_callback}) 145 | 146 | with pytest.raises(subprocess.CalledProcessError): 147 | get_version(repo) 148 | 149 | 150 | @pytest.mark.parametrize( 151 | "version, real_version", 152 | [ 153 | ("1.0.0", "1.0.0"), 154 | ("v1.2.3", "1.2.3"), 155 | ("1.2.3dev1", "1.2.3.dev1"), 156 | ("1.2.3.dev1", "1.2.3.dev1"), 157 | ("1.2.3-dev1", "1.2.3.dev1"), 158 | ("1.2.3+local", "1.2.3+local"), 159 | ("1.2.3+local-abc", "1.2.3+local.abc"), 160 | ("1.2.3+local_abc", "1.2.3+local.abc"), 161 | ("1.2.3+local/abc", "1.2.3+local.abc"), 162 | ("1.2.3+local/abc/-", "1.2.3+local.abc"), 163 | ], 164 | ) 165 | def test_version_callback_sanitization(repo, version, real_version, create_config): 166 | create_file(repo, "version.py", VERSION_PY.format(version=version), commit=False) 167 | create_config(repo, {"version_callback": "version:get_version"}) 168 | assert get_version(repo) == real_version 169 | 170 | 171 | @pytest.mark.parametrize( 172 | "version", 173 | [ 174 | "alpha1.0.0", 175 | "1.0.0abc", 176 | "1.0.0.abc", 177 | "1.0.0-abc", 178 | "1.0.0_abc", 179 | ], 180 | ) 181 | def test_version_callback_wrong_version_number(repo, version, create_config): 182 | create_file(repo, "version.py", VERSION_PY.format(version=version), commit=False) 183 | create_config(repo, {"version_callback": "version:get_version"}) 184 | 185 | with pytest.raises(subprocess.CalledProcessError): 186 | get_version(repo) 187 | 188 | 189 | def test_version_callback_not_a_repo(repo_dir, create_config): 190 | version = "1.0.0" 191 | create_file(repo_dir, "version.py", VERSION_PY.format(version=version), add=False, commit=False) 192 | create_config( 193 | repo_dir, 194 | {"version_callback": "version:get_version"}, 195 | add=False, 196 | commit=False, 197 | ) 198 | 199 | assert get_version(repo_dir) == version 200 | assert get_version_script(repo_dir) == version 201 | assert get_version_module(repo_dir) == version 202 | 203 | # path to the repo can be passed as positional argument 204 | assert get_version_script(os.getcwd(), args=[repo_dir]) == version 205 | assert get_version_module(os.getcwd(), args=[repo_dir]) == version 206 | 207 | 208 | def test_version_callback_has_higher_priority_than_tag(repo, create_config): 209 | version = "1.0.0" 210 | 211 | create_file(repo, "version.py", VERSION_PY.format(version=version), commit=False) 212 | create_config(repo, {"version_callback": "version:get_version"}) 213 | 214 | create_tag(repo, "1.2.3") 215 | assert get_version(repo) == version 216 | 217 | 218 | def test_version_callback_conflicts_with_version_file(repo, create_config): 219 | create_config( 220 | repo, 221 | { 222 | "version_file": "VERSION.txt", 223 | "version_callback": "version:get_version", 224 | }, 225 | ) 226 | 227 | with pytest.raises(subprocess.CalledProcessError): 228 | get_version(repo) 229 | 230 | 231 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 232 | @pytest.mark.parametrize( 233 | "version_callback", 234 | [ 235 | "version:get_version", 236 | "version:__version__", 237 | ], 238 | ids=["callable", "str"], 239 | ) 240 | @pytest.mark.parametrize( 241 | "template", 242 | [ 243 | "{tag}.post{env:PIPELINE_ID:123}", 244 | "{tag}.post{env:PIPELINE_ID:123}", 245 | "{tag}.post{env:PIPELINE_ID:IGNORE}", 246 | "{tag}.post{env:PIPELINE_ID:IGNORE}", 247 | "{tag}.post{env:PIPELINE_ID:{ccount}}", 248 | "{tag}.post{env:PIPELINE_ID:{ccount}}", 249 | "{tag}.post{timestamp}", 250 | "{tag}.post{timestamp:%s}", 251 | "{timestamp:%Y}.{timestamp:%m}.{timestamp:%d}+{timestamp:%H%M%S}", 252 | "{tag}.post{ccount}+{timestamp:%Y-%m-%dT%H-%M-%S}", 253 | "{tag}.post{ccount}+git.{full_sha}", 254 | "{tag}.post{ccount}+git.{sha}", 255 | "{tag}.post{ccount}", 256 | "{tag}.{branch}{ccount}", 257 | "{tag}", 258 | ], 259 | ) 260 | def test_version_callback_template_substitutions_are_ignored( 261 | repo, 262 | template, 263 | version_callback, 264 | create_config, 265 | ): 266 | version = "1.0.0" 267 | 268 | create_file( 269 | repo, 270 | "version.py", 271 | VERSION_PY.format(version=version), 272 | commit=False, 273 | ) 274 | create_config(repo, {"version_callback": version_callback, "dev_template": template}) 275 | 276 | assert get_version(repo) == version 277 | -------------------------------------------------------------------------------- /tests/test_integration/test_version_file.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | import pytest 4 | 5 | from tests.lib.util import ( 6 | create_commit, 7 | create_file, 8 | create_tag, 9 | get_full_sha, 10 | get_sha, 11 | get_version, 12 | ) 13 | 14 | pytestmark = pytest.mark.all 15 | 16 | 17 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 18 | @pytest.mark.important 19 | @pytest.mark.parametrize( 20 | "template", 21 | [ 22 | None, 23 | "{tag}.post{ccount}+git.{full_sha}", 24 | ], 25 | ) 26 | def test_version_file(repo, create_config, template): 27 | config = { 28 | "version_file": "VERSION.txt", 29 | } 30 | if template: 31 | # template is ignored 32 | config["dev_template"] = template 33 | 34 | create_config(repo, config) 35 | 36 | create_file(repo, "VERSION.txt", "1.0.0") 37 | assert get_version(repo) == "1.0.0" 38 | 39 | create_file(repo) 40 | assert get_version(repo) == "1.0.0" 41 | 42 | create_file(repo, add=True, commit=False) 43 | assert get_version(repo) == "1.0.0" 44 | create_commit(repo, "add file") 45 | 46 | create_file(repo, add=False) 47 | assert get_version(repo) == "1.0.0" 48 | 49 | 50 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 51 | @pytest.mark.parametrize( 52 | "template, subst", 53 | [ 54 | (None, "1.0.0.post{ccount}+git.{sha}"), 55 | ("{tag}.post{ccount}+git.{full_sha}", "1.0.0.post{ccount}+git.{full_sha}"), 56 | ("{tag}.post{ccount}+git.{sha}", "1.0.0.post{ccount}+git.{sha}"), 57 | ("{tag}.post{ccount}", "1.0.0.post{ccount}"), 58 | ("{tag}", "1.0.0"), 59 | ], 60 | ) 61 | def test_version_file_count_commits(repo, create_config, template, subst): 62 | config = { 63 | "version_file": "VERSION.txt", 64 | "count_commits_from_version_file": True, 65 | } 66 | if template: 67 | # template is ignored 68 | config["dev_template"] = template 69 | 70 | create_config(repo, config) 71 | create_file(repo, "VERSION.txt", "1.0.0") 72 | 73 | full_sha = get_full_sha(repo) 74 | sha = get_sha(repo) 75 | assert get_version(repo) == subst.format(sha=sha, full_sha=full_sha, ccount=0) 76 | 77 | create_file(repo) 78 | 79 | full_sha = get_full_sha(repo) 80 | sha = get_sha(repo) 81 | assert get_version(repo) == subst.format(sha=sha, full_sha=full_sha, ccount=1) 82 | 83 | 84 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 85 | @pytest.mark.parametrize( 86 | "template, subst", 87 | [ 88 | (None, "1.0.0.post{ccount}+git.{sha}.dirty"), 89 | ("{tag}.post{ccount}+git.{full_sha}.dirty", "1.0.0.post{ccount}+git.{full_sha}.dirty"), 90 | ("{tag}.post{ccount}+git.{sha}.dirty", "1.0.0.post{ccount}+git.{sha}.dirty"), 91 | ("{tag}.post{ccount}+dirty", "1.0.0.post{ccount}+dirty"), 92 | ("{tag}+dirty", "1.0.0+dirty"), 93 | ], 94 | ) 95 | @pytest.mark.parametrize("add", [True, False]) 96 | def test_version_file_dirty(repo, create_config, add, template, subst): 97 | config = { 98 | "version_file": "VERSION.txt", 99 | "count_commits_from_version_file": True, 100 | } 101 | if template: 102 | # template is not ignored 103 | config["dirty_template"] = template 104 | 105 | create_config(repo, config) 106 | 107 | create_file(repo, "VERSION.txt", "1.0.0") 108 | create_file(repo, commit=False) 109 | 110 | full_sha = get_full_sha(repo) 111 | sha = get_sha(repo) 112 | assert get_version(repo) == subst.format(sha=sha, full_sha=full_sha, ccount=0) 113 | 114 | create_commit(repo, "add file") 115 | create_file(repo, add=False) 116 | 117 | full_sha = get_full_sha(repo) 118 | sha = get_sha(repo) 119 | assert get_version(repo) == subst.format(sha=sha, full_sha=full_sha, ccount=1) 120 | 121 | 122 | @pytest.mark.parametrize( 123 | "version, real_version", 124 | [ 125 | ("1.0.0", "1.0.0"), 126 | ("v1.2.3", "1.2.3"), 127 | ("1.2.3dev1", "1.2.3.dev1"), 128 | ("1.2.3.dev1", "1.2.3.dev1"), 129 | ("1.2.3-dev1", "1.2.3.dev1"), 130 | ("1.2.3+local", "1.2.3+local"), 131 | ("1.2.3+local-abc", "1.2.3+local.abc"), 132 | ("1.2.3+local_abc", "1.2.3+local.abc"), 133 | ("1.2.3+local/abc", "1.2.3+local.abc"), 134 | ("1.2.3+local/abc/-", "1.2.3+local.abc"), 135 | ], 136 | ) 137 | @pytest.mark.parametrize("count_commits_from_version_file", [True, False]) 138 | def test_version_file_sanitization(repo, create_config, version, real_version, count_commits_from_version_file): 139 | create_config( 140 | repo, 141 | { 142 | "version_file": "VERSION.txt", 143 | "dev_template": "{tag}", 144 | "count_commits_from_version_file": count_commits_from_version_file, 145 | }, 146 | ) 147 | create_file(repo, "VERSION.txt", version) 148 | 149 | assert get_version(repo) == real_version 150 | 151 | 152 | @pytest.mark.parametrize( 153 | "version", 154 | [ 155 | "alpha1.0.0", 156 | "1.0.0abc", 157 | "1.0.0.abc", 158 | "1.0.0-abc", 159 | "1.0.0_abc", 160 | ], 161 | ) 162 | @pytest.mark.parametrize("count_commits_from_version_file", [True, False]) 163 | def test_version_file_wrong_version_number(repo, version, create_config, count_commits_from_version_file): 164 | create_config( 165 | repo, 166 | { 167 | "version_file": "VERSION.txt", 168 | "dev_template": "{tag}", 169 | "count_commits_from_version_file": count_commits_from_version_file, 170 | }, 171 | ) 172 | create_file(repo, "VERSION.txt", version) 173 | 174 | with pytest.raises(subprocess.CalledProcessError): 175 | get_version(repo) 176 | 177 | 178 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 179 | def test_version_file_tagged_history(repo, create_config): 180 | create_tag(repo, "1.2.3") 181 | 182 | create_file(repo, "VERSION.txt", "1.0.0") 183 | create_config( 184 | repo, 185 | { 186 | "version_file": "VERSION.txt", 187 | "count_commits_from_version_file": True, 188 | }, 189 | ) 190 | 191 | sha = get_sha(repo) 192 | assert get_version(repo) == f"1.0.0.post1+git.{sha}" 193 | 194 | 195 | @pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again 196 | def test_version_file_tagged_head(repo, create_config): 197 | create_file(repo, "VERSION.txt", "1.0.0") 198 | create_config( 199 | repo, 200 | { 201 | "version_file": "VERSION.txt", 202 | "count_commits_from_version_file": True, 203 | }, 204 | ) 205 | create_tag(repo, "1.2.3") 206 | 207 | # template is for release, because commit is tagged 208 | # but version_file content is up to user 209 | assert get_version(repo) == "1.0.0" 210 | 211 | 212 | @pytest.mark.parametrize("starting_version, version", [(None, "0.0.1"), ("1.2.3", "1.2.3")]) 213 | @pytest.mark.parametrize("create_version", [True, False]) 214 | def test_version_file_missing(repo, create_config, create_version, starting_version, version): 215 | config = { 216 | "version_file": "VERSION.txt", 217 | "count_commits_from_version_file": True, 218 | } 219 | if starting_version: 220 | config["starting_version"] = starting_version 221 | if create_version: 222 | create_file(repo, "VERSION.txt", "") 223 | 224 | create_config(repo, config) 225 | 226 | assert get_version(repo) == version 227 | 228 | 229 | @pytest.mark.parametrize("count_commits", [True, False]) 230 | def test_version_file_not_a_repo(repo_dir, create_config, count_commits): 231 | create_file( 232 | repo_dir, 233 | "VERSION.txt", 234 | "1.0.0", 235 | add=False, 236 | commit=False, 237 | ) 238 | create_config( 239 | repo_dir, 240 | { 241 | "version_file": "VERSION.txt", 242 | "count_commits_from_version_file": count_commits, 243 | }, 244 | add=False, 245 | commit=False, 246 | ) 247 | 248 | assert get_version(repo_dir) == "1.0.0" 249 | --------------------------------------------------------------------------------