Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/options/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Config options
dirty_template
version_file
count_commits
prefer_tag
version_callback
sort_by
branch_formatter
Expand Down
26 changes: 26 additions & 0 deletions docs/options/prefer_tag.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.. _prefer-tag-option:

``prefer_tag``
~~~~~~~~~~~~~~

Used together with the :ref:`version-file-option` option.

By default, when :ref:`version-file-option` is set, any tags in the repo are
ignored (see :issue:`155`). With this option enabled, the latest Git tag takes precedence over the version file content.

.. note::

This option is used only with :ref:`version-file-option`,
and only takes effect when the repo actually contains at least one tag;
otherwise the version file content is used as usual.

When the tag and the version file content are different,
a warning is logged, so stale version files are easier to notice.

Type
^^^^
``bool``

Default value
^^^^^^^^^^^^^
``False``
8 changes: 8 additions & 0 deletions docs/schemas/file/version_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,16 @@ of this file (``1.0.0``) will be returned instead default version number.

**Please take into account that any tags in the repo are ignored if this option is being used.**

.. note::

If you want the latest Git tag to take precedence over the version file
(for example, when a release tag like ``v2.1.0rc1`` is pushed but the
version file still contains ``2.1.0``), set the
:ref:`prefer-tag-option` option to ``true``.

See also
"""""""""
- :ref:`version-callback`
- :ref:`version-file-option` option
- :ref:`prefer-tag-option` option
- :ref:`runtime-version`
1 change: 1 addition & 0 deletions setuptools_git_versioning/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"version_callback": None,
"version_file": None,
"count_commits_from_version_file": False,
"prefer_tag": False,
"tag_formatter": None,
"branch_formatter": None,
"tag_filter": None,
Expand Down
65 changes: 42 additions & 23 deletions setuptools_git_versioning/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os # noqa: TC003
import re
from pathlib import Path
from typing import Callable
from typing import Callable, cast

# avoid importing 'packaging' because setuptools-git-versioning can be installed using sdist
# where 'packaging' is not installed yet
Expand Down Expand Up @@ -96,6 +96,7 @@ def version_from_git( # noqa: PLR0915, PLR0912, PLR0913, C901
version_callback: str | Callable[[], str] | None = None,
version_file: str | os.PathLike | None = None,
count_commits_from_version_file: bool = False,
prefer_tag: bool = False,
tag_formatter: Callable[[str], str] | str | None = None,
branch_formatter: Callable[[str], str] | str | None = None,
tag_filter: Callable[[str], str | None] | str | None = None,
Expand Down Expand Up @@ -134,6 +135,9 @@ def version_from_git( # noqa: PLR0915, PLR0912, PLR0913, C901
raise ValueError(msg)
return get_version_from_callback(version_callback, package_name, root=root)

ccount: int | None = 0
on_tag = False
tag_sha = None
head_sha = get_sha(root=root)
log.log(INFO, "HEAD SHA-256: %r", head_sha)

Expand All @@ -146,15 +150,21 @@ def version_from_git( # noqa: PLR0915, PLR0912, PLR0913, C901
tag = get_tag(sort_by=sort_by, root=root, filter_callback=filter_callback)
if not tag:
log.log(INFO, "No tags found")
tag_sha = None
on_tag = False
else:
tag_sha = get_sha(tag, root=root)
tag_sha = cast("str", get_sha(tag, root=root))
log.log(INFO, "Tag SHA-256: %r", tag_sha)

on_tag = head_sha is not None and head_sha == tag_sha
log.log(INFO, "HEAD is tagged: %r", on_tag)

ccount = count_since(tag_sha, root=root)
log.log(INFO, "Commits count between HEAD and last tag: %r", ccount)

if tag_formatter is not None:
tag_format_callback = create_tag_formatter(tag_formatter, package_name=package_name, root=root)
tag = tag_format_callback(tag)
log.log(DEBUG, "Tag after formatting: %r", tag)

if version_file:
log.log(INFO, "Checking for 'version_file'")

Expand All @@ -166,35 +176,44 @@ def version_from_git( # noqa: PLR0915, PLR0912, PLR0913, C901
version_file_path,
starting_version,
)
tag = None
file_tag = None
else:
log.log(INFO, "Reading version_file '%s' content", version_file)
tag = version_file_path.read_text().strip() or None
file_tag = version_file_path.read_text().strip() or None

if not tag:
if not file_tag:
log.log(INFO, "File %r is empty", version_file)
else:
log.log(DEBUG, "File content: %r", tag)
if not count_commits_from_version_file:
return sanitize_version(tag)
log.log(DEBUG, "File content: %r", file_tag)

if prefer_tag and file_tag and tag:
log.log(
INFO,
"Both 'version_file' and a Git tag (%r) are present, and 'prefer_tag' is enabled: using the tag",
tag,
)
if file_tag and sanitize_version(file_tag) != sanitize_version(tag):
log.warning(
"Git tag %r and 'version_file' content %r are different: using the tag. "
"Update the version file to silence this warning",
tag,
file_tag,
)

file_sha = get_latest_file_commit(version_file, root=root)
log.log(DEBUG, "File SHA-256: %r", file_sha)
elif file_tag:
tag = file_tag

ccount = count_since(file_sha, root=root) if file_sha else None
log.log(INFO, "Commits count between HEAD and last version file change: %r", ccount)
if not count_commits_from_version_file:
return sanitize_version(tag)

elif not head_sha:
log.log(INFO, "Not a git repo, or repo without any branch")
file_sha = get_latest_file_commit(version_file, root=root)
log.log(DEBUG, "File SHA-256: %r", file_sha)

elif tag_sha:
ccount = count_since(tag_sha, root=root)
log.log(INFO, "Commits count between HEAD and last tag: %r", ccount)
ccount = count_since(file_sha, root=root) if file_sha else None
log.log(INFO, "Commits count between HEAD and last version file change: %r", ccount)

if tag_formatter is not None:
tag_format_callback = create_tag_formatter(tag_formatter, package_name=package_name, root=root)
tag = tag_format_callback(tag)
log.log(DEBUG, "Tag after formatting: %r", tag)
elif not head_sha:
log.log(INFO, "Not a git repo, or repo without any branch")

if not tag:
log.log(INFO, "No source for version, return starting_version %r", starting_version)
Expand Down
89 changes: 89 additions & 0 deletions tests/test_integration/test_version_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,92 @@ def test_version_file_git_not_executable(repo, create_config, count_commits, tmp
tmp_path = tmp_path_factory.mktemp("bin")
tmp_path.joinpath("git").touch()
assert get_version(repo, env={"PATH": os.fspath(tmp_path)}) == "1.0.0"


@pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again
def test_version_file_prefer_tag_off_by_default(repo, create_config):
# without prefer_tag, the version file wins even when a tag is present
create_file(repo, "VERSION.txt", "1.0.0")
create_config(
repo,
{
"version_file": "VERSION.txt",
"count_commits_from_version_file": True,
},
)
create_tag(repo, "1.2.3")

assert get_version(repo) == "1.0.0"


@pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again
@pytest.mark.parametrize("count_commits_from_version_file", [True, False])
def test_version_file_prefer_tag_on_tagged_head(repo, create_config, count_commits_from_version_file):
# prefer_tag makes the tag win over the version file content on a tagged HEAD
create_file(repo, "VERSION.txt", "1.0.0")
create_config(
repo,
{
"version_file": "VERSION.txt",
"count_commits_from_version_file": count_commits_from_version_file,
"prefer_tag": True,
},
)
create_tag(repo, "1.2.3")

assert get_version(repo) == "1.2.3"


@pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again
def test_version_file_prefer_tag_dev_commits(repo, create_config):
# prefer_tag + commits after the tag: tag is used as the version source,
# dev template kicks in because HEAD is not on the tag
create_file(repo, "VERSION.txt", "1.0.0")
create_config(
repo,
{
"version_file": "VERSION.txt",
"count_commits_from_version_file": True,
"prefer_tag": True,
},
)
create_tag(repo, "1.2.3")
# create_file with default add=True/commit=True creates the commit after the tag
create_file(repo, "work.txt", "work after tag")

sha = get_sha(repo)
assert get_version(repo) == f"1.2.3.post1+git.{sha}"


@pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again
def test_version_file_prefer_tag_without_any_tag(repo, create_config):
# prefer_tag with no tags in the repo: version file is still the source
# (count_commits_from_version_file is off here so the file content is
# returned as-is, without the dev suffix)
create_file(repo, "VERSION.txt", "1.0.0")
create_config(
repo,
{
"version_file": "VERSION.txt",
"prefer_tag": True,
},
)

assert get_version(repo) == "1.0.0"


@pytest.mark.flaky(reruns=3) # sha and full_sha can start with 0 which are removed, just try again
def test_version_file_prefer_tag_v_prefix(repo, create_config):
# tag with a "v" prefix is compared to the version file without the prefix
create_file(repo, "VERSION.txt", "1.2.3")
create_config(
repo,
{
"version_file": "VERSION.txt",
"count_commits_from_version_file": True,
"prefer_tag": True,
},
)
create_tag(repo, "v1.2.3")

assert get_version(repo) == "1.2.3"
Loading