Repository: pypa/setuptools_scm
Pull request #880 (2023-07-20 14:07:44)

setup more detailed mkdocs documentation


Patch:
diff --git a/.gitignore b/.gitignore
index 529a7a2..cd25c48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,8 @@ htmlcov/
 .cache
 .pytest_cache
 .mypy_cache/
+.entangled/
+
 nosetests.xml
 coverage.xml
 *,cover
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e8c7fdd..9ee53c7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,6 +6,7 @@ repos:
     hooks:
     - id: black
       args: [--safe, --quiet]
+      exclude: docs/examples/
 -   repo: https://github.com/asottile/reorder-python-imports
     rev: v3.10.0
     hooks:
@@ -15,7 +16,6 @@ repos:
     rev: v4.4.0
     hooks:
     -   id: trailing-whitespace
-    -   id: end-of-file-fixer
     -   id: check-yaml
     -   id: debug-statements
 -   repo: https://github.com/PyCQA/flake8
@@ -31,6 +31,7 @@ repos:
   rev: "0.13.0"
   hooks:
     - id: pyproject-fmt
+      exclude: docs/examples/
 
 -   repo: https://github.com/pre-commit/mirrors-mypy
     rev: 'v1.4.1'
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b18369b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,100 @@
+# setuptools_scm
+[![github ci](https://github.com/pypa/setuptools_scm/workflows/python%20tests+artifacts+release/badge.svg)](https://github.com/pypa/setuptools_scm/actions)
+[![tidelift](https://tidelift.com/badges/package/pypi/setuptools-scm) ](https://tidelift.com/subscription/pkg/pypi-setuptools-scm?utm_source=pypi-setuptools-scm&utm_medium=readme)
+
+## about
+
+[setuptools-scm] extracts Python package versions from `git` or
+`hg` metadata instead of declaring them as the version argument
+or in an SCM managed file.
+
+Additionally, [setuptools-scm] provides setuptools
+with a list of files that are managed by the SCM <br/>
+(i.e. it automatically adds **all of** the SCM-managed files to the sdist).<br/>
+Unwanted files must be excluded via `MANIFEST.in`.
+
+
+## `pyproject.toml` usage
+
+The preferred way to configure [setuptools-scm] is to author
+settings in a `tool.setuptools_scm` section of `pyproject.toml`.
+
+This feature requires setuptools 60 or later.
+First, ensure that [setuptools-scm] is present during the project's
+build step by specifying it as one of the build requirements.
+
+```toml
+[build-system]
+requires = [
+    "setuptools>=60",
+    "setuptools-scm>=8.0"]
+```
+
+That will be sufficient to require [setuptools-scm] for projects
+that support [PEP 518] like [pip] and [build].
+
+[pip]: https://pypi.org/project/pip
+[build]: https://pypi.org/project/build
+[PEP 518]: https://peps.python.org/pep-0518/
+
+
+To enable version inference, you need to set the version
+dynamically in the `project` section of `pyproject.toml`:
+
+```toml title="pyproject.toml"
+[project]
+# version = "0.0.1"  # Remove any existing version parameter.
+dynamic = ["version"]
+[tool.setuptools_scm]
+```
+
+Additionally, a version file can be written by specifying:
+
+```toml title="pyproject.toml"
+[tool.setuptools_scm]
+version_file = "pkg/_version.py"
+```
+
+Where `pkg` is the name of your package.
+
+If you need to confirm which version string is being generated or debug the configuration,
+you can install [setuptools-scm] directly in your working environment and run:
+
+[setuptools-scm]: https://github.com/pypa/setuptools_scm
+
+```console
+$ python -m setuptools_scm
+# To explore other options, try:
+$ python -m setuptools_scm --help
+```
+
+
+
+## Interaction with Enterprise Distributions
+
+Some enterprise distributions like RHEL7
+ship rather old setuptools versions.
+
+In those cases its typically possible to build by using an sdist against `setuptools_scm<2.0`.
+As those old setuptools versions lack sensible types for versions,
+modern [setuptools-scm] is unable to support them sensibly.
+
+It's strongly recommended to build a wheel artifact using modern Python and setuptools,
+then installing the artifact instead of trying to run against old setuptools versions.
+
+
+## Code of Conduct
+
+
+Everyone interacting in the [setuptools-scm] project's codebases, issue
+trackers, chat rooms, and mailing lists is expected to follow the
+[PSF Code of Conduct].
+
+[PSF Code of Conduct]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
+
+
+## Security Contact
+
+To report a security vulnerability, please use the
+[Tidelift security contact](https://tidelift.com/security).
+Tidelift will coordinate the fix and disclosure.
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 3a17d5a..0000000
--- a/README.rst
+++ /dev/null
@@ -1,618 +0,0 @@
-setuptools_scm
-==============
-
-``setuptools_scm`` extracts Python package versions from ``git`` or
-``hg`` metadata instead of declaring them as the version argument
-or in a SCM managed file.
-
-Additionally ``setuptools_scm`` provides setuptools with a list of
-files that are managed by the SCM (i.e. it automatically adds all of
-the SCM-managed files to the sdist). Unwanted files must be excluded
-by discarding them via ``MANIFEST.in``.
-
-``setuptools_scm`` supports the following scm out of the box:
-
-* git
-* mercurial
-
-.. image:: https://github.com/pypa/setuptools_scm/workflows/python%20tests+artifacts+release/badge.svg
-    :target: https://github.com/pypa/setuptools_scm/actions
-
-.. image:: https://tidelift.com/badges/package/pypi/setuptools-scm
-   :target: https://tidelift.com/subscription/pkg/pypi-setuptools-scm?utm_source=pypi-setuptools-scm&utm_medium=readme
-
-
-``pyproject.toml`` usage
-------------------------
-
-The preferred way to configure ``setuptools_scm`` is to author
-settings in a ``tool.setuptools_scm`` section of ``pyproject.toml``.
-
-This feature requires Setuptools 42 or later, released in Nov, 2019.
-If your project needs to support build from sdist on older versions
-of Setuptools, you will need to also implement the ``setup.py usage``
-for those legacy environments.
-
-First, ensure that ``setuptools_scm`` is present during the project's
-built step by specifying it as one of the build requirements.
-
-.. code:: toml
-
-    # pyproject.toml
-    [build-system]
-    requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"]
-
-That will be sufficient to require ``setuptools_scm`` for projects
-that support PEP 518 (`pip <https://pypi.org/project/pip>`_ and
-`pep517 <https://pypi.org/project/pep517/>`_). Many tools,
-especially those that invoke ``setup.py`` for any reason, may
-continue to rely on ``setup_requires``. For maximum compatibility
-with those uses, consider also including a ``setup_requires`` directive
-(described below in ``setup.py usage`` and ``setup.cfg``).
-
-To enable version inference, you need to set the version
-dynamically in the ``project`` section of ``pyproject.toml``:
-
-.. code:: toml
-
-    # pyproject.toml
-    [project]
-    # version = "0.0.1"  # Remove any existing version parameter.
-    dynamic = ["version"]
-
-Then add this section to your ``pyproject.toml``:
-
-.. code:: toml
-
-    # pyproject.toml
-    [tool.setuptools_scm]
-
-Including this section is comparable to supplying
-``use_scm_version=True`` in ``setup.py``. Additionally,
-include arbitrary keyword arguments in that section
-to be supplied to ``get_version()``. For example:
-
-.. code:: toml
-
-    # pyproject.toml
-    [tool.setuptools_scm]
-    version_file = "pkg/_version.py"
-
-Where ``pkg`` is the name of your package.
-
-If you need to confirm which version string is being generated
-or debug the configuration, you can install
-`setuptools-scm <https://github.com/pypa/setuptools_scm>`_
-directly in your working environment and run:
-
-.. code-block:: shell
-
-    $ python -m setuptools_scm
-
-    # To explore other options, try:
-    $ python -m setuptools_scm --help
-
-
-
-
-
-Programmatic usage
-------------------
-
-In order to use ``setuptools_scm`` from code that is one directory deeper
-than the project's root, you can use:
-
-.. code:: python
-
-    from setuptools_scm import get_version
-    version = get_version(root='..', relative_to=__file__)
-
-
-Retrieving package version at runtime
--------------------------------------
-
-If you have opted not to hardcode the version number inside the package,
-you can retrieve it at runtime from PEP-0566_ metadata using
-``importlib.metadata`` from the standard library (added in Python 3.8)
-or the `importlib_metadata`_ backport:
-
-.. code:: python
-
-    from importlib.metadata import version, PackageNotFoundError
-
-    try:
-        __version__ = version("package-name")
-    except PackageNotFoundError:
-        # package is not installed
-        pass
-
-
-.. _PEP-0566: https://www.python.org/dev/peps/pep-0566/
-.. _importlib_metadata: https://pypi.org/project/importlib-metadata/
-
-
-Usage from Sphinx
------------------
-
-It is discouraged to use ``setuptools_scm`` from Sphinx itself,
-instead use ``importlib.metadata`` after editable/real installation:
-
-.. code:: python
-
-    # contents of docs/conf.py
-    from importlib.metadata import version
-    release = version('myproject')
-    # for example take major/minor
-    version = '.'.join(release.split('.')[:2])
-
-The underlying reason is, that services like *Read the Docs* sometimes change
-the working directory for good reasons and using the installed metadata
-prevents using needless volatile data there.
-
-
-Usage from Docker
------------------
-
-By default, docker will not copy the ``.git``  folder into your container.
-Therefore, builds with version inference might fail.
-Consequently, you can use the following snippet to infer the version from
-the host os without copying the entire ``.git`` folder to your Dockerfile.
-
-.. code:: dockerfile
-
-    RUN --mount=source=.git,target=.git,type=bind \
-        pip install --no-cache-dir -e .
-
-However, this build step introduces a dependency to the state of your local
-.git folder the build cache and triggers the long-running pip install process on every build.
-To optimize build caching, one can use an environment variable to pretend a pseudo
-version that is used to cache the results of the pip install process:
-
-.. code:: dockerfile
-
-    FROM python
-    COPY pyproject.toml
-    ARG PSEUDO_VERSION=1
-    RUN SETUPTOOLS_SCM_PRETEND_VERSION=${PSEUDO_VERSION} pip install -e .[test]
-    RUN --mount=source=.git,target=.git,type=bind pip install -e .
-
-Note that running this Dockerfile requires docker with BuildKit enabled
-`[docs] <https://github.com/moby/buildkit/blob/v0.8.3/frontend/dockerfile/docs/syntax.md>`_.
-
-To avoid BuildKit and mounting of the .git folder altogether, one can also pass the desired
-version as a build argument. Note that ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME}``
-is preferred over ``SETUPTOOLS_SCM_PRETEND_VERSION``.
-
-
-Default versioning scheme
--------------------------
-
-In the standard configuration ``setuptools_scm`` takes a look at three things:
-
-1. latest tag (with a version number)
-2. the distance to this tag (e.g. number of revisions since latest tag)
-3. workdir state (e.g. uncommitted changes since latest tag)
-
-and uses roughly the following logic to render the version:
-
-no distance and clean:
-    ``{tag}``
-distance and clean:
-    ``{next_version}.dev{distance}+{scm letter}{revision hash}``
-no distance and not clean:
-    ``{tag}+dYYYYMMDD``
-distance and not clean:
-    ``{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYYMMDD``
-
-The next version is calculated by adding ``1`` to the last numeric component of
-the tag.
-
-For Git projects, the version relies on `git describe <https://git-scm.com/docs/git-describe>`_,
-so you will see an additional ``g`` prepended to the ``{revision hash}``.
-
-.. note::
-
-    According to `PEP 440 <https://peps.python.org/pep-0440/#local-version-identifiers>`_,
-    if a version includes a local component, the package cannot be published to public
-    package indexes like PyPI or TestPyPI. The disallowed version segments may
-    be seen in auto-publishing workflows or when a configuration mistake is made.
-
-    However, some package indexes such as devpi or other alternatives allow local
-    versions. Local version identifiers must comply with `PEP 440`_.
-
-Semantic Versioning (SemVer)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Due to the default behavior it's necessary to always include a
-patch version (the ``3`` in ``1.2.3``), or else the automatic guessing
-will increment the wrong part of the SemVer (e.g. tag ``2.0`` results in
-``2.1.devX`` instead of ``2.0.1.devX``). So please make sure to tag
-accordingly.
-
-.. note::
-
-    Future versions of ``setuptools_scm`` will switch to `SemVer
-    <http://semver.org/>`_ by default hiding the the old behavior as an
-    configurable option.
-
-
-Builtin mechanisms for obtaining version numbers
-------------------------------------------------
-
-1. the SCM itself (git/hg)
-2. ``.hg_archival`` files (mercurial archives)
-3. ``.git_archival.txt`` files (git archives, see subsection below)
-4. ``PKG-INFO``
-
-
-Git archives
-~~~~~~~~~~~~
-
-Git archives are supported, but a few changes to your repository are required.
-
-Create a ``.git_archival.txt`` file in the root directory of your repository,
-and copy-paste this into it::
-
-    node: $Format:%H$
-    node-date: $Format:%cI$
-    describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
-    ref-names: $Format:%D$
-
-Create the ``.gitattributes`` file in the root directory of your repository
-if it doesn't already exist, and copy-paste this into it::
-
-    .git_archival.txt  export-subst
-
-Finally, don't forget to commit those two files::
-
-    git add .git_archival.txt .gitattributes && git commit
-
-Note that if you are creating a ``_version.py`` file, note that it should not
-be kept in version control.
-
-
-File finders hook makes most of MANIFEST.in unnecessary
--------------------------------------------------------
-
-``setuptools_scm`` implements a `file_finders
-<https://setuptools.pypa.io/en/latest/userguide/extension.html#adding-support-for-revision-control-systems>`_
-entry point which returns all files tracked by your SCM. This eliminates
-the need for a manually constructed ``MANIFEST.in`` in most cases where this
-would be required when not using ``setuptools_scm``, namely:
-
-* To ensure all relevant files are packaged when running the ``sdist`` command.
-
-* When using `include_package_data <https://setuptools.readthedocs.io/en/latest/setuptools.html#including-data-files>`_
-  to include package data as part of the ``build`` or ``bdist_wheel``.
-
-``MANIFEST.in`` may still be used: anything defined there overrides the hook.
-This is mostly useful to exclude files tracked in your SCM from packages,
-although in principle it can be used to explicitly include non-tracked files
-too.
-
-
-Configuration parameters
-------------------------
-
-In order to configure the way ``use_scm_version`` works you can provide
-a mapping with options instead of a boolean value.
-
-The currently supported configuration keys are:
-
-:root:
-    Relative path to cwd, used for finding the SCM root; defaults to ``.``
-
-:version_scheme:
-    Configures how the local version number is constructed; either an
-    entrypoint name or a callable.
-
-:local_scheme:
-    Configures how the local component of the version is constructed; either an
-    entrypoint name or a callable.
-
-:version_file:
-    A path to a file that gets replaced with a file containing the current
-    version. It is ideal for creating a ``_version.py`` file within the
-    package, typically used to avoid using `pkg_resources.get_distribution`
-    (which adds some overhead).
-
-    .. warning::
-
-      Only files with :code:`.py` and :code:`.txt` extensions have builtin
-      templates, for other file types it is necessary to provide
-      :code:`write_to_template`.
-
-:version_file_template_template:
-    A newstyle format string that is given the current version as
-    the ``version`` keyword argument for formatting.
-
-:write_to:
-   (deprecated) legacy option to create a version file relative to the scm root
-   its broken for usage from a sdist and fixing it would be a fatal breaking change,
-   use ``version_file`` instead
-:relative_to:
-    A file from which the root can be resolved.
-    Typically called by a script or module that is not in the root of the
-    repository to point ``setuptools_scm`` at the root of the repository by
-    supplying ``__file__``.
-
-:tag_regex:
-   A Python regex string to extract the version part from any SCM tag.
-    The regex needs to contain either a single match group, or a group
-    named ``version``, that captures the actual version information.
-
-    Defaults to the value of ``setuptools_scm.config.DEFAULT_TAG_REGEX``
-    (see `_config.py <src/setuptools_scm/_config.py>`_).
-
-:parentdir_prefix_version:
-    If the normal methods for detecting the version (SCM version,
-    sdist metadata) fail, and the parent directory name starts with
-    ``parentdir_prefix_version``, then this prefix is stripped and the rest of
-    the parent directory name is matched with ``tag_regex`` to get a version
-    string.  If this parameter is unset (the default), then this fallback is
-    not used.
-
-    This is intended to cover GitHub's "release tarballs", which extract into
-    directories named ``projectname-tag/`` (in which case
-    ``parentdir_prefix_version`` can be set e.g. to ``projectname-``).
-
-:fallback_version:
-    A version string that will be used if no other method for detecting the
-    version worked (e.g., when using a tarball with no metadata). If this is
-    unset (the default), setuptools_scm will error if it fails to detect the
-    version.
-
-:parse:
-    A function that will be used instead of the discovered SCM for parsing the
-    version.
-    Use with caution, this is a function for advanced use, and you should be
-    familiar with the ``setuptools_scm`` internals to use it.
-
-:git_describe_command:
-    This command will be used instead the default ``git describe`` command.
-    Use with caution, this is a function for advanced use, and you should be
-    familiar with the ``setuptools_scm`` internals to use it.
-
-    Defaults to the value set by ``setuptools_scm.git.DEFAULT_DESCRIBE``
-    (see `git.py <src/setuptools_scm/git.py>`_).
-
-:normalize:
-    A boolean flag indicating if the version string should be normalized.
-    Defaults to ``True``. Setting this to ``False`` is equivalent to setting
-    ``version_cls`` to ``setuptools_scm.version.NonNormalizedVersion``
-
-:version_cls:
-    An optional class used to parse, verify and possibly normalize the version
-    string. Its constructor should receive a single string argument, and its
-    ``str`` should return the normalized version string to use.
-    This option can also receive a class qualified name as a string.
-
-    This defaults to ``packaging.version.Version`` if available. If
-    ``packaging`` is not installed, ``pkg_resources.packaging.version.Version``
-    is used. Note that it is known to modify git release candidate schemes.
-
-    The ``setuptools_scm.NonNormalizedVersion`` convenience class is
-    provided to disable the normalization step done by
-    ``packaging.version.Version``. If this is used while ``setuptools_scm``
-    is integrated in a setuptools packaging process, the non-normalized
-    version number will appear in all files (see ``write_to``) BUT note
-    that setuptools will still normalize it to create the final distribution,
-    so as to stay compliant with the python packaging standards.
-
-To use ``setuptools_scm`` in other Python code you can use the ``get_version``
-function:
-
-.. code:: python
-
-    from setuptools_scm import get_version
-    my_version = get_version()
-
-It optionally accepts the keys of the ``use_scm_version`` parameter as
-keyword arguments.
-
-
-Environment variables
----------------------
-
-:SETUPTOOLS_SCM_PRETEND_VERSION:
-    when defined and not empty,
-    its used as the primary source for the version number
-    in which case it will be an string
-
-    .. warning::
-
-      its strongly  recommended to use use distribution name specific pretend versions
-
-
-
-:SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME}:
-    when defined and not empty,
-    its used as the primary source for the version number
-    in which case it will be an unparsed string
-
-    the dist name normalization follows adapted PEP-503 semantics, with one or
-    more of ".-_" being replaced by a single "_", and the name being upper-cased
-
-    it takes precedence over ``SETUPTOOLS_SCM_PRETEND_VERSION``
-
-:SETUPTOOLS_SCM_DEBUG:
-    when defined and not empty,
-    a lot of debug information will be printed as part of ``setuptools_scm``
-    operating
-
-:SOURCE_DATE_EPOCH:
-    when defined, used as the timestamp from which the
-    ``node-and-date`` and ``node-and-timestamp`` local parts are
-    derived, otherwise the current time is used
-    (https://reproducible-builds.org/docs/source-date-epoch/)
-
-:SETUPTOOLS_SCM_IGNORE_VCS_ROOTS:
-    when defined, a ``os.pathsep`` separated list
-    of directory names to ignore for root finding
-
-
-Extending setuptools_scm
-------------------------
-
-``setuptools_scm`` ships with a few ``setuptools`` entrypoints based hooks to
-extend its default capabilities.
-
-
-Adding a new SCM
-~~~~~~~~~~~~~~~~
-
-``setuptools_scm`` provides two entrypoints for adding new SCMs:
-
-``setuptools_scm.parse_scm``
-    A function used to parse the metadata of the current workdir
-    using the name of the control directory/file of your SCM as the
-    entrypoint's name. E.g. for the built-in entrypoint for git the
-    entrypoint is named ``.git`` and references ``setuptools_scm.git:parse``
-
-  The return value MUST be a ``setuptools_scm.version.ScmVersion`` instance
-  created by the function ``setuptools_scm.version:meta``.
-
-``setuptools_scm.files_command``
-  Either a string containing a shell command that prints all SCM managed
-  files in its current working directory or a callable, that given a
-  pathname will return that list.
-
-  Also use then name of your SCM control directory as name of the entrypoint.
-
-
-Version number construction
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-``setuptools_scm.version_scheme``
-    Configures how the version number is constructed given a
-    ``setuptools_scm.version.ScmVersion`` instance and should return a string
-    representing the version.
-
-    Available implementations:
-
-    :guess-next-dev: Automatically guesses the next development version (default).
-        Guesses the upcoming release by incrementing the pre-release segment if present,
-        otherwise by incrementing the micro segment. Then appends :code:`.devN`.
-        In case the tag ends with ``.dev0`` the version is not bumped
-        and custom ``.devN`` versions will trigger a error.
-    :post-release: generates post release versions (adds :code:`.postN`)
-    :python-simplified-semver: Basic semantic versioning. Guesses the upcoming release
-        by incrementing the minor segment and setting the micro segment to zero if the
-        current branch contains the string ``'feature'``, otherwise by incrementing the
-        micro version. Then appends :code:`.devN`. Not compatible with pre-releases.
-    :release-branch-semver: Semantic versioning for projects with release branches. The
-        same as ``guess-next-dev`` (incrementing the pre-release or micro segment) if on
-        a release branch: a branch whose name (ignoring namespace) parses as a version
-        that matches the most recent tag up to the minor segment. Otherwise if on a
-        non-release branch, increments the minor segment and sets the micro segment to
-        zero, then appends :code:`.devN`.
-    :no-guess-dev: Does no next version guessing, just adds :code:`.post1.devN`
-
-``setuptools_scm.local_scheme``
-    Configures how the local part of a version is rendered given a
-    ``setuptools_scm.version.ScmVersion`` instance and should return a string
-    representing the local version.
-    Dates and times are in Coordinated Universal Time (UTC), because as part
-    of the version, they should be location independent.
-
-    Available implementations:
-
-    :node-and-date: adds the node on dev versions and the date on dirty
-                    workdir (default)
-    :node-and-timestamp: like ``node-and-date`` but with a timestamp of
-                         the form ``{:%Y%m%d%H%M%S}`` instead
-    :dirty-tag: adds ``+dirty`` if the current workdir has changes
-    :no-local-version: omits local version, useful e.g. because pypi does
-                       not support it
-
-
-Importing in ``setup.py``
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To support usage in ``setup.py`` passing a callable into ``use_scm_version``
-is supported.
-
-Within that callable, ``setuptools_scm`` is available for import.
-The callable must return the configuration.
-
-.. code:: python
-
-    # content of setup.py
-    import setuptools
-
-    def myversion():
-        from setuptools_scm.version import get_local_dirty_tag
-        def clean_scheme(version):
-            return get_local_dirty_tag(version) if version.dirty else '+clean'
-
-        return {'local_scheme': clean_scheme}
-
-    setup(
-        ...,
-        use_scm_version=myversion,
-        ...
-    )
-
-
-Customizing Version Scheme with pyproject.toml
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To support custom version schemes in pyproject.toml, you may specify your own function as an entrypoint for getting the version.
-
-.. code:: toml
-
-    # pyproject.toml
-    [tool.setuptools_scm]
-    version_scheme = "myproject.my_file:myversion_func"
-
-.. code:: python
-
-    # myproject/my_file
-    def myversion_func(version: ScmVersion):
-        from setuptools_scm.version import guess_next_version
-        return version.format_next_version(guess_next_version, '{guessed}b{distance}')
-
-
-Note on testing non-installed versions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-While the general advice is to test against a installed version,
-some environments require a test prior to install,
-
-.. code::
-
-  $ python setup.py egg_info
-  $ PYTHONPATH=$PWD:$PWD/src pytest
-
-
-Interaction with Enterprise Distributions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Some enterprise distributions like RHEL7 and others
-ship rather old setuptools versions due to various release management details.
-
-In those case its typically possible to build by using a sdist against ``setuptools_scm<2.0``.
-As those old setuptools versions lack sensible types for versions,
-modern setuptools_scm is unable to support them sensibly.
-
-In case the project you need to build can not be patched to either use old setuptools_scm,
-its still possible to install a more recent version of setuptools in order to handle the build
-and/or install the package by using wheels or eggs.
-
-
-Code of Conduct
----------------
-
-Everyone interacting in the ``setuptools_scm`` project's codebases, issue
-trackers, chat rooms, and mailing lists is expected to follow the
-`PSF Code of Conduct`_.
-
-.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
-
-
-Security Contact
-================
-
-To report a security vulnerability, please use the
-`Tidelift security contact <https://tidelift.com/security>`_.
-Tidelift will coordinate the fix and disclosure.
diff --git a/docs/config.md b/docs/config.md
new file mode 100644
index 0000000..f1e97ea
--- /dev/null
+++ b/docs/config.md
@@ -0,0 +1,156 @@
+# Configuration
+
+
+## configuration parameters
+
+Configuration parameters can be configured in `pyproject.toml` or `setup.py`.
+Callables or other Python objects have to be passed in `setup.py` (via the `use_scm_version` keyword argument).
+
+
+`root : Path | PathLike[str]`
+: Relative path to the SCM root, defaults to `.` and is relative to the file path passed in `relative_to`
+
+`version_scheme : str | Callable[[ScmVersion], str]`
+: Configures how the local version number is constructed; either an entrypoint name or a callable.
+
+`local_scheme : str | Callable[[ScmVersion], str]`
+: Configures how the local component of the version is constructed
+  either an entrypoint name or a callable.
+
+
+`version_file: Path | PathLike[str] | None = None`
+:   A path to a file that gets replaced with a file containing the current
+    version. It is ideal for creating a ``_version.py`` file within the
+    package, typically used to avoid using `pkg_resources.get_distribution`
+    (which adds some overhead).
+
+    !!! warning ""
+
+        Only files with `.py` and `.txt` extensions have builtin templates,
+        for other file types it is necessary to provide `write_to_template`.
+
+`version_file_template_template: str | None = None`
+:   A new-style format string that is given the current version as
+    the `version` keyword argument for formatting.
+
+`write_to: Pathlike[str] | Path | None = None`
+:  (deprecated) legacy option to create a version file relative to the scm root
+   it's broken for usage from a sdist and fixing it would be a fatal breaking change,
+   use `version_file` instead.
+
+`relative_to: Path|Pathlike[str] = "pyproject.toml"`
+:   A file/directory from which the root can be resolved.
+    Typically called by a script or module that is not in the root of the
+    repository to point `setuptools_scm` at the root of the repository by
+    supplying `__file__`.
+
+`tag_regex: str|Pattern[str]`
+:   A Python regex string to extract the version part from any SCM tag.
+    The regex needs to contain either a single match group, or a group
+    named `version`, that captures the actual version information.
+
+    Defaults to the value of [setuptools_scm._config.DEFAULT_TAG_REGEX][]
+
+`parentdir_prefix_version: str|None = None`
+:   If the normal methods for detecting the version (SCM version,
+    sdist metadata) fail, and the parent directory name starts with
+    `parentdir_prefix_version`, then this prefix is stripped and the rest of
+    the parent directory name is matched with `tag_regex` to get a version
+    string.  If this parameter is unset (the default), then this fallback is
+    not used.
+
+    This was intended to cover GitHub's "release tarballs",
+    which extract into directories named `projectname-tag/`
+    (in which case `parentdir_prefix_version` can be set e.g. to `projectname-`).
+
+`fallback_version: str | None = None`
+ :  A version string that will be used if no other method for detecting the
+    version worked (e.g., when using a tarball with no metadata). If this is
+    unset (the default), `setuptools_scm` will error if it fails to detect the
+    version.
+
+`parse: Callable[[Path, Config], ScmVersion] | None = None`
+:   A function that will be used instead of the discovered SCM
+    for parsing the version. Use with caution,
+    this is a function for advanced use and you should be
+    familiar with the `setuptools_scm` internals to use it.
+
+`git_describe_command`
+:   This command will be used instead the default `git describe --long` command.
+
+    Defaults to the value set by [setuptools_scm.git.DEFAULT_DESCRIBE][]
+
+`normalize`
+:   A boolean flag indicating if the version string should be normalized.
+    Defaults to `True`. Setting this to `False` is equivalent to setting
+    `version_cls` to [setuptools_scm.NonNormalizedVersion][]
+
+`version_cls: type|str = packaging.version.Version`
+:   An optional class used to parse, verify and possibly normalize the version
+    string. Its constructor should receive a single string argument, and its
+    `str` should return the normalized version string to use.
+    This option can also receive a class qualified name as a string.
+
+    The [setuptools_scm.NonNormalizedVersion][] convenience class is
+    provided to disable the normalization step done by
+    `packaging.version.Version`. If this is used while `setuptools_scm`
+    is integrated in a setuptools packaging process, the non-normalized
+    version number will appear in all files (see `version_file` note).
+
+    !!! note "normalization still applies to artifact filenames"
+        Setuptools will still normalize it to create the final distribution,
+        so as to stay compliant with the python packaging standards.
+
+
+## environment variables
+
+`SETUPTOOLS_SCM_PRETEND_VERSION`
+:   used as the primary source for the version number
+    in which case it will be an unparsed string
+
+    !!! warning "its strongly  recommended to use use distribution name specific pretend versions"
+
+
+`SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME}`
+:   used as the primary source for the version number
+    in which case it will be an unparsed string
+
+    the dist name normalization follows adapted PEP 503 semantics, with one or
+    more of ".-_" being replaced by a single "_", and the name being upper-cased
+
+    it takes precedence over ``SETUPTOOLS_SCM_PRETEND_VERSION``
+
+`SETUPTOOLS_SCM_DEBUG`
+:    enable the debug logging
+
+`SOURCE_DATE_EPOCH`
+:   used as the timestamp from which the
+    ``node-and-date`` and ``node-and-timestamp`` local parts are
+    derived, otherwise the current time is used
+    (https://reproducible-builds.org/docs/source-date-epoch/)
+
+`SETUPTOOLS_SCM_IGNORE_VCS_ROOTS`
+:   a ``os.pathsep`` separated list
+    of directory names to ignore for root finding
+
+
+
+
+
+## api reference
+
+### constants
+
+::: setuptools_scm._config.DEFAULT_TAG_REGEX
+    options:
+      heading_level: 4
+
+::: setuptools_scm.git.DEFAULT_DESCRIBE
+    options:
+      heading_level: 4
+
+
+### the configuration class
+::: setuptools_scm.Configuration
+    options:
+      heading_level: 4
diff --git a/docs/customizing.md b/docs/customizing.md
new file mode 100644
index 0000000..9236531
--- /dev/null
+++ b/docs/customizing.md
@@ -0,0 +1,77 @@
+# Customizing
+
+## providing project local version schemes
+
+As PEP 621 provides no way to specify local code as a build backend plugin,
+setuptools_scm has to piggyback on setuptools for passing functions over.
+
+To facilitate that one needs to write a `setup.py` file and
+pass partial setuptools_scm configuration in via the use_scm_version keyword.
+
+It's strongly recommended to experiment with using stock version schemes or creating plugins as package.
+(This recommendation will change if there ever is something like build-time entrypoints).
+
+
+``` { .python title="setup.py" file="docs/examples/version_scheme_code/setup.py" }
+# we presume installed build dependencies
+from __future__ import annotations
+
+from setuptools import setup
+
+from setuptools_scm import ScmVersion
+
+
+def myversion_func(version: ScmVersion) -> str:
+    from setuptools_scm.version import guess_next_version
+
+    return version.format_next_version(guess_next_version, "{guessed}b{distance}")
+
+
+setup(use_scm_version={"version_scheme": myversion_func})
+```
+
+
+``` { .python title="pyproject.toml" file="docs/examples/version_scheme_code/pyproject.toml" }
+[build-system]
+requires = [
+  "setuptools>=64",
+  "setuptools_scm>=8",
+  "wheel",
+]
+
+[project]
+name = "scm-example"
+dynamic = [
+  "version",
+]
+
+[tool.setuptools_scm]
+```
+
+- [ ] add a build block that adds example output
+- [ ] correct config after [entangled mkdocs bug] is fixed
+
+[entangled mkdocs bug]: https://github.com/entangled/mkdocs-plugin/issues/1
+
+
+
+
+##  Importing in setup.py
+
+With the pep 517/518 build backend, setuptools_scm is importable from `setup.py`
+
+``` { .python title="setup.py" }
+import setuptools
+from setuptools_scm.version import get_local_dirty_tag
+
+def clean_scheme(version):
+    return get_local_dirty_tag(version) if version.dirty else '+clean'
+
+setup(use_scm_version={'local_scheme': clean_scheme})
+```
+
+
+
+## alternative version classes
+
+::: setuptools_scm.NonNormalizedVersion
diff --git a/docs/examples/version_scheme_code/pyproject.toml b/docs/examples/version_scheme_code/pyproject.toml
new file mode 100644
index 0000000..05da2f6
--- /dev/null
+++ b/docs/examples/version_scheme_code/pyproject.toml
@@ -0,0 +1,16 @@
+# ~/~ begin <<docs/customizing.md#docs/examples/version_scheme_code/pyproject.toml>>[init]
+[build-system]
+requires = [
+  "setuptools>=64",
+  "setuptools_scm>=8",
+  "wheel",
+]
+
+[project]
+name = "scm-example"
+dynamic = [
+  "version",
+]
+
+[tool.setuptools_scm]
+# ~/~ end
\ No newline at end of file
diff --git a/docs/examples/version_scheme_code/setup.py b/docs/examples/version_scheme_code/setup.py
new file mode 100644
index 0000000..69f903f
--- /dev/null
+++ b/docs/examples/version_scheme_code/setup.py
@@ -0,0 +1,17 @@
+# ~/~ begin <<docs/customizing.md#docs/examples/version_scheme_code/setup.py>>[init]
+# we presume installed build dependencies
+from __future__ import annotations
+
+from setuptools import setup
+
+from setuptools_scm import ScmVersion
+
+
+def myversion_func(version: ScmVersion) -> str:
+    from setuptools_scm.version import guess_next_version
+
+    return version.format_next_version(guess_next_version, "{guessed}b{distance}")
+
+
+setup(use_scm_version={"version_scheme": myversion_func})
+# ~/~ end
diff --git a/docs/extending.md b/docs/extending.md
new file mode 100644
index 0000000..173685b
--- /dev/null
+++ b/docs/extending.md
@@ -0,0 +1,105 @@
+# Extending setuptools_scm
+
+`setuptools_scm` uses [entry-point][entry-point] based hooks to extend its default capabilities.
+
+[entry-point]: https://packaging.python.org/en/latest/specifications/entry-points/
+
+## Adding a new SCM
+
+`setuptools_scm` provides two entrypoints for adding new SCMs:
+
+`setuptools_scm.parse_scm`
+:   A function used to parse the metadata of the current workdir
+    using the name of the control directory/file of your SCM as the
+    entrypoint's name. E.g. for the built-in entrypoint for Git the
+    entrypoint is named `.git` and references `setuptools_scm.git:parse`
+
+    The return value MUST be a [`setuptools_scm.version.ScmVersion`][] instance
+    created by the function [`setuptools_scm.version.meta`][].
+
+`setuptools_scm.files_command`
+:  Either a string containing a shell command that prints all SCM managed
+   files in its current working directory or a callable, that given a
+   pathname will return that list.
+
+   Also uses then name of your SCM control directory as name of the entrypoint.
+
+
+### api reference for scm version objects
+
+::: setuptools_scm.version.ScmVersion
+    options:
+      show_root_heading: yes
+      heading_level: 4
+
+::: setuptools_scm.version.meta
+    options:
+      show_root_heading: yes
+      heading_level: 4
+
+## Version number construction
+
+
+
+
+
+### `setuptools_scm.version_scheme`
+Configures how the version number is constructed given a
+[ScmVersion][setuptools_scm.version.ScmVersion] instance and should return a string
+representing the version.
+
+### Available implementations
+
+`guess-next-dev (default)`
+:   Automatically guesses the next development version (default).
+    Guesses the upcoming release by incrementing the pre-release segment if present,
+    otherwise by incrementing the micro segment. Then appends :code:`.devN`.
+    In case the tag ends with `.dev0` the version is not bumped
+    and custom `.devN` versions will trigger a error.
+`post-release (deprecated)`
+:   Generates post release versions (adds `.postN`)
+    after review of the version number pep this is considered a bad idea
+    as post releases are intended to be chosen not autogenerated.
+
+    !!! warning "the recommended replacement is `no-guess-dev`"
+
+`python-simplified-semver`
+:   Basic semantic versioning.
+
+    Guesses the upcoming release by incrementing the minor segment
+    and setting the micro segment to zero if the current branch contains the string `feature`,
+    otherwise by incrementing the micro version. Then appending `.devN`.
+
+    This scheme is not compatible with pre-releases.
+
+`release-branch-semver`
+:   Semantic versioning for projects with release branches.
+    The same as `guess-next-dev` (incrementing the pre-release or micro segment)
+    however when on a release branch: a branch whose name (ignoring namespace) parses as a version
+    that matches the most recent tag up to the minor segment. Otherwise if on a
+    non-release branch, increments the minor segment and sets the micro segment to
+    zero, then appends `.devN`
+`no-guess-dev`
+: Does no next version guessing, just adds `.post1.devN`
+
+
+### `setuptools_scm.local_scheme`
+Configures how the local part of a version is rendered given a
+[ScmVersion][setuptools_scm.version.ScmVersion] instance and should return a string
+representing the local version.
+Dates and times are in Coordinated Universal Time (UTC), because as part
+of the version, they should be location independent.
+
+#### Available implementations
+
+`node-and-date (default)`
+: adds the node on dev versions and the date on dirty workdir
+
+`node-and-timestamp`
+: like `node-and-date` but with a timestamp of the form `%Y%m%d%H%M%S` instead
+
+`dirty-tag`
+: adds `+dirty` if the current workdir has changes
+
+`no-local-version`
+: omits local version, useful e.g. because pypi does not support it
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..d1c3995
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,44 @@
+# About
+
+
+`setuptools_scm` extracts Python package versions from `git` or `hg` metadata
+instead of declaring them as the version argument or in a SCM managed file.
+
+Additionally `setuptools_scm` provides setuptools with a list of
+files that are managed by the SCM (i.e. it automatically adds all
+the SCM-managed files to the sdist). Unwanted files must be excluded
+via `MANIFEST.in`.
+
+## basic usage
+
+### with setuptools
+
+Note: `setuptools_scm>=8` intentionally doesn't depend on setuptools to ease non-setuptools usage.
+Please ensure a recent version of setuptools (>=64) is installed.
+
+
+```toml
+# pyproject.toml
+[build-system]
+requires = [
+    "setuptools>=64",
+    "setuptools_scm>=8",
+    "wheel",
+]
+[project]
+name = "example"
+# Important: Remove any existing version declaration
+# version = "0.0.1"
+dynamic = ["version"]
+# more missing
+
+[tool.setuptools_scm]
+
+```
+
+
+### with hatch
+
+[Hatch-vcs](https://github.com/ofek/hatch-vcs) integrates with setuptools_scm
+but provides its own configuration options,
+please see its [documentation](https://github.com/ofek/hatch-vcs#readme)
diff --git a/docs/overrides.md b/docs/overrides.md
new file mode 100644
index 0000000..5114a84
--- /dev/null
+++ b/docs/overrides.md
@@ -0,0 +1,16 @@
+# Overrides
+
+## pretend versions
+
+setuptools_scm provides a mechanism to override the version number build time.
+
+the environment variable `SETUPTOOLS_SCM_PRETEND_VERSION` is used
+as the override source for the version number unparsed string.
+
+to be specific about the package this applies for, one can use `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME}`
+where the dist name normalization follows adapted PEP 503 semantics.
+
+## config overrides
+
+setuptools_scm parses the environment variable `SETUPTOOLS_SCM_OVERRIDES_FOR_${NORMALIZED_DIST_NAME}`
+as a toml inline map to override the configuration data from `pyproject.toml`.
diff --git a/docs/usage.md b/docs/usage.md
new file mode 100644
index 0000000..8392796
--- /dev/null
+++ b/docs/usage.md
@@ -0,0 +1,275 @@
+# Usage
+
+## at build time
+
+The preferred way to configure `setuptools_scm` is to author
+settings in the `tool.setuptools_scm` section of `pyproject.toml`.
+
+It's necessary to use a setuptools version released after 2022.
+
+```toml
+# pyproject.toml
+[build-system]
+requires = ["setuptools>=62", "setuptools_scm[toml]>=8.0"]
+
+[project]
+# version = "0.0.1"  # Remove any existing version parameter.
+dynamic = ["version"]
+
+[tool.setuptools_scm]
+# can be empty if no extra settings are needed, presence enables setuptools_scm
+```
+
+That will be sufficient to require `setuptools_scm` for projects
+that support PEP 518 ([pip](https://pypi.org/project/pip) and
+[pep517](https://pypi.org/project/pep517/)).
+Tools that still invoke `setup.py` must ensure build requirements are installed
+
+### version files
+
+```toml
+# pyproject.toml
+...
+[tool.setuptools_scm]
+version_file = "pkg/_version.py"
+```
+Where ``pkg`` is the name of your package.
+
+
+.. code-block:: shell
+
+    $ python -m setuptools_scm
+
+    # To explore other options, try:
+    $ python -m setuptools_scm --help
+
+
+## as cli tool
+
+If you need to confirm which version string is being generated
+or debug the configuration, you can install
+[setuptools-scm](https://github.com/pypa/setuptools_scm)
+directly in your working environment and run:
+
+```commandline
+$ python -m setuptools_scm # example from running local after changes
+7.1.1.dev149+g5197d0f.d20230727
+```
+
+ and to list all tracked by the scm:
+
+```commandline
+$ python -m setuptools_scm ls # output trimmed for brevity
+./LICENSE
+...
+./src/setuptools_scm/__init__.py
+./src/...
+...
+```
+
+!!! note "committed files only"
+
+    currently only committed files are listed, this might change in the future
+
+!!! warning "sdists/archives don't provide file lists"
+
+    currently there is no builtin mechanism
+    to safely transfer the file lists to sdists or obtaining them from archives
+    coordination for setuptools and hatch is ongoing
+
+## at runtime (strongly discouraged)
+
+the most simple **looking** way to use `setuptools_scm` at runtime is:
+
+```python
+from setuptools_scm import get_version
+version = get_version()
+```
+
+
+In order to use `setuptools_scm` from code that is one directory deeper
+than the project's root, you can use:
+
+```python
+from setuptools_scm import get_version
+version = get_version(root='..', relative_to=__file__)
+```
+
+
+## Python package metadata
+
+
+
+
+### version at runtime
+
+If you have opted not to hardcode the version number inside the package,
+you can retrieve it at runtime from PEP-0566_ metadata using
+``importlib.metadata`` from the standard library (added in Python 3.8)
+or the `importlib_metadata`_ backport:
+
+```python
+# contents of package_name/__init__.py
+from importlib.metadata import version, PackageNotFoundError
+
+try:
+    __version__ = version("package-name")
+except PackageNotFoundError:
+    # package is not installed
+    pass
+```
+
+.. _PEP-0566: https://www.python.org/dev/peps/pep-0566/
+.. _importlib_metadata: https://pypi.org/project/importlib-metadata/
+
+
+### Usage from Sphinx
+
+
+``` {.python file=docs/.entangled/sphinx_conf.py}
+from importlib.metadata import version as get_version
+release: str = get_version('setuptools_scm')
+# for example take major/minor
+version: str = ".".join(release.split('.')[:2])
+```
+
+The underlying reason is that services like *Read the Docs* sometimes change
+the working directory for good reasons and using the installed metadata
+prevents using needless volatile data there.
+
+
+## with Docker/Podman
+
+
+By default, Docker will not copy the `.git`  folder into your container.
+Therefore, builds with version inference might fail.
+Consequently, you can use the following snippet to infer the version from
+the host OS without copying the entire `.git` folder to your `Dockerfile`.
+
+```dockerfile
+RUN --mount=source=.git,target=.git,type=bind \
+    pip install --no-cache-dir -e .
+```
+However, this build step introduces a dependency to the state of your local
+`.git` folder the build cache and triggers the long-running pip install process on every build.
+To optimize build caching, one can use an environment variable to pretend a pseudo
+version that is used to cache the results of the pip install process:
+
+
+```dockerfile
+FROM python
+COPY pyproject.toml
+ARG PSEUDO_VERSION=1 # strongly recommended to update based on git describe
+RUN SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MY_PACKAGE=${PSEUDO_VERSION} pip install -e .[test]
+RUN --mount=source=.git,target=.git,type=bind pip install -e .
+```
+
+Note that running this Dockerfile requires docker with BuildKit enabled
+[docs](https://github.com/moby/buildkit/blob/v0.8.3/frontend/dockerfile/docs/syntax.md).
+
+To avoid BuildKit and mounting of the .git folder altogether, one can also pass the desired
+version as a build argument.
+Note that `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME}`
+is preferred over `SETUPTOOLS_SCM_PRETEND_VERSION`.
+
+
+
+## Default versioning scheme
+
+In the standard configuration `setuptools_scm` takes a look at three things:
+
+1. latest tag (with a version number)
+2. the distance to this tag (e.g. number of revisions since latest tag)
+3. workdir state (e.g. uncommitted changes since latest tag)
+
+and uses roughly the following logic to render the version:
+
+
+| distance | state     | format                                                               |
+|----------|-----------|----------------------------------------------------------------------|
+| no       | unchanged | `{tag}`                                                              |
+| yes      | unchanged | `{next_version}.dev{distance}+{scm letter}{revision hash}`           |
+| no       | changed   | `{tag}+dYYYYMMDD`                                                    |
+| yes      | changed   | `{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYYMMDD` |
+
+where `{next_version}` is the next version number after the latest tag
+
+The next version is calculated by adding `1` to the last numeric component of
+the tag.
+
+For Git projects, the version relies on  [git describe](https://git-scm.com/docs/git-describe),
+so you will see an additional `g` prepended to the `{revision hash}`.
+
+
+!!! note
+
+    According to [PEP 440](https://peps.python.org/pep-0440/#local-version-identifiers>),
+    if a version includes a local component, the package cannot be published to public
+    package indexes like PyPI or TestPyPI. The disallowed version segments may
+    be seen in auto-publishing workflows or when a configuration mistake is made.
+
+    However, some package indexes such as devpi or other alternatives allow local
+    versions. Local version identifiers must comply with [PEP 440].
+
+## Semantic Versioning (SemVer)
+
+Due to the default behavior it's necessary to always include a
+patch version (the `3` in `1.2.3`), or else the automatic guessing
+will increment the wrong part of the SemVer (e.g. tag `2.0` results in
+`2.1.devX` instead of `2.0.1.devX`). So please make sure to tag
+accordingly.
+
+
+## Builtin mechanisms for obtaining version numbers
+
+1. the SCM itself (Git/Mercurial)
+2. `.hg_archival` files (Mercurial archives)
+3. `.git_archival.txt` files (Git archives, see subsection below)
+4. `PKG-INFO`
+
+
+### Git archives
+
+Git archives are supported, but a few changes to your repository are required.
+
+Ensure the content of the following files:
+
+```{ .text file=".git_archival.txt"}
+
+node: $Format:%H$
+node-date: $Format:%cI$
+describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
+ref-names: $Format:%D$
+```
+
+``` {.text file=".gitattributes"}
+.git_archival.txt  export-subst
+```
+
+Finally, don't forget to commit the two files:
+```commandline
+$ git add .git_archival.txt .gitattributes && git commit -m "add export config"
+```
+
+
+Note that if you are creating a `_version.py` file, note that it should not
+be kept in version control. It's strongly recommended to be put into gitignore.
+
+
+
+### File finders hook makes most of MANIFEST.in unnecessary
+
+`setuptools_scm` implements a [file_finders] entry point
+which returns all files tracked by your SCM.
+This eliminates the need for a manually constructed `MANIFEST.in` in most cases where this
+would be required when not using `setuptools_scm`, namely:
+
+* To ensure all relevant files are packaged when running the `sdist` command.
+  * When using [include_package_data] to include package data as part of the `build` or `bdist_wheel`.
+
+`MANIFEST.in` may still be used: anything defined there overrides the hook.
+This is mostly useful to exclude files tracked in your SCM from packages,
+although in principle it can be used to explicitly include non-tracked files too.
+
+[file_finders]: https://setuptools.pypa.io/en/latest/userguide/extension.html#adding-support-for-revision-control-systems
+[include_package_data]: https://setuptools.readthedocs.io/en/latest/setuptools.html#including-data-files
diff --git a/hatch.toml b/hatch.toml
new file mode 100644
index 0000000..62e2af8
--- /dev/null
+++ b/hatch.toml
@@ -0,0 +1,9 @@
+[envs.docs]
+python = "3.11"
+extras = ["docs"]
+
+[envs.docs.scripts]
+build = "mkdocs build --clean --strict"
+serve = "mkdocs serve --dev-addr localhost:8000"
+init = "mkdocs {args}"
+sync = ["entangled sync"]
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..79ad48a
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,37 @@
+site_name: setuptools scm
+
+nav:
+  - index.md
+  - usage.md
+  - customizing.md
+  - config.md
+  - extending.md
+  - overrides.md
+theme:
+  name: material
+
+watch:
+- src/setuptools_scm
+- docs
+markdown_extensions:
+  - def_list
+  - admonition
+  - pymdownx.tasklist:
+      custom_checkbox: true
+
+  - pymdownx.superfences
+
+plugins:
+- entangled
+- search
+- mkdocstrings:
+    default_handler: python
+    handlers:
+      python:
+        paths: [ src ]
+
+        options:
+          separate_signature: true
+          show_signature_annotations: true
+          allow_inspection: true
+          show_root_heading: true
diff --git a/pyproject.toml b/pyproject.toml
index 74bf19f..dc5475c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,7 +18,7 @@ backend-path = [
 [project]
 name = "setuptools-scm"
 description = "the blessed package to manage your versions by scm tags"
-readme = "README.rst"
+readme = "README.md"
 license.file = "LICENSE"
 authors = [
     {name="Ronny Pfannschmidt", email="opensource@ronnypfannschmidt.de"}
@@ -50,6 +50,14 @@ dependencies = [
   'typing-extensions; python_version < "3.11"',
 ]
 [project.optional-dependencies]
+docs = [
+  "entangled_cli[rich]",
+  "mkdocs",
+  "mkdocs-entangled-plugin",
+  "mkdocs-material",
+  "mkdocstrings[python]",
+  "pygments",
+]
 rich = [
   "rich",
 ]
@@ -102,3 +110,5 @@ namespaces = false
 
 [tool.setuptools.dynamic]
 version = { attr = "_own_version_helper.version"}
+
+[tool.setuptools_scm]
diff --git a/src/setuptools_scm/__init__.py b/src/setuptools_scm/__init__.py
index f7c3e50..954751e 100644
--- a/src/setuptools_scm/__init__.py
+++ b/src/setuptools_scm/__init__.py
@@ -70,5 +70,6 @@ __all__ = [
     "dump_version",
     "Configuration",
     "Version",
+    "ScmVersion",
     "NonNormalizedVersion",
 ]
diff --git a/src/setuptools_scm/_config.py b/src/setuptools_scm/_config.py
index c966975..6a83491 100644
--- a/src/setuptools_scm/_config.py
+++ b/src/setuptools_scm/_config.py
@@ -26,6 +26,9 @@ log = _log.log.getChild("config")
 DEFAULT_TAG_REGEX = re.compile(
     r"^(?:[\w-]+-)?(?P<version>[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$"
 )
+"""default tag regex that tries to match PEP440 style versions
+with prefix consisting of dashed words"""
+
 DEFAULT_VERSION_SCHEME = "guess-next-dev"
 DEFAULT_LOCAL_SCHEME = "node-and-date"
 
@@ -68,8 +71,8 @@ def _check_absolute_root(root: _t.PathT, relative_to: _t.PathT | None) -> str:
         if os.path.isdir(relative_to):
             warnings.warn(
                 "relative_to is expected to be a file,"
-                " its the directory %r\n"
-                "assuming the parent directory was passed" % (relative_to,)
+                f" its the directory {relative_to}\n"
+                "assuming the parent directory was passed"
             )
             log.debug("dir %s", relative_to)
             root = os.path.join(relative_to, root)
@@ -132,6 +135,10 @@ class Configuration:
     def from_data(
         cls, relative_to: str | os.PathLike[str], data: dict[str, Any]
     ) -> Configuration:
+        """
+        given configuration data
+        create a config instance after validating tag regex/version class
+        """
         tag_regex = _check_tag_regex(data.pop("tag_regex", None))
         version_cls = _validate_version_cls(
             data.pop("version_cls", None), data.pop("normalize", True)
diff --git a/src/setuptools_scm/_modify_version.py b/src/setuptools_scm/_modify_version.py
index a364adc..63c0dfd 100644
--- a/src/setuptools_scm/_modify_version.py
+++ b/src/setuptools_scm/_modify_version.py
@@ -5,7 +5,7 @@ import re
 from . import _types as _t
 
 
-def _strip_local(version_string: str) -> str:
+def strip_local(version_string: str) -> str:
     public, sep, local = version_string.partition("+")
     return public
 
@@ -37,12 +37,12 @@ def _bump_regex(version: str) -> str:
     match = re.match(r"(.*?)(\d+)$", version)
     if match is None:
         raise ValueError(
-            "{version} does not end with a number to bump, "
-            "please correct or use a custom version scheme".format(version=version)
+            f"{version} does not end with a number to bump, "
+            "please correct or use a custom version scheme"
         )
     else:
         prefix, tail = match.groups()
-        return "%s%d" % (prefix, int(tail) + 1)
+        return f"{prefix}{int(tail) + 1}"
 
 
 def _format_local_with_time(version: _t.SCMVERSION, time_format: str) -> str:
@@ -57,5 +57,5 @@ def _format_local_with_time(version: _t.SCMVERSION, time_format: str) -> str:
 
 
 def _dont_guess_next_version(tag_version: _t.SCMVERSION) -> str:
-    version = _strip_local(str(tag_version.tag))
+    version = strip_local(str(tag_version.tag))
     return _bump_dev(version) or _add_post(version)
diff --git a/src/setuptools_scm/_overrides.py b/src/setuptools_scm/_overrides.py
index 05d4b88..e7efe28 100644
--- a/src/setuptools_scm/_overrides.py
+++ b/src/setuptools_scm/_overrides.py
@@ -3,6 +3,8 @@ from __future__ import annotations
 import os
 import re
 from typing import Any
+from typing import cast
+from typing import TypedDict
 
 from . import _config
 from . import _log
@@ -18,6 +20,7 @@ PRETEND_KEY_NAMED = PRETEND_KEY + "_FOR_{name}"
 def read_named_env(
     *, tool: str = "SETUPTOOLS_SCM", name: str, dist_name: str | None
 ) -> str | None:
+    """ """
     if dist_name is not None:
         # Normalize the dist name as per PEP 503.
         normalized_dist_name = re.sub(r"[-_.]+", "-", dist_name)
@@ -48,13 +51,23 @@ def _read_pretended_version_for(
         return None
 
 
+class _CheatTomlData(TypedDict):
+    cheat: dict[str, Any]
+
+
+def load_toml_or_inline_map(data: str | None) -> dict[str, Any]:
+    """
+    load toml data - with a special hack if only a inline map is given
+    """
+    if not data:
+        return {}
+    elif data[0] == "{":
+        data = "cheat=" + data
+        loaded: _CheatTomlData = cast(_CheatTomlData, lazy_toml_load(data))
+        return loaded["cheat"]
+    return lazy_toml_load(data)
+
+
 def read_toml_overrides(dist_name: str | None) -> dict[str, Any]:
     data = read_named_env(name="OVERRIDES", dist_name=dist_name)
-    if data:
-        if data[0] == "{":
-            data = "cheat=" + data
-            loaded = lazy_toml_load(data)
-            return loaded["cheat"]  # type: ignore[no-any-return]
-        return lazy_toml_load(data)
-    else:
-        return {}
+    return load_toml_or_inline_map(data)
diff --git a/src/setuptools_scm/git.py b/src/setuptools_scm/git.py
index d1da7f8..c74dc0a 100644
--- a/src/setuptools_scm/git.py
+++ b/src/setuptools_scm/git.py
@@ -121,7 +121,6 @@ class GitWorkdir(Workdir):
         return res.parse_success(
             parse=parse_timestamp,
             error_msg="logging the iso date for head failed",
-            default=None,
         )
 
     def is_shallow(self) -> bool:
@@ -219,7 +218,7 @@ def version_from_describe(
     if describe_command is not None:
         if isinstance(describe_command, str):
             describe_command = shlex.split(describe_command)
-            # todo: figure how ot ensure git with gitdir gets correctly invoked
+            # todo: figure how to ensure git with gitdir gets correctly invoked
         if describe_command[0] == "git":
             describe_res = run_git(describe_command[1:], wd.path)
         else:
diff --git a/src/setuptools_scm/version.py b/src/setuptools_scm/version.py
index b72da25..05abd50 100644
--- a/src/setuptools_scm/version.py
+++ b/src/setuptools_scm/version.py
@@ -22,6 +22,8 @@ if TYPE_CHECKING:
 
     _P = ParamSpec("_P")
 
+from typing import TypedDict
+
 
 from ._version_cls import Version as PkgVersion, _VersionT
 from . import _version_cls as _v
@@ -35,30 +37,32 @@ SEMVER_PATCH = 3
 SEMVER_LEN = 3
 
 
+class _TagDict(TypedDict):
+    version: str
+    prefix: str
+    suffix: str
+
+
 def _parse_version_tag(
     tag: str | object, config: _config.Configuration
-) -> dict[str, str] | None:
+) -> _TagDict | None:
     match = config.tag_regex.match(str(tag))
 
     if match:
-        key: str | int
-        if len(match.groups()) == 1:
-            key = 1
-        else:
-            key = "version"
-
+        key: str | int = 1 if len(match.groups()) == 1 else "version"
         full = match.group(0)
         log.debug("%r %r %s", tag, config.tag_regex, match)
         log.debug(
             "key %s data %s, %s, %r", key, match.groupdict(), match.groups(), full
         )
-        result = {
-            "version": match.group(key),
-            "prefix": full[: match.start(key)],
-            "suffix": full[match.end(key) :],
-        }
+        result = _TagDict(
+            version=match.group(key),
+            prefix=full[: match.start(key)],
+            suffix=full[match.end(key) :],
+        )
 
         log.debug("tag %r parsed to %r", tag, result)
+        assert result["version"]
         return result
     else:
         log.debug("tag %r did not parse", tag)
@@ -86,20 +90,16 @@ def tag_to_version(
     """
     log.debug("tag %s", tag)
 
-    tagdict = _parse_version_tag(tag, config)
-    if not isinstance(tagdict, dict) or not tagdict.get("version", None):
+    tag_dict = _parse_version_tag(tag, config)
+    if tag_dict is None or not tag_dict.get("version", None):
         warnings.warn(f"tag {tag!r} no version found")
         return None
 
-    version_str = tagdict["version"]
+    version_str = tag_dict["version"]
     log.debug("version pre parse %s", version_str)
 
-    if tagdict.get("suffix", ""):
-        warnings.warn(
-            "tag {!r} will be stripped of its suffix '{}'".format(
-                tag, tagdict["suffix"]
-            )
-        )
+    if suffix := tag_dict.get("suffix", ""):
+        warnings.warn(f"tag {tag!r} will be stripped of its suffix {suffix!r}")
 
     version: _VersionT = config.version_cls(version_str)
     log.debug("version=%r", version)
@@ -117,20 +117,33 @@ def _source_epoch_or_utc_now() -> datetime:
 
 @dataclasses.dataclass
 class ScmVersion:
+    """represents a parsed version from scm"""
+
     tag: _v.Version | _v.NonNormalizedVersion | str
+    """the related tag or preformatted version string"""
     config: _config.Configuration
+    """the configuration used to parse the version"""
     distance: int = 0
+    """the number of commits since the tag"""
     node: str | None = None
+    """the shortened node id"""
     dirty: bool = False
+    """whether the working copy had uncommitted changes"""
     preformatted: bool = False
+    """whether the version string was preformatted"""
     branch: str | None = None
+    """the branch name if any"""
     node_date: date | None = None
-    time: datetime = dataclasses.field(
-        init=False, default_factory=_source_epoch_or_utc_now
-    )
+    """the date of the commit if available"""
+    time: datetime = dataclasses.field(default_factory=_source_epoch_or_utc_now)
+    """the current time or source epoch time
+    only set for unit-testing version schemes
+    for real usage it must be `now(utc)` or `SOURCE_EPOCH`
+    """
 
     @property
     def exact(self) -> bool:
+        """returns true checked out exactly on a tag and no local changes apply"""
         return self.distance == 0 and not self.dirty
 
     def __repr__(self) -> str:
@@ -140,6 +153,7 @@ class ScmVersion:
         )
 
     def format_with(self, fmt: str, **kw: object) -> str:
+        """format a given format string with attributes of this object"""
         return fmt.format(
             time=self.time,
             tag=self.tag,
@@ -152,6 +166,10 @@ class ScmVersion:
         )
 
     def format_choice(self, clean_format: str, dirty_format: str, **kw: object) -> str:
+        """given `clean_format` and `dirty_format`
+
+        choose one based on `self.dirty` and format it using `self.format_with`"""
+
         return self.format_with(dirty_format if self.dirty else clean_format, **kw)
 
     def format_next_version(
@@ -205,7 +223,7 @@ def meta(
 
 
 def guess_next_version(tag_version: ScmVersion) -> str:
-    version = _modify_version._strip_local(str(tag_version.tag))
+    version = _modify_version.strip_local(str(tag_version.tag))
     return _modify_version._bump_dev(version) or _modify_version._bump_regex(version)
 
 
@@ -273,8 +291,8 @@ def release_branch_semver_version(version: ScmVersion) -> str:
 
 def release_branch_semver(version: ScmVersion) -> str:
     warnings.warn(
-        "release_branch_semver is deprecated and will be removed in future. "
-        + "Use release_branch_semver_version instead",
+        "release_branch_semver is deprecated and will be removed in the future. "
+        "Use release_branch_semver_version instead",
         category=DeprecationWarning,
         stacklevel=2,
     )
@@ -321,7 +339,7 @@ def guess_next_date_ver(
         # deduct date format if not provided
         if date_fmt is None:
             date_fmt = "%Y.%m.%d" if len(match.group("year")) == 4 else "%y.%m.%d"
-    today = datetime.now(timezone.utc).date()
+    today = version.time.date()
     head_date = node_date or today
     # compute patch
     if match is None:
@@ -372,15 +390,15 @@ def get_local_node_and_date(version: ScmVersion) -> str:
     return _modify_version._format_local_with_time(version, time_format="%Y%m%d")
 
 
-def get_local_node_and_timestamp(version: ScmVersion, fmt: str = "%Y%m%d%H%M%S") -> str:
-    return _modify_version._format_local_with_time(version, time_format=fmt)
+def get_local_node_and_timestamp(version: ScmVersion) -> str:
+    return _modify_version._format_local_with_time(version, time_format="%Y%m%d%H%M%S")
 
 
 def get_local_dirty_tag(version: ScmVersion) -> str:
     return version.format_choice("", "+dirty")
 
 
-def get_no_local_node(_: Any) -> str:
+def get_no_local_node(version: ScmVersion) -> str:
     return ""
 
 


Test patch:
diff --git a/testing/test_git.py b/testing/test_git.py
index 5a7e9bf..cec9afa 100644
--- a/testing/test_git.py
+++ b/testing/test_git.py
@@ -421,7 +421,6 @@ def test_not_matching_tags(wd: WorkDir) -> None:
 
 
 @pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/411")
-@pytest.mark.xfail(reason="https://github.com/pypa/setuptools_scm/issues/449")
 def test_non_dotted_version(wd: WorkDir) -> None:
     wd.commit_testfile()
     wd("git tag apache-arrow-1")
diff --git a/testing/test_integration.py b/testing/test_integration.py
index 014039c..40372b5 100644
--- a/testing/test_integration.py
+++ b/testing/test_integration.py
@@ -151,7 +151,7 @@ def test_distribution_provides_extras() -> None:
 
     dist = distribution("setuptools_scm")
     pe: list[str] = dist.metadata.get_all("Provides-Extra", [])
-    assert sorted(pe) == ["rich", "test", "toml"]
+    assert sorted(pe) == ["docs", "rich", "test", "toml"]
 
 
 @pytest.mark.issue(760)
diff --git a/testing/test_version.py b/testing/test_version.py
index 778b0c2..b3bee8d 100644
--- a/testing/test_version.py
+++ b/testing/test_version.py
@@ -7,6 +7,7 @@ from typing import Any
 import pytest
 
 from setuptools_scm import Configuration
+from setuptools_scm import NonNormalizedVersion
 from setuptools_scm.version import calver_by_date
 from setuptools_scm.version import format_version
 from setuptools_scm.version import guess_next_version
@@ -18,6 +19,7 @@ from setuptools_scm.version import simplified_semver_version
 
 
 c = Configuration()
+c_non_normalize = Configuration(version_cls=NonNormalizedVersion)
 
 
 @pytest.mark.parametrize(
@@ -224,26 +226,34 @@ def test_custom_version_schemes() -> None:
     assert custom_computed == no_guess_dev_version(version)
 
 
+def date_offset(base_date: date | None = None, days_offset: int = 0) -> date:
+    if base_date is None:
+        from setuptools_scm.version import _source_epoch_or_utc_now
+
+        base_date = _source_epoch_or_utc_now().date()
+    return base_date - timedelta(days=days_offset)
+
+
 def date_to_str(
-    date_: date | None = None,
+    base_date: date | None = None,
     days_offset: int = 0,
-    fmt: str = "{dt:%y}.{dt.month}.{dt.day}",
+    fmt: str = "%y.%m.%d",
 ) -> str:
-    date_ = date_ or date.today()
-    date_ = date_ - timedelta(days=days_offset)
-    return fmt.format(dt=date_)
+    return format(date_offset(base_date, days_offset), fmt)
 
 
 @pytest.mark.parametrize(
     "version, expected_next",
     [
         pytest.param(
-            meta(date_to_str(days_offset=3), config=c),
+            meta(date_to_str(days_offset=3), config=c_non_normalize),
             date_to_str(days_offset=3),
             id="exact",
         ),
         pytest.param(
-            meta(date_to_str() + ".1", config=c), date_to_str() + ".1", id="exact patch"
+            meta(date_to_str() + ".1", config=c_non_normalize),
+            date_to_str() + ".1",
+            id="exact patch",
         ),
         pytest.param(
             meta("20.01.02", config=c),
@@ -251,35 +261,45 @@ def date_to_str(
             id="leading 0s",
         ),
         pytest.param(
-            meta(date_to_str(days_offset=3), config=c, dirty=True),
+            meta(date_to_str(days_offset=3), config=c_non_normalize, dirty=True),
             date_to_str() + ".0.dev0",
             id="dirty other day",
         ),
         pytest.param(
-            meta(date_to_str(), config=c, distance=2, branch="default"),
+            meta(date_to_str(), config=c_non_normalize, distance=2, branch="default"),
             date_to_str() + ".1.dev2",
             id="normal branch",
         ),
         pytest.param(
-            meta(date_to_str(fmt="{dt:%Y}.{dt.month}.{dt.day}"), config=c),
-            date_to_str(fmt="{dt:%Y}.{dt.month}.{dt.day}"),
+            meta(date_to_str(fmt="%Y.%m.%d"), config=c_non_normalize),
+            date_to_str(fmt="%Y.%m.%d"),
             id="4 digits year",
         ),
         pytest.param(
-            meta(date_to_str(), config=c, distance=2, branch="release-2021.05.06"),
+            meta(
+                date_to_str(),
+                config=c_non_normalize,
+                distance=2,
+                branch="release-2021.05.06",
+            ),
             "2021.05.06",
             id="release branch",
         ),
         pytest.param(
-            meta(date_to_str() + ".2", config=c, distance=2, branch="release-21.5.1"),
+            meta(
+                date_to_str() + ".2",
+                config=c_non_normalize,
+                distance=2,
+                branch="release-21.5.1",
+            ),
             "21.5.1",
             id="release branch short",
         ),
         pytest.param(
             meta(
                 date_to_str(days_offset=3) + ".2",
-                config=c,
-                node_date=date.today() - timedelta(days=2),
+                config=c_non_normalize,
+                node_date=date_offset(days_offset=2),
             ),
             date_to_str(days_offset=3) + ".2",
             id="node date clean",
@@ -287,19 +307,19 @@ def date_to_str(
         pytest.param(
             meta(
                 date_to_str(days_offset=2) + ".2",
-                config=c,
+                config=c_non_normalize,
                 distance=2,
-                node_date=date.today() - timedelta(days=2),
+                node_date=date_offset(days_offset=2),
             ),
-            date_to_str(date.today() - timedelta(days=2)) + ".3.dev2",
+            date_to_str(days_offset=2) + ".3.dev2",
             id="node date distance",
         ),
         pytest.param(
             meta(
                 "1.2.0",
-                config=c,
+                config=c_non_normalize,
                 distance=2,
-                node_date=date.today() - timedelta(days=2),
+                node_date=date_offset(days_offset=2),
             ),
             date_to_str(days_offset=2) + ".0.dev2",
             marks=pytest.mark.filterwarnings(
@@ -333,7 +353,9 @@ def test_calver_by_date_semver(version: ScmVersion, expected_next: str) -> None:
 
 def test_calver_by_date_future_warning() -> None:
     with pytest.warns(UserWarning, match="your previous tag*"):
-        calver_by_date(meta(date_to_str(days_offset=-2), config=c, distance=2))
+        calver_by_date(
+            meta(date_to_str(days_offset=-2), config=c_non_normalize, distance=2)
+        )
 
 
 def test_custom_version_cls() -> None: