Skip to content

Index

gitversioned

Opinionated PEP 440 Python versioning for Git repos and submodules.

Provides an automated, deterministic system for generating rich version information from Git repository metadata. It enforces CI/User authority and creates version files with deep metadata for auditability, integrating natively with Hatch and Setuptools.

Example: :: from gitversioned import Settings, resolve_version from gitversioned.utils import BuildEnvironment, GitRepository

version, ref = resolve_version(
    Settings(), GitRepository(), BuildEnvironment()
)
print(f"Current version: {version}")

LoggingSettings

Bases: BaseSettings

Settings model for configuring the loguru logging infrastructure.

This Pydantic model loads configuration from environment variables prefixed with GITVERSIONED__LOGGING__ and provides typed fields for controlling log output, formatting, and OpenTelemetry integration.

Example: .. code-block:: python

    from gitversioned.logging import LoggingSettings, configure_logger

    settings = LoggingSettings(enabled=True, level="DEBUG")
    configure_logger(settings)
Source code in src/gitversioned/logging.py
class LoggingSettings(BaseSettings):
    """
    Settings model for configuring the loguru logging infrastructure.

    This Pydantic model loads configuration from environment variables prefixed
    with ``GITVERSIONED__LOGGING__`` and provides typed fields for controlling
    log output, formatting, and OpenTelemetry integration.

    Example:
        .. code-block:: python

            from gitversioned.logging import LoggingSettings, configure_logger

            settings = LoggingSettings(enabled=True, level="DEBUG")
            configure_logger(settings)
    """

    enabled: bool = Field(
        default=False,
        description=(
            "Whether to enable GitVersioned loguru logging across the application."
        ),
    )
    clear_loggers: bool = Field(
        default=False,
        description=(
            "If true, removes all existing loguru handlers before configuring new ones."
        ),
    )
    sink: str | Any = Field(
        default=sys.stdout,
        description=(
            "The output sink for log messages. Can be an object or string "
            "alias ('stdout')."
        ),
    )
    level: str = Field(
        default="INFO",
        description="The minimum severity level for emitted log messages.",
    )
    otel_formatting: Literal["auto", "enable", "disable"] = Field(
        default="auto",
        description=(
            "Controls OpenTelemetry JSON formatting. 'auto' enables it if "
            "otel is installed."
        ),
    )
    format: str | Callable[..., Any] | None = Field(
        default="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | "
        "<cyan>{name}</cyan>:<cyan>{function}</cyan> - <level>{message}</level>\n",
        description=(
            "The log format string or function to use when otel formatting is disabled."
        ),
    )
    filter: Any = Field(
        default=True,
        description=(
            "Filters log records. Defaults to True to filter by the "
            "'gitversioned' prefix."
        ),
    )
    enqueue: bool = Field(
        default=True,
        description="Whether to enable thread-safe asynchronous logging.",
    )
    kwargs: dict[str, Any] = Field(
        default_factory=dict,
        description=(
            "Additional keyword arguments to pass directly to loguru's add() method."
        ),
    )

    model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
        env_prefix="GITVERSIONED__LOGGING__",
        env_nested_delimiter="__",
    )
    """Pydantic configuration dict dictating environment variable prefixes."""

    @field_validator("sink", mode="before")
    @classmethod
    def _parse_sink(cls, value: Any) -> Any:
        """Convert string aliases for stdout/stderr to actual objects."""
        if isinstance(value, str):
            mapping = {
                "stdout": sys.stdout,
                "sys.stdout": sys.stdout,
                "stderr": sys.stderr,
                "sys.stderr": sys.stderr,
            }
            return mapping.get(value.lower(), value)
        return value

model_config = SettingsConfigDict(env_prefix='GITVERSIONED__LOGGING__', env_nested_delimiter='__') class-attribute

Pydantic configuration dict dictating environment variable prefixes.

Settings

Bases: BaseSettings

Configuration for GitVersioned, handling formatting, strictness, and outputs.

This class aggregates and prioritizes configuration from multiple sources, providing a unified state for version resolution across the tool. It is built on top of pydantic-settings to allow validation and type coercion.

Example: ::

    settings = Settings(package_name="my_pkg")
    print(settings.format_main)
Source code in src/gitversioned/settings.py
class Settings(BaseSettings):
    """
    Configuration for GitVersioned, handling formatting, strictness, and outputs.

    This class aggregates and prioritizes configuration from multiple sources,
    providing a unified state for version resolution across the tool. It is built
    on top of pydantic-settings to allow validation and type coercion.

    Example:
        ::

            settings = Settings(package_name="my_pkg")
            print(settings.format_main)
    """

    model_config = SettingsConfigDict(
        arbitrary_types_allowed=True,
        extra="ignore",
        populate_by_name=True,
        validate_assignment=True,
        env_prefix="GITVERSIONED__",
        cli_prefix="gitversioned_",
        cli_parse_args=True,
        pyproject_toml_table_header=("tool", "gitversioned"),
    )

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        """Customizes the configuration sources and their priority.

        This method overrides the default pydantic-settings source priority to
        inject our custom `SetupCfgSettingsSource` and define the specific order
        of resolution.

        :param settings_cls: The settings class being instantiated.
        :param init_settings: The initial settings provided via kwargs.
        :param env_settings: Settings loaded from environment variables.
        :param dotenv_settings: Settings loaded from a .env file.
        :param file_secret_settings: Settings loaded from secret files.
        :return: A tuple of settings sources in priority order.
        """
        _ = (file_secret_settings,)  # Allow unused variable to satisfy lint/format
        input_args = init_settings()
        project_root = input_args.get("project_root") or Path.cwd()

        return (
            init_settings,
            SetupCfgSettingsSource(settings_cls, project_root=project_root),
            PyprojectTomlConfigSettingsSource(
                settings_cls, toml_file=project_root / "pyproject.toml"
            ),
            dotenv_settings,
            env_settings,
            CliSettingsSource(
                settings_cls,
                cli_ignore_unknown_args=True,
                cli_parse_args=True,
                cli_prefix=settings_cls.model_config.get("cli_prefix", ""),
            ),
        )

    # GitVersioned Configuration
    package_name: str = Field(
        description="The package name being versioned.",
    )
    version: str = Field(
        default="auto",
        description="Explicit version override. 'auto' enables dynamic resolution.",
    )
    project_root: EnsurePath = Field(
        default_factory=Path.cwd,
        description="The root directory of the project.",
    )
    src_root: EnsurePath = Field(
        default_factory=Path.cwd,
        description="The root directory of the project source code.",
    )
    build_is_editable: bool = Field(
        default=False,
        description="Flag indicating if the current build is an editable install.",
    )

    # Formatting properties
    format_main: str = Field(
        default="{version.major}.{version.minor}.{version.micro}",
        description="Format for main semantic versioning.",
    )
    format_dev: str = Field(
        default="dev{ref.timestamp:%Y%m%d}+{ref.short_sha}",
        description="Format for dev builds.",
    )
    format_pre: str = Field(
        default="a{ref.timestamp:%Y%m%d}",
        description="Format for pre/alpha builds.",
    )
    format_post: str = Field(
        default="post{ref.distance_from_head}",
        description="Format for post builds.",
    )

    # Sourcing properties
    regex_version: EnsureList[str] = Field(
        default_factory=lambda: [  # type: ignore[arg-type]
            r"^(?:releases?/)?v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$"
        ],
        description="Regex used to extract version from explicit version strings.",
    )
    regex_tag: EnsureList[str] = Field(
        default_factory=lambda: [  # type: ignore[arg-type]
            r"^(?:releases?/)?v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$"
        ],
        description="Regex used to find version tags and extract the version.",
    )
    regex_branch: EnsureList[str] = Field(
        default_factory=lambda: [  # type: ignore[arg-type]
            r"^(?:releases?/)?v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"
        ],
        description="Regex used to extract version from the current branch.",
    )
    regex_commit: EnsureList[str] = Field(
        default_factory=lambda: [  # type: ignore[arg-type]
            r"(?i)^(?:release\s+|bump(?:\s+\w+)*\s+)?"
            r"v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"
        ],
        description="Regex used to extract version from previous commits.",
    )
    regex_file: EnsureList[str] = Field(
        default_factory=lambda: [  # type: ignore[arg-type]
            r"(?i)(?:version|__version__)\s*[:=]\s*['\"]?"
            r"(v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:[a-zA-Z0-9.\-]+)?)"
            r"['\"]?"
        ],
        description="Regex used to extract version from a file.",
    )
    regex_archive: EnsureList[str] = Field(
        default_factory=lambda: [  # type: ignore[arg-type]
            r"(?sm)"
            r"(?=.*^commit_sha:\s*(?P<commit_sha>[^\n]*))"
            r"(?=.*^short_sha:\s*(?P<short_sha>[^\n]*))"
            r"(?=.*^timestamp:\s*(?P<timestamp>[^\n]*))"
            r"(?=.*^author_name:\s*(?P<author_name>[^\n]*))"
            r"(?=.*^author_email:\s*(?P<author_email>[^\n]*))"
            r"(?=.*^ref_names:\s*(?P<ref_names>[^\n]*))"
            r"(?=.*^ref_names:.*?(?:v)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+))"
            r"(?=.*^distance_from_head:\s*(?P<distance_from_head>[^\n]*))"
            r"(?=.*^is_head_commit:\s*(?P<is_head_commit>[^\n]*))"
            r"(?=.*^total_commits:\s*(?P<total_commits>[^\n]*))"
            r"(?=.*^is_current_branch:\s*(?P<is_current_branch>[^\n]*))"
            r"(?=.*^commit_message:\n(?P<commit_message>.*))"
        ],
        description=(
            "Regex patterns used to extract versions/metadata from an archive export."
        ),
    )
    version_source_file: str | None = Field(
        default="version.txt",
        description="File to pull version from if searching local sources.",
    )
    version_source_archive: str | None = Field(
        default=".git_archival.txt",
        description="File to pull version from if executed from a git archive.",
    )
    version_source_function: str | None = Field(
        default=None,
        description="Module and function to resolve version and git reference.",
    )
    source_type: EnsureList[str] = Field(
        default_factory=lambda: ["auto"],  # type: ignore[arg-type]
        description="Priority order of source types to extract the version from.",
    )
    dirty_ignore: EnsureList[str] = Field(
        default_factory=list,  # type: ignore[arg-type]
        description=(
            "List of file paths to ignore when checking if the repository is dirty."
        ),
    )

    # Creation & output properties
    auto_increment: (
        dict[
            Literal["release", "dev", "pre", "alpha", "nightly", "post"],
            Literal["major", "minor", "micro", "patch"],
        ]
        | None
    ) = Field(
        default=None,
        description=(
            "Target increment for specific version types when the repo is ahead of the "
            "last tag source."
        ),
    )
    version_type: Literal[
        "auto", "release", "dev", "pre", "alpha", "nightly", "post"
    ] = Field(
        default="auto",
        description="Type of version to create.",
    )
    output_file: str = Field(
        default="version.py",
        description="File path where the generated version string is written.",
    )
    template_release: str = Field(
        default_factory=(
            Path(__file__).parent / "templates" / "release.py.template"
        ).read_text,
        description="The ExStr template used for release builds.",
    )
    template_dev: str = Field(
        default_factory=(
            Path(__file__).parent / "templates" / "dev.py.template"
        ).read_text,
        description="The ExStr template used for dev builds.",
    )

    def __str__(self) -> str:
        """Return a concise string representation."""
        return (
            f"Settings(package_name={self.package_name!r}, version={self.version!r}, "
            f"version_type={self.version_type!r}, project_root={self.project_root!r}, "
            f"src_root={self.src_root!r}, source_type={self.source_type!r}, "
            f"auto_increment={self.auto_increment!r}, output_file={self.output_file!r},"
            f" dirty_ignore={self.dirty_ignore!r})"
        )

    def __repr__(self) -> str:
        """Return a detailed string representation."""
        return (
            f"Settings("
            f"package_name={self.package_name!r}, version={self.version!r}, "
            f"project_root={self.project_root!r}, src_root={self.src_root!r}, "
            f"build_is_editable={self.build_is_editable!r}, "
            f"format_main={self.format_main!r}, format_dev={self.format_dev!r}, "
            f"format_pre={self.format_pre!r}, format_post={self.format_post!r}, "
            f"regex_tag={self.regex_tag!r}, regex_branch={self.regex_branch!r}, "
            f"regex_commit={self.regex_commit!r}, regex_file={self.regex_file!r}, "
            f"version_source_file={self.version_source_file!r}, "
            f"version_source_function={self.version_source_function!r}, "
            f"source_type={self.source_type!r}, dirty_ignore={self.dirty_ignore!r}, "
            f"auto_increment={self.auto_increment!r}, "
            f"version_type={self.version_type!r}, output_file={self.output_file!r}, "
            f"template_release={self.template_release!r}, "
            f"template_dev={self.template_dev!r}"
            f")"
        )

__repr__()

Return a detailed string representation.

Source code in src/gitversioned/settings.py
def __repr__(self) -> str:
    """Return a detailed string representation."""
    return (
        f"Settings("
        f"package_name={self.package_name!r}, version={self.version!r}, "
        f"project_root={self.project_root!r}, src_root={self.src_root!r}, "
        f"build_is_editable={self.build_is_editable!r}, "
        f"format_main={self.format_main!r}, format_dev={self.format_dev!r}, "
        f"format_pre={self.format_pre!r}, format_post={self.format_post!r}, "
        f"regex_tag={self.regex_tag!r}, regex_branch={self.regex_branch!r}, "
        f"regex_commit={self.regex_commit!r}, regex_file={self.regex_file!r}, "
        f"version_source_file={self.version_source_file!r}, "
        f"version_source_function={self.version_source_function!r}, "
        f"source_type={self.source_type!r}, dirty_ignore={self.dirty_ignore!r}, "
        f"auto_increment={self.auto_increment!r}, "
        f"version_type={self.version_type!r}, output_file={self.output_file!r}, "
        f"template_release={self.template_release!r}, "
        f"template_dev={self.template_dev!r}"
        f")"
    )

__str__()

Return a concise string representation.

Source code in src/gitversioned/settings.py
def __str__(self) -> str:
    """Return a concise string representation."""
    return (
        f"Settings(package_name={self.package_name!r}, version={self.version!r}, "
        f"version_type={self.version_type!r}, project_root={self.project_root!r}, "
        f"src_root={self.src_root!r}, source_type={self.source_type!r}, "
        f"auto_increment={self.auto_increment!r}, output_file={self.output_file!r},"
        f" dirty_ignore={self.dirty_ignore!r})"
    )

settings_customise_sources(settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings) classmethod

Customizes the configuration sources and their priority.

This method overrides the default pydantic-settings source priority to inject our custom SetupCfgSettingsSource and define the specific order of resolution.

Parameters:

Name Type Description Default
settings_cls type[BaseSettings]

The settings class being instantiated.

required
init_settings PydanticBaseSettingsSource

The initial settings provided via kwargs.

required
env_settings PydanticBaseSettingsSource

Settings loaded from environment variables.

required
dotenv_settings PydanticBaseSettingsSource

Settings loaded from a .env file.

required
file_secret_settings PydanticBaseSettingsSource

Settings loaded from secret files.

required

Returns:

Type Description
tuple[PydanticBaseSettingsSource, ...]

A tuple of settings sources in priority order.

Source code in src/gitversioned/settings.py
@classmethod
def settings_customise_sources(
    cls,
    settings_cls: type[BaseSettings],
    init_settings: PydanticBaseSettingsSource,
    env_settings: PydanticBaseSettingsSource,
    dotenv_settings: PydanticBaseSettingsSource,
    file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
    """Customizes the configuration sources and their priority.

    This method overrides the default pydantic-settings source priority to
    inject our custom `SetupCfgSettingsSource` and define the specific order
    of resolution.

    :param settings_cls: The settings class being instantiated.
    :param init_settings: The initial settings provided via kwargs.
    :param env_settings: Settings loaded from environment variables.
    :param dotenv_settings: Settings loaded from a .env file.
    :param file_secret_settings: Settings loaded from secret files.
    :return: A tuple of settings sources in priority order.
    """
    _ = (file_secret_settings,)  # Allow unused variable to satisfy lint/format
    input_args = init_settings()
    project_root = input_args.get("project_root") or Path.cwd()

    return (
        init_settings,
        SetupCfgSettingsSource(settings_cls, project_root=project_root),
        PyprojectTomlConfigSettingsSource(
            settings_cls, toml_file=project_root / "pyproject.toml"
        ),
        dotenv_settings,
        env_settings,
        CliSettingsSource(
            settings_cls,
            cli_ignore_unknown_args=True,
            cli_parse_args=True,
            cli_prefix=settings_cls.model_config.get("cli_prefix", ""),
        ),
    )

configure_logger(settings=None)

Initializes the loguru logger with the provided settings or from the environment.

This function configures the global loguru logger instance based on the provided LoggingSettings. It handles enabling/disabling the logger, managing sinks, and injecting the appropriate formatter (including OpenTelemetry).

Example: .. code-block:: python

    from gitversioned.logging import configure_logger, LoggingSettings

    configure_logger(LoggingSettings(level="DEBUG"))

Parameters:

Name Type Description Default
settings LoggingSettings | None

An optional instance of LoggingSettings. If not provided, settings are automatically loaded from the environment.

None

Returns:

Type Description
None

None

Raises:

Type Description
ImportError

If OpenTelemetry formatting is explicitly enabled but the package is not installed.

Source code in src/gitversioned/logging.py
def configure_logger(settings: LoggingSettings | None = None) -> None:
    """
    Initializes the loguru logger with the provided settings or from the environment.

    This function configures the global loguru logger instance based on the provided
    ``LoggingSettings``. It handles enabling/disabling the logger, managing sinks,
    and injecting the appropriate formatter (including OpenTelemetry).

    Example:
        .. code-block:: python

            from gitversioned.logging import configure_logger, LoggingSettings

            configure_logger(LoggingSettings(level="DEBUG"))

    :param settings: An optional instance of ``LoggingSettings``. If not provided,
                     settings are automatically loaded from the environment.
    :return: None
    :raises ImportError: If OpenTelemetry formatting is explicitly enabled but the
                         package is not installed.
    """
    global _GITVERSIONED_HANDLER_ID  # noqa: PLW0603

    settings = settings or LoggingSettings()

    if not settings.enabled:
        logger.disable("gitversioned")
        return

    logger.enable("gitversioned")

    if settings.clear_loggers:
        logger.remove()
        _GITVERSIONED_HANDLER_ID = None
    elif isinstance(_GITVERSIONED_HANDLER_ID, int):
        with contextlib.suppress(ValueError):
            logger.remove(_GITVERSIONED_HANDLER_ID)
        _GITVERSIONED_HANDLER_ID = None

    use_otel = settings.otel_formatting == "enable" or (
        settings.otel_formatting == "auto" and opentelemetry_trace is not None
    )
    if settings.otel_formatting == "enable" and opentelemetry_trace is None:
        raise ImportError(
            "OpenTelemetry is not installed but 'otel_formatting' was set to 'enable'."
        )

    log_format = _otel_formatter if use_otel else settings.format
    filter_val = "gitversioned" if settings.filter is True else settings.filter

    if isinstance(filter_val, (list, tuple)):
        prefixes = tuple(filter_val)

        def final_filter(record: dict[str, Any]) -> bool:
            return bool(record["name"] and record["name"].startswith(prefixes))

    elif isinstance(filter_val, str):

        def final_filter(record: dict[str, Any]) -> bool:
            return bool(record["name"] and record["name"].startswith(filter_val))

    else:
        final_filter = None if filter_val is False else filter_val  # type: ignore[assignment]

    _GITVERSIONED_HANDLER_ID = logger.add(
        settings.sink,  # type: ignore[arg-type]
        level=settings.level,
        filter=final_filter,  # type: ignore[arg-type]
        format=log_format,  # type: ignore[arg-type]
        enqueue=settings.enqueue,
        **settings.kwargs,
    )

generate_version_py(version, reference, settings, repository, environment)

Writes the resolved version metadata to a python file using templates.

This function utilizes the configured release or development templates to generate a python file containing version information, which can then be included directly within the target package.

Example: >>> path = generate_version_py(version, ref, settings, repo, env)

Parameters:

Name Type Description Default
version Version

The resolved PEP 440 version object.

required
reference GitReference

The resolved Git reference object.

required
settings Settings

Configuration rules for resolving the version.

required
repository GitRepository

The current git repository state.

required
environment BuildEnvironment

Build environment metadata.

required

Returns:

Type Description
Path | None

The Path object pointing to the written file.

Source code in src/gitversioned/versioning.py
def generate_version_py(
    version: Version,
    reference: GitReference,
    settings: Settings,
    repository: GitRepository,
    environment: BuildEnvironment,
) -> Path | None:
    """
    Writes the resolved version metadata to a python file using templates.

    This function utilizes the configured release or development templates to
    generate a python file containing version information, which can then be
    included directly within the target package.

    Example:
        >>> path = generate_version_py(version, ref, settings, repo, env)

    :param version: The resolved PEP 440 version object.
    :param reference: The resolved Git reference object.
    :param settings: Configuration rules for resolving the version.
    :param repository: The current git repository state.
    :param environment: Build environment metadata.
    :return: The Path object pointing to the written file.
    """
    logger.debug(
        f"generate_version_py called for version={version} reference={reference} "
        f"settings={settings} repository={repository} environment={environment}"
    )

    if not settings.output_file:
        logger.debug("No output file configured, skipping generation of version file.")
        return None

    template = (
        settings.template_dev if version.dev is not None else settings.template_release
    )
    context = {
        "version": version,
        "repo": repository,
        "config": settings,
        "env": environment,
        "ref": reference,
    }
    content = str(render(generate_template(template, context, use_eval=True)))

    try:
        output_path = Path(settings.output_file)
        if not output_path.is_absolute():
            output_path = settings.src_root / output_path
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_text(content, encoding="utf-8")
        logger.info(f"Generated version py file successfully at: {output_path}")
    except Exception as error:
        logger.exception(
            f"Failed to write version python file to {output_path}: {error}"
        )
        raise

    return output_path

resolve_and_generate_version(settings, repository, environment)

Main entry point to resolve the version and write the output file if configured.

This function wraps the core version resolution logic and subsequently triggers the generation of the version python file if an output file is specified in the settings. It provides a convenient single call for build hooks and integrations.

Example: >>> version, path = resolve_and_generate_version(settings, repo, env)

Parameters:

Name Type Description Default
settings Settings

Configuration rules for resolving the version.

required
repository GitRepository

The current git repository state.

required
environment BuildEnvironment

Build environment metadata.

required

Returns:

Type Description
tuple[Version, Path | None]

A tuple of the resolved Version and output Path (if generated).

Source code in src/gitversioned/versioning.py
def resolve_and_generate_version(
    settings: Settings, repository: GitRepository, environment: BuildEnvironment
) -> tuple[Version, Path | None]:
    """
    Main entry point to resolve the version and write the output file if configured.

    This function wraps the core version resolution logic and subsequently triggers
    the generation of the version python file if an output file is specified in the
    settings. It provides a convenient single call for build hooks and integrations.

    Example:
        >>> version, path = resolve_and_generate_version(settings, repo, env)

    :param settings: Configuration rules for resolving the version.
    :param repository: The current git repository state.
    :param environment: Build environment metadata.
    :return: A tuple of the resolved Version and output Path (if generated).
    """
    logger.debug(
        f"resolve_and_generate_version called for {settings} with "
        f"repo={repository} env={environment}"
    )
    version, reference = resolve_version(settings, repository, environment)
    output_path = generate_version_py(
        version, reference, settings, repository, environment
    )

    return version, output_path

resolve_version(settings, repository, environment)

Computes the PEP 440 version based on configuration and repository state.

This function coordinates the resolution of version sources according to the provided settings, performs auto-increments if necessary, and formats the final version string based on the target build type (e.g., release, dev, alpha).

Example: >>> version, reference = resolve_version(settings, repo, env)

Parameters:

Name Type Description Default
settings Settings

Configuration rules for resolving the version.

required
repository GitRepository

The current git repository state.

required
environment BuildEnvironment

Build environment metadata.

required

Returns:

Type Description
tuple[Version, GitReference]

A tuple containing the resolved Version and the Git reference object.

Raises:

Type Description
ValueError

If an unknown source type or git type is encountered.

Source code in src/gitversioned/versioning.py
def resolve_version(
    settings: Settings, repository: GitRepository, environment: BuildEnvironment
) -> tuple[Version, GitReference]:
    """
    Computes the PEP 440 version based on configuration and repository state.

    This function coordinates the resolution of version sources according to the
    provided settings, performs auto-increments if necessary, and formats the final
    version string based on the target build type (e.g., release, dev, alpha).

    Example:
        >>> version, reference = resolve_version(settings, repo, env)

    :param settings: Configuration rules for resolving the version.
    :param repository: The current git repository state.
    :param environment: Build environment metadata.
    :return: A tuple containing the resolved Version and the Git reference object.
    :raises ValueError: If an unknown source type or git type is encountered.
    """

    logger.debug(
        f"resolving version for {settings} in repo={repository} env={environment}"
    )
    base_version, reference = _resolve_version_sources(
        settings.source_type, settings, repository, environment
    )
    logger.info(f"Resolved base version: {base_version} for git reference {reference}")

    # Determine version type to build (release, dev, alpha, post)
    version_type = str(settings.version_type).strip().lower()
    distance = reference.distance_from_head if repository.is_available else 0
    if version_type == "auto":
        if not repository.is_available and reference.commit_sha:
            on_head = True
            is_dirty = False
        else:
            on_head = repository.is_available and reference.is_head_commit
            dirty_files = _get_dirty_files(repository, settings)
            is_dirty = len(dirty_files) > 0

        version_type = "release" if on_head and not is_dirty else "dev"
        logger.info(
            f"Auto-resolved version type to: '{version_type}' "
            f"for ref {reference} and repo {repository}"
        )

    target_str = str(
        settings.auto_increment.get(
            cast(
                "Literal['release', 'dev', 'pre', 'alpha', 'nightly', 'post']",
                version_type,
            ),
            "",
        )
        if settings.auto_increment
        else ""
    ).lower()
    target_idx = {"major": 0, "minor": 1, "micro": 2, "patch": 2}.get(target_str)

    if target_idx is not None and distance > 0:
        parts = [base_version.major, base_version.minor, base_version.micro]
        parts[target_idx] += 1
        for index in range(target_idx + 1, len(parts)):
            parts[index] = 0

        version = Version(".".join(map(str, parts)))
        logger.info(
            f"Auto-incremented version from {base_version} to {version} "
            f"(target='{target_str}')"
        )
    else:
        version = base_version

    context = {
        "version": version,
        "repo": repository,
        "config": settings,
        "env": environment,
        "ref": reference,
    }

    main_version = str(
        render(generate_template(settings.format_main, context, use_eval=True))
    )
    segment = ""
    if version_type == "dev":
        segment = str(
            render(generate_template(settings.format_dev, context, use_eval=True))
        )
    elif version_type in ("pre", "alpha", "nightly"):
        segment = str(
            render(generate_template(settings.format_pre, context, use_eval=True))
        )
    elif version_type == "post":
        segment = str(
            render(generate_template(settings.format_post, context, use_eval=True))
        )

    final_version = Version(f"{main_version}.{segment}".rstrip("+."))
    logger.info(f"Resolved final version: {final_version}")

    return final_version, reference