How can I rewrite python __version__ with git?
I would like to define a __version__
variable in my module which should be automatically updated on git commit similarly to what SVN keywords do. Is there a way to achieve it in Git? Does anyone have a working example?
I considered using GitPython, but I would not like to introduce another dependency and I want users who download the module from SVN repo or as a zip package to have the same versioning scheme (I do not care that is some illegible hash).
Edit: My particular problem is that I have to run simulations whose result depend on the exact version of the simulation script. Therefore each time I have to store version number together with the simulation results. If both run out of sync, it may have very dire outcomes.
It might be better to do this as part of your packaging, rather than after every commit.
There are two primary options:
Use
git-archive
to package, and use theexport-subst
attribute. Unfortunately, the things you can substitute in are limited to the placeholders fromgit log --format=...
. For example, you could write__version__ = $Format:%H$
in your file, put<filename> export-subst
in your .gitattributes, and when you rungit archive
, that'd be changed to the full hash of the commit you're archiving with. This is just about what you're asking for, but I do prefer the next option.Do it yourself as part of a packaging process (often a build process for compiled packages), and use
git describe
. That will get you a nice pretty string likev1.7.4.1-59-ge3d3f7d
, meaning "59 commits past the tagv1.7.4.1
, at commitge3d3f7d
" which you can then insert somehow into the right place in your code as you package/build. This is what Git itself does; the result is dumped to a file, whose contents are read into the makefile and then passed into the build via a-D
preprocessor option, and placed into various filenames (e.g. the release tarball) directly.
If you really, really want to do this after every commit, you could, with a post-commit hook, but then only you (and those you give the hook to) will have it, and it's very very possible to get out of sync - you'll also have to have a post-checkout hook, and so on and so on. It's really better for whatever processes that create something needing this version number to get it themselves.
You could also use a smudge/clean filter, which would be more like what you actually want (rather than simply after every commit).
Another possibility other than Versioneer is setuptools_scm
.
I have successfully implemented something very similar to the OP by adding the following to setup.py
(or by modifying it accordingly):
from setuptools import setup
setup(
...,
use_scm_version=True,
setup_requires=['setuptools_scm'],
...,
)
and, in order to have __version__
updated automatically, added this to __init__.py
of my package:
from pkg_resources import get_distribution, DistributionNotFound
try:
__version__ = get_distribution(__name__).version
except DistributionNotFound:
# package is not installed
pass
For those finding this question in 2018 you can also use Versioneer. Once enabled, it automatically sets version
in setup.py
and __version__
in your module based on the latest Git tag at time of build.
For example, if you build your project at tag 1.0.0
, Versioneer will set the project's version to 1.0.0. If you do two more commits, edit some without checking in, and build, Versioneer will set the version to something like 1.0.0+2.g1076c97.dirty
.
Of course you can customize which tags Versioneer recognizes as version tags.
This is also how larger projects like pandas and matplotlib handle their versioning.
(1) One option is to get the version number in real-time from the release version written into a published package. To do this, you would add a dependency to __init__.py
, release the product with something like setup.py
and at runtime execute python3 setup.py --version
. This approach uses the lightweight importlib_metadata module [importlib_metadata
(for pre Python 3.8) and importlib.metadata
(for Python 3.8+)]:
from importlib.metadata import version, PackageNotFoundError
# pre-3.8 import statement
# from importlib_metadata import version, PackageNotFoundError
VERSION_FALLBACK = "0.0.0"
try:
__version__ = version(__name__)
except PackageNotFoundError:
# package is not installed
# assign signal or sane value as a default
__version__ = VERSION_FALLBACK
pass
This implements metadata recommendations from PEP 566. If you release with setuptools>=42.0.0
this works great, and it may work with packages released by other tooling, also.
(2) A second option is to do something with Git to collect the last tag value (assuming you are tagging the application). Then increment the point version number. Then replace the value in the init file with the new value and tag with the new value.
# get and increment semantic version
version=$( git tag --list | sort -rV | head -1 ); # v0.1.1
version_point=${version##*.} # 1
version_point=$((${version_point} + 1)) # 2
version="${version%.*}.${version_point}" # v0.1.2
# replace in __init__.py (NOTE: THIS OVERWRITES!)
cat __init.py__ | sed -e "s/VERSION=.*/VERSION=${version}/i" > __init__.py
git add __init__.py && git commit -m "Updated version in __init__.py"
git tag -a ${version} -m "Latest tagged version"