Imported Upstream version 4.1.2 upstream upstream/4.1.2
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 5 Apr 2021 07:03:29 +0000 (16:03 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 5 Apr 2021 07:03:29 +0000 (16:03 +0900)
48 files changed:
.github/FUNDING.yml [new file with mode: 0644]
.github/workflows/pre-commit.yml [new file with mode: 0644]
.github/workflows/python-tests.yml [new file with mode: 0644]
.gitignore [new file with mode: 0644]
CHANGELOG.rst [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
README.rst [new file with mode: 0644]
pyproject.toml [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0644]
src/setuptools_scm.egg-info/PKG-INFO [new file with mode: 0644]
src/setuptools_scm.egg-info/SOURCES.txt [new file with mode: 0644]
src/setuptools_scm.egg-info/dependency_links.txt [new file with mode: 0644]
src/setuptools_scm.egg-info/entry_points.txt [new file with mode: 0644]
src/setuptools_scm.egg-info/requires.txt [new file with mode: 0644]
src/setuptools_scm.egg-info/top_level.txt [new file with mode: 0644]
src/setuptools_scm.egg-info/zip-safe [new file with mode: 0644]
src/setuptools_scm/__init__.py [new file with mode: 0644]
src/setuptools_scm/__main__.py [new file with mode: 0644]
src/setuptools_scm/config.py [new file with mode: 0644]
src/setuptools_scm/discover.py [new file with mode: 0644]
src/setuptools_scm/file_finder.py [new file with mode: 0644]
src/setuptools_scm/file_finder_git.py [new file with mode: 0644]
src/setuptools_scm/file_finder_hg.py [new file with mode: 0644]
src/setuptools_scm/git.py [new file with mode: 0644]
src/setuptools_scm/hacks.py [new file with mode: 0644]
src/setuptools_scm/hg.py [new file with mode: 0644]
src/setuptools_scm/integration.py [new file with mode: 0644]
src/setuptools_scm/utils.py [new file with mode: 0644]
src/setuptools_scm/version.py [new file with mode: 0644]
src/setuptools_scm/win_py31_compat.py [new file with mode: 0644]
testing/check_self_install.py [new file with mode: 0644]
testing/conftest.py [new file with mode: 0644]
testing/play_out_381.bash [new file with mode: 0755]
testing/test_basic_api.py [new file with mode: 0644]
testing/test_config.py [new file with mode: 0644]
testing/test_file_finder.py [new file with mode: 0644]
testing/test_functions.py [new file with mode: 0644]
testing/test_git.py [new file with mode: 0644]
testing/test_integration.py [new file with mode: 0644]
testing/test_main.py [new file with mode: 0644]
testing/test_mercurial.py [new file with mode: 0644]
testing/test_regressions.py [new file with mode: 0644]
testing/test_setuptools_support.py [new file with mode: 0644]
testing/test_version.py [new file with mode: 0644]
tox.ini [new file with mode: 0644]

diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644 (file)
index 0000000..ac779ab
--- /dev/null
@@ -0,0 +1 @@
+tidelift: pypi/setuptools-scm
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644 (file)
index 0000000..c154a12
--- /dev/null
@@ -0,0 +1,20 @@
+name: pre-commit
+
+on:
+  pull_request:
+  push:
+    branches: [master]
+
+jobs:
+  pre-commit:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - uses: actions/setup-python@v1
+    - name: set PY
+      run: echo "::set-env name=PY::$(python --version --version | sha256sum | cut -d' ' -f1)"
+    - uses: actions/cache@v1
+      with:
+        path: ~/.cache/pre-commit
+        key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
+    - uses: pre-commit/action@v1.0.0
diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml
new file mode 100644 (file)
index 0000000..ca8df3f
--- /dev/null
@@ -0,0 +1,155 @@
+name: python tests+artifacts+release
+
+on:
+  pull_request:
+  push:
+    branches:
+    - master
+    tags:
+    - "v*"
+  release:
+
+jobs:
+  test:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        python_version: [ '2.7', '3.5', '3.6', '3.7', '3.8', 'pypy2', 'pypy3' ]
+        os: [windows-latest, ubuntu-latest] #, macos-latest]
+        exclude:
+        - os: windows-latest
+          python_version: "pypy2"
+        include:
+        - os: ubuntu-latest
+          python_version: '3.9-dev'
+
+    name: ${{ matrix.os }} - Python ${{ matrix.python_version }}
+    steps:
+      - uses: actions/checkout@v1
+      - name: Setup python
+        uses: actions/setup-python@v2
+        if: matrix.python_version != '3.9-dev'
+        with:
+          python-version: ${{ matrix.python_version }}
+          architecture: x64
+      - name: Set up Python ${{ matrix.python_version }} (deadsnakes)
+        uses: deadsnakes/action@v1.0.0
+        if: matrix.python_version == '3.9-dev'
+        with:
+          python-version: ${{ matrix.python_version }}
+          architecture: x64
+      - run: pip install -U setuptools
+      - run: pip install -e .[toml] pytest
+      - run: pytest
+
+  check_selfinstall:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        python_version: [ '2.7', '3.5', '3.6', '3.7', '3.8', 'pypy2', 'pypy3' ]
+    name: check self install - Python ${{ matrix.python_version }}
+    steps:
+      - uses: actions/checkout@v1
+      - name: Setup python
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python_version }}
+          architecture: x64
+      # self install testing needs some clarity
+      # so its being executed without any other tools running
+      - run: pip install -U setuptools
+      - run: python setup.py egg_info
+      - run: python setup.py sdist
+      - run: easy_install dist/*
+      - run: python testing/check_self_install.py
+
+
+  eggs:
+    runs-on: ubuntu-latest
+
+    needs: [test]
+    name: Python ${{ matrix.python_version }} eggs
+    strategy:
+      matrix:
+        python_version: ['2.7', '3.5', '3.6', '3.7', '3.8', '3.9-dev']
+    steps:
+    - uses: actions/checkout@v1
+    - name: Setup python
+      uses: actions/setup-python@v2
+      if: matrix.python_version != '3.9-dev'
+      with:
+        python-version: ${{ matrix.python_version }}
+        architecture: x64
+    - name: Set up Python ${{ matrix.python_version }} (deadsnakes)
+      uses: deadsnakes/action@v1.0.0
+      if: matrix.python_version == '3.9-dev'
+      with:
+        python-version: ${{ matrix.python_version }}
+        architecture: x64
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install --upgrade wheel setuptools
+    - run: python setup.py egg_info
+    - name: Build package
+      run: python setup.py bdist_egg
+    - uses: actions/upload-artifact@v2
+      with:
+        name: dist
+        path: dist
+
+  dist:
+    runs-on: ubuntu-latest
+
+    needs: [test]
+    name: Python bdist/wheel
+    steps:
+    - uses: actions/checkout@v1
+    - uses: actions/setup-python@v1
+      with:
+        python-version: "3.8"
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install --upgrade wheel setuptools
+    - run: python setup.py egg_info
+    - name: Build package
+      run: python setup.py bdist_wheel sdist
+    - uses: actions/upload-artifact@v2
+      with:
+        name: dist
+        path: dist
+
+
+  dist_check:
+    runs-on: ubuntu-latest
+    needs: [eggs, dist]
+    steps:
+    - uses: actions/setup-python@v2
+      with:
+        python-version: "3.8"
+    - name: Install dependencies
+      run: pip install twine
+    - uses: actions/download-artifact@v2
+      with:
+        name: dist
+        path: dist
+    - run: twine check dist/*
+
+  dist_upload:
+
+    runs-on: ubuntu-latest
+    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+    needs: [dist_check]
+    steps:
+    - uses: actions/download-artifact@v2
+      with:
+        name: dist
+        path: dist
+    - name: Publish package to PyPI
+      uses: pypa/gh-action-pypi-publish@master
+      with:
+        user: __token__
+        password: ${{ secrets.pypi_token }}
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c51b132
--- /dev/null
@@ -0,0 +1,48 @@
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion
+
+*.iml
+
+## Directory-based project format:
+.idea/
+
+### Other editors
+.*.swp
+
+
+### Python template
+# Byte-compiled / optimized
+__pycache__/
+*.py[cod]
+*$py.class
+
+
+# Distribution / packaging
+.env/
+env/
+.venv/
+venv/
+build/
+dist/
+.eggs/
+lib/
+lib64/
+*.egg-info/
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+.pytest_cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Sphinx documentation
+docs/_build/
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644 (file)
index 0000000..251ee8d
--- /dev/null
@@ -0,0 +1,504 @@
+v4.1.2
+=======
+
+* disallow git tags without dots by default again - #449
+
+v4.1.1
+=======
+
+* drop jaraco.windows from pyproject.toml, allows for wheel builds on python2
+
+
+v4.1.0
+=======
+
+* include python 3.9 via the deadsnakes action
+* return release_branch_semver scheme (it got dropped in a bad rebase)
+* undo the devendoring of the samefile backport for python2.7 on windows
+* re-enable the building of universal wheels
+* fix handling of missing git/hg on python2.7 (python 3 exceptions where used)
+* correct the tox flake8 invocation
+* trigger builds on tags again
+
+v4.0.0
+======
+
+* Add ``parentdir_project_version`` to support installs from GitHub release
+  tarballs.
+* use  Coordinated Universal Time (UTC)
+* switch to github actions for ci
+* fix documentation for ``tag_regex`` and add support for single digit versions
+* document handling of enterprise distros with unsupported setuptools versions #312
+* switch to declarative metadata
+* drop the internal copy of samefile and use a dependency on jaraco.windows on legacy systems
+* select git tags based on the presence of numbers instead of dots
+* enable getting a version form a parent folder prefix
+* add release-branch-semver version scheme
+* make global configuration available to version metadata
+* drop official support for python 3.4
+
+v3.5.0
+======
+
+* add ``no-local-version`` local scheme and improve documentation for schemes
+
+v3.4.4
+======
+
+* fix #403: also sort out resource warnings when dealing with git file finding
+
+v3.4.3
+======
+
+* fix #399: ensure the git file finder terminates subprocess after reading archive
+
+v3.4.2
+======
+
+* fix #395: correctly transfer tag regex in the Configuration constructor
+* rollback --first-parent for git describe as it turns out to be a regression for some users
+
+v3.4.1
+======
+
+* pull in #377 to fix #374: correctly set up the default version scheme for pyproject usage.
+  this bugfix got missed when ruushing the  release.
+
+v3.4.0
+======
+
+* fix #181 - add support for projects built under setuptools declarative config
+  by way of the setuptools.finalize_distribution_options hook in Setuptools 42.
+
+* fix #305 - ensure the git file finder closes filedescriptors even when errors happen
+
+* fix #381 - clean out env vars from the git hook system to ensure correct function from within
+
+* modernize docs wrt importlib.metadata
+
+*edited*
+
+* use --first-parent for git describe
+
+v3.3.3
+======
+
+* add eggs  for python3.7 and 3.8 to the deploy
+
+v3.3.2
+======
+
+
+* fix #335 - fix python3.8 support and add builds for up to python3.8
+
+v3.3.1
+======
+
+* fix #333 (regression from #198) - use a specific fallback root when calling fallbacks. Remove old
+  hack that resets the root when fallback entrypoints are present.
+
+v3.3.0
+======
+
+* fix #198 by adding the ``fallback_version`` option, which sets the version to be used when everything else fails.
+
+v3.2.0
+======
+
+* fix #303 and #283 by adding the option ``git_describe_command`` to allow the user to control the
+way that `git describe` is called.
+
+v3.1.0
+=======
+
+* fix #297 - correct the invocation in version_from_scm and deprecate it as its exposed by accident
+* fix #298 - handle git file listing on empty repositories
+* fix #268 - deprecate ScmVersion.extra
+
+
+v3.0.6
+======
+* fix #295 - correctly handle selfinstall from tarballs
+
+v3.0.5
+======
+
+* fix #292 - match leading ``V`` character as well
+
+  https://www.python.org/dev/peps/pep-0440/#preceding-v-character
+
+v3.0.4
+=======
+
+* rerelease of 3.0.3 after fixing the release process
+
+v3.0.3  (pulled from pypi due to a packaging issue)
+======
+
+* fix #286 - duo an oversight a helper functio nwas returning a generator instead of a list
+
+
+v3.0.2
+======
+
+* fix a regression from tag parsing - support for multi-dashed prefixes - #284
+
+
+v3.0.1
+=======
+
+* fix a regression in setuptools_scm.git.parse - reorder arguments so the positional invocation from before works as expected #281
+
+v3.0.0
+=======
+
+* introduce pre-commit and use black
+* print the origin module to help testing
+* switch to src layout (breaking change)
+* no longer alias tag and parsed_version in order to support understanding a version parse failure
+* require parse results to be ScmVersion or None (breaking change)
+* fix #266 by requiring the prefix word to be a word again
+  (breaking change as the bug allowed arbitrary prefixes while the original feature only allowed words")
+* introduce a internal config object to allow the configruation fo tag parsing and prefixes
+  (thanks to @punkadiddle for introducing it and passing it trough)
+
+v2.1.0
+======
+
+* enhance docs for sphinx usage
+* add symlink support to file finder for git #247
+  (thanks Stéphane Bidoul)
+* enhance tests handling win32
+  (thanks Stéphane Bidoul)
+
+v2.0.0
+========
+
+* fix #237 - correct imports in code examples
+* improve mercurial commit detection (thanks Aaron)
+* breaking change: remove support for setuptools before parsed versions
+* reintroduce manifest as the travis deploy cant use the file finder
+* reconfigure flake8 for future compatibility with black
+* introduce support for branch name in version metadata and support a opt-in simplified semver version scheme
+
+v1.17.0
+========
+
+* fix regression in git support - use a function to ensure it works in egg isntalled mode
+* actually fail if file finding fails in order to see broken setups instead of generating broken dists
+
+  (thanks Mehdi ABAAKOUK for both)
+
+
+v1.16.2
+========
+
+* fix regression in handling git export ignores
+  (thanks Mehdi ABAAKOUK)
+
+v1.16.1
+=======
+
+* fix regression in support for old setuptools versions
+  (thanks Marco Clemencic)
+
+
+v1.16.0
+=======
+
+* drop support for eol python versions
+* #214 - fix missuse in surogate-escape api
+* add the node-and-timestamp local version sheme
+* respect git export ignores
+* avoid shlex.split on windows
+* fix #218 - better handling of mercurial edge-cases with tag commits
+  being considered as the tagged commit
+* fix #223 - remove the dependency on the interal SetupttoolsVersion
+  as it was removed after long-standing deprecation
+
+v1.15.7
+======
+
+* Fix #174 with #207: Re-use samefile backport as developed in
+  jaraco.windows, and only use the backport where samefile is
+  not available.
+
+v1.15.6
+=======
+
+* fix #171 by unpinning the py version to allow a fixed one to get installed
+
+v1.15.5
+=======
+
+* fix #167 by correctly respecting preformatted version metadata
+  from PKG-INFO/EGG-INFO
+
+v1.15.4
+=======
+
+* fix issue #164: iterate all found entry points to avoid erros when pip remakes egg-info
+* enhance self-use to enable pip install from github again
+
+v1.15.3
+=======
+
+* bring back correctly getting our version in the own sdist, finalizes #114
+* fix issue #150: strip local components of tags
+
+v1.15.2
+=======
+
+* fix issue #128: return None when a scm specific parse fails in a worktree to ease parse reuse
+
+
+v1.15.1
+=======
+
+* fix issue #126: the local part of any tags is discarded
+  when guessing new versions
+* minor performance optimization by doing fewer git calls
+  in the usual cases
+
+
+v1.15.0
+=======
+
+* more sophisticated ignoring of mercurial tag commits
+  when considering distance in commits
+  (thanks Petre Mierlutiu)
+* fix issue #114: stop trying to be smart for the sdist
+  and ensure its always correctly usign itself
+* update trove classifiers
+* fix issue #84: document using the installed package metadata for sphinx
+* fix issue #81: fail more gracious when git/hg are missing
+* address issue #93: provide an experimental api to customize behaviour on shallow git repos
+  a custom parse function may pick pre parse actions to do when using git
+
+
+v1.14.1
+=======
+
+* fix #109: when detecting a dirty git workdir
+            don't consider untracked file
+            (this was a regression due to #86 in v1.13.1)
+* consider the distance 0 when the git node is unknown
+  (happens when you haven't commited anything)
+
+v1.14.0
+=======
+
+* publish bdist_egg for python 2.6, 2.7 and 3.3-3.5
+* fix issue #107 - dont use node if it is None
+
+v1.13.1
+=======
+
+* fix issue #86 - detect dirty git workdir without tags
+
+v1.13.0
+=======
+
+* fix regression caused by the fix of #101
+  * assert types for version dumping
+  * strictly pass all versions trough parsed version metadata
+
+v1.12.0
+=======
+
+* fix issue #97 - add support for mercurial plugins
+* fix issue #101 - write version cache even for pretend version
+  (thanks anarcat for reporting and fixing)
+
+v1.11.1
+========
+
+* fix issue #88 - better docs for sphinx usage (thanks Jason)
+* fix issue #89 - use normpath to deal with windows
+  (thanks Te-jé Rodgers for reporting and fixing)
+
+v1.11.0
+=======
+
+* always run tag_to_version so in order to handle prefixes on old setuptools
+  (thanks to Brian May)
+* drop support for python 3.2
+* extend the error message on missing scm metadata
+  (thanks Markus Unterwaditzer)
+* fix bug when using callable version_scheme
+  (thanks Esben Haabendal)
+
+v1.10.1
+=======
+
+* fix issue #73 - in hg pre commit merge, consider parent1 instead of failing
+
+v1.10.0
+=======
+
+* add support for overriding the version number via the
+  environment variable SETUPTOOLS_SCM_PRETEND_VERSION
+
+* fix isssue #63 by adding the --match parameter to the git describe call
+  and prepare the possibility of passing more options to scm backends
+
+* fix issue #70 and #71 by introducing the parse keyword
+  to specify custom scm parsing, its an expert feature,
+  use with caution
+
+  this change also introduces the setuptools_scm.parse_scm_fallback
+  entrypoint which can be used to register custom archive fallbacks
+
+
+v1.9.0
+======
+
+* Add :code:`relative_to` parameter to :code:`get_version` function;
+  fixes #44 per #45.
+
+v1.8.0
+======
+
+* fix issue with setuptools wrong version warnings being printed to standard
+  out. User is informed now by distutils-warnings.
+* restructure root finding, we now reliably ignore outer scm
+  and prefer PKG-INFO over scm, fixes #43 and #45
+
+v1.7.0
+======
+
+* correct the url to github
+  thanks David Szotten
+* enhance scm not found errors with a note on git tarballs
+  thanks Markus
+* add support for :code:`write_to_template`
+
+v1.6.0
+======
+
+* bail out early if the scm is missing
+
+  this brings issues with git tarballs and
+  older devpi-client releases to light,
+  before we would let the setup stay at version 0.0,
+  now there is a ValueError
+
+* propperly raise errors on write_to missuse (thanks Te-jé Rodgers)
+
+v1.5.5
+======
+
+* Fix bug on Python 2 on Windows when environment has unicode fields.
+
+v1.5.4
+======
+
+* Fix bug on Python 2 when version is loaded from existing metadata.
+
+v1.5.3
+======
+
+* #28: Fix decoding error when PKG-INFO contains non-ASCII.
+
+v1.5.2
+======
+
+* add zip_safe flag
+
+v1.5.1
+======
+
+* fix file access bug i missed in 1.5
+
+v1.5.0
+======
+
+* moved setuptools integration related code to own file
+* support storing version strings into a module/text file
+  using the :code:`write_to` coniguration parameter
+
+v1.4.0
+======
+
+* propper handling for sdist
+* fix file-finder failure from windows
+* resuffle docs
+
+v1.3.0
+======
+
+* support setuptools easy_install egg creation details
+  by hardwireing the version in the sdist
+
+v1.2.0
+======
+
+* enhance self-use
+
+v1.1.0
+======
+
+* enable self-use
+
+v1.0.0
+======
+
+* documentation enhancements
+
+v0.26
+=====
+
+* rename to setuptools_scm
+* split into package, add lots of entry points for extension
+* pluggable version schemes
+
+v0.25
+=====
+
+* fix pep440 support
+  this reshuffles the complete code for version guessing
+
+v0.24
+=====
+
+* dont drop dirty flag on node finding
+* fix distance for dirty flagged versions
+* use dashes for time again,
+  its normalisation with setuptools
+* remove the own version attribute,
+  it was too fragile to test for
+* include file finding
+* handle edge cases around dirty tagged versions
+
+v0.23
+=====
+
+* windows compatibility fix (thanks stefan)
+  drop samefile since its missing in
+  some python2 versions on windows
+* add tests to the source tarballs
+
+
+v0.22
+=====
+
+* windows compatibility fix (thanks stefan)
+  use samefile since it does path normalisation
+
+v0.21
+=====
+
+* fix the own version attribute (thanks stefan)
+
+v0.20
+=====
+
+* fix issue 11: always take git describe long format
+  to avoid the source of the ambiguity
+* fix issue 12: add a __version__ attribute via pkginfo
+
+v0.19
+=====
+
+* configurable next version guessing
+* fix distance guessing (thanks stefan)
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..89de354
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,17 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..4bbd88d
--- /dev/null
@@ -0,0 +1,10 @@
+exclude *.nix
+exclude .travis.yaml
+exclude .pre-commit-config.yaml
+include *.py
+include testing/*.py
+include tox.ini
+include *.rst
+include LICENSE
+include *.toml
+recursive-include testing *.bash
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..1f3b082
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,615 @@
+Metadata-Version: 2.1
+Name: setuptools_scm
+Version: 4.1.2
+Summary: the blessed package to manage your versions by scm tags
+Home-page: https://github.com/pypa/setuptools_scm/
+Author: Ronny Pfannschmidt
+Author-email: opensource@ronnypfannschmidt.de
+License: MIT
+Description: setuptools_scm
+        ===============
+        
+        ``setuptools_scm`` handles managing your Python package versions
+        in SCM 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``.
+        
+        .. image:: https://travis-ci.org/pypa/setuptools_scm.svg?branch=master
+            :target: https://travis-ci.org/pypa/setuptools_scm
+        
+        .. 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>=42", "wheel", "setuptools_scm[toml]>=3.4"]
+        
+        Note that the ``toml`` extra must be supplied.
+        
+        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, 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]
+            write_to = "pkg/version.py"
+        
+        
+        ``setup.py`` usage
+        ------------------
+        
+        The following settings are considered legacy behavior and
+        superseded by the ``pyproject.toml`` usage, but for maximal
+        compatibility, projects may also supply the configuration in
+        this older form.
+        
+        To use ``setuptools_scm`` just modify your project's ``setup.py`` file
+        like this:
+        
+        * Add ``setuptools_scm`` to the ``setup_requires`` parameter.
+        * Add the ``use_scm_version`` parameter and set it to ``True``.
+        
+        For example:
+        
+        .. code:: python
+        
+            from setuptools import setup
+            setup(
+                ...,
+                use_scm_version=True,
+                setup_requires=['setuptools_scm'],
+                ...,
+            )
+        
+        Arguments to ``get_version()`` (see below) may be passed as a dictionary to
+        ``use_scm_version``. For example:
+        
+        .. code:: python
+        
+            from setuptools import setup
+            setup(
+                ...,
+                use_scm_version = {
+                    "root": "..",
+                    "relative_to": __file__,
+                    "local_scheme": "node-and-timestamp"
+                },
+                setup_requires=['setuptools_scm'],
+                ...,
+            )
+        
+        You can confirm the version number locally via ``setup.py``:
+        
+        .. code-block:: shell
+        
+            $ python setup.py --version
+        
+        .. note::
+        
+           If you see unusual version numbers for packages but ``python setup.py
+           --version`` reports the expected version number, ensure ``[egg_info]`` is
+           not defined in ``setup.cfg``.
+        
+        
+        ``setup.cfg`` usage
+        -------------------
+        
+        If using `setuptools 30.3.0
+        <https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files>`_
+        or greater, you can store ``setup_requires`` configuration in ``setup.cfg``.
+        However, ``use_scm_version`` must still be placed in ``setup.py``. For example:
+        
+        .. code:: python
+        
+            # setup.py
+            from setuptools import setup
+            setup(
+                use_scm_version=True,
+            )
+        
+        .. code:: ini
+        
+            # setup.cfg
+            [metadata]
+            ...
+        
+            [options]
+            setup_requires =
+              setuptools_scm
+            ...
+        
+        .. important::
+        
+            Ensure neither the ``[metadata]`` ``version`` option nor the ``[egg_info]``
+            section are defined, as these will interfere with ``setuptools_scm``.
+        
+        You may also need to define a ``pyproject.toml`` file (`PEP-0518
+        <https://www.python.org/dev/peps/pep-0518>`_) to ensure you have the required
+        version of ``setuptools``:
+        
+        .. code:: ini
+        
+            # pyproject.toml
+            [build-system]
+            requires = ["setuptools>=30.3.0", "wheel", "setuptools_scm"]
+        
+        For more information, refer to the `setuptools issue #1002
+        <https://github.com/pypa/setuptools/issues/1002>`_.
+        
+        
+        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__)
+        
+        See `setup.py Usage`_ above for how to use this within ``setup.py``.
+        
+        
+        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
+        or the `importlib_metadata`_ backport:
+        
+        .. code:: python
+        
+            from importlib.metadata import version, PackageNotFoundError
+        
+            try:
+                __version__ = version(__name__)
+            except PackageNotFoundError:
+                # package is not installed
+               pass
+        
+        Alternatively, you can use ``pkg_resources`` which is included in
+        ``setuptools``:
+        
+        .. code:: python
+        
+           from pkg_resources import get_distribution, DistributionNotFound
+        
+           try:
+               __version__ = get_distribution(__name__).version
+           except DistributionNotFound:
+                # package is not installed
+               pass
+        
+        This does place a runtime dependency on ``setuptools``.
+        
+        .. _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 ``pkg_resources`` after editable/real installation:
+        
+        .. code:: python
+        
+            # contents of docs/conf.py
+            from pkg_resources import get_distribution
+            release = get_distribution('myproject').version
+            # 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.
+        
+        Notable Plugins
+        ----------------
+        
+        `setuptools_scm_git_archive <https://pypi.python.org/pypi/setuptools_scm_git_archive>`_
+            Provides partial support for obtaining versions from git archives that
+            belong to tagged versions. The only reason for not including it in
+            ``setuptools_scm`` itself is Git/GitHub not supporting sufficient metadata
+            for untagged/followup commits, which is preventing a consistent UX.
+        
+        
+        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}``.
+        
+        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. ``PKG-INFO``
+        
+        .. note::
+        
+            Git archives are not supported due to Git shortcomings
+        
+        
+        File finders hook makes most of MANIFEST.in unnecessary
+        -------------------------------------------------------
+        
+        ``setuptools_scm`` implements a `file_finders
+        <https://setuptools.readthedocs.io/en/latest/setuptools.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.
+        
+        :write_to:
+            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`.
+        
+        :write_to_template:
+            A newstyle format string that is given the current version as
+            the ``version`` keyword argument for formatting.
+        
+        :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>`_).
+        
+        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.
+        
+        Example configuration in ``setup.py`` format:
+        
+        .. code:: python
+        
+            from setuptools import setup
+        
+            setup(
+                use_scm_version={
+                    'write_to': 'version.py',
+                    'write_to_template': '__version__ = "{version}"',
+                    'tag_regex': r'^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$',
+                }
+            )
+        
+        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 a unparsed string
+        
+        :SETUPTOOLS_SCM_DEBUG:
+            when defined and not empty,
+            a lot of debug information will be printed as part of ``setuptools_scm``
+            operating
+        
+        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`.
+            :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`.
+        
+        ``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,
+                ...
+            )
+        
+        
+        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.
+        
+        On such distributions one might observe errors like:
+        
+        :code:``setuptools_scm.version.SetuptoolsOutdatedWarning: your setuptools is too old (<12)``
+        
+        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
+        `PyPA Code of Conduct`_.
+        
+        .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
+        
+        Security Contact
+        ================
+        
+        To report a security vulnerability, please use the
+        `Tidelift security contact <https://tidelift.com/security>`_.
+        Tidelift will coordinate the fix and disclosure.
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Version Control
+Classifier: Topic :: System :: Software Distribution
+Classifier: Topic :: Utilities
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
+Provides-Extra: toml
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..074eb6c
--- /dev/null
@@ -0,0 +1,589 @@
+setuptools_scm
+===============
+
+``setuptools_scm`` handles managing your Python package versions
+in SCM 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``.
+
+.. image:: https://travis-ci.org/pypa/setuptools_scm.svg?branch=master
+    :target: https://travis-ci.org/pypa/setuptools_scm
+
+.. 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>=42", "wheel", "setuptools_scm[toml]>=3.4"]
+
+Note that the ``toml`` extra must be supplied.
+
+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, 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]
+    write_to = "pkg/version.py"
+
+
+``setup.py`` usage
+------------------
+
+The following settings are considered legacy behavior and
+superseded by the ``pyproject.toml`` usage, but for maximal
+compatibility, projects may also supply the configuration in
+this older form.
+
+To use ``setuptools_scm`` just modify your project's ``setup.py`` file
+like this:
+
+* Add ``setuptools_scm`` to the ``setup_requires`` parameter.
+* Add the ``use_scm_version`` parameter and set it to ``True``.
+
+For example:
+
+.. code:: python
+
+    from setuptools import setup
+    setup(
+        ...,
+        use_scm_version=True,
+        setup_requires=['setuptools_scm'],
+        ...,
+    )
+
+Arguments to ``get_version()`` (see below) may be passed as a dictionary to
+``use_scm_version``. For example:
+
+.. code:: python
+
+    from setuptools import setup
+    setup(
+        ...,
+        use_scm_version = {
+            "root": "..",
+            "relative_to": __file__,
+            "local_scheme": "node-and-timestamp"
+        },
+        setup_requires=['setuptools_scm'],
+        ...,
+    )
+
+You can confirm the version number locally via ``setup.py``:
+
+.. code-block:: shell
+
+    $ python setup.py --version
+
+.. note::
+
+   If you see unusual version numbers for packages but ``python setup.py
+   --version`` reports the expected version number, ensure ``[egg_info]`` is
+   not defined in ``setup.cfg``.
+
+
+``setup.cfg`` usage
+-------------------
+
+If using `setuptools 30.3.0
+<https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files>`_
+or greater, you can store ``setup_requires`` configuration in ``setup.cfg``.
+However, ``use_scm_version`` must still be placed in ``setup.py``. For example:
+
+.. code:: python
+
+    # setup.py
+    from setuptools import setup
+    setup(
+        use_scm_version=True,
+    )
+
+.. code:: ini
+
+    # setup.cfg
+    [metadata]
+    ...
+
+    [options]
+    setup_requires =
+      setuptools_scm
+    ...
+
+.. important::
+
+    Ensure neither the ``[metadata]`` ``version`` option nor the ``[egg_info]``
+    section are defined, as these will interfere with ``setuptools_scm``.
+
+You may also need to define a ``pyproject.toml`` file (`PEP-0518
+<https://www.python.org/dev/peps/pep-0518>`_) to ensure you have the required
+version of ``setuptools``:
+
+.. code:: ini
+
+    # pyproject.toml
+    [build-system]
+    requires = ["setuptools>=30.3.0", "wheel", "setuptools_scm"]
+
+For more information, refer to the `setuptools issue #1002
+<https://github.com/pypa/setuptools/issues/1002>`_.
+
+
+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__)
+
+See `setup.py Usage`_ above for how to use this within ``setup.py``.
+
+
+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
+or the `importlib_metadata`_ backport:
+
+.. code:: python
+
+    from importlib.metadata import version, PackageNotFoundError
+
+    try:
+        __version__ = version(__name__)
+    except PackageNotFoundError:
+        # package is not installed
+       pass
+
+Alternatively, you can use ``pkg_resources`` which is included in
+``setuptools``:
+
+.. code:: python
+
+   from pkg_resources import get_distribution, DistributionNotFound
+
+   try:
+       __version__ = get_distribution(__name__).version
+   except DistributionNotFound:
+        # package is not installed
+       pass
+
+This does place a runtime dependency on ``setuptools``.
+
+.. _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 ``pkg_resources`` after editable/real installation:
+
+.. code:: python
+
+    # contents of docs/conf.py
+    from pkg_resources import get_distribution
+    release = get_distribution('myproject').version
+    # 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.
+
+Notable Plugins
+----------------
+
+`setuptools_scm_git_archive <https://pypi.python.org/pypi/setuptools_scm_git_archive>`_
+    Provides partial support for obtaining versions from git archives that
+    belong to tagged versions. The only reason for not including it in
+    ``setuptools_scm`` itself is Git/GitHub not supporting sufficient metadata
+    for untagged/followup commits, which is preventing a consistent UX.
+
+
+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}``.
+
+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. ``PKG-INFO``
+
+.. note::
+
+    Git archives are not supported due to Git shortcomings
+
+
+File finders hook makes most of MANIFEST.in unnecessary
+-------------------------------------------------------
+
+``setuptools_scm`` implements a `file_finders
+<https://setuptools.readthedocs.io/en/latest/setuptools.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.
+
+:write_to:
+    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`.
+
+:write_to_template:
+    A newstyle format string that is given the current version as
+    the ``version`` keyword argument for formatting.
+
+: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>`_).
+
+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.
+
+Example configuration in ``setup.py`` format:
+
+.. code:: python
+
+    from setuptools import setup
+
+    setup(
+        use_scm_version={
+            'write_to': 'version.py',
+            'write_to_template': '__version__ = "{version}"',
+            'tag_regex': r'^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$',
+        }
+    )
+
+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 a unparsed string
+
+:SETUPTOOLS_SCM_DEBUG:
+    when defined and not empty,
+    a lot of debug information will be printed as part of ``setuptools_scm``
+    operating
+
+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`.
+    :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`.
+
+``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,
+        ...
+    )
+
+
+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.
+
+On such distributions one might observe errors like:
+
+:code:``setuptools_scm.version.SetuptoolsOutdatedWarning: your setuptools is too old (<12)``
+
+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
+`PyPA Code of Conduct`_.
+
+.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
+
+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/pyproject.toml b/pyproject.toml
new file mode 100644 (file)
index 0000000..f90d4d1
--- /dev/null
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools>=34.4", "wheel"]
+build-backend = "setuptools.build_meta"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..110f700
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,76 @@
+[metadata]
+license_file = LICENSE
+license = MIT
+name = setuptools_scm
+url = https://github.com/pypa/setuptools_scm/
+author = Ronny Pfannschmidt
+author_email = opensource@ronnypfannschmidt.de
+description = the blessed package to manage your versions by scm tags
+long_description = file:README.rst
+classifiers = 
+       Development Status :: 5 - Production/Stable
+       Intended Audience :: Developers
+       License :: OSI Approved :: MIT License
+       Programming Language :: Python
+       Programming Language :: Python :: 2.7
+       Programming Language :: Python :: 3
+       Programming Language :: Python :: 3.5
+       Programming Language :: Python :: 3.6
+       Programming Language :: Python :: 3.7
+       Programming Language :: Python :: 3.8
+       Topic :: Software Development :: Libraries
+       Topic :: Software Development :: Version Control
+       Topic :: System :: Software Distribution
+       Topic :: Utilities
+
+[options]
+zip_safe = true
+python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
+install_requires = 
+       setuptools
+packages = find:
+package_dir = 
+       =src
+
+[options.packages.find]
+where = src
+
+[options.extras_require]
+toml = toml
+
+[options.entry_points]
+distutils.setup_keywords = 
+       use_scm_version = setuptools_scm.integration:version_keyword
+setuptools.file_finders = 
+       setuptools_scm = setuptools_scm.integration:find_files
+setuptools.finalize_distribution_options = 
+       setuptools_scm = setuptools_scm.integration:infer_version
+setuptools_scm.parse_scm = 
+       .hg = setuptools_scm.hg:parse
+       .git = setuptools_scm.git:parse
+setuptools_scm.parse_scm_fallback = 
+       .hg_archival.txt = setuptools_scm.hg:parse_archival
+       PKG-INFO = setuptools_scm.hacks:parse_pkginfo
+       pip-egg-info = setuptools_scm.hacks:parse_pip_egg_info
+       setup.py = setuptools_scm.hacks:fallback_version
+setuptools_scm.files_command = 
+       .hg = setuptools_scm.file_finder_hg:hg_find_files
+       .git = setuptools_scm.file_finder_git:git_find_files
+setuptools_scm.version_scheme = 
+       guess-next-dev = setuptools_scm.version:guess_next_dev_version
+       post-release = setuptools_scm.version:postrelease_version
+       python-simplified-semver = setuptools_scm.version:simplified_semver_version
+       release-branch-semver = setuptools_scm.version:release_branch_semver_version
+setuptools_scm.local_scheme = 
+       node-and-date = setuptools_scm.version:get_local_node_and_date
+       node-and-timestamp = setuptools_scm.version:get_local_node_and_timestamp
+       dirty-tag = setuptools_scm.version:get_local_dirty_tag
+       no-local-version = setuptools_scm.version:get_no_local_node
+
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..0b3e0c7
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,53 @@
+"""\
+important note:
+
+the setup of setuptools_scm is self-using,
+the first execution of `python setup.py egg_info`
+will generate partial data
+its critical to run `python setup.py egg_info`
+once before running sdist or easy_install on a fresh checkouts
+
+pip usage is recommended
+"""
+from __future__ import print_function
+import os
+import sys
+import setuptools
+
+
+def scm_config():
+    here = os.path.dirname(os.path.abspath(__file__))
+    src = os.path.join(here, "src")
+    egg_info = os.path.join(src, "setuptools_scm.egg-info")
+    has_entrypoints = os.path.isdir(egg_info)
+    import pkg_resources
+
+    sys.path.insert(0, src)
+    pkg_resources.working_set.add_entry(src)
+    # FIXME: remove debug
+    print(src)
+    print(pkg_resources.working_set)
+    from setuptools_scm.hacks import parse_pkginfo
+    from setuptools_scm.git import parse as parse_git
+    from setuptools_scm.version import guess_next_dev_version, get_local_node_and_date
+
+    def parse(root):
+        try:
+            return parse_pkginfo(root)
+        except IOError:
+            return parse_git(root)
+
+    config = dict(
+        version_scheme=guess_next_dev_version, local_scheme=get_local_node_and_date
+    )
+
+    if has_entrypoints:
+        return dict(use_scm_version=config)
+    else:
+        from setuptools_scm import get_version
+
+        return dict(version=get_version(root=here, parse=parse, **config))
+
+
+if __name__ == "__main__":
+    setuptools.setup(**scm_config())
diff --git a/src/setuptools_scm.egg-info/PKG-INFO b/src/setuptools_scm.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..ea5bbd2
--- /dev/null
@@ -0,0 +1,615 @@
+Metadata-Version: 2.1
+Name: setuptools-scm
+Version: 4.1.2
+Summary: the blessed package to manage your versions by scm tags
+Home-page: https://github.com/pypa/setuptools_scm/
+Author: Ronny Pfannschmidt
+Author-email: opensource@ronnypfannschmidt.de
+License: MIT
+Description: setuptools_scm
+        ===============
+        
+        ``setuptools_scm`` handles managing your Python package versions
+        in SCM 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``.
+        
+        .. image:: https://travis-ci.org/pypa/setuptools_scm.svg?branch=master
+            :target: https://travis-ci.org/pypa/setuptools_scm
+        
+        .. 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>=42", "wheel", "setuptools_scm[toml]>=3.4"]
+        
+        Note that the ``toml`` extra must be supplied.
+        
+        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, 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]
+            write_to = "pkg/version.py"
+        
+        
+        ``setup.py`` usage
+        ------------------
+        
+        The following settings are considered legacy behavior and
+        superseded by the ``pyproject.toml`` usage, but for maximal
+        compatibility, projects may also supply the configuration in
+        this older form.
+        
+        To use ``setuptools_scm`` just modify your project's ``setup.py`` file
+        like this:
+        
+        * Add ``setuptools_scm`` to the ``setup_requires`` parameter.
+        * Add the ``use_scm_version`` parameter and set it to ``True``.
+        
+        For example:
+        
+        .. code:: python
+        
+            from setuptools import setup
+            setup(
+                ...,
+                use_scm_version=True,
+                setup_requires=['setuptools_scm'],
+                ...,
+            )
+        
+        Arguments to ``get_version()`` (see below) may be passed as a dictionary to
+        ``use_scm_version``. For example:
+        
+        .. code:: python
+        
+            from setuptools import setup
+            setup(
+                ...,
+                use_scm_version = {
+                    "root": "..",
+                    "relative_to": __file__,
+                    "local_scheme": "node-and-timestamp"
+                },
+                setup_requires=['setuptools_scm'],
+                ...,
+            )
+        
+        You can confirm the version number locally via ``setup.py``:
+        
+        .. code-block:: shell
+        
+            $ python setup.py --version
+        
+        .. note::
+        
+           If you see unusual version numbers for packages but ``python setup.py
+           --version`` reports the expected version number, ensure ``[egg_info]`` is
+           not defined in ``setup.cfg``.
+        
+        
+        ``setup.cfg`` usage
+        -------------------
+        
+        If using `setuptools 30.3.0
+        <https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files>`_
+        or greater, you can store ``setup_requires`` configuration in ``setup.cfg``.
+        However, ``use_scm_version`` must still be placed in ``setup.py``. For example:
+        
+        .. code:: python
+        
+            # setup.py
+            from setuptools import setup
+            setup(
+                use_scm_version=True,
+            )
+        
+        .. code:: ini
+        
+            # setup.cfg
+            [metadata]
+            ...
+        
+            [options]
+            setup_requires =
+              setuptools_scm
+            ...
+        
+        .. important::
+        
+            Ensure neither the ``[metadata]`` ``version`` option nor the ``[egg_info]``
+            section are defined, as these will interfere with ``setuptools_scm``.
+        
+        You may also need to define a ``pyproject.toml`` file (`PEP-0518
+        <https://www.python.org/dev/peps/pep-0518>`_) to ensure you have the required
+        version of ``setuptools``:
+        
+        .. code:: ini
+        
+            # pyproject.toml
+            [build-system]
+            requires = ["setuptools>=30.3.0", "wheel", "setuptools_scm"]
+        
+        For more information, refer to the `setuptools issue #1002
+        <https://github.com/pypa/setuptools/issues/1002>`_.
+        
+        
+        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__)
+        
+        See `setup.py Usage`_ above for how to use this within ``setup.py``.
+        
+        
+        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
+        or the `importlib_metadata`_ backport:
+        
+        .. code:: python
+        
+            from importlib.metadata import version, PackageNotFoundError
+        
+            try:
+                __version__ = version(__name__)
+            except PackageNotFoundError:
+                # package is not installed
+               pass
+        
+        Alternatively, you can use ``pkg_resources`` which is included in
+        ``setuptools``:
+        
+        .. code:: python
+        
+           from pkg_resources import get_distribution, DistributionNotFound
+        
+           try:
+               __version__ = get_distribution(__name__).version
+           except DistributionNotFound:
+                # package is not installed
+               pass
+        
+        This does place a runtime dependency on ``setuptools``.
+        
+        .. _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 ``pkg_resources`` after editable/real installation:
+        
+        .. code:: python
+        
+            # contents of docs/conf.py
+            from pkg_resources import get_distribution
+            release = get_distribution('myproject').version
+            # 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.
+        
+        Notable Plugins
+        ----------------
+        
+        `setuptools_scm_git_archive <https://pypi.python.org/pypi/setuptools_scm_git_archive>`_
+            Provides partial support for obtaining versions from git archives that
+            belong to tagged versions. The only reason for not including it in
+            ``setuptools_scm`` itself is Git/GitHub not supporting sufficient metadata
+            for untagged/followup commits, which is preventing a consistent UX.
+        
+        
+        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}``.
+        
+        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. ``PKG-INFO``
+        
+        .. note::
+        
+            Git archives are not supported due to Git shortcomings
+        
+        
+        File finders hook makes most of MANIFEST.in unnecessary
+        -------------------------------------------------------
+        
+        ``setuptools_scm`` implements a `file_finders
+        <https://setuptools.readthedocs.io/en/latest/setuptools.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.
+        
+        :write_to:
+            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`.
+        
+        :write_to_template:
+            A newstyle format string that is given the current version as
+            the ``version`` keyword argument for formatting.
+        
+        :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>`_).
+        
+        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.
+        
+        Example configuration in ``setup.py`` format:
+        
+        .. code:: python
+        
+            from setuptools import setup
+        
+            setup(
+                use_scm_version={
+                    'write_to': 'version.py',
+                    'write_to_template': '__version__ = "{version}"',
+                    'tag_regex': r'^(?P<prefix>v)?(?P<version>[^\+]+)(?P<suffix>.*)?$',
+                }
+            )
+        
+        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 a unparsed string
+        
+        :SETUPTOOLS_SCM_DEBUG:
+            when defined and not empty,
+            a lot of debug information will be printed as part of ``setuptools_scm``
+            operating
+        
+        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`.
+            :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`.
+        
+        ``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,
+                ...
+            )
+        
+        
+        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.
+        
+        On such distributions one might observe errors like:
+        
+        :code:``setuptools_scm.version.SetuptoolsOutdatedWarning: your setuptools is too old (<12)``
+        
+        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
+        `PyPA Code of Conduct`_.
+        
+        .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
+        
+        Security Contact
+        ================
+        
+        To report a security vulnerability, please use the
+        `Tidelift security contact <https://tidelift.com/security>`_.
+        Tidelift will coordinate the fix and disclosure.
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: Version Control
+Classifier: Topic :: System :: Software Distribution
+Classifier: Topic :: Utilities
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
+Provides-Extra: toml
diff --git a/src/setuptools_scm.egg-info/SOURCES.txt b/src/setuptools_scm.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..3953c8e
--- /dev/null
@@ -0,0 +1,47 @@
+.gitignore
+CHANGELOG.rst
+LICENSE
+MANIFEST.in
+README.rst
+pyproject.toml
+setup.cfg
+setup.py
+tox.ini
+.github/FUNDING.yml
+.github/workflows/pre-commit.yml
+.github/workflows/python-tests.yml
+src/setuptools_scm/__init__.py
+src/setuptools_scm/__main__.py
+src/setuptools_scm/config.py
+src/setuptools_scm/discover.py
+src/setuptools_scm/file_finder.py
+src/setuptools_scm/file_finder_git.py
+src/setuptools_scm/file_finder_hg.py
+src/setuptools_scm/git.py
+src/setuptools_scm/hacks.py
+src/setuptools_scm/hg.py
+src/setuptools_scm/integration.py
+src/setuptools_scm/utils.py
+src/setuptools_scm/version.py
+src/setuptools_scm/win_py31_compat.py
+src/setuptools_scm.egg-info/PKG-INFO
+src/setuptools_scm.egg-info/SOURCES.txt
+src/setuptools_scm.egg-info/dependency_links.txt
+src/setuptools_scm.egg-info/entry_points.txt
+src/setuptools_scm.egg-info/requires.txt
+src/setuptools_scm.egg-info/top_level.txt
+src/setuptools_scm.egg-info/zip-safe
+testing/check_self_install.py
+testing/conftest.py
+testing/play_out_381.bash
+testing/test_basic_api.py
+testing/test_config.py
+testing/test_file_finder.py
+testing/test_functions.py
+testing/test_git.py
+testing/test_integration.py
+testing/test_main.py
+testing/test_mercurial.py
+testing/test_regressions.py
+testing/test_setuptools_support.py
+testing/test_version.py
\ No newline at end of file
diff --git a/src/setuptools_scm.egg-info/dependency_links.txt b/src/setuptools_scm.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/setuptools_scm.egg-info/entry_points.txt b/src/setuptools_scm.egg-info/entry_points.txt
new file mode 100644 (file)
index 0000000..7e46afc
--- /dev/null
@@ -0,0 +1,35 @@
+[distutils.setup_keywords]
+use_scm_version = setuptools_scm.integration:version_keyword
+
+[setuptools.file_finders]
+setuptools_scm = setuptools_scm.integration:find_files
+
+[setuptools.finalize_distribution_options]
+setuptools_scm = setuptools_scm.integration:infer_version
+
+[setuptools_scm.files_command]
+.git = setuptools_scm.file_finder_git:git_find_files
+.hg = setuptools_scm.file_finder_hg:hg_find_files
+
+[setuptools_scm.local_scheme]
+dirty-tag = setuptools_scm.version:get_local_dirty_tag
+no-local-version = setuptools_scm.version:get_no_local_node
+node-and-date = setuptools_scm.version:get_local_node_and_date
+node-and-timestamp = setuptools_scm.version:get_local_node_and_timestamp
+
+[setuptools_scm.parse_scm]
+.git = setuptools_scm.git:parse
+.hg = setuptools_scm.hg:parse
+
+[setuptools_scm.parse_scm_fallback]
+.hg_archival.txt = setuptools_scm.hg:parse_archival
+PKG-INFO = setuptools_scm.hacks:parse_pkginfo
+pip-egg-info = setuptools_scm.hacks:parse_pip_egg_info
+setup.py = setuptools_scm.hacks:fallback_version
+
+[setuptools_scm.version_scheme]
+guess-next-dev = setuptools_scm.version:guess_next_dev_version
+post-release = setuptools_scm.version:postrelease_version
+python-simplified-semver = setuptools_scm.version:simplified_semver_version
+release-branch-semver = setuptools_scm.version:release_branch_semver_version
+
diff --git a/src/setuptools_scm.egg-info/requires.txt b/src/setuptools_scm.egg-info/requires.txt
new file mode 100644 (file)
index 0000000..1fad750
--- /dev/null
@@ -0,0 +1,4 @@
+setuptools
+
+[toml]
+toml
diff --git a/src/setuptools_scm.egg-info/top_level.txt b/src/setuptools_scm.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..cba8d88
--- /dev/null
@@ -0,0 +1 @@
+setuptools_scm
diff --git a/src/setuptools_scm.egg-info/zip-safe b/src/setuptools_scm.egg-info/zip-safe
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/setuptools_scm/__init__.py b/src/setuptools_scm/__init__.py
new file mode 100644 (file)
index 0000000..6b22b28
--- /dev/null
@@ -0,0 +1,163 @@
+"""
+:copyright: 2010-2015 by Ronny Pfannschmidt
+:license: MIT
+"""
+import os
+import warnings
+
+from .config import (
+    Configuration,
+    DEFAULT_VERSION_SCHEME,
+    DEFAULT_LOCAL_SCHEME,
+    DEFAULT_TAG_REGEX,
+)
+from .utils import function_has_arg, string_types
+from .version import format_version, meta
+from .discover import iter_matching_entrypoints
+
+PRETEND_KEY = "SETUPTOOLS_SCM_PRETEND_VERSION"
+
+TEMPLATES = {
+    ".py": """\
+# coding: utf-8
+# file generated by setuptools_scm
+# don't change, don't track in version control
+version = {version!r}
+""",
+    ".txt": "{version}",
+}
+
+
+def version_from_scm(root):
+    warnings.warn(
+        "version_from_scm is deprecated please use get_version",
+        category=DeprecationWarning,
+    )
+    config = Configuration()
+    config.root = root
+    # TODO: Is it API?
+    return _version_from_entrypoints(config)
+
+
+def _call_entrypoint_fn(root, config, fn):
+    if function_has_arg(fn, "config"):
+        return fn(root, config=config)
+    else:
+        warnings.warn(
+            "parse functions are required to provide a named argument"
+            " 'config' in the future.",
+            category=PendingDeprecationWarning,
+            stacklevel=2,
+        )
+        return fn(root)
+
+
+def _version_from_entrypoints(config, fallback=False):
+    if fallback:
+        entrypoint = "setuptools_scm.parse_scm_fallback"
+        root = config.fallback_root
+    else:
+        entrypoint = "setuptools_scm.parse_scm"
+        root = config.absolute_root
+    for ep in iter_matching_entrypoints(root, entrypoint):
+        version = _call_entrypoint_fn(root, config, ep.load())
+
+        if version:
+            return version
+
+
+def dump_version(root, version, write_to, template=None):
+    assert isinstance(version, string_types)
+    if not write_to:
+        return
+    target = os.path.normpath(os.path.join(root, write_to))
+    ext = os.path.splitext(target)[1]
+    template = template or TEMPLATES.get(ext)
+
+    if template is None:
+        raise ValueError(
+            "bad file format: '{}' (of {}) \nonly *.txt and *.py are supported".format(
+                os.path.splitext(target)[1], target
+            )
+        )
+    with open(target, "w") as fp:
+        fp.write(template.format(version=version))
+
+
+def _do_parse(config):
+    pretended = os.environ.get(PRETEND_KEY)
+    if pretended:
+        # we use meta here since the pretended version
+        # must adhere to the pep to begin with
+        return meta(tag=pretended, preformatted=True, config=config)
+
+    if config.parse:
+        parse_result = _call_entrypoint_fn(config.absolute_root, config, config.parse)
+        if isinstance(parse_result, string_types):
+            raise TypeError(
+                "version parse result was a string\nplease return a parsed version"
+            )
+        version = parse_result or _version_from_entrypoints(config, fallback=True)
+    else:
+        # include fallbacks after dropping them from the main entrypoint
+        version = _version_from_entrypoints(config) or _version_from_entrypoints(
+            config, fallback=True
+        )
+
+    if version:
+        return version
+
+    raise LookupError(
+        "setuptools-scm was unable to detect version for %r.\n\n"
+        "Make sure you're either building from a fully intact git repository "
+        "or PyPI tarballs. Most other sources (such as GitHub's tarballs, a "
+        "git checkout without the .git folder) don't contain the necessary "
+        "metadata and will not work.\n\n"
+        "For example, if you're using pip, instead of "
+        "https://github.com/user/proj/archive/master.zip "
+        "use git+https://github.com/user/proj.git#egg=proj" % config.absolute_root
+    )
+
+
+def get_version(
+    root=".",
+    version_scheme=DEFAULT_VERSION_SCHEME,
+    local_scheme=DEFAULT_LOCAL_SCHEME,
+    write_to=None,
+    write_to_template=None,
+    relative_to=None,
+    tag_regex=DEFAULT_TAG_REGEX,
+    parentdir_prefix_version=None,
+    fallback_version=None,
+    fallback_root=".",
+    parse=None,
+    git_describe_command=None,
+):
+    """
+    If supplied, relative_to should be a file from which root may
+    be resolved. Typically called by a script or module that is not
+    in the root of the repository to direct setuptools_scm to the
+    root of the repository by supplying ``__file__``.
+    """
+
+    config = Configuration(**locals())
+    return _get_version(config)
+
+
+def _get_version(config):
+    parsed_version = _do_parse(config)
+
+    if parsed_version:
+        version_string = format_version(
+            parsed_version,
+            version_scheme=config.version_scheme,
+            local_scheme=config.local_scheme,
+        )
+        dump_version(
+            root=config.root,
+            version=version_string,
+            write_to=config.write_to,
+            template=config.write_to_template,
+        )
+
+        return version_string
diff --git a/src/setuptools_scm/__main__.py b/src/setuptools_scm/__main__.py
new file mode 100644 (file)
index 0000000..a464c51
--- /dev/null
@@ -0,0 +1,17 @@
+from __future__ import print_function
+import sys
+from setuptools_scm import get_version
+from setuptools_scm.integration import find_files
+from setuptools_scm.version import _warn_if_setuptools_outdated
+
+
+def main():
+    _warn_if_setuptools_outdated()
+    print("Guessed Version", get_version())
+    if "ls" in sys.argv:
+        for fname in find_files("."):
+            print(fname)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/setuptools_scm/config.py b/src/setuptools_scm/config.py
new file mode 100644 (file)
index 0000000..e7f4d72
--- /dev/null
@@ -0,0 +1,125 @@
+""" configuration """
+from __future__ import print_function, unicode_literals
+import os
+import re
+import warnings
+
+from .utils import trace
+
+DEFAULT_TAG_REGEX = r"^(?:[\w-]+-)?(?P<version>[vV]?\d+(?:\.\d+){0,2}[^\+]*)(?:\+.*)?$"
+DEFAULT_VERSION_SCHEME = "guess-next-dev"
+DEFAULT_LOCAL_SCHEME = "node-and-date"
+
+
+def _check_tag_regex(value):
+    if not value:
+        value = DEFAULT_TAG_REGEX
+    regex = re.compile(value)
+
+    group_names = regex.groupindex.keys()
+    if regex.groups == 0 or (regex.groups > 1 and "version" not in group_names):
+        warnings.warn(
+            "Expected tag_regex to contain a single match group or a group named"
+            " 'version' to identify the version part of any tag."
+        )
+
+    return regex
+
+
+def _check_absolute_root(root, relative_to):
+    if relative_to:
+        if os.path.isabs(root) and not root.startswith(relative_to):
+            warnings.warn(
+                "absolute root path '%s' overrides relative_to '%s'"
+                % (root, relative_to)
+            )
+        root = os.path.join(os.path.dirname(relative_to), root)
+    return os.path.abspath(root)
+
+
+class Configuration(object):
+    """ Global configuration model """
+
+    def __init__(
+        self,
+        relative_to=None,
+        root=".",
+        version_scheme=DEFAULT_VERSION_SCHEME,
+        local_scheme=DEFAULT_LOCAL_SCHEME,
+        write_to=None,
+        write_to_template=None,
+        tag_regex=DEFAULT_TAG_REGEX,
+        parentdir_prefix_version=None,
+        fallback_version=None,
+        fallback_root=".",
+        parse=None,
+        git_describe_command=None,
+    ):
+        # TODO:
+        self._relative_to = relative_to
+        self._root = "."
+
+        self.root = root
+        self.version_scheme = version_scheme
+        self.local_scheme = local_scheme
+        self.write_to = write_to
+        self.write_to_template = write_to_template
+        self.parentdir_prefix_version = parentdir_prefix_version
+        self.fallback_version = fallback_version
+        self.fallback_root = fallback_root
+        self.parse = parse
+        self.tag_regex = tag_regex
+        self.git_describe_command = git_describe_command
+
+    @property
+    def fallback_root(self):
+        return self._fallback_root
+
+    @fallback_root.setter
+    def fallback_root(self, value):
+        self._fallback_root = os.path.abspath(value)
+
+    @property
+    def absolute_root(self):
+        return self._absolute_root
+
+    @property
+    def relative_to(self):
+        return self._relative_to
+
+    @relative_to.setter
+    def relative_to(self, value):
+        self._absolute_root = _check_absolute_root(self._root, value)
+        self._relative_to = value
+        trace("root", repr(self._absolute_root))
+
+    @property
+    def root(self):
+        return self._root
+
+    @root.setter
+    def root(self, value):
+        self._absolute_root = _check_absolute_root(value, self._relative_to)
+        self._root = value
+        trace("root", repr(self._absolute_root))
+
+    @property
+    def tag_regex(self):
+        return self._tag_regex
+
+    @tag_regex.setter
+    def tag_regex(self, value):
+        self._tag_regex = _check_tag_regex(value)
+
+    @classmethod
+    def from_file(cls, name="pyproject.toml"):
+        """
+        Read Configuration from pyproject.toml (or similar).
+        Raises exceptions when file is not found or toml is
+        not installed or the file has invalid format or does
+        not contain the [tool.setuptools_scm] section.
+        """
+        with open(name) as strm:
+            defn = __import__("toml").load(strm)
+        section = defn.get("tool", {})["setuptools_scm"]
+        return cls(**section)
diff --git a/src/setuptools_scm/discover.py b/src/setuptools_scm/discover.py
new file mode 100644 (file)
index 0000000..019f1c5
--- /dev/null
@@ -0,0 +1,13 @@
+import os
+from pkg_resources import iter_entry_points
+from .utils import trace
+
+
+def iter_matching_entrypoints(path, entrypoint):
+    trace("looking for ep", entrypoint, path)
+    for ep in iter_entry_points(entrypoint):
+        if os.path.exists(os.path.join(path, ep.name)):
+            if os.path.isabs(ep.name):
+                trace("ignoring bad ep", ep)
+            trace("found ep", ep)
+            yield ep
diff --git a/src/setuptools_scm/file_finder.py b/src/setuptools_scm/file_finder.py
new file mode 100644 (file)
index 0000000..77ec146
--- /dev/null
@@ -0,0 +1,55 @@
+import os
+
+
+def scm_find_files(path, scm_files, scm_dirs):
+    """ setuptools compatible file finder that follows symlinks
+
+    - path: the root directory from which to search
+    - scm_files: set of scm controlled files and symlinks
+      (including symlinks to directories)
+    - scm_dirs: set of scm controlled directories
+      (including directories containing no scm controlled files)
+
+    scm_files and scm_dirs must be absolute with symlinks resolved (realpath),
+    with normalized case (normcase)
+
+    Spec here: http://setuptools.readthedocs.io/en/latest/setuptools.html#\
+        adding-support-for-revision-control-systems
+    """
+    realpath = os.path.normcase(os.path.realpath(path))
+    seen = set()
+    res = []
+    for dirpath, dirnames, filenames in os.walk(realpath, followlinks=True):
+        # dirpath with symlinks resolved
+        realdirpath = os.path.normcase(os.path.realpath(dirpath))
+
+        def _link_not_in_scm(n):
+            fn = os.path.join(realdirpath, os.path.normcase(n))
+            return os.path.islink(fn) and fn not in scm_files
+
+        if realdirpath not in scm_dirs:
+            # directory not in scm, don't walk it's content
+            dirnames[:] = []
+            continue
+        if os.path.islink(dirpath) and not os.path.relpath(
+            realdirpath, realpath
+        ).startswith(os.pardir):
+            # a symlink to a directory not outside path:
+            # we keep it in the result and don't walk its content
+            res.append(os.path.join(path, os.path.relpath(dirpath, path)))
+            dirnames[:] = []
+            continue
+        if realdirpath in seen:
+            # symlink loop protection
+            dirnames[:] = []
+            continue
+        dirnames[:] = [dn for dn in dirnames if not _link_not_in_scm(dn)]
+        for filename in filenames:
+            if _link_not_in_scm(filename):
+                continue
+            # dirpath + filename with symlinks preserved
+            fullfilename = os.path.join(dirpath, filename)
+            if os.path.normcase(os.path.realpath(fullfilename)) in scm_files:
+                res.append(os.path.join(path, os.path.relpath(fullfilename, realpath)))
+        seen.add(realdirpath)
+    return res
diff --git a/src/setuptools_scm/file_finder_git.py b/src/setuptools_scm/file_finder_git.py
new file mode 100644 (file)
index 0000000..9aa6245
--- /dev/null
@@ -0,0 +1,69 @@
+import os
+import subprocess
+import tarfile
+import logging
+from .file_finder import scm_find_files
+from .utils import trace
+
+log = logging.getLogger(__name__)
+
+
+def _git_toplevel(path):
+    try:
+        with open(os.devnull, "wb") as devnull:
+            out = subprocess.check_output(
+                ["git", "rev-parse", "--show-toplevel"],
+                cwd=(path or "."),
+                universal_newlines=True,
+                stderr=devnull,
+            )
+        trace("find files toplevel", out)
+        return os.path.normcase(os.path.realpath(out.strip()))
+    except subprocess.CalledProcessError:
+        # git returned error, we are not in a git repo
+        return None
+    except OSError:
+        # git command not found, probably
+        return None
+
+
+def _git_interpret_archive(fd, toplevel):
+    with tarfile.open(fileobj=fd, mode="r|*") as tf:
+        git_files = set()
+        git_dirs = {toplevel}
+        for member in tf.getmembers():
+            name = os.path.normcase(member.name).replace("/", os.path.sep)
+            if member.type == tarfile.DIRTYPE:
+                git_dirs.add(name)
+            else:
+                git_files.add(name)
+        return git_files, git_dirs
+
+
+def _git_ls_files_and_dirs(toplevel):
+    # use git archive instead of git ls-file to honor
+    # export-ignore git attribute
+    cmd = ["git", "archive", "--prefix", toplevel + os.path.sep, "HEAD"]
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=toplevel)
+    try:
+        try:
+            return _git_interpret_archive(proc.stdout, toplevel)
+        finally:
+            # ensure we avoid resource warnings by cleaning up the process
+            proc.stdout.close()
+            proc.terminate()
+    except Exception:
+        if proc.wait() != 0:
+            log.exception("listing git files failed - pretending there aren't any")
+        return (), ()
+
+
+def git_find_files(path=""):
+    toplevel = _git_toplevel(path)
+    if not toplevel:
+        return []
+    fullpath = os.path.abspath(os.path.normpath(path))
+    if not fullpath.startswith(toplevel):
+        trace("toplevel mismatch", toplevel, fullpath)
+    git_files, git_dirs = _git_ls_files_and_dirs(toplevel)
+    return scm_find_files(path, git_files, git_dirs)
diff --git a/src/setuptools_scm/file_finder_hg.py b/src/setuptools_scm/file_finder_hg.py
new file mode 100644 (file)
index 0000000..2aa1e16
--- /dev/null
@@ -0,0 +1,47 @@
+import os
+import subprocess
+
+from .file_finder import scm_find_files
+
+
+def _hg_toplevel(path):
+    try:
+        with open(os.devnull, "wb") as devnull:
+            out = subprocess.check_output(
+                ["hg", "root"],
+                cwd=(path or "."),
+                universal_newlines=True,
+                stderr=devnull,
+            )
+        return os.path.normcase(os.path.realpath(out.strip()))
+    except subprocess.CalledProcessError:
+        # hg returned error, we are not in a mercurial repo
+        return None
+    except OSError:
+        # hg command not found, probably
+        return None
+
+
+def _hg_ls_files_and_dirs(toplevel):
+    hg_files = set()
+    hg_dirs = {toplevel}
+    out = subprocess.check_output(
+        ["hg", "files"], cwd=toplevel, universal_newlines=True
+    )
+    for name in out.splitlines():
+        name = os.path.normcase(name).replace("/", os.path.sep)
+        fullname = os.path.join(toplevel, name)
+        hg_files.add(fullname)
+        dirname = os.path.dirname(fullname)
+        while len(dirname) > len(toplevel) and dirname not in hg_dirs:
+            hg_dirs.add(dirname)
+            dirname = os.path.dirname(dirname)
+    return hg_files, hg_dirs
+
+
+def hg_find_files(path=""):
+    toplevel = _hg_toplevel(path)
+    if not toplevel:
+        return []
+    hg_files, hg_dirs = _hg_ls_files_and_dirs(toplevel)
+    return scm_find_files(path, hg_files, hg_dirs)
diff --git a/src/setuptools_scm/git.py b/src/setuptools_scm/git.py
new file mode 100644 (file)
index 0000000..afefa34
--- /dev/null
@@ -0,0 +1,153 @@
+from .config import Configuration
+from .utils import do_ex, trace, has_command
+from .version import meta
+
+from os.path import isfile, join
+import warnings
+
+
+try:
+    from os.path import samefile
+except ImportError:
+    from .win_py31_compat import samefile
+
+
+DEFAULT_DESCRIBE = "git describe --dirty --tags --long --match *.*"
+
+
+class GitWorkdir(object):
+    """experimental, may change at any time"""
+
+    def __init__(self, path):
+        self.path = path
+
+    def do_ex(self, cmd):
+        return do_ex(cmd, cwd=self.path)
+
+    @classmethod
+    def from_potential_worktree(cls, wd):
+        real_wd, _, ret = do_ex("git rev-parse --show-toplevel", wd)
+        if ret:
+            return
+        trace("real root", real_wd)
+        if not samefile(real_wd, wd):
+            return
+
+        return cls(real_wd)
+
+    def is_dirty(self):
+        out, _, _ = self.do_ex("git status --porcelain --untracked-files=no")
+        return bool(out)
+
+    def get_branch(self):
+        branch, err, ret = self.do_ex("git rev-parse --abbrev-ref HEAD")
+        if ret:
+            trace("branch err", branch, err, ret)
+            return
+        return branch
+
+    def is_shallow(self):
+        return isfile(join(self.path, ".git/shallow"))
+
+    def fetch_shallow(self):
+        self.do_ex("git fetch --unshallow")
+
+    def node(self):
+        rev_node, _, ret = self.do_ex("git rev-parse --verify --quiet HEAD")
+        if not ret:
+            return rev_node[:7]
+
+    def count_all_nodes(self):
+        revs, _, _ = self.do_ex("git rev-list HEAD")
+        return revs.count("\n") + 1
+
+
+def warn_on_shallow(wd):
+    """experimental, may change at any time"""
+    if wd.is_shallow():
+        warnings.warn('"{}" is shallow and may cause errors'.format(wd.path))
+
+
+def fetch_on_shallow(wd):
+    """experimental, may change at any time"""
+    if wd.is_shallow():
+        warnings.warn('"%s" was shallow, git fetch was used to rectify')
+        wd.fetch_shallow()
+
+
+def fail_on_shallow(wd):
+    """experimental, may change at any time"""
+    if wd.is_shallow():
+        raise ValueError(
+            "%r is shallow, please correct with " '"git fetch --unshallow"' % wd.path
+        )
+
+
+def parse(
+    root, describe_command=DEFAULT_DESCRIBE, pre_parse=warn_on_shallow, config=None
+):
+    """
+    :param pre_parse: experimental pre_parse action, may change at any time
+    """
+    if not config:
+        config = Configuration(root=root)
+
+    if not has_command("git"):
+        return
+
+    wd = GitWorkdir.from_potential_worktree(config.absolute_root)
+    if wd is None:
+        return
+    if pre_parse:
+        pre_parse(wd)
+
+    if config.git_describe_command:
+        describe_command = config.git_describe_command
+
+    out, unused_err, ret = wd.do_ex(describe_command)
+    if ret:
+        # If 'git git_describe_command' failed, try to get the information otherwise.
+        rev_node = wd.node()
+        dirty = wd.is_dirty()
+
+        if rev_node is None:
+            return meta("0.0", distance=0, dirty=dirty, config=config)
+
+        return meta(
+            "0.0",
+            distance=wd.count_all_nodes(),
+            node="g" + rev_node,
+            dirty=dirty,
+            branch=wd.get_branch(),
+            config=config,
+        )
+    else:
+        tag, number, node, dirty = _git_parse_describe(out)
+
+        branch = wd.get_branch()
+        if number:
+            return meta(
+                tag,
+                config=config,
+                distance=number,
+                node=node,
+                dirty=dirty,
+                branch=branch,
+            )
+        else:
+            return meta(tag, config=config, node=node, dirty=dirty, branch=branch)
+
+
+def _git_parse_describe(describe_output):
+    # 'describe_output' looks e.g. like 'v1.5.0-0-g4060507' or
+    # 'v1.15.1rc1-37-g9bd1298-dirty'.
+
+    if describe_output.endswith("-dirty"):
+        dirty = True
+        describe_output = describe_output[:-6]
+    else:
+        dirty = False
+
+    tag, number, node = describe_output.rsplit("-", 2)
+    number = int(number)
+    return tag, number, node, dirty
diff --git a/src/setuptools_scm/hacks.py b/src/setuptools_scm/hacks.py
new file mode 100644 (file)
index 0000000..349d26f
--- /dev/null
@@ -0,0 +1,37 @@
+import os
+from .utils import data_from_mime, trace
+from .version import tag_to_version, meta
+
+
+def parse_pkginfo(root, config=None):
+
+    pkginfo = os.path.join(root, "PKG-INFO")
+    trace("pkginfo", pkginfo)
+    data = data_from_mime(pkginfo)
+    version = data.get("Version")
+    if version != "UNKNOWN":
+        return meta(version, preformatted=True, config=config)
+
+
+def parse_pip_egg_info(root, config=None):
+    pipdir = os.path.join(root, "pip-egg-info")
+    if not os.path.isdir(pipdir):
+        return
+    items = os.listdir(pipdir)
+    trace("pip-egg-info", pipdir, items)
+    if not items:
+        return
+    return parse_pkginfo(os.path.join(pipdir, items[0]), config=config)
+
+
+def fallback_version(root, config=None):
+    if config.parentdir_prefix_version is not None:
+        _, parent_name = os.path.split(os.path.abspath(root))
+        if parent_name.startswith(config.parentdir_prefix_version):
+            version = tag_to_version(
+                parent_name[len(config.parentdir_prefix_version) :], config
+            )
+            if version is not None:
+                return meta(str(version), preformatted=True, config=config)
+    if config.fallback_version is not None:
+        return meta(config.fallback_version, preformatted=True, config=config)
diff --git a/src/setuptools_scm/hg.py b/src/setuptools_scm/hg.py
new file mode 100644 (file)
index 0000000..d699d45
--- /dev/null
@@ -0,0 +1,113 @@
+import os
+from .config import Configuration
+from .utils import do, trace, data_from_mime, has_command
+from .version import meta, tags_to_versions
+
+
+def _hg_tagdist_normalize_tagcommit(config, tag, dist, node, branch):
+    dirty = node.endswith("+")
+    node = "h" + node.strip("+")
+
+    # Detect changes since the specified tag
+    revset = (
+        "(branch(.)"  # look for revisions in this branch only
+        " and tag({tag!r})::."  # after the last tag
+        # ignore commits that only modify .hgtags and nothing else:
+        " and (merge() or file('re:^(?!\\.hgtags).*$'))"
+        " and not tag({tag!r}))"  # ignore the tagged commit itself
+    ).format(tag=tag)
+    if tag != "0.0":
+        commits = do(
+            ["hg", "log", "-r", revset, "--template", "{node|short}"],
+            config.absolute_root,
+        )
+    else:
+        commits = True
+    trace("normalize", locals())
+    if commits or dirty:
+        return meta(
+            tag, distance=dist, node=node, dirty=dirty, branch=branch, config=config
+        )
+    else:
+        return meta(tag, config=config)
+
+
+def parse(root, config=None):
+    if not config:
+        config = Configuration(root=root)
+
+    if not has_command("hg"):
+        return
+    identity_data = do("hg id -i -b -t", config.absolute_root).split()
+    if not identity_data:
+        return
+    node = identity_data.pop(0)
+    branch = identity_data.pop(0)
+    if "tip" in identity_data:
+        # tip is not a real tag
+        identity_data.remove("tip")
+    tags = tags_to_versions(identity_data)
+    dirty = node[-1] == "+"
+    if tags:
+        return meta(tags[0], dirty=dirty, branch=branch, config=config)
+
+    if node.strip("+") == "0" * 12:
+        trace("initial node", config.absolute_root)
+        return meta("0.0", config=config, dirty=dirty, branch=branch)
+
+    try:
+        tag = get_latest_normalizable_tag(config.absolute_root)
+        dist = get_graph_distance(config.absolute_root, tag)
+        if tag == "null":
+            tag = "0.0"
+            dist = int(dist) + 1
+        return _hg_tagdist_normalize_tagcommit(config, tag, dist, node, branch)
+    except ValueError:
+        pass  # unpacking failed, old hg
+
+
+def get_latest_normalizable_tag(root):
+    # Gets all tags containing a '.' (see #229) from oldest to newest
+    cmd = [
+        "hg",
+        "log",
+        "-r",
+        "ancestors(.) and tag('re:\\.')",
+        "--template",
+        "{tags}\n",
+    ]
+    outlines = do(cmd, root).split()
+    if not outlines:
+        return "null"
+    tag = outlines[-1].split()[-1]
+    return tag
+
+
+def get_graph_distance(root, rev1, rev2="."):
+    cmd = ["hg", "log", "-q", "-r", "{}::{}".format(rev1, rev2)]
+    out = do(cmd, root)
+    return len(out.strip().splitlines()) - 1
+
+
+def archival_to_version(data, config=None):
+    trace("data", data)
+    node = data.get("node", "")[:12]
+    if node:
+        node = "h" + node
+    if "tag" in data:
+        return meta(data["tag"], config=config)
+    elif "latesttag" in data:
+        return meta(
+            data["latesttag"],
+            distance=data["latesttagdistance"],
+            node=node,
+            config=config,
+        )
+    else:
+        return meta("0.0", node=node, config=config)
+
+
+def parse_archival(root, config=None):
+    archival = os.path.join(root, ".hg_archival.txt")
+    data = data_from_mime(archival)
+    return archival_to_version(data, config=config)
diff --git a/src/setuptools_scm/integration.py b/src/setuptools_scm/integration.py
new file mode 100644 (file)
index 0000000..c623db7
--- /dev/null
@@ -0,0 +1,48 @@
+from pkg_resources import iter_entry_points
+
+from .version import _warn_if_setuptools_outdated
+from .utils import do, trace_exception
+from . import _get_version, Configuration
+
+
+def version_keyword(dist, keyword, value):
+    _warn_if_setuptools_outdated()
+    if not value:
+        return
+    if value is True:
+        value = {}
+    if getattr(value, "__call__", None):
+        value = value()
+    config = Configuration(**value)
+    dist.metadata.version = _get_version(config)
+
+
+def find_files(path=""):
+    for ep in iter_entry_points("setuptools_scm.files_command"):
+        command = ep.load()
+        if isinstance(command, str):
+            # this technique is deprecated
+            res = do(ep.load(), path or ".").splitlines()
+        else:
+            res = command(path)
+        if res:
+            return res
+    return []
+
+
+def _args_from_toml(name="pyproject.toml"):
+    # todo: more sensible config initialization
+    # move this elper back to config and unify it with the code from get_config
+
+    with open(name) as strm:
+        defn = __import__("toml").load(strm)
+    return defn.get("tool", {})["setuptools_scm"]
+
+
+def infer_version(dist):
+
+    try:
+        config = Configuration.from_file()
+    except Exception:
+        return trace_exception()
+    dist.metadata.version = _get_version(config)
diff --git a/src/setuptools_scm/utils.py b/src/setuptools_scm/utils.py
new file mode 100644 (file)
index 0000000..c31007a
--- /dev/null
@@ -0,0 +1,163 @@
+"""
+utils
+"""
+from __future__ import print_function, unicode_literals
+import inspect
+import warnings
+import sys
+import shlex
+import subprocess
+import os
+import io
+import platform
+import traceback
+import datetime
+
+
+DEBUG = bool(os.environ.get("SETUPTOOLS_SCM_DEBUG"))
+IS_WINDOWS = platform.system() == "Windows"
+PY2 = sys.version_info < (3,)
+PY3 = sys.version_info > (3,)
+string_types = (str,) if PY3 else (str, unicode)  # noqa
+
+
+def no_git_env(env):
+    # adapted from pre-commit
+    # Too many bugs dealing with environment variables and GIT:
+    # https://github.com/pre-commit/pre-commit/issues/300
+    # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
+    # pre-commit hooks
+    # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE
+    # while running pre-commit hooks in submodules.
+    # GIT_DIR: Causes git clone to clone wrong thing
+    # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit
+    for k, v in env.items():
+        if k.startswith("GIT_"):
+            trace(k, v)
+    return {
+        k: v
+        for k, v in env.items()
+        if not k.startswith("GIT_")
+        or k in ("GIT_EXEC_PATH", "GIT_SSH", "GIT_SSH_COMMAND")
+    }
+
+
+def trace(*k):
+    if DEBUG:
+        print(*k)
+        sys.stdout.flush()
+
+
+def trace_exception():
+    DEBUG and traceback.print_exc()
+
+
+def ensure_stripped_str(str_or_bytes):
+    if isinstance(str_or_bytes, str):
+        return str_or_bytes.strip()
+    else:
+        return str_or_bytes.decode("utf-8", "surrogateescape").strip()
+
+
+def _always_strings(env_dict):
+    """
+    On Windows and Python 2, environment dictionaries must be strings
+    and not unicode.
+    """
+    if IS_WINDOWS or PY2:
+        env_dict.update((key, str(value)) for (key, value) in env_dict.items())
+    return env_dict
+
+
+def _popen_pipes(cmd, cwd):
+    return subprocess.Popen(
+        cmd,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        cwd=str(cwd),
+        env=_always_strings(
+            dict(
+                no_git_env(os.environ),
+                # os.environ,
+                # try to disable i18n
+                LC_ALL="C",
+                LANGUAGE="",
+                HGPLAIN="1",
+            )
+        ),
+    )
+
+
+def do_ex(cmd, cwd="."):
+    trace("cmd", repr(cmd))
+    if os.name == "posix" and not isinstance(cmd, (list, tuple)):
+        cmd = shlex.split(cmd)
+
+    p = _popen_pipes(cmd, cwd)
+    out, err = p.communicate()
+    if out:
+        trace("out", repr(out))
+    if err:
+        trace("err", repr(err))
+    if p.returncode:
+        trace("ret", p.returncode)
+    return ensure_stripped_str(out), ensure_stripped_str(err), p.returncode
+
+
+def do(cmd, cwd="."):
+    out, err, ret = do_ex(cmd, cwd)
+    if ret:
+        print(err)
+    return out
+
+
+def data_from_mime(path):
+    with io.open(path, encoding="utf-8") as fp:
+        content = fp.read()
+    trace("content", repr(content))
+    # the complex conditions come from reading pseudo-mime-messages
+    data = dict(x.split(": ", 1) for x in content.splitlines() if ": " in x)
+    trace("data", data)
+    return data
+
+
+class UTC(datetime.tzinfo):
+    _ZERO = datetime.timedelta(0)
+
+    def utcoffset(self, dt):
+        return self._ZERO
+
+    def tzname(self, dt):
+        return "UTC"
+
+    def dst(self, dt):
+        return self._ZERO
+
+
+utc = UTC()
+
+
+def function_has_arg(fn, argname):
+    assert inspect.isfunction(fn)
+
+    if PY2:
+        argspec = inspect.getargspec(fn).args
+    else:
+
+        argspec = inspect.signature(fn).parameters
+
+    return argname in argspec
+
+
+def has_command(name):
+    try:
+        p = _popen_pipes([name, "help"], ".")
+    except OSError:
+        trace(*sys.exc_info())
+        res = False
+    else:
+        p.communicate()
+        res = not p.returncode
+    if not res:
+        warnings.warn("%r was not found" % name)
+    return res
diff --git a/src/setuptools_scm/version.py b/src/setuptools_scm/version.py
new file mode 100644 (file)
index 0000000..a3c8b94
--- /dev/null
@@ -0,0 +1,349 @@
+from __future__ import print_function
+import datetime
+import warnings
+import re
+
+from .config import Configuration
+from .utils import trace, string_types, utc
+
+from pkg_resources import iter_entry_points
+
+from pkg_resources import parse_version as pkg_parse_version
+
+SEMVER_MINOR = 2
+SEMVER_PATCH = 3
+SEMVER_LEN = 3
+
+
+def _parse_version_tag(tag, config):
+    tagstring = tag if not isinstance(tag, string_types) else str(tag)
+    match = config.tag_regex.match(tagstring)
+
+    result = None
+    if match:
+        if len(match.groups()) == 1:
+            key = 1
+        else:
+            key = "version"
+
+        result = {
+            "version": match.group(key),
+            "prefix": match.group(0)[: match.start(key)],
+            "suffix": match.group(0)[match.end(key) :],
+        }
+
+    trace("tag '{}' parsed to {}".format(tag, result))
+    return result
+
+
+def _get_version_class():
+    modern_version = pkg_parse_version("1.0")
+    if isinstance(modern_version, tuple):
+        return None
+    else:
+        return type(modern_version)
+
+
+VERSION_CLASS = _get_version_class()
+
+
+class SetuptoolsOutdatedWarning(Warning):
+    pass
+
+
+# append so integrators can disable the warning
+warnings.simplefilter("error", SetuptoolsOutdatedWarning, append=True)
+
+
+def _warn_if_setuptools_outdated():
+    if VERSION_CLASS is None:
+        warnings.warn("your setuptools is too old (<12)", SetuptoolsOutdatedWarning)
+
+
+def callable_or_entrypoint(group, callable_or_name):
+    trace("ep", (group, callable_or_name))
+
+    if callable(callable_or_name):
+        return callable_or_name
+
+    for ep in iter_entry_points(group, callable_or_name):
+        trace("ep found:", ep.name)
+        return ep.load()
+
+
+def tag_to_version(tag, config=None):
+    """
+    take a tag that might be prefixed with a keyword and return only the version part
+    :param config: optional configuration object
+    """
+    trace("tag", tag)
+
+    if not config:
+        config = Configuration()
+
+    tagdict = _parse_version_tag(tag, config)
+    if not isinstance(tagdict, dict) or not tagdict.get("version", None):
+        warnings.warn("tag {!r} no version found".format(tag))
+        return None
+
+    version = tagdict["version"]
+    trace("version pre parse", version)
+
+    if tagdict.get("suffix", ""):
+        warnings.warn(
+            "tag {!r} will be stripped of its suffix '{}'".format(
+                tag, tagdict["suffix"]
+            )
+        )
+
+    if VERSION_CLASS is not None:
+        version = pkg_parse_version(version)
+        trace("version", repr(version))
+
+    return version
+
+
+def tags_to_versions(tags, config=None):
+    """
+    take tags that might be prefixed with a keyword and return only the version part
+    :param tags: an iterable of tags
+    :param config: optional configuration object
+    """
+    result = []
+    for tag in tags:
+        tag = tag_to_version(tag, config=config)
+        if tag:
+            result.append(tag)
+    return result
+
+
+class ScmVersion(object):
+    def __init__(
+        self,
+        tag_version,
+        distance=None,
+        node=None,
+        dirty=False,
+        preformatted=False,
+        branch=None,
+        config=None,
+        **kw
+    ):
+        if kw:
+            trace("unknown args", kw)
+        self.tag = tag_version
+        if dirty and distance is None:
+            distance = 0
+        self.distance = distance
+        self.node = node
+        self.time = datetime.datetime.now(utc)
+        self._extra = kw
+        self.dirty = dirty
+        self.preformatted = preformatted
+        self.branch = branch
+        self.config = config
+
+    @property
+    def extra(self):
+        warnings.warn(
+            "ScmVersion.extra is deprecated and will be removed in future",
+            category=DeprecationWarning,
+            stacklevel=2,
+        )
+        return self._extra
+
+    @property
+    def exact(self):
+        return self.distance is None
+
+    def __repr__(self):
+        return self.format_with(
+            "<ScmVersion {tag} d={distance} n={node} d={dirty} b={branch}>"
+        )
+
+    def format_with(self, fmt, **kw):
+        return fmt.format(
+            time=self.time,
+            tag=self.tag,
+            distance=self.distance,
+            node=self.node,
+            dirty=self.dirty,
+            branch=self.branch,
+            **kw
+        )
+
+    def format_choice(self, clean_format, dirty_format, **kw):
+        return self.format_with(dirty_format if self.dirty else clean_format, **kw)
+
+    def format_next_version(self, guess_next, fmt="{guessed}.dev{distance}", **kw):
+        guessed = guess_next(self.tag, **kw)
+        return self.format_with(fmt, guessed=guessed)
+
+
+def _parse_tag(tag, preformatted, config):
+    if preformatted:
+        return tag
+    if VERSION_CLASS is None or not isinstance(tag, VERSION_CLASS):
+        tag = tag_to_version(tag, config)
+    return tag
+
+
+def meta(
+    tag,
+    distance=None,
+    dirty=False,
+    node=None,
+    preformatted=False,
+    branch=None,
+    config=None,
+    **kw
+):
+    if not config:
+        warnings.warn(
+            "meta invoked without explicit configuration,"
+            " will use defaults where required."
+        )
+    parsed_version = _parse_tag(tag, preformatted, config)
+    trace("version", tag, "->", parsed_version)
+    assert parsed_version is not None, "cant parse version %s" % tag
+    return ScmVersion(
+        parsed_version, distance, node, dirty, preformatted, branch, config, **kw
+    )
+
+
+def guess_next_version(tag_version):
+    version = _strip_local(str(tag_version))
+    return _bump_dev(version) or _bump_regex(version)
+
+
+def _strip_local(version_string):
+    public, sep, local = version_string.partition("+")
+    return public
+
+
+def _bump_dev(version):
+    if ".dev" not in version:
+        return
+
+    prefix, tail = version.rsplit(".dev", 1)
+    assert tail == "0", "own dev numbers are unsupported"
+    return prefix
+
+
+def _bump_regex(version):
+    prefix, tail = re.match(r"(.*?)(\d+)$", version).groups()
+    return "%s%d" % (prefix, int(tail) + 1)
+
+
+def guess_next_dev_version(version):
+    if version.exact:
+        return version.format_with("{tag}")
+    else:
+        return version.format_next_version(guess_next_version)
+
+
+def guess_next_simple_semver(version, retain, increment=True):
+    parts = [int(i) for i in str(version).split(".")[:retain]]
+    while len(parts) < retain:
+        parts.append(0)
+    if increment:
+        parts[-1] += 1
+    while len(parts) < SEMVER_LEN:
+        parts.append(0)
+    return ".".join(str(i) for i in parts)
+
+
+def simplified_semver_version(version):
+    if version.exact:
+        return guess_next_simple_semver(version.tag, retain=SEMVER_LEN, increment=False)
+    else:
+        if version.branch is not None and "feature" in version.branch:
+            return version.format_next_version(
+                guess_next_simple_semver, retain=SEMVER_MINOR
+            )
+        else:
+            return version.format_next_version(
+                guess_next_simple_semver, retain=SEMVER_PATCH
+            )
+
+
+def release_branch_semver_version(version):
+    if version.exact:
+        return version.format_with("{tag}")
+    if version.branch is not None:
+        # Does the branch name (stripped of namespace) parse as a version?
+        branch_ver = _parse_version_tag(version.branch.split("/")[-1], version.config)
+        if branch_ver is not None:
+            # Does the branch version up to the minor part match the tag? If not it
+            # might be like, an issue number or something and not a version number, so
+            # we only want to use it if it matches.
+            tag_ver_up_to_minor = str(version.tag).split(".")[:SEMVER_MINOR]
+            branch_ver_up_to_minor = branch_ver["version"].split(".")[:SEMVER_MINOR]
+            if branch_ver_up_to_minor == tag_ver_up_to_minor:
+                # We're in a release/maintenance branch, next is a patch/rc/beta bump:
+                return version.format_next_version(guess_next_version)
+    # We're in a development branch, next is a minor bump:
+    return version.format_next_version(guess_next_simple_semver, retain=SEMVER_MINOR)
+
+
+def release_branch_semver(version):
+    warnings.warn(
+        "release_branch_semver is deprecated and will be removed in future. "
+        + "Use release_branch_semver_version instead",
+        category=DeprecationWarning,
+        stacklevel=2,
+    )
+    return release_branch_semver_version(version)
+
+
+def _format_local_with_time(version, time_format):
+
+    if version.exact or version.node is None:
+        return version.format_choice(
+            "", "+d{time:{time_format}}", time_format=time_format
+        )
+    else:
+        return version.format_choice(
+            "+{node}", "+{node}.d{time:{time_format}}", time_format=time_format
+        )
+
+
+def get_local_node_and_date(version):
+    return _format_local_with_time(version, time_format="%Y%m%d")
+
+
+def get_local_node_and_timestamp(version, fmt="%Y%m%d%H%M%S"):
+    return _format_local_with_time(version, time_format=fmt)
+
+
+def get_local_dirty_tag(version):
+    return version.format_choice("", "+dirty")
+
+
+def get_no_local_node(_):
+    return ""
+
+
+def postrelease_version(version):
+    if version.exact:
+        return version.format_with("{tag}")
+    else:
+        return version.format_with("{tag}.post{distance}")
+
+
+def format_version(version, **config):
+    trace("scm version", version)
+    trace("config", config)
+    if version.preformatted:
+        return version.tag
+    version_scheme = callable_or_entrypoint(
+        "setuptools_scm.version_scheme", config["version_scheme"]
+    )
+    local_scheme = callable_or_entrypoint(
+        "setuptools_scm.local_scheme", config["local_scheme"]
+    )
+    main_version = version_scheme(version)
+    trace("version", main_version)
+    local_version = local_scheme(version)
+    trace("local_version", local_version)
+    return version_scheme(version) + local_scheme(version)
diff --git a/src/setuptools_scm/win_py31_compat.py b/src/setuptools_scm/win_py31_compat.py
new file mode 100644 (file)
index 0000000..82a11eb
--- /dev/null
@@ -0,0 +1,214 @@
+"""
+Backport of os.path.samefile for Python prior to 3.2
+on Windows from jaraco.windows 3.8.
+
+DON'T EDIT THIS FILE!
+
+Instead, file tickets and PR's with `jaraco.windows
+<https://github.com/jaraco/jaraco.windows>`_ and request
+a port to setuptools_scm.
+"""
+
+import os
+import nt
+import posixpath
+import ctypes.wintypes
+import sys
+import __builtin__ as builtins
+
+
+##
+# From jaraco.windows.error
+
+def format_system_message(errno):
+       """
+       Call FormatMessage with a system error number to retrieve
+       the descriptive error message.
+       """
+       # first some flags used by FormatMessageW
+       ALLOCATE_BUFFER = 0x100
+       FROM_SYSTEM = 0x1000
+
+       # Let FormatMessageW allocate the buffer (we'll free it below)
+       # Also, let it know we want a system error message.
+       flags = ALLOCATE_BUFFER | FROM_SYSTEM
+       source = None
+       message_id = errno
+       language_id = 0
+       result_buffer = ctypes.wintypes.LPWSTR()
+       buffer_size = 0
+       arguments = None
+       bytes = ctypes.windll.kernel32.FormatMessageW(
+               flags,
+               source,
+               message_id,
+               language_id,
+               ctypes.byref(result_buffer),
+               buffer_size,
+               arguments,
+       )
+       # note the following will cause an infinite loop if GetLastError
+       #  repeatedly returns an error that cannot be formatted, although
+       #  this should not happen.
+       handle_nonzero_success(bytes)
+       message = result_buffer.value
+       ctypes.windll.kernel32.LocalFree(result_buffer)
+       return message
+
+
+class WindowsError(builtins.WindowsError):
+       """
+       More info about errors at
+       http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx
+       """
+
+       def __init__(self, value=None):
+               if value is None:
+                       value = ctypes.windll.kernel32.GetLastError()
+               strerror = format_system_message(value)
+               if sys.version_info > (3, 3):
+                       args = 0, strerror, None, value
+               else:
+                       args = value, strerror
+               super(WindowsError, self).__init__(*args)
+
+       @property
+       def message(self):
+               return self.strerror
+
+       @property
+       def code(self):
+               return self.winerror
+
+       def __str__(self):
+               return self.message
+
+       def __repr__(self):
+               return '{self.__class__.__name__}({self.winerror})'.format(**vars())
+
+
+def handle_nonzero_success(result):
+       if result == 0:
+               raise WindowsError()
+
+
+##
+# From jaraco.windows.api.filesystem
+
+FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
+FILE_FLAG_BACKUP_SEMANTICS = 0x2000000
+OPEN_EXISTING = 3
+FILE_ATTRIBUTE_NORMAL = 0x80
+FILE_READ_ATTRIBUTES = 0x80
+INVALID_HANDLE_VALUE = ctypes.wintypes.HANDLE(-1).value
+
+
+class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
+       _fields_ = [
+               ('file_attributes', ctypes.wintypes.DWORD),
+               ('creation_time', ctypes.wintypes.FILETIME),
+               ('last_access_time', ctypes.wintypes.FILETIME),
+               ('last_write_time', ctypes.wintypes.FILETIME),
+               ('volume_serial_number', ctypes.wintypes.DWORD),
+               ('file_size_high', ctypes.wintypes.DWORD),
+               ('file_size_low', ctypes.wintypes.DWORD),
+               ('number_of_links', ctypes.wintypes.DWORD),
+               ('file_index_high', ctypes.wintypes.DWORD),
+               ('file_index_low', ctypes.wintypes.DWORD),
+       ]
+
+       @property
+       def file_size(self):
+               return (self.file_size_high << 32) + self.file_size_low
+
+       @property
+       def file_index(self):
+               return (self.file_index_high << 32) + self.file_index_low
+
+
+class SECURITY_ATTRIBUTES(ctypes.Structure):
+       _fields_ = (
+               ('length', ctypes.wintypes.DWORD),
+               ('p_security_descriptor', ctypes.wintypes.LPVOID),
+               ('inherit_handle', ctypes.wintypes.BOOLEAN),
+       )
+
+
+LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)
+
+
+CreateFile = ctypes.windll.kernel32.CreateFileW
+CreateFile.argtypes = (
+       ctypes.wintypes.LPWSTR,
+       ctypes.wintypes.DWORD,
+       ctypes.wintypes.DWORD,
+       LPSECURITY_ATTRIBUTES,
+       ctypes.wintypes.DWORD,
+       ctypes.wintypes.DWORD,
+       ctypes.wintypes.HANDLE,
+)
+CreateFile.restype = ctypes.wintypes.HANDLE
+
+GetFileInformationByHandle = ctypes.windll.kernel32.GetFileInformationByHandle
+GetFileInformationByHandle.restype = ctypes.wintypes.BOOL
+GetFileInformationByHandle.argtypes = (
+       ctypes.wintypes.HANDLE,
+       ctypes.POINTER(BY_HANDLE_FILE_INFORMATION),
+)
+
+
+##
+# From jaraco.windows.filesystem
+
+def compat_stat(path):
+       """
+       Generate stat as found on Python 3.2 and later.
+       """
+       stat = os.stat(path)
+       info = get_file_info(path)
+       # rewrite st_ino, st_dev, and st_nlink based on file info
+       return nt.stat_result(
+               (stat.st_mode,) +
+               (info.file_index, info.volume_serial_number, info.number_of_links) +
+               stat[4:]
+       )
+
+
+def samefile(f1, f2):
+       """
+       Backport of samefile from Python 3.2 with support for Windows.
+       """
+       return posixpath.samestat(compat_stat(f1), compat_stat(f2))
+
+
+def get_file_info(path):
+       # open the file the same way CPython does in posixmodule.c
+       desired_access = FILE_READ_ATTRIBUTES
+       share_mode = 0
+       security_attributes = None
+       creation_disposition = OPEN_EXISTING
+       flags_and_attributes = (
+               FILE_ATTRIBUTE_NORMAL |
+               FILE_FLAG_BACKUP_SEMANTICS |
+               FILE_FLAG_OPEN_REPARSE_POINT
+       )
+       template_file = None
+
+       handle = CreateFile(
+               path,
+               desired_access,
+               share_mode,
+               security_attributes,
+               creation_disposition,
+               flags_and_attributes,
+               template_file,
+       )
+
+       if handle == INVALID_HANDLE_VALUE:
+               raise WindowsError()
+
+       info = BY_HANDLE_FILE_INFORMATION()
+       res = GetFileInformationByHandle(handle, info)
+       handle_nonzero_success(res)
+
+       return info
diff --git a/testing/check_self_install.py b/testing/check_self_install.py
new file mode 100644 (file)
index 0000000..de3ac79
--- /dev/null
@@ -0,0 +1,5 @@
+import pkg_resources
+import setuptools_scm
+
+dist = pkg_resources.get_distribution("setuptools_scm")
+assert dist.version == setuptools_scm.get_version(), dist.version
diff --git a/testing/conftest.py b/testing/conftest.py
new file mode 100644 (file)
index 0000000..5cb65f6
--- /dev/null
@@ -0,0 +1,96 @@
+import os
+import itertools
+import pytest
+import six
+
+os.environ["SETUPTOOLS_SCM_DEBUG"] = "1"
+VERSION_PKGS = ["setuptools", "setuptools_scm"]
+
+
+def pytest_report_header():
+    import pkg_resources
+
+    res = []
+    for pkg in VERSION_PKGS:
+        version = pkg_resources.get_distribution(pkg).version
+        path = __import__(pkg).__file__
+        res.append("{} version {} from {!r}".format(pkg, version, path))
+    return res
+
+
+class Wd(object):
+    commit_command = None
+    add_command = None
+
+    def __repr__(self):
+        return "<WD {cwd}>".format(cwd=self.cwd)
+
+    def __init__(self, cwd):
+        self.cwd = cwd
+        self.__counter = itertools.count()
+
+    def __call__(self, cmd, **kw):
+        if kw:
+            cmd = cmd.format(**kw)
+        from setuptools_scm.utils import do
+
+        return do(cmd, self.cwd)
+
+    def write(self, name, value, **kw):
+        filename = self.cwd / name
+        if kw:
+            value = value.format(**kw)
+        if isinstance(value, six.text_type):
+            filename.write_text(value)
+        else:
+            filename.write_bytes(value)
+        return filename
+
+    def _reason(self, given_reason):
+        if given_reason is None:
+            return "number-{c}".format(c=next(self.__counter))
+        else:
+            return given_reason
+
+    def add_and_commit(self, reason=None):
+        self(self.add_command)
+        self.commit(reason)
+
+    def commit(self, reason=None):
+        reason = self._reason(reason)
+        self(self.commit_command, reason=reason)
+
+    def commit_testfile(self, reason=None):
+        reason = self._reason(reason)
+        self.write("test.txt", "test {reason}", reason=reason)
+        self(self.add_command)
+        self.commit(reason=reason)
+
+    def get_version(self, **kw):
+        __tracebackhide__ = True
+        from setuptools_scm import get_version
+
+        version = get_version(root=str(self.cwd), fallback_root=str(self.cwd), **kw)
+        print(version)
+        return version
+
+    @property
+    def version(self):
+        __tracebackhide__ = True
+        return self.get_version()
+
+
+@pytest.yield_fixture(autouse=True)
+def debug_mode():
+    from setuptools_scm import utils
+
+    utils.DEBUG = True
+    yield
+    utils.DEBUG = False
+
+
+@pytest.fixture
+def wd(tmp_path):
+    target_wd = tmp_path.resolve() / "wd"
+    target_wd.mkdir()
+    return Wd(target_wd)
diff --git a/testing/play_out_381.bash b/testing/play_out_381.bash
new file mode 100755 (executable)
index 0000000..be9d23c
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+set -euxo pipefail
+
+rm -rf y z home venv tmp
+
+[ ! -d black ] && git clone https://github.com/psf/black
+export SETUPTOOLS_SCM_DEBUG=1
+export PRE_COMMIT_HOME="$PWD/home"
+export TMPDIR="$PWD/tmp"
+
+git init y
+git init z
+git -C z commit --allow-empty -m 'commit!'
+git -C y submodule add "$PWD/z"
+cat > "$PWD/y/.git/modules/z/hooks/pre-commit" <<EOF
+#!/usr/bin/env bash
+virtualenv "$PWD/venv"
+"$PWD/venv/bin/pip" install -e "$1"
+"$PWD/venv/bin/pip" install --no-clean "$PWD/black"
+EOF
+chmod +x "$PWD/y/.git/modules/z/hooks/pre-commit"
+cd y/z
+git commit -m "test"
diff --git a/testing/test_basic_api.py b/testing/test_basic_api.py
new file mode 100644 (file)
index 0000000..c5104f5
--- /dev/null
@@ -0,0 +1,117 @@
+import os
+import sys
+import py
+import pytest
+
+import setuptools_scm
+from setuptools_scm import dump_version
+from setuptools_scm.utils import data_from_mime, do
+
+
+@pytest.mark.parametrize("cmd", ["ls", "dir"])
+def test_do(cmd, tmpdir):
+    if not py.path.local.sysfind(cmd):
+        pytest.skip(cmd + " not found")
+    do(cmd, str(tmpdir))
+
+
+def test_data_from_mime(tmpdir):
+    tmpfile = tmpdir.join("test.archival")
+    tmpfile.write("name: test\nrevision: 1")
+
+    res = data_from_mime(str(tmpfile))
+    assert res == {"name": "test", "revision": "1"}
+
+
+def test_version_from_pkginfo(wd, monkeypatch):
+    wd.write("PKG-INFO", "Version: 0.1")
+
+    assert wd.version == "0.1"
+
+    # replicate issue 167
+    assert wd.get_version(version_scheme="1.{0.distance}.0".format) == "0.1"
+
+
+def assert_root(monkeypatch, expected_root):
+    """
+    Patch version_from_scm to simply assert that root is expected root
+    """
+
+    def assertion(config):
+        assert config.absolute_root == expected_root
+
+    monkeypatch.setattr(setuptools_scm, "_do_parse", assertion)
+
+
+def test_root_parameter_creation(monkeypatch):
+    assert_root(monkeypatch, os.getcwd())
+    setuptools_scm.get_version()
+
+
+def test_version_from_scm(wd):
+    with pytest.warns(DeprecationWarning, match=".*version_from_scm.*"):
+        setuptools_scm.version_from_scm(str(wd))
+
+
+def test_root_parameter_pass_by(monkeypatch, tmpdir):
+    assert_root(monkeypatch, tmpdir)
+    setuptools_scm.get_version(root=tmpdir.strpath)
+
+
+def test_parentdir_prefix(tmpdir, monkeypatch):
+    monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+    p = tmpdir.ensure("projectname-v12.34", dir=True)
+    p.join("setup.py").write(
+        """from setuptools import setup
+setup(use_scm_version={"parentdir_prefix_version": "projectname-"})
+"""
+    )
+    res = do((sys.executable, "setup.py", "--version"), p)
+    assert res == "12.34"
+
+
+def test_fallback(tmpdir, monkeypatch):
+    monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+    p = tmpdir.ensure("sub/package", dir=1)
+    p.join("setup.py").write(
+        """from setuptools import setup
+setup(use_scm_version={"fallback_version": "12.34"})
+"""
+    )
+    res = do((sys.executable, "setup.py", "--version"), p)
+    assert res == "12.34"
+
+
+@pytest.mark.parametrize(
+    "version", ["1.0", "1.2.3.dev1+ge871260", "1.2.3.dev15+ge871260.d20180625", "2345"]
+)
+def test_pretended(version, monkeypatch):
+    monkeypatch.setenv(setuptools_scm.PRETEND_KEY, version)
+    assert setuptools_scm.get_version() == version
+
+
+def test_root_relative_to(monkeypatch, tmpdir):
+    assert_root(monkeypatch, tmpdir.join("alt").strpath)
+    __file__ = tmpdir.join("module/file.py").strpath
+    setuptools_scm.get_version(root="../alt", relative_to=__file__)
+
+
+def test_dump_version(tmpdir):
+    sp = tmpdir.strpath
+
+    dump_version(sp, "1.0", "first.txt")
+    assert tmpdir.join("first.txt").read() == "1.0"
+    dump_version(sp, "1.0", "first.py")
+    content = tmpdir.join("first.py").read()
+    assert repr("1.0") in content
+    import ast
+
+    ast.parse(content)
+
+
+def test_parse_plain_fails(recwarn):
+    def parse(root):
+        return "tricked you"
+
+    with pytest.raises(TypeError):
+        setuptools_scm.get_version(parse=parse)
diff --git a/testing/test_config.py b/testing/test_config.py
new file mode 100644 (file)
index 0000000..49f1d7a
--- /dev/null
@@ -0,0 +1,39 @@
+from __future__ import unicode_literals
+
+from setuptools_scm.config import Configuration
+import re
+import pytest
+
+
+@pytest.mark.parametrize(
+    "tag, expected_version",
+    [
+        ("apache-arrow-0.9.0", "0.9.0"),
+        ("arrow-0.9.0", "0.9.0"),
+        ("arrow-0.9.0-rc", "0.9.0-rc"),
+        ("arrow-1", "1"),
+        ("arrow-1+", "1"),
+        ("arrow-1+foo", "1"),
+        ("arrow-1.1+foo", "1.1"),
+        ("v1.1", "v1.1"),
+        ("V1.1", "V1.1"),
+    ],
+)
+def test_tag_regex(tag, expected_version):
+    config = Configuration()
+    match = config.tag_regex.match(tag)
+    assert match
+    version = match.group("version")
+    assert version == expected_version
+
+
+def test_config_from_pyproject(tmpdir):
+    fn = tmpdir / "pyproject.toml"
+    fn.write_text("[tool.setuptools_scm]\n", encoding="utf-8")
+    assert Configuration.from_file(str(fn))
+
+
+def test_config_regex_init():
+    tag_regex = re.compile(r"v(\d+)")
+    conf = Configuration(tag_regex=tag_regex)
+    assert conf.tag_regex is tag_regex
diff --git a/testing/test_file_finder.py b/testing/test_file_finder.py
new file mode 100644 (file)
index 0000000..463d3d4
--- /dev/null
@@ -0,0 +1,182 @@
+import os
+import sys
+
+import pytest
+
+from setuptools_scm.integration import find_files
+
+
+@pytest.fixture(params=["git", "hg"])
+def inwd(request, wd, monkeypatch):
+    if request.param == "git":
+        if sys.platform == "win32" and sys.version_info[0] < 3:
+            pytest.skip("Long/short path names supported on Windows Python 2.7")
+        try:
+            wd("git init")
+        except OSError:
+            pytest.skip("git executable not found")
+        wd("git config user.email test@example.com")
+        wd('git config user.name "a test"')
+        wd.add_command = "git add ."
+        wd.commit_command = "git commit -m test-{reason}"
+    elif request.param == "hg":
+        try:
+            wd("hg init")
+        except OSError:
+            pytest.skip("hg executable not found")
+        wd.add_command = "hg add ."
+        wd.commit_command = 'hg commit -m test-{reason} -u test -d "0 0"'
+    (wd.cwd / "file1").touch()
+    adir = wd.cwd / "adir"
+    adir.mkdir()
+    (adir / "filea").touch()
+    bdir = wd.cwd / "bdir"
+    bdir.mkdir()
+    (bdir / "fileb").touch()
+    wd.add_and_commit()
+    monkeypatch.chdir(wd.cwd)
+    yield wd
+
+
+def _sep(paths):
+    return {path.replace("/", os.path.sep) for path in paths}
+
+
+def test_basic(inwd):
+    assert set(find_files()) == _sep({"file1", "adir/filea", "bdir/fileb"})
+    assert set(find_files(".")) == _sep({"./file1", "./adir/filea", "./bdir/fileb"})
+    assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+def test_whitespace(inwd):
+    (inwd.cwd / "adir" / "space file").touch()
+    inwd.add_and_commit()
+    assert set(find_files("adir")) == _sep({"adir/space file", "adir/filea"})
+
+
+def test_case(inwd):
+    (inwd.cwd / "CamelFile").touch()
+    (inwd.cwd / "file2").touch()
+    inwd.add_and_commit()
+    assert set(find_files()) == _sep(
+        {"CamelFile", "file2", "file1", "adir/filea", "bdir/fileb"}
+    )
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_dir(inwd):
+    (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir")
+    inwd.add_and_commit()
+    assert set(find_files("adir")) == _sep({"adir/filea", "adir/bdirlink/fileb"})
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_dir_source_not_in_scm(inwd):
+    (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir")
+    assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+@pytest.mark.skipif(
+    sys.platform == "win32", reason="symlinks to files not supported on windows"
+)
+def test_symlink_file(inwd):
+    (inwd.cwd / "adir" / "file1link").symlink_to("../file1")
+    inwd.add_and_commit()
+    assert set(find_files("adir")) == _sep(
+        {"adir/filea", "adir/file1link"}
+    )  # -> ../file1
+
+
+@pytest.mark.skipif(
+    sys.platform == "win32", reason="symlinks to files not supported on windows"
+)
+def test_symlink_file_source_not_in_scm(inwd):
+    (inwd.cwd / "adir" / "file1link").symlink_to("../file1")
+    assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_loop(inwd):
+    (inwd.cwd / "adir" / "loop").symlink_to("../adir")
+    inwd.add_and_commit()
+    assert set(find_files("adir")) == _sep({"adir/filea", "adir/loop"})  # -> ../adir
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_loop_outside_path(inwd):
+    (inwd.cwd / "bdir" / "loop").symlink_to("../bdir")
+    (inwd.cwd / "adir" / "bdirlink").symlink_to("../bdir")
+    inwd.add_and_commit()
+    assert set(find_files("adir")) == _sep({"adir/filea", "adir/bdirlink/fileb"})
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks to dir not supported")
+def test_symlink_dir_out_of_git(inwd):
+    (inwd.cwd / "adir" / "outsidedirlink").symlink_to(os.path.join(__file__, ".."))
+    inwd.add_and_commit()
+    assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+@pytest.mark.skipif(
+    sys.platform == "win32", reason="symlinks to files not supported on windows"
+)
+def test_symlink_file_out_of_git(inwd):
+    (inwd.cwd / "adir" / "outsidefilelink").symlink_to(__file__)
+    inwd.add_and_commit()
+    assert set(find_files("adir")) == _sep({"adir/filea"})
+
+
+def test_empty_root(inwd):
+    subdir = inwd.cwd / "cdir" / "subdir"
+    subdir.mkdir(parents=True)
+    (subdir / "filec").touch()
+    inwd.add_and_commit()
+    assert set(find_files("cdir")) == _sep({"cdir/subdir/filec"})
+
+
+def test_empty_subdir(inwd):
+    subdir = inwd.cwd / "adir" / "emptysubdir" / "subdir"
+    subdir.mkdir(parents=True)
+    (subdir / "xfile").touch()
+    inwd.add_and_commit()
+    assert set(find_files("adir")) == _sep(
+        {"adir/filea", "adir/emptysubdir/subdir/xfile"}
+    )
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks not supported on windows")
+def test_double_include_through_symlink(inwd):
+    (inwd.cwd / "data").mkdir()
+    (inwd.cwd / "data" / "datafile").touch()
+    (inwd.cwd / "adir" / "datalink").symlink_to("../data")
+    (inwd.cwd / "adir" / "filealink").symlink_to("filea")
+    inwd.add_and_commit()
+    assert set(find_files()) == _sep(
+        {
+            "file1",
+            "adir/datalink",  # -> ../data
+            "adir/filealink",  # -> filea
+            "adir/filea",
+            "bdir/fileb",
+            "data/datafile",
+        }
+    )
+
+
+@pytest.mark.skipif(sys.platform == "win32", reason="symlinks not supported on windows")
+def test_symlink_not_in_scm_while_target_is(inwd):
+    (inwd.cwd / "data").mkdir()
+    (inwd.cwd / "data" / "datafile").touch()
+    inwd.add_and_commit()
+    (inwd.cwd / "adir" / "datalink").symlink_to("../data")
+    (inwd.cwd / "adir" / "filealink").symlink_to("filea")
+    assert set(find_files()) == _sep(
+        {
+            "file1",
+            "adir/filea",
+            # adir/datalink and adir/afilelink not included
+            # because the symlink_to themselves are not in scm
+            "bdir/fileb",
+            "data/datafile",
+        }
+    )
diff --git a/testing/test_functions.py b/testing/test_functions.py
new file mode 100644 (file)
index 0000000..808a1d1
--- /dev/null
@@ -0,0 +1,106 @@
+import pytest
+import sys
+import pkg_resources
+from setuptools_scm import dump_version, get_version, PRETEND_KEY
+from setuptools_scm.version import (
+    guess_next_version,
+    meta,
+    format_version,
+    tag_to_version,
+)
+
+from setuptools_scm.config import Configuration
+from setuptools_scm.utils import has_command
+
+PY3 = sys.version_info > (2,)
+
+
+class MockTime(object):
+    def __format__(self, *k):
+        return "time"
+
+
+@pytest.mark.parametrize(
+    "tag, expected",
+    [
+        ("1.1", "1.2"),
+        ("1.2.dev", "1.2"),
+        ("1.1a2", "1.1a3"),
+        ("23.24.post2+deadbeef", "23.24.post3"),
+    ],
+)
+def test_next_tag(tag, expected):
+    version = pkg_resources.parse_version(tag)
+    assert guess_next_version(version) == expected
+
+
+c = Configuration()
+
+VERSIONS = {
+    "exact": meta("1.1", distance=None, dirty=False, config=c),
+    "zerodistance": meta("1.1", distance=0, dirty=False, config=c),
+    "dirty": meta("1.1", distance=None, dirty=True, config=c),
+    "distance": meta("1.1", distance=3, dirty=False, config=c),
+    "distancedirty": meta("1.1", distance=3, dirty=True, config=c),
+}
+
+
+@pytest.mark.parametrize(
+    "version,scheme,expected",
+    [
+        ("exact", "guess-next-dev node-and-date", "1.1"),
+        ("zerodistance", "guess-next-dev node-and-date", "1.2.dev0"),
+        ("zerodistance", "guess-next-dev no-local-version", "1.2.dev0"),
+        ("dirty", "guess-next-dev node-and-date", "1.2.dev0+dtime"),
+        ("dirty", "guess-next-dev no-local-version", "1.2.dev0"),
+        ("distance", "guess-next-dev node-and-date", "1.2.dev3"),
+        ("distancedirty", "guess-next-dev node-and-date", "1.2.dev3+dtime"),
+        ("distancedirty", "guess-next-dev no-local-version", "1.2.dev3"),
+        ("exact", "post-release node-and-date", "1.1"),
+        ("zerodistance", "post-release node-and-date", "1.1.post0"),
+        ("dirty", "post-release node-and-date", "1.1.post0+dtime"),
+        ("distance", "post-release node-and-date", "1.1.post3"),
+        ("distancedirty", "post-release node-and-date", "1.1.post3+dtime"),
+    ],
+)
+def test_format_version(version, monkeypatch, scheme, expected):
+    version = VERSIONS[version]
+    monkeypatch.setattr(version, "time", MockTime())
+    vs, ls = scheme.split()
+    assert format_version(version, version_scheme=vs, local_scheme=ls) == expected
+
+
+def test_dump_version_doesnt_bail_on_value_error(tmpdir):
+    write_to = "VERSION"
+    version = str(VERSIONS["exact"].tag)
+    with pytest.raises(ValueError) as exc_info:
+        dump_version(tmpdir.strpath, version, write_to)
+    assert str(exc_info.value).startswith("bad file format:")
+
+
+@pytest.mark.parametrize(
+    "version", ["1.0", "1.2.3.dev1+ge871260", "1.2.3.dev15+ge871260.d20180625"]
+)
+def test_dump_version_works_with_pretend(version, tmpdir, monkeypatch):
+    monkeypatch.setenv(PRETEND_KEY, version)
+    get_version(write_to=str(tmpdir.join("VERSION.txt")))
+    assert tmpdir.join("VERSION.txt").read() == version
+
+
+def test_has_command(recwarn):
+    assert not has_command("yadayada_setuptools_aint_ne")
+    msg = recwarn.pop()
+    assert "yadayada" in str(msg.message)
+
+
+@pytest.mark.parametrize(
+    "tag, expected_version",
+    [
+        ("1.1", "1.1"),
+        ("release-1.1", "1.1"),
+        pytest.param("3.3.1-rc26", "3.3.1rc26", marks=pytest.mark.issue(266)),
+    ],
+)
+def test_tag_to_version(tag, expected_version):
+    version = str(tag_to_version(tag))
+    assert version == expected_version
diff --git a/testing/test_git.py b/testing/test_git.py
new file mode 100644 (file)
index 0000000..542a29f
--- /dev/null
@@ -0,0 +1,273 @@
+import sys
+
+from setuptools_scm import integration
+from setuptools_scm.utils import do, has_command
+from setuptools_scm import git
+import pytest
+from datetime import datetime
+from os.path import join as opj
+from setuptools_scm.file_finder_git import git_find_files
+import warnings
+
+
+skip_if_win_27 = pytest.mark.skipif(
+    sys.platform == "win32" and sys.version_info[0] < 3,
+    reason="Not supported on Windows + Python 2.7",
+)
+
+
+with warnings.catch_warnings():
+    warnings.filterwarnings("ignore")
+    if not has_command("git"):
+        pytestmark = pytest.mark.skip(reason="git executable not found")
+
+
+@pytest.fixture
+def wd(wd, monkeypatch):
+    monkeypatch.delenv("HOME", raising=False)
+    wd("git init")
+    wd("git config user.email test@example.com")
+    wd('git config user.name "a test"')
+    wd.add_command = "git add ."
+    wd.commit_command = "git commit -m test-{reason}"
+    return wd
+
+
+@pytest.mark.parametrize(
+    "given, tag, number, node, dirty",
+    [
+        ("3.3.1-rc26-0-g9df187b", "3.3.1-rc26", 0, "g9df187b", False),
+        ("17.33.0-rc-17-g38c3047c0", "17.33.0-rc", 17, "g38c3047c0", False),
+    ],
+)
+def test_parse_describe_output(given, tag, number, node, dirty):
+    parsed = git._git_parse_describe(given)
+    assert parsed == (tag, number, node, dirty)
+
+
+def test_root_relative_to(tmpdir, wd, monkeypatch):
+    monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+    p = wd.cwd.joinpath("sub/package")
+    p.mkdir(parents=True)
+    p.joinpath("setup.py").write_text(
+        u"""from setuptools import setup
+setup(use_scm_version={"root": "../..",
+                       "relative_to": __file__})
+"""
+    )
+    res = do((sys.executable, "setup.py", "--version"), p)
+    assert res == "0.1.dev0"
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/298")
+@pytest.mark.issue(403)
+def test_file_finder_no_history(wd, caplog):
+    file_list = git_find_files(str(wd.cwd))
+    assert file_list == []
+
+    assert "listing git files failed - pretending there aren't any" in caplog.text
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/281")
+def test_parse_call_order(wd):
+    git.parse(str(wd.cwd), git.DEFAULT_DESCRIBE)
+
+
+def test_version_from_git(wd):
+    assert wd.version == "0.1.dev0"
+
+    wd.commit_testfile()
+    assert wd.version.startswith("0.1.dev1+g")
+    assert not wd.version.endswith("1-")
+
+    wd("git tag v0.1")
+    assert wd.version == "0.1"
+
+    wd.write("test.txt", "test2")
+    assert wd.version.startswith("0.2.dev0+g")
+
+    wd.commit_testfile()
+    assert wd.version.startswith("0.2.dev1+g")
+
+    wd("git tag version-0.2")
+    assert wd.version.startswith("0.2")
+
+    wd.commit_testfile()
+    wd("git tag version-0.2.post210+gbe48adfpost3+g0cc25f2")
+    with pytest.warns(
+        UserWarning, match="tag '.*' will be stripped of its suffix '.*'"
+    ):
+        assert wd.version.startswith("0.2")
+
+    wd.commit_testfile()
+    wd("git tag 17.33.0-rc")
+    assert wd.version == "17.33.0rc0"
+
+
+@pytest.mark.issue(179)
+def test_unicode_version_scheme(wd):
+    scheme = b"guess-next-dev".decode("ascii")
+    assert wd.get_version(version_scheme=scheme)
+
+
+@pytest.mark.issue(108)
+@pytest.mark.issue(109)
+def test_git_worktree(wd):
+    wd.write("test.txt", "test2")
+    # untracked files dont change the state
+    assert wd.version == "0.1.dev0"
+    wd("git add test.txt")
+    assert wd.version.startswith("0.1.dev0+d")
+
+
+@pytest.mark.issue(86)
+def test_git_dirty_notag(wd):
+    wd.commit_testfile()
+    wd.write("test.txt", "test2")
+    wd("git add test.txt")
+    assert wd.version.startswith("0.1.dev1")
+    # the date on the tag is in UTC
+    today = datetime.utcnow().date()
+    # we are dirty, check for the tag
+    assert today.strftime(".d%Y%m%d") in wd.version
+
+
+@pytest.mark.issue(193)
+def test_git_worktree_support(wd, tmpdir):
+    wd.commit_testfile()
+    worktree = tmpdir.join("work_tree")
+    wd("git worktree add -b work-tree %s" % worktree)
+
+    res = do([sys.executable, "-m", "setuptools_scm", "ls"], cwd=worktree)
+    assert str(worktree) in res
+
+
+@pytest.fixture
+def shallow_wd(wd, tmpdir):
+    wd.commit_testfile()
+    wd.commit_testfile()
+    wd.commit_testfile()
+    target = tmpdir.join("wd_shallow")
+    do(["git", "clone", "file://%s" % wd.cwd, str(target), "--depth=1"])
+    return target
+
+
+def test_git_parse_shallow_warns(shallow_wd, recwarn):
+    git.parse(str(shallow_wd))
+    msg = recwarn.pop()
+    assert "is shallow and may cause errors" in str(msg.message)
+
+
+def test_git_parse_shallow_fail(shallow_wd):
+    with pytest.raises(ValueError) as einfo:
+        git.parse(str(shallow_wd), pre_parse=git.fail_on_shallow)
+
+    assert "git fetch" in str(einfo.value)
+
+
+def test_git_shallow_autocorrect(shallow_wd, recwarn):
+    git.parse(str(shallow_wd), pre_parse=git.fetch_on_shallow)
+    msg = recwarn.pop()
+    assert "git fetch was used to rectify" in str(msg.message)
+    git.parse(str(shallow_wd), pre_parse=git.fail_on_shallow)
+
+
+def test_find_files_stop_at_root_git(wd):
+    wd.commit_testfile()
+    project = wd.cwd / "project"
+    project.mkdir()
+    project.joinpath("setup.cfg").touch()
+    assert integration.find_files(str(project)) == []
+
+
+@pytest.mark.issue(128)
+def test_parse_no_worktree(tmpdir):
+    ret = git.parse(str(tmpdir))
+    assert ret is None
+
+
+def test_alphanumeric_tags_match(wd):
+    wd.commit_testfile()
+    wd("git tag newstyle-development-started")
+    assert wd.version.startswith("0.1.dev1+g")
+
+
+@skip_if_win_27
+def test_git_archive_export_ignore(wd, monkeypatch):
+    wd.write("test1.txt", "test")
+    wd.write("test2.txt", "test")
+    wd.write(
+        ".git/info/attributes",
+        # Explicitly include test1.txt so that the test is not affected by
+        # a potentially global gitattributes file on the test machine.
+        "/test1.txt -export-ignore\n/test2.txt export-ignore",
+    )
+    wd("git add test1.txt test2.txt")
+    wd.commit()
+    monkeypatch.chdir(wd.cwd)
+    assert integration.find_files(".") == [opj(".", "test1.txt")]
+
+
+@skip_if_win_27
+@pytest.mark.issue(228)
+def test_git_archive_subdirectory(wd, monkeypatch):
+    wd("mkdir foobar")
+    wd.write("foobar/test1.txt", "test")
+    wd("git add foobar")
+    wd.commit()
+    monkeypatch.chdir(wd.cwd)
+    assert integration.find_files(".") == [opj(".", "foobar", "test1.txt")]
+
+
+@skip_if_win_27
+@pytest.mark.issue(251)
+def test_git_archive_run_from_subdirectory(wd, monkeypatch):
+    wd("mkdir foobar")
+    wd.write("foobar/test1.txt", "test")
+    wd("git add foobar")
+    wd.commit()
+    monkeypatch.chdir(wd.cwd / "foobar")
+    assert integration.find_files(".") == [opj(".", "test1.txt")]
+
+
+def test_git_feature_branch_increments_major(wd):
+    wd.commit_testfile()
+    wd("git tag 1.0.0")
+    wd.commit_testfile()
+    assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.0.1")
+    wd("git checkout -b feature/fun")
+    wd.commit_testfile()
+    assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.1.0")
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/303")
+def test_not_matching_tags(wd):
+    wd.commit_testfile()
+    wd("git tag apache-arrow-0.11.1")
+    wd.commit_testfile()
+    wd("git tag apache-arrow-js-0.9.9")
+    wd.commit_testfile()
+    assert wd.get_version(
+        tag_regex=r"^apache-arrow-([\.0-9]+)$",
+        git_describe_command="git describe --dirty --tags --long --exclude *js* ",
+    ).startswith("0.11.2")
+
+
+@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):
+    wd.commit_testfile()
+    wd("git tag apache-arrow-1")
+    wd.commit_testfile()
+    assert wd.get_version().startswith("2")
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/381")
+def test_gitdir(monkeypatch, wd):
+    """
+    """
+    wd.commit_testfile()
+    normal = wd.version
+    # git hooks set this and break subsequent setuptools_scm unless we clean
+    monkeypatch.setenv("GIT_DIR", __file__)
+    assert wd.version == normal
diff --git a/testing/test_integration.py b/testing/test_integration.py
new file mode 100644 (file)
index 0000000..68c3bfe
--- /dev/null
@@ -0,0 +1,42 @@
+import sys
+
+import pytest
+
+from setuptools_scm.utils import do
+
+
+@pytest.fixture
+def wd(wd):
+    try:
+        wd("git init")
+    except OSError:
+        pytest.skip("git executable not found")
+
+    wd("git config user.email test@example.com")
+    wd('git config user.name "a test"')
+    wd.add_command = "git add ."
+    wd.commit_command = "git commit -m test-{reason}"
+    return wd
+
+
+def test_pyproject_support(tmpdir, monkeypatch):
+    pytest.importorskip("toml")
+    monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+    pkg = tmpdir.ensure("package", dir=42)
+    pkg.join("pyproject.toml").write(
+        """[tool.setuptools_scm]
+fallback_version = "12.34"
+"""
+    )
+    pkg.join("setup.py").write("__import__('setuptools').setup()")
+    res = do((sys.executable, "setup.py", "--version"), pkg)
+    assert res == "12.34"
+
+
+def test_pyproject_support_with_git(tmpdir, monkeypatch, wd):
+    monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+    pkg = tmpdir.join("wd")
+    pkg.join("pyproject.toml").write("""[tool.setuptools_scm]""")
+    pkg.join("setup.py").write("__import__('setuptools').setup()")
+    res = do((sys.executable, "setup.py", "--version"), pkg)
+    assert res == "0.1.dev0"
diff --git a/testing/test_main.py b/testing/test_main.py
new file mode 100644 (file)
index 0000000..97ea05e
--- /dev/null
@@ -0,0 +1,10 @@
+import os.path
+
+
+def test_main():
+    mainfile = os.path.join(
+        os.path.dirname(__file__), "..", "src", "setuptools_scm", "__main__.py"
+    )
+    with open(mainfile) as f:
+        code = compile(f.read(), "__main__.py", "exec")
+        exec(code)
diff --git a/testing/test_mercurial.py b/testing/test_mercurial.py
new file mode 100644 (file)
index 0000000..29370c1
--- /dev/null
@@ -0,0 +1,182 @@
+from setuptools_scm import format_version
+from setuptools_scm.hg import archival_to_version, parse
+from setuptools_scm import integration
+from setuptools_scm.config import Configuration
+from setuptools_scm.utils import has_command
+import pytest
+import warnings
+
+
+with warnings.catch_warnings():
+    warnings.filterwarnings("ignore")
+    if not has_command("hg"):
+        pytestmark = pytest.mark.skip(reason="hg executable not found")
+
+
+@pytest.fixture
+def wd(wd):
+    wd("hg init")
+    wd.add_command = "hg add ."
+    wd.commit_command = 'hg commit -m test-{reason} -u test -d "0 0"'
+    return wd
+
+
+archival_mapping = {
+    "1.0": {"tag": "1.0"},
+    "1.1.dev3+h000000000000": {
+        "latesttag": "1.0",
+        "latesttagdistance": "3",
+        "node": "0" * 20,
+    },
+    "0.0": {"node": "0" * 20},
+    "1.2.2": {"tag": "release-1.2.2"},
+    "1.2.2.dev0": {"tag": "release-1.2.2.dev"},
+}
+
+
+@pytest.mark.parametrize("expected,data", sorted(archival_mapping.items()))
+def test_archival_to_version(expected, data):
+    config = Configuration()
+    version = archival_to_version(data, config=config)
+    assert (
+        format_version(
+            version, version_scheme="guess-next-dev", local_scheme="node-and-date"
+        )
+        == expected
+    )
+
+
+def test_find_files_stop_at_root_hg(wd, monkeypatch):
+    wd.commit_testfile()
+    project = wd.cwd / "project"
+    project.mkdir()
+    project.joinpath("setup.cfg").touch()
+    # setup.cfg has not been committed
+    assert integration.find_files(str(project)) == []
+    # issue 251
+    wd.add_and_commit()
+    monkeypatch.chdir(project)
+    assert integration.find_files() == ["setup.cfg"]
+
+
+# XXX: better tests for tag prefixes
+def test_version_from_hg_id(wd):
+    assert wd.version == "0.0"
+
+    wd.commit_testfile()
+    assert wd.version.startswith("0.1.dev2+")
+
+    # tagging commit is considered the tag
+    wd('hg tag v0.1 -u test -d "0 0"')
+    assert wd.version == "0.1"
+
+    wd.commit_testfile()
+    assert wd.version.startswith("0.2.dev2")
+
+    wd("hg up v0.1")
+    assert wd.version == "0.1"
+
+    # commit originating from the taged revision
+    # that is not a actual tag
+    wd.commit_testfile()
+    assert wd.version.startswith("0.2.dev1+")
+
+    # several tags
+    wd("hg up")
+    wd('hg tag v0.2 -u test -d "0 0"')
+    wd('hg tag v0.3 -u test -d "0 0" -r v0.2')
+    assert wd.version == "0.3"
+
+
+def test_version_from_archival(wd):
+    # entrypoints are unordered,
+    # cleaning the wd ensure this test wont break randomly
+    wd.cwd.joinpath(".hg").rename(wd.cwd / ".nothg")
+    wd.write(".hg_archival.txt", "node: 000000000000\n" "tag: 0.1\n")
+    assert wd.version == "0.1"
+
+    wd.write(
+        ".hg_archival.txt",
+        "node: 000000000000\n" "latesttag: 0.1\n" "latesttagdistance: 3\n",
+    )
+
+    assert wd.version == "0.2.dev3+h000000000000"
+
+
+@pytest.mark.issue("#72")
+def test_version_in_merge(wd):
+    wd.commit_testfile()
+    wd.commit_testfile()
+    wd("hg up 0")
+    wd.commit_testfile()
+    wd("hg merge --tool :merge")
+    assert wd.version is not None
+
+
+@pytest.mark.issue(128)
+def test_parse_no_worktree(tmpdir):
+    ret = parse(str(tmpdir))
+    assert ret is None
+
+
+@pytest.fixture
+def version_1_0(wd):
+    wd("hg branch default")
+    wd.commit_testfile()
+    wd('hg tag 1.0.0 -u test -d "0 0"')
+    return wd
+
+
+@pytest.fixture
+def pre_merge_commit_after_tag(wd, version_1_0):
+    wd("hg branch testbranch")
+    wd.write("branchfile", "branchtext")
+    wd(wd.add_command)
+    wd.commit()
+    wd("hg update default")
+    wd("hg merge testbranch")
+    return wd
+
+
+@pytest.mark.usefixtures("pre_merge_commit_after_tag")
+def test_version_bump_before_merge_commit(wd):
+    assert wd.version.startswith("1.0.1.dev1+")
+
+
+@pytest.mark.issue(219)
+@pytest.mark.usefixtures("pre_merge_commit_after_tag")
+def test_version_bump_from_merge_commit(wd):
+    wd.commit()
+    assert wd.version.startswith("1.0.1.dev3+")  # issue 219
+
+
+@pytest.mark.usefixtures("version_1_0")
+def test_version_bump_from_commit_including_hgtag_mods(wd):
+    """ Test the case where a commit includes changes to .hgtags and other files
+    """
+    with wd.cwd.joinpath(".hgtags").open("ab") as tagfile:
+        tagfile.write(b"0  0\n")
+    wd.write("branchfile", "branchtext")
+    wd(wd.add_command)
+    assert wd.version.startswith("1.0.1.dev1+")  # bump from dirty version
+    wd.commit()  # commits both the testfile _and_ .hgtags
+    assert wd.version.startswith("1.0.1.dev2+")
+
+
+@pytest.mark.issue(229)
+@pytest.mark.usefixtures("version_1_0")
+def test_latest_tag_detection(wd):
+    """ Tests that tags not containing a "." are ignored, the same as for git.
+    Note that will be superceded by the fix for pypa/setuptools_scm/issues/235
+    """
+    wd('hg tag some-random-tag -u test -d "0 0"')
+    assert wd.version == "1.0.0"
+
+
+@pytest.mark.usefixtures("version_1_0")
+def test_feature_branch_increments_major(wd):
+
+    wd.commit_testfile()
+    assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.0.1")
+    wd("hg branch feature/fun")
+    assert wd.get_version(version_scheme="python-simplified-semver").startswith("1.1.0")
diff --git a/testing/test_regressions.py b/testing/test_regressions.py
new file mode 100644 (file)
index 0000000..8bde373
--- /dev/null
@@ -0,0 +1,90 @@
+import sys
+import subprocess
+
+from setuptools_scm import get_version
+from setuptools_scm.git import parse
+from setuptools_scm.utils import do_ex, do
+
+import pytest
+
+
+def test_pkginfo_noscmroot(tmpdir, monkeypatch):
+    """if we are indeed a sdist, the root does not apply"""
+    monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+
+    # we should get the version from pkg-info if git is broken
+    p = tmpdir.ensure("sub/package", dir=1)
+    tmpdir.mkdir(".git")
+    p.join("setup.py").write(
+        "from setuptools import setup;" 'setup(use_scm_version={"root": ".."})'
+    )
+
+    _, stderr, ret = do_ex((sys.executable, "setup.py", "--version"), p)
+    assert "setuptools-scm was unable to detect version for" in stderr
+    assert ret == 1
+
+    p.join("PKG-INFO").write("Version: 1.0")
+    res = do((sys.executable, "setup.py", "--version"), p)
+    assert res == "1.0"
+
+    try:
+        do("git init", p.dirpath())
+    except OSError:
+        pass
+    else:
+        res = do((sys.executable, "setup.py", "--version"), p)
+        assert res == "0.1.dev0"
+
+
+def test_pip_egg_info(tmpdir, monkeypatch):
+    """if we are indeed a sdist, the root does not apply"""
+
+    # we should get the version from pkg-info if git is broken
+    p = tmpdir.ensure("sub/package", dir=1)
+    tmpdir.mkdir(".git")
+    p.join("setup.py").write(
+        "from setuptools import setup;" 'setup(use_scm_version={"root": ".."})'
+    )
+
+    with pytest.raises(LookupError):
+        get_version(root=p.strpath, fallback_root=p.strpath)
+
+    p.ensure("pip-egg-info/random.egg-info/PKG-INFO").write("Version: 1.0")
+    assert get_version(root=p.strpath, fallback_root=p.strpath) == "1.0"
+
+
+@pytest.mark.issue(164)
+def test_pip_download(tmpdir, monkeypatch):
+    monkeypatch.chdir(tmpdir)
+    subprocess.check_call([sys.executable, "-m", "pip", "download", "lz4==0.9.0"])
+
+
+def test_use_scm_version_callable(tmpdir, monkeypatch):
+    """use of callable as use_scm_version argument"""
+    monkeypatch.delenv("SETUPTOOLS_SCM_DEBUG")
+
+    p = tmpdir.ensure("sub/package", dir=1)
+    p.join("setup.py").write(
+        """from setuptools import setup
+def vcfg():
+    from setuptools_scm.version import guess_next_dev_version
+    def vs(v):
+        return guess_next_dev_version(v)
+    return {"version_scheme": vs}
+setup(use_scm_version=vcfg)
+"""
+    )
+    p.join("PKG-INFO").write("Version: 1.0")
+
+    res = do((sys.executable, "setup.py", "--version"), p)
+    assert res == "1.0"
+
+
+@pytest.mark.skipif(sys.platform != "win32", reason="this bug is only valid on windows")
+def test_case_mismatch_on_windows_git(tmpdir):
+    """Case insensitive path checks on Windows"""
+    p = tmpdir.ensure("CapitalizedDir", dir=1)
+
+    do("git init", p)
+    res = parse(str(p).lower())
+    assert res is not None
diff --git a/testing/test_setuptools_support.py b/testing/test_setuptools_support.py
new file mode 100644 (file)
index 0000000..ce3444e
--- /dev/null
@@ -0,0 +1,83 @@
+"""
+integration tests that check setuptools version support
+"""
+import sys
+import os
+import subprocess
+import pytest
+
+pytestmark = [
+    pytest.mark.skipif(
+        "sys.version_info >= (3,6,0)",
+        reason="integration with old versions no longer needed on py3.6+",
+    ),
+    pytest.mark.xfail(
+        sys.platform == "win32", reason="path behaves unexpected on windows ci"
+    ),
+]
+
+
+@pytest.fixture(scope="session")
+def get_setuptools_packagedir(request):
+    targets = request.config.cache.makedir("setuptools_installs")
+
+    def makeinstall(version):
+        target = targets.ensure(version, dir=1)
+        subprocess.check_call(
+            [
+                sys.executable,
+                "-m",
+                "pip",
+                "install",
+                "--no-binary",
+                "setuptools",
+                "setuptools==" + version,
+                "-t",
+                str(target),
+            ]
+        )
+        return target
+
+    return makeinstall
+
+
+SCRIPT = """
+from __future__ import print_function
+import sys
+import setuptools
+print(setuptools.__version__, 'expected', sys.argv[1])
+import setuptools_scm.version
+from setuptools_scm.__main__ import main
+main()
+"""
+
+
+def check(packagedir, expected_version, **env):
+
+    old_pythonpath = os.environ.get("PYTHONPATH")
+    if old_pythonpath:
+        pythonpath = "{}:{}".format(old_pythonpath, packagedir)
+    else:
+        pythonpath = str(packagedir)
+    subprocess.check_call(
+        [sys.executable, "-c", SCRIPT, expected_version],
+        env=dict(os.environ, PYTHONPATH=pythonpath, **env),
+    )
+
+
+def test_old_setuptools_fails(get_setuptools_packagedir):
+    packagedir = get_setuptools_packagedir("0.9.8")
+    with pytest.raises(subprocess.CalledProcessError):
+        check(packagedir, "0.9.8")
+
+
+def test_old_setuptools_allows_with_warnings(get_setuptools_packagedir):
+
+    packagedir = get_setuptools_packagedir("0.9.8")
+    # filter using warning since in the early python startup
+    check(packagedir, "0.9.8", PYTHONWARNINGS="once::Warning")
+
+
+def test_distlib_setuptools_works(get_setuptools_packagedir):
+    packagedir = get_setuptools_packagedir("12.0.1")
+    check(packagedir, "12.0.1")
diff --git a/testing/test_version.py b/testing/test_version.py
new file mode 100644 (file)
index 0000000..0b487b3
--- /dev/null
@@ -0,0 +1,111 @@
+import pytest
+from setuptools_scm.config import Configuration
+from setuptools_scm.version import (
+    meta,
+    simplified_semver_version,
+    release_branch_semver_version,
+    tags_to_versions,
+)
+
+
+c = Configuration()
+
+
+@pytest.mark.parametrize(
+    "version, expected_next",
+    [
+        pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"),
+        pytest.param(meta("1.0", config=c), "1.0.0", id="short_tag"),
+        pytest.param(
+            meta("1.0.0", distance=2, branch="default", config=c),
+            "1.0.1.dev2",
+            id="normal_branch",
+        ),
+        pytest.param(
+            meta("1.0", distance=2, branch="default", config=c),
+            "1.0.1.dev2",
+            id="normal_branch_short_tag",
+        ),
+        pytest.param(
+            meta("1.0.0", distance=2, branch="feature", config=c),
+            "1.1.0.dev2",
+            id="feature_branch",
+        ),
+        pytest.param(
+            meta("1.0", distance=2, branch="feature", config=c),
+            "1.1.0.dev2",
+            id="feature_branch_short_tag",
+        ),
+        pytest.param(
+            meta("1.0.0", distance=2, branch="features/test", config=c),
+            "1.1.0.dev2",
+            id="feature_in_branch",
+        ),
+    ],
+)
+def test_next_semver(version, expected_next):
+    computed = simplified_semver_version(version)
+    assert computed == expected_next
+
+
+@pytest.mark.parametrize(
+    "version, expected_next",
+    [
+        pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"),
+        pytest.param(
+            meta("1.0.0", distance=2, branch="master", config=c),
+            "1.1.0.dev2",
+            id="development_branch",
+        ),
+        pytest.param(
+            meta("1.0.0rc1", distance=2, branch="master", config=c),
+            "1.1.0.dev2",
+            id="development_branch_release_candidate",
+        ),
+        pytest.param(
+            meta("1.0.0", distance=2, branch="maintenance/1.0.x", config=c),
+            "1.0.1.dev2",
+            id="release_branch_legacy_version",
+        ),
+        pytest.param(
+            meta("1.0.0", distance=2, branch="release-1.0", config=c),
+            "1.0.1.dev2",
+            id="release_branch_with_prefix",
+        ),
+        pytest.param(
+            meta("1.0.0", distance=2, branch="bugfix/3434", config=c),
+            "1.1.0.dev2",
+            id="false_positive_release_branch",
+        ),
+    ],
+)
+def test_next_release_branch_semver(version, expected_next):
+    computed = release_branch_semver_version(version)
+    assert computed == expected_next
+
+
+@pytest.mark.parametrize(
+    "tag, expected",
+    [
+        pytest.param("v1.0.0", "1.0.0"),
+        pytest.param("v1.0.0-rc.1", "1.0.0rc1"),
+        pytest.param("v1.0.0-rc.1+-25259o4382757gjurh54", "1.0.0rc1"),
+    ],
+)
+def test_tag_regex1(tag, expected):
+    config = Configuration()
+    if "+" in tag:
+        # pytest bug wrt cardinality
+        with pytest.warns(UserWarning):
+            result = meta(tag, config=config)
+    else:
+        result = meta(tag, config=config)
+
+    assert result.tag.public == expected
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/286")
+def test_tags_to_versions():
+    config = Configuration()
+    versions = tags_to_versions(["1.0", "2.0", "3.0"], config=config)
+    assert isinstance(versions, list)  # enable subscription
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..447c67e
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,83 @@
+[tox]
+envlist=py{27,34,35,36,37,38}-test,flake8,check_readme,py{27,37}-selfcheck
+
+[pytest]
+testpaths=testing
+filterwarnings=error
+markers=
+    issue(id): reference to github issue
+
+[flake8]
+max-complexity = 10
+max-line-length = 88
+ignore=E203,W503
+exclude=
+       .git,
+       .tox,
+       .env,
+       .venv,
+       .pytest_cache,
+       __pycache__,
+       ./setuptools_scm/win_py31_compat.py
+
+[testenv]
+usedevelop=True
+skip_install=
+    selfcheck: True
+    test: False
+deps=
+    pytest
+    setuptools >= 42
+commands=
+    test: pytest []
+    selfcheck: python setup.py --version
+extras =
+    toml
+
+[testenv:flake8]
+skip_install=True
+deps=
+    flake8
+    mccabe
+commands =
+    flake8 src/setuptools_scm/ testing/ setup.py --exclude=setuptools_scm/win_py31_compat.py
+
+[testenv:check_readme]
+skip_install=True
+setenv = SETUPTOOLS_SCM_PRETEND_VERSION=2.0
+deps=
+    readme
+    check-manifest
+commands=
+    python setup.py check -r
+    rst2html.py README.rst {envlogdir}/README.html --strict []
+    check-manifest
+
+[testenv:upload]
+deps=
+    wheel
+    twine
+commands=
+    python setup.py clean --all rotate -k - -m .whl,.tar.gz,.zip
+    python setup.py -q egg_info
+    python setup.py -q sdist --formats zip bdist_wheel register
+
+
+
+[testenv:dist]
+deps= wheel
+whitelist_externals = rm
+commands=
+    python setup.py -q clean --all
+    python setup.py -q rotate -k 0 -m .egg,.zip,.whl,.tar.gz
+    python setup.py -q egg_info
+    python setup.py -q sdist --formats zip,bztar bdist_wheel upload
+
+[testenv:devpi]
+deps=
+    devpi-client
+commands =
+    python setup.py -q egg_info
+    devpi upload --from-dir dist
+
+#XXX: envs for hg versions