mirror of
https://github.com/TECHNOFAB11/bumpver.git
synced 2025-12-12 14:30:09 +01:00
update readme
This commit is contained in:
parent
fe019fa546
commit
1cec699750
2 changed files with 426 additions and 432 deletions
854
README.md
854
README.md
|
|
@ -1,15 +1,10 @@
|
||||||
# [PyCalVer: Automatic CalVer Versioning for Python Packages][repo_ref]
|
# [PyCalVer: Automatic CalVer Versioning for Python Packages][repo_ref]
|
||||||
|
|
||||||
PyCalVer is a simple calendar based versioning system. With a single
|
PyCalVer is for projects that only have one semantic: newer ==
|
||||||
`pycalver bump` command it will:
|
better. PyCalVer version strings are compatible with python
|
||||||
|
packaging software [setuptools][setuptools_ref] and
|
||||||
- Automatically update version strings across files in your project.
|
[PEP440][pep_440_ref], but can in principle be used with any
|
||||||
- Commit those changes and tag the commit with the new version.
|
project.
|
||||||
|
|
||||||
Version strings generated by pycalver are compatible with python
|
|
||||||
packaging software
|
|
||||||
[setuptools](https://setuptools.readthedocs.io/en/latest/setuptools.html#specifying-your-project-s-version>)
|
|
||||||
[PEP440](https://www.python.org/dev/peps/pep-0440/).
|
|
||||||
|
|
||||||
|
|
||||||
Project/Repo:
|
Project/Repo:
|
||||||
|
|
@ -43,25 +38,17 @@ Code Quality/CI:
|
||||||
[](TOC)
|
[](TOC)
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Format](#format)
|
- [Semantics of PyCalVer](#semantics-of-pycalver)
|
||||||
- [Versioning Behaviour](#versioning-behaviour)
|
- [Breaking Changes](#breaking-changes)
|
||||||
- [Lexical Ids](#lexical-ids)
|
- [Zeno's 1.0 and the Eternal Beta](#zenos-10-and-the-eternal-beta)
|
||||||
|
- [Version String Format](#version-string-format)
|
||||||
|
- [Incrementing Behaviour](#incrementing-behaviour)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Bump It Up](#bump-it-up)
|
|
||||||
- [Pattern Search and Replacement](#pattern-search-and-replacement)
|
- [Pattern Search and Replacement](#pattern-search-and-replacement)
|
||||||
- [Rational](#rational)
|
- [Bump It Up](#bump-it-up)
|
||||||
- [Other Versioning Software](#other-versioning-software)
|
- [Version State](#version-state)
|
||||||
- [Some Details](#some-details)
|
- [Lexical Ids](#lexical-ids)
|
||||||
- [Realities of Version Numbers](#realities-of-version-numbers)
|
|
||||||
- [Should I use PyCalVer for my Project?](#should-i-use-pycalver-for-my-project)
|
|
||||||
- [Marketing/Vanity](#marketing-vanity)
|
|
||||||
- [Rational](#rational-1)
|
|
||||||
- [Breaking Things is a Big Deal](#breaking-things-is-a-big-deal)
|
|
||||||
- [A Word on Marketing](#a-word-on-marketing)
|
|
||||||
- [Commitment to Compatibility](#commitment-to-compatibility)
|
|
||||||
- [The Life of a Library](#the-life-of-a-library)
|
|
||||||
- [FAQ](#faq)
|
|
||||||
|
|
||||||
[](TOC)
|
[](TOC)
|
||||||
|
|
||||||
|
|
@ -95,6 +82,197 @@ v202207.18133
|
||||||
v202207.18134
|
v202207.18134
|
||||||
```
|
```
|
||||||
|
|
||||||
|
PyCalVer is inspired by:
|
||||||
|
|
||||||
|
- ["Speculation" talk by Rich
|
||||||
|
Hicky](https://www.youtube.com/watch?v=oyLBGkS5ICk)
|
||||||
|
- [Designing a Version by Mahmoud
|
||||||
|
Hashemi](http://sedimental.org/designing_a_version.html)
|
||||||
|
- [calver.org](https://calver.org/)
|
||||||
|
- ["The cargo cult of versioning" by Kartik
|
||||||
|
Agaram](http://akkartik.name/post/versioning)
|
||||||
|
- The [bumpversion][bumpversion_ref] project, upon which
|
||||||
|
PyCalVer is partially based.
|
||||||
|
|
||||||
|
If you are familiar with these, feel free to skip ahead to
|
||||||
|
[Usage](#usage)
|
||||||
|
|
||||||
|
|
||||||
|
## Semantics of PyCalVer
|
||||||
|
|
||||||
|
> Disclaimer: This section is aspirational and of course there
|
||||||
|
> is nothing to prevent package maintainers from publishing
|
||||||
|
> packages with different semantics than what is laid out here.
|
||||||
|
|
||||||
|
PyCalVer places a greater burden on package maintainers than
|
||||||
|
SemVer. Backward incompatibility is not encoded, because
|
||||||
|
maintainers should not make any breaking changes. This is great
|
||||||
|
for users of a package, who can worry a bit less about an update
|
||||||
|
breaking their project. If they're paranoid, they can of course
|
||||||
|
still pin to known good versions, but ideally they don't need
|
||||||
|
version specifier in their requirements.txt so they always get
|
||||||
|
the latest bug fixes and features. Ideally users can trust the
|
||||||
|
promise of a maintainer that the following semantics will always
|
||||||
|
be true:
|
||||||
|
|
||||||
|
- Newer is compatible.
|
||||||
|
- Newer has fewer bugs.
|
||||||
|
- Newer has more features.
|
||||||
|
- Newer has similar or better performance.
|
||||||
|
|
||||||
|
The world is not ideal of course, so how do users and
|
||||||
|
maintainers deal with changes that might violate these promises?
|
||||||
|
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
> Namespaces are one honking great idea
|
||||||
|
> -- let's do more of those!
|
||||||
|
|
||||||
|
If you must make a breaking change to a package, *instead of
|
||||||
|
incrementing a number*, the recommended approach with PyCalVer
|
||||||
|
is to *create a whole new package*. Put differently, the major
|
||||||
|
version becomes part of the package/module namespace. Typically
|
||||||
|
you might add a numerical suffix, eg. `mypkg -> mypkg2`.
|
||||||
|
|
||||||
|
The other kind of breaking change is the non-intentional kind,
|
||||||
|
otherwise known as a bug. Realize first of all, that it is
|
||||||
|
impossible for any versioning system to encode that this has
|
||||||
|
happened: Since the maintainer isn't knowingly introducing a bug
|
||||||
|
they naturally won't set a version to reflect something they
|
||||||
|
don't know about. Instead we have to deal with this issue after
|
||||||
|
the fact.
|
||||||
|
|
||||||
|
The first thing a package maintainer can do is to minimize the
|
||||||
|
chance of inflicting buggy software on users. After any
|
||||||
|
non-trivial (maybe unsafe) change, create a new
|
||||||
|
`-alpha`/`-beta`/`-rc` release. These so called `--pre` releases
|
||||||
|
are downloaded only by the few and the brave, who are willing to
|
||||||
|
participate in testing. After any any issues are ironed with the
|
||||||
|
`--pre` releases, a `final` release can be made for more
|
||||||
|
regular/conservative users.
|
||||||
|
|
||||||
|
Note that the default behaviour of `pip install <package>`
|
||||||
|
(without any version specifier) is to download the latest
|
||||||
|
`final` release. It will download a `--pre` release *only* if
|
||||||
|
|
||||||
|
1. there is no `final` release available yet
|
||||||
|
2. the `--pre` flag is explicitly used, or
|
||||||
|
3. if the requirement specifier explicitly includes the version
|
||||||
|
number of a pre release, eg. `pip install
|
||||||
|
mypkg==v201812.0007-alpha`.
|
||||||
|
|
||||||
|
Should a release include a bug (heaven forbid and despite all
|
||||||
|
precautions), then the maintainer should publish a new release
|
||||||
|
which either fixes the or reverts the change. It is important
|
||||||
|
that the new release have a greater version than the release
|
||||||
|
that contained the issue and that it use the same release
|
||||||
|
suffix. If users downloaded a version of the package which
|
||||||
|
included the bug, they only have to do `pip install --upgrade
|
||||||
|
<package>` and the issue will be resolved.
|
||||||
|
|
||||||
|
Perhaps a timeline will illustrate more clearly:
|
||||||
|
|
||||||
|
```
|
||||||
|
v201812.0665 # last stable release
|
||||||
|
v201812.0666-beta # pre release for testers
|
||||||
|
v201901.0667 # final release after testing
|
||||||
|
|
||||||
|
# bug is discovered which effects v201812.0666-beta and v201901.0667
|
||||||
|
|
||||||
|
v201901.0668-beta # fix is issued for testers
|
||||||
|
v201901.0669 # fix is issued everybody
|
||||||
|
|
||||||
|
# Alternatively, revert before fixing
|
||||||
|
|
||||||
|
v201901.0668 # identical code to v201812.0665
|
||||||
|
v201901.0669-beta # reintroduce change from v201812.0666-beta + fix
|
||||||
|
v201901.0670 # final release after testing
|
||||||
|
```
|
||||||
|
|
||||||
|
In the absolute worst case, a change is discovered to break
|
||||||
|
backward compatibility, but the change is still considered to be
|
||||||
|
desirable. At that point, a new release should be made to revert
|
||||||
|
the change. This way:
|
||||||
|
|
||||||
|
- users who were not exposed to the breaking change will
|
||||||
|
download the the newest release with the reverted changes.
|
||||||
|
- users who were exposed to the breaking change can update to
|
||||||
|
the latest release and get the old working code again.
|
||||||
|
|
||||||
|
Remember that the goal is to always make things easy for users
|
||||||
|
who depend on a package. If there is any issue whatsoever, all
|
||||||
|
they should have to do is `pip install --update`. If this
|
||||||
|
doesn't work, they may have to temporarily pin to a known good
|
||||||
|
version of a dependency, at least until a fixed release is
|
||||||
|
uploaded.
|
||||||
|
|
||||||
|
After this immediate fire has been put out, if the maintainer
|
||||||
|
considers the breaking change worth keeping, they can **create a
|
||||||
|
new package**, with a new namespace. This package will perhaps
|
||||||
|
have 99% overlap to the previous one and the old one may
|
||||||
|
eventually be abandoned.
|
||||||
|
|
||||||
|
```
|
||||||
|
mypkg v201812.0665 # last stable release
|
||||||
|
mypkg v201812.0666-rc # pre release for testers
|
||||||
|
mypkg v201901.0667 # final release after testing period
|
||||||
|
|
||||||
|
# bug is discovered in v201812.0666-beta and v201901.0667
|
||||||
|
|
||||||
|
mypkg v201901.0668 # identical code to v201812.0665
|
||||||
|
|
||||||
|
# new package is created with compatibility breaking code
|
||||||
|
|
||||||
|
mypkg2 v201901.0669 # identical code to v201901.0667
|
||||||
|
mypkg v201901.0669 # updated readme, declaring support
|
||||||
|
# level for mypkg, pointing to mypgk2
|
||||||
|
# and documenting how to migrate.
|
||||||
|
```
|
||||||
|
|
||||||
|
If this seems like overkill, consider investing time to minimize
|
||||||
|
the overhead of creating new packages. Consider also that your
|
||||||
|
projects may recursively depend on dozens of libraries which
|
||||||
|
you've never even heard of. If every maintainer introduced
|
||||||
|
breaking changes only once per year, users who depend on these
|
||||||
|
libraries would be dealing with packaging issues every month! In
|
||||||
|
other words: *Breaking things is a big deal*. A bit of extra
|
||||||
|
effort for a few maintainers seems like a fair trade compared to
|
||||||
|
the effort of many users who would be perfectly happy to use the
|
||||||
|
old code until they can find the time to migrate.
|
||||||
|
|
||||||
|
When creating a new package, it may be worthwhile to rename not
|
||||||
|
just the package, but also its python module(s). The benefit of
|
||||||
|
this is that users can install both packages in the same
|
||||||
|
environment and import both old and new modules. The downside is
|
||||||
|
that users have to change their code, even if the breaking
|
||||||
|
change did not affect them.
|
||||||
|
|
||||||
|
|
||||||
|
### Zeno's 1.0 and the Eternal Beta
|
||||||
|
|
||||||
|
With PyCalVer, the release tag (`-alpha`, `-beta`, `-rc`) says
|
||||||
|
something about the stability of a *particular release*. This is
|
||||||
|
similar ([perhaps identical][pep_101_ref]) to the meaning of
|
||||||
|
release tags used by the CPython interpreter. A release tag is
|
||||||
|
not a statement general stability of the software as a whole, it
|
||||||
|
is metadata about a particular release artifact of a package,
|
||||||
|
eg. a `.whl` file.
|
||||||
|
|
||||||
|
There is a temptation for maintainers to avoid any commitment to
|
||||||
|
backward compatibility by forever staying in beta or in the case
|
||||||
|
of SemVer, by never incriminating the major version, leading to
|
||||||
|
the [Zeno 1.0 paradox][zeno_1_dot_0_ref]. Of course an unpaid
|
||||||
|
Open Source developer *does not owe anybody a commitment to
|
||||||
|
backward compatibility*. Especially when a project is young and
|
||||||
|
going through major changes, such a commitment may not make any
|
||||||
|
sense. For these cases you can still use PyCalVer, just so long
|
||||||
|
as there is a big fat warning at the top of your README. Another
|
||||||
|
way to signify that a project is still in early development is
|
||||||
|
to not publish a `final` release until the codebase has become
|
||||||
|
stable.
|
||||||
|
|
||||||
|
|
||||||
### Version String Format
|
### Version String Format
|
||||||
|
|
||||||
The format for PyCalVer version strings can be parsed with this
|
The format for PyCalVer version strings can be parsed with this
|
||||||
|
|
@ -149,22 +327,26 @@ assert version_info == {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Versioning Behaviour
|
### Incrementing Behaviour
|
||||||
|
|
||||||
To see how version strings are incremented, we can use
|
To see how version strings are incremented, we can use
|
||||||
`pycalver incr`:
|
`pycalver test`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ pip install pycalver
|
$ pip install pycalver
|
||||||
...
|
...
|
||||||
$ pycalver incr v201801.0033-beta
|
Successfully installed pycalver-201812.12
|
||||||
|
$ pycavler --version
|
||||||
|
pycalver, version v201812.0012
|
||||||
|
$ pycalver test v201801.0033-beta
|
||||||
PyCalVer Version: v201809.0034-beta
|
PyCalVer Version: v201809.0034-beta
|
||||||
PEP440 Version: 201809.34b0
|
PEP440 Version : 201809.34b0
|
||||||
```
|
```
|
||||||
|
|
||||||
This is the simple case:
|
This is the simple case:
|
||||||
|
|
||||||
- The calendar component is update to the current year and month.
|
- The calendar component is updated to the current year and
|
||||||
|
month.
|
||||||
- The build number is incremented by 1.
|
- The build number is incremented by 1.
|
||||||
- The optional release tag is preserved as is.
|
- The optional release tag is preserved as is.
|
||||||
|
|
||||||
|
|
@ -172,88 +354,17 @@ You can explicitly update the release tag using the
|
||||||
`--release=<tag>` argument:
|
`--release=<tag>` argument:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ pycalver incr v201801.0033-alpha --release=beta
|
$ pycalver test v201801.0033-alpha --release=beta
|
||||||
PyCalVer Version: v201809.0034-beta
|
PyCalVer Version: v201809.0034-beta
|
||||||
PEP440 Version: 201809.34b0
|
PEP440 Version : 201809.34b0
|
||||||
$ pycalver incr v201809.0034-beta --release=final
|
$ pycalver test v201809.0034-beta --release=final
|
||||||
PyCalVer Version: v201809.0035
|
PyCalVer Version: v201809.0035
|
||||||
PEP440 Version: 201809.35
|
PEP440 Version : 201809.35
|
||||||
```
|
```
|
||||||
|
|
||||||
The version number is padded with extra zeros, to maintain the
|
To maintain lexical ordering of version numbers, the version
|
||||||
lexical ordering of version numbers. What happens when the
|
number is padded with extra zeros (see [Lexical
|
||||||
padding is exhausted?
|
Ids](#lexical-ids) ).
|
||||||
|
|
||||||
```shell
|
|
||||||
$ pycalver incr v201809.0999
|
|
||||||
PyCalVer Version: v201809.11000
|
|
||||||
PEP440 Version: 201809.11000
|
|
||||||
```
|
|
||||||
|
|
||||||
This is because the build number is generated as a sequence of
|
|
||||||
lexical ids.
|
|
||||||
|
|
||||||
|
|
||||||
### Lexical Ids
|
|
||||||
|
|
||||||
The build number padding may eventually be exhausted. In order to
|
|
||||||
preserve both lexical ordering, build numbers are incremented in
|
|
||||||
a special way. Examples will perhaps illustrate more clearly.
|
|
||||||
|
|
||||||
```python
|
|
||||||
"0001"
|
|
||||||
"0002"
|
|
||||||
"0003"
|
|
||||||
...
|
|
||||||
"0999"
|
|
||||||
"11000"
|
|
||||||
"11001"
|
|
||||||
...
|
|
||||||
"19998"
|
|
||||||
"19999"
|
|
||||||
"220000"
|
|
||||||
"220001"
|
|
||||||
```
|
|
||||||
|
|
||||||
What is happening here is that the left-most digit is incremented
|
|
||||||
early/preemptively. Whenever the left-most digit would change,
|
|
||||||
the padding of the id is expanded using this simple formula:
|
|
||||||
|
|
||||||
```python
|
|
||||||
prev_id = "0999"
|
|
||||||
next_id = str(int(prev_id, 10) + 1) # "1000"
|
|
||||||
if prev_id[0] != next_id[0]: # "0" != "1"
|
|
||||||
next_id = str(int(next_id, 10) * 11) # 1000 * 11 = 11000
|
|
||||||
```
|
|
||||||
|
|
||||||
This behaviour ensures that the following semantic is always
|
|
||||||
preserved: `old_version < new_version`. This will be the case,
|
|
||||||
even if the padding was expanded and the version number was
|
|
||||||
incremented multiple times in the same month. To illustrate the
|
|
||||||
issue, imagine we did not expand the padding and instead just
|
|
||||||
incremented numerically.
|
|
||||||
|
|
||||||
```python
|
|
||||||
"0001"
|
|
||||||
"0002"
|
|
||||||
"0003"
|
|
||||||
...
|
|
||||||
"0999"
|
|
||||||
"1000"
|
|
||||||
...
|
|
||||||
"9999"
|
|
||||||
"10000"
|
|
||||||
```
|
|
||||||
|
|
||||||
Here we eventually run into a build number where the lexical
|
|
||||||
ordering is not preserved, since `"9999" < "10000" == False`.
|
|
||||||
This is a very rare corner case, but it's better to not have
|
|
||||||
to think about it.
|
|
||||||
|
|
||||||
Just as an example of why lexical ordering is a nice property to
|
|
||||||
have, there are lots of software which read git tags, but which
|
|
||||||
have no logic to parse version strings, which can nonetheless
|
|
||||||
order the version tags correctly.
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
@ -289,8 +400,8 @@ README.md =
|
||||||
{pep440_version}
|
{pep440_version}
|
||||||
```
|
```
|
||||||
|
|
||||||
This may or may not cover all version numbers across your
|
This probably won't cover all instances of version numbers across
|
||||||
repository. Something like the following may illustrate
|
your repository. Something like the following may illustrate
|
||||||
additional changes you might need to make.
|
additional changes you might need to make.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
|
|
@ -305,368 +416,249 @@ setup.cfg =
|
||||||
current_version = {version}
|
current_version = {version}
|
||||||
setup.py =
|
setup.py =
|
||||||
version="{pep440_version}"
|
version="{pep440_version}"
|
||||||
src/myproject.py =
|
myproject/__init__.py =
|
||||||
__version__ = "{version}"
|
__version__ = "{version}"
|
||||||
README.md =
|
README.md =
|
||||||
[PyCalVer {calver}{build}-{release}]
|
[PyCalVer {calver}{build}{release}]
|
||||||
img.shields.io/badge/PyCalVer-{calver}{build}--{release}-blue
|
img.shields.io/badge/PyCalVer-{calver}{build}-{release}-blue
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bump It Up
|
|
||||||
|
|
||||||
To increment and publish a new version, you can use the
|
To see if a pattern is found, you can use the `--dry` flag, which
|
||||||
`pycalver bump` command:
|
will leave your repository untouched and only show you a diff.
|
||||||
|
|
||||||
```shell
|
|
||||||
$ pycalver bump
|
|
||||||
```
|
|
||||||
|
|
||||||
This will do a few things
|
|
||||||
|
|
||||||
0. Check that you don't have any non-committed local changes.
|
|
||||||
1. Fetch the most recent global vcs tags from origin.
|
|
||||||
2. Generate a new version, incremented from on the most recent
|
|
||||||
tag on any branch.
|
|
||||||
3. Update version strings in all configured files.
|
|
||||||
4. Commit the updated version strings.
|
|
||||||
5. Tag the new commit.
|
|
||||||
6. Push the new commit to origin.
|
|
||||||
|
|
||||||
The current version is defined either as
|
|
||||||
|
|
||||||
- The lexically largest git/mercurial tag in the repository.
|
|
||||||
- The value of `pycalver.current_version` in setup.cfg
|
|
||||||
|
|
||||||
The git/mercurial tags are used to minimize the chance that the
|
|
||||||
same version will be generated for different revisions. As part
|
|
||||||
of doing `pycalver bump`, your local repository is updated
|
|
||||||
using `git fetch --tags`/`hg pull`, to ensure that all tags are
|
|
||||||
known locally
|
|
||||||
git push --follow-tags
|
|
||||||
|
|
||||||
the version To
|
|
||||||
avoid this completely you n
|
|
||||||
The value in setup.cfg is only used on projects that don't use
|
|
||||||
revision control.
|
|
||||||
If your project does not use git or mercurial for version
|
|
||||||
control, then the current_version will be set by `pycalver init`.
|
|
||||||
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ pycalver show
|
$ pycalver bump --dry --no-fetch --release rc
|
||||||
Current Version: v201809.0001-beta
|
INFO - Old Version: v201809.0001-beta
|
||||||
PEP440 Version: 201809.1b0
|
INFO - New Version: v201809.0002-rc
|
||||||
|
--- README.md
|
||||||
|
+++ README.md
|
||||||
|
@@ -11,7 +11,7 @@
|
||||||
|
|
||||||
$ pycalver bump --dry
|
[![Supported Python Versions][pyversions_img]][pyversions_ref]
|
||||||
TODO: diff output
|
-[![PyCalVer v201809.0001-beta][version_img]][version_ref]
|
||||||
Don't forget to do $ git push --tags
|
+[![PyCalVer v201809.0002-rc][version_img]][version_ref]
|
||||||
|
[![PyPI Releases][pypi_img]][pypi_ref]
|
||||||
|
|
||||||
|
--- myprojcet/__init__.py
|
||||||
|
+++ myprojcet/__init__.py
|
||||||
|
@@ -1,1 +1,1 @@
|
||||||
|
-__version__ = "v201809.0001-beta"
|
||||||
|
+__version__ = "v201809.0002-rc"
|
||||||
|
|
||||||
|
--- setup.py
|
||||||
|
+++ setup.py
|
||||||
|
@@ -44,7 +44,7 @@
|
||||||
|
name="myproject",
|
||||||
|
- version="201812.11b0",
|
||||||
|
+ version="201812.12rc0",
|
||||||
|
license="MIT",
|
||||||
```
|
```
|
||||||
|
|
||||||
TODO: commits and tags
|
|
||||||
|
|
||||||
|
|
||||||
### Pattern Search and Replacement
|
### Pattern Search and Replacement
|
||||||
|
|
||||||
`patterns` is used both to search for version strings and to
|
The `pycalver:file_patterns` section of the configuration is
|
||||||
generate the replacement strings. The following placeholders are
|
used both to search and also to replace version strings in your
|
||||||
available for use, everything else in a pattern is treated as
|
projects files. The following placeholders are available for
|
||||||
literal text.
|
use, everything else in a pattern is treated as literal text.
|
||||||
|
|
||||||
|
|
||||||
| placeholder | example |
|
| placeholder | example |
|
||||||
|------------------|--------------------|
|
|--------------------|--------------------|
|
||||||
| `pep440_version` | 201809.1b0 |
|
| `{pep440_version}` | 201809.1b0 |
|
||||||
| `version` | v201809.0001-alpha |
|
| `{version}` | v201809.0001-alpha |
|
||||||
| `calver` | v201809 |
|
| `{calver}` | v201809 |
|
||||||
| `year` | 2018 |
|
| `{year}` | 2018 |
|
||||||
| `month` | 09 |
|
| `{month}` | 09 |
|
||||||
| `build` | .0001 |
|
| `{build}` | .0001 |
|
||||||
| `release` | -alpha |
|
| `{build_no}` | 0001 |
|
||||||
|
| `{release}` | -alpha |
|
||||||
|
| `{release_tag}` | alpha |
|
||||||
|
|
||||||
|
|
||||||
Note that the separator/prefix characters are part of what is
|
Note that the separator/prefix characters can be part of what is
|
||||||
matched and generated for a given placeholder, and they should
|
matched and generated for a given placeholder. In other words,
|
||||||
not be included in your patterns.
|
assuming you have the following text in your README.md (note the
|
||||||
|
two dashes before alpha):
|
||||||
A further restriction is, that a version string cannot span
|
|
||||||
multiple lines in your source file.
|
|
||||||
|
|
||||||
Now we can call `pycalver bump` to bump all occurrences of
|
|
||||||
version strings in these files. Normally this will change local
|
|
||||||
files, but the `--dry` flag will instead display a diff of the
|
|
||||||
changes that would be applied.
|
|
||||||
|
|
||||||
|
|
||||||
## Rational
|
|
||||||
|
|
||||||
### Other Versioning Software
|
|
||||||
|
|
||||||
This project is very similar to
|
|
||||||
[bumpversion](https://github.com/peritus/bumpversion), upon which
|
|
||||||
it is partially based. So why another library?
|
|
||||||
PyCalVer version strings can be
|
|
||||||
generated automatically, usage is a bit more simple.
|
|
||||||
|
|
||||||
|
|
||||||
### Some Details
|
|
||||||
|
|
||||||
- Version numbers are for public releases. For the purposes of
|
|
||||||
development of the project itself, reference VCS branches and
|
|
||||||
commit ids are more appropriate.
|
|
||||||
- There should be only one person or system responsible for
|
|
||||||
updating the version number at the time of release, otherwise
|
|
||||||
the same version number may be generated for different builds.
|
|
||||||
- Lexical order is
|
|
||||||
|
|
||||||
|
|
||||||
Canonical PyCalVer version strings can be parsed with this
|
|
||||||
regular expression:
|
|
||||||
|
|
||||||
|
|
||||||
These are the full version strings, for public announcements and
|
|
||||||
conversations it will often be sufficient to refer simply to
|
|
||||||
`v201801`, by which the most recent `post` release build of
|
|
||||||
that month is meant.
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
version_str = "v201712.0027-beta"
|
https://img.shields.io/badge/PyCalVer-v201809.0001--alpha-blue.svg
|
||||||
version_dict = pycalver_re.match("v201712.0027-beta").groupdict()
|
```
|
||||||
import pkg_resources # from setuptools
|
|
||||||
version = pkg_resources.parse_version(version_str)
|
|
||||||
--
|
|
||||||
|
|
||||||
In [2]: version_dict
|
An appropriate pattern would be:
|
||||||
{'year': '2017', 'month': '12', 'build_nr': '0027', 'tag': 'beta'}
|
|
||||||
>>> str(version)
|
```ini
|
||||||
201712.27b0
|
README.md =
|
||||||
|
/badge/PyCalVer {calver}{build}-{release}-blue.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that neither the "v" prefix, nor the "." and "-"
|
||||||
|
separators are included in the pattern text, as they are
|
||||||
|
respectivly part of the `calver`, `build` and `release`
|
||||||
|
placeholders. Alternatively you can be more explicit.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
README.md =
|
||||||
|
/badge/PyCalVer v{year}{month}.{build_no}--{release_tag}-blue.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
One limitation to keep in mind is that a version string cannot
|
||||||
|
span multiple lines.
|
||||||
|
|
||||||
|
|
||||||
|
### Bump It Up
|
||||||
|
|
||||||
|
The current version that will be bumped is defined either as
|
||||||
|
|
||||||
|
- Initially: The value of `pycalver.current_version` in
|
||||||
|
`setup.cfg`/`pyproject.toml`/`pycalver.toml`. This is only
|
||||||
|
used if a project does not use a supported VCS or if no
|
||||||
|
version tags have been set so far.
|
||||||
|
- Typically: The lexically largest git/mercurial tag in the
|
||||||
|
repository.
|
||||||
|
|
||||||
|
As part of doing `pycalver bump`, your local VCS index is
|
||||||
|
updated using `git fetch --tags`/`hg pull`, to ensure that all
|
||||||
|
tags are known locally and the same version is not generated for
|
||||||
|
different commits. This is mitigates a rare corner case where
|
||||||
|
`pycalver bump` is invoked on different machines.
|
||||||
|
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pycalver show
|
||||||
|
INFO - fetching tags from remote (to turn off use: -n / --no-fetch)
|
||||||
|
Current Version: v201812.0005-beta
|
||||||
|
PEP440 Version : 201812.5b0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
To increment and publish a new version, you can use the
|
||||||
|
`pycalver bump` command, which will do a few things:
|
||||||
|
|
||||||
### Realities of Version Numbers
|
0. Check that your repo doesn't have any local changes.
|
||||||
|
1. *Fetch* the most recent global VCS tags from origin
|
||||||
Nobody knows what the semantics of a version number are, because
|
(--no-fetch to disable).
|
||||||
nobody can guarantee that a given release adheres to whatever
|
2. Generate a new version, incremented from on the most recent
|
||||||
convention one would like to imbibe it with. Lets just keep things
|
tag on any branch.
|
||||||
simple.
|
3. Update version strings in all configured files.
|
||||||
|
4. *Commit* the updated version strings.
|
||||||
- Version numbers should be recognizable as such, that's what
|
5. *Tag* the new commit.
|
||||||
the "v" prefix does.
|
6. *Push* the new commit and tag.
|
||||||
- A number like 201808 is recognizable to many as a number
|
|
||||||
derived from a calendar.
|
|
||||||
- alpha, beta are common parlance indicating software which is
|
|
||||||
still under development.
|
|
||||||
|
|
||||||
Some additional constraints are applied to conform with PEP440
|
|
||||||
|
|
||||||
|
|
||||||
### Should I use PyCalVer for my Project?
|
|
||||||
|
|
||||||
If your project is 1. not useful by itself, but only when used
|
|
||||||
by other software, 2. has a finite scope/a definition of "done",
|
|
||||||
3. your project has CI, a test suite with and decent code
|
|
||||||
coverage, then PyCalVer is worth considering.
|
|
||||||
You release at most once per month.
|
|
||||||
|
|
||||||
|
|
||||||
### Marketing/Vanity
|
|
||||||
|
|
||||||
Quotes from http://sedimental.org/designing_a_version.html
|
|
||||||
|
|
||||||
|
|
||||||
### Rational
|
|
||||||
|
|
||||||
PyCalVer is opinionated software. This keeps things simple,
|
|
||||||
when the opinion match yours, but makes it useless for
|
|
||||||
everybody else.
|
|
||||||
|
|
||||||
The less semantics you put in your version string, the better.
|
|
||||||
The ideal would be to only have a single semantic: newer ==
|
|
||||||
better.
|
|
||||||
|
|
||||||
Some projects depend recursively on hundreds of libraries, so
|
|
||||||
compatibility issues generated by your project can be a heavy
|
|
||||||
burden on thousands of users; users who learn of the existence
|
|
||||||
of your library for the first time in the form of a stack-trace.
|
|
||||||
PyCalVer is for projects that are committed to and can maintain
|
|
||||||
backward compatibility. Newer versions are always better,
|
|
||||||
updates are always safe, an update won't break things, and if it
|
|
||||||
does, the maintainer's hair is on fire and they will publish a
|
|
||||||
new release containing a fix ASAP.
|
|
||||||
|
|
||||||
Ideally, your user can just declare your library as a
|
|
||||||
dependency, without any extra version qualifier, and never have
|
|
||||||
to think about it again. If you do break something by accident,
|
|
||||||
their remedy is not to change their code, but to temporarily pin
|
|
||||||
an earlier version, until your bugfix release is ready.
|
|
||||||
|
|
||||||
PyCalVer is for projects which are the mundane but dependable
|
|
||||||
foundations of other big shiny projects, which get to do their
|
|
||||||
big and exciting 2.0 major releases.
|
|
||||||
|
|
||||||
|
|
||||||
### Breaking Things is a Big Deal
|
|
||||||
|
|
||||||
Using an increment in a version string to express that a release
|
|
||||||
may break client code is not tenable. A developer cannot be
|
|
||||||
expected to think about how their code may or may not break as a
|
|
||||||
consequence of your decision to rename some functions. As the
|
|
||||||
author of any software, there is a great temptation to move fast
|
|
||||||
and break things. This is great when no other software depends
|
|
||||||
on yours. If something breaks, you jump up and fix it. The story
|
|
||||||
is quite different even when only a few dozen people depend on
|
|
||||||
your software.
|
|
||||||
|
|
||||||
|
|
||||||
The less the users of your library have to know about your
|
|
||||||
project, the better. The less they have to deal with issues
|
|
||||||
of compatibility, the better. SemVer can be overly specific
|
|
||||||
for some kinds of projects. If you are writing a library
|
|
||||||
and you have a commitment to backward compatibility
|
|
||||||
|
|
||||||
PyCalVer version strings can be parsed according to PEP440
|
|
||||||
https://www.python.org/dev/peps/pep-0440/
|
|
||||||
|
|
||||||
|
|
||||||
### A Word on Marketing
|
|
||||||
|
|
||||||
This setup of expectations for users can go one of two ways,
|
|
||||||
|
|
||||||
We use version numbers to communicate between the authors
|
|
||||||
of software and its users. For users of libraries Particularly
|
|
||||||
for libraries, it pays to keep things as simple as possible for
|
|
||||||
your human users.
|
|
||||||
|
|
||||||
|
|
||||||
### Commitment to Compatibility
|
|
||||||
|
|
||||||
Software projects can depend on many libraries. Consider that one
|
|
||||||
package introducing a breaking change is enough to mess up your
|
|
||||||
day. Especially in the case of libraries, your users should be
|
|
||||||
able to write code that uses it and not have that code break at
|
|
||||||
any point in the future. Users cannot be asked to keep track of
|
|
||||||
all the changes to every little library that they use.
|
|
||||||
|
|
||||||
PyCalVer is explicitly non semantic. A PyCalVer version number does
|
|
||||||
not express anything about
|
|
||||||
|
|
||||||
- Don't ever break things. When users depend on your
|
|
||||||
software, backward compatibility matters and the way to
|
|
||||||
express backward incompatible changes is not to bump a
|
|
||||||
version number, but to change the package name. A change
|
|
||||||
in the package name clearly communicates that a user must
|
|
||||||
change their code so that it will work with the changed
|
|
||||||
API. Everybody who does not have the bandwidth for those
|
|
||||||
changes, doesn't even have to be aware of your new
|
|
||||||
release.
|
|
||||||
|
|
||||||
- When you do break something, that should be considered a
|
|
||||||
bug that has to be fixed as quickly as possible in a new
|
|
||||||
version. It should always be safe for a user to update
|
|
||||||
their dependencies. If something does break, users have to
|
|
||||||
temporarily pin an older (known good) version, or update
|
|
||||||
to a newer fixed version.
|
|
||||||
|
|
||||||
- Version numbers should not require a parser (present
|
|
||||||
package excluded of course). A newer version number should
|
|
||||||
always be lexically greater than an older one.
|
|
||||||
TODO:
|
|
||||||
https://setuptools.readthedocs.io/en/latest/setuptools.html#specifying-your-project-s-version
|
|
||||||
|
|
||||||
|
|
||||||
The main component of the version number is based on the
|
|
||||||
calendar date. This is allows you to show your commitment (or
|
|
||||||
lack thereof) to the maintenance of your library. It also
|
|
||||||
allows users to see at a glance that their dependency might be
|
|
||||||
out of date. In this versioning scheme it is completely
|
|
||||||
reasonable to bump the version number without any changes,
|
|
||||||
simply to express to your users, that you are still actively
|
|
||||||
maintaining the software and that it is in a known good state.
|
|
||||||
|
|
||||||
|
|
||||||
For a much more detailed exposition of CalVer, see
|
|
||||||
http://sedimental.org/designing_a_version.html
|
|
||||||
https://calver.org/
|
|
||||||
|
|
||||||
from pkg_resources import parse_version
|
|
||||||
|
|
||||||
|
|
||||||
### The Life of a Library
|
|
||||||
|
|
||||||
```
|
```
|
||||||
mylib v201711.001-alpha # birth of a project (in alpha)
|
$ pycalver bump --dry
|
||||||
mylib v201711.002-alpha # new features (in alpha)
|
--- setup.cfg
|
||||||
mylib v201712.003-beta # bugfix release (in beta)
|
+++ setup.cfg
|
||||||
mylib v201712.004-rc # release candidate
|
@@ -65,7 +65,7 @@
|
||||||
mylib v201712.005 # stable release
|
|
||||||
mylib v201712.006 # stable bugfix release
|
|
||||||
|
|
||||||
mylib2 v201712.007-beta # breaking change (new package name!)
|
|
||||||
mylib2 v201801.008-beta # new features (in beta)
|
|
||||||
mylib2 v201801.009 # stable release
|
|
||||||
|
|
||||||
mylib v201802.007 # security fix for legacy version
|
|
||||||
mylib2 v201802.010 # security fix
|
|
||||||
|
|
||||||
mylib2 v202604.9900 # threshold for four digit build numbers
|
|
||||||
mylib2 v202604.9901 # still four digits in the same month
|
|
||||||
mylib2 v202604.9911 # last build number with four digits
|
|
||||||
mylib2 v202605.09912 # build number zero padding added with date turnover
|
|
||||||
mylib2 v202605.09913 # stable release
|
|
||||||
|
|
||||||
mylib2 v203202.16051-rc # release candidate
|
|
||||||
mylib2 v203202.16052 # stable release
|
|
||||||
|
|
||||||
|
[pycalver]
|
||||||
|
-current_version = v201812.0005-beta
|
||||||
|
+current_version = v201812.0006-beta
|
||||||
|
commit = True
|
||||||
|
tag = True
|
||||||
|
push = True
|
||||||
...
|
...
|
||||||
v202008.500 # 500 is the limit for four digit build numbers, but
|
|
||||||
v202008.508 # zero padding is added only after the turnover to
|
|
||||||
v202009.0509 # a new month, so that lexical ordering is preserved.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
The date portion of the version, gives the user an indication of
|
### Version State
|
||||||
how up their dependency is, whether or not a project is still
|
|
||||||
being maintained.
|
|
||||||
|
|
||||||
The build number, gives the user an idea of the maturity of the
|
The "current version" is considered global state that needs to
|
||||||
project. A project which has been around long enough to produce
|
be stored somewhere. Typically this might be stored in a
|
||||||
hundreds of builds, might be considered mature, or at least a
|
`VERSION` file, or some other file which is part of the
|
||||||
project that is only on build number 10, is probably still in
|
repository. This creates the possibility that different parallel
|
||||||
early development.
|
branches can have different states, which might lead to the same
|
||||||
|
version number being generated for different commits.
|
||||||
|
|
||||||
|
To avoid this issue, pycalver treats VCS tags as the canonical /
|
||||||
|
(SSOT)[https://en.wikipedia.org/wiki/Single_source_of_truth] for
|
||||||
|
the most recent version and attempts to change this state in the
|
||||||
|
most atomic way possible. This is why some actions of the
|
||||||
|
`pycalver` command can take a while, as it is synchronizing with
|
||||||
|
the remote repository to get the most recent versions and to
|
||||||
|
push any new version tags as soon as possible.
|
||||||
|
|
||||||
|
|
||||||
### FAQ
|
### Lexical Ids
|
||||||
|
|
||||||
Q: "So you're trying to tell me I need to create a whole new
|
The build number padding may eventually be exhausted. In order
|
||||||
package every time I introduce a introduce a breaking change?!".
|
to preserve lexical ordering, build numbers are incremented in a
|
||||||
|
special way. Examples will perhaps illustrate more clearly.
|
||||||
|
|
||||||
A: First of all, what the hell are you doing? Secondly, YES!
|
```python
|
||||||
Let's assume your little package has even just 100 users. Do you
|
"0001"
|
||||||
have any idea about the total effort that will be expended
|
"0002"
|
||||||
because you decided it would be nice to change the name of a
|
"0003"
|
||||||
function? It is completely reasonable introduce that the
|
...
|
||||||
friction for the package author when the price to users is
|
"0999"
|
||||||
orders of magnitude larger.
|
"11000"
|
||||||
|
"11001"
|
||||||
|
...
|
||||||
1801
|
"19998"
|
||||||
|
"19999"
|
||||||
https://calver.org/
|
"220000"
|
||||||
|
"220001"
|
||||||
I have given up on the idea that version numbers express
|
```
|
||||||
anything about changes made between versions. Trying to
|
|
||||||
express such information assumes 1. that the author of a package
|
What is happening here is that the left-most digit is
|
||||||
is aware of how a given change needs to be reflected in a
|
incremented early/preemptively. Whenever the left-most digit
|
||||||
version number and 2. that users and packaging software correctly
|
would change, the padding of the id is expanded using this
|
||||||
parse that meaning. When I used semantic versioning, I realized that
|
simple formula:
|
||||||
the major version number of my packages would never change,
|
|
||||||
because I don't think breaking changes should ever be
|
```python
|
||||||
|
prev_id = "0999"
|
||||||
|
next_id = str(int(prev_id, 10) + 1) # "1000"
|
||||||
|
if prev_id[0] != next_id[0]: # "0" != "1"
|
||||||
|
next_id = str(int(next_id, 10) * 11) # 1000 * 11 = 11000
|
||||||
|
```
|
||||||
|
|
||||||
|
This behaviour ensures that the following semantic is always
|
||||||
|
preserved: `new_version > old_version`. This will always be the
|
||||||
|
case, even if the padding was expanded and the version number
|
||||||
|
was incremented multiple times in the same month. To illustrate
|
||||||
|
the issue, consider what would happen if we did not expand the
|
||||||
|
padding and instead just incremented numerically.
|
||||||
|
|
||||||
|
```python
|
||||||
|
"0001"
|
||||||
|
"0002"
|
||||||
|
"0003"
|
||||||
|
...
|
||||||
|
"0999"
|
||||||
|
"1000"
|
||||||
|
...
|
||||||
|
"9999"
|
||||||
|
"10000"
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we eventually run into a build number where the lexical
|
||||||
|
ordering is not preserved, since `"10000" < "9999"` (because the
|
||||||
|
string `"1"` is lexically smaller than `"9"`). This is a very
|
||||||
|
rare corner case, but it's better to not have to think about it.
|
||||||
|
|
||||||
|
Just as an example of why lexical ordering is a nice property to
|
||||||
|
have, there are lots of software which read git tags, but which
|
||||||
|
have no logic to parse version strings, which can nonetheless
|
||||||
|
order the version tags correctly.
|
||||||
|
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
[repo_ref]: https://gitlab.com/mbarkhau/pycalver
|
[repo_ref]: https://gitlab.com/mbarkhau/pycalver
|
||||||
|
|
||||||
|
[setuptools_ref]: https://setuptools.readthedocs.io/en/latest/setuptools.html#specifying-your-project-s-version
|
||||||
|
|
||||||
|
[pep_440_ref]: https://www.python.org/dev/peps/pep-0440/
|
||||||
|
|
||||||
|
[zeno_1_dot_0_ref]: http://sedimental.org/designing_a_version.html#semver-and-release-blockage
|
||||||
|
|
||||||
|
[pep_101_ref]: https://www.python.org/dev/peps/pep-0101/
|
||||||
|
|
||||||
|
[bumpversion_ref]: https://github.com/peritus/bumpversion
|
||||||
|
|
||||||
|
|
||||||
[build_img]: https://gitlab.com/mbarkhau/pycalver/badges/master/pipeline.svg
|
[build_img]: https://gitlab.com/mbarkhau/pycalver/badges/master/pipeline.svg
|
||||||
[build_ref]: https://gitlab.com/mbarkhau/pycalver/pipelines
|
[build_ref]: https://gitlab.com/mbarkhau/pycalver/pipelines
|
||||||
|
|
||||||
|
|
@ -682,7 +674,7 @@ because I don't think breaking changes should ever be
|
||||||
[style_img]: https://img.shields.io/badge/code%20style-%20sjfmt-f71.svg
|
[style_img]: https://img.shields.io/badge/code%20style-%20sjfmt-f71.svg
|
||||||
[style_ref]: https://gitlab.com/mbarkhau/straitjacket/
|
[style_ref]: https://gitlab.com/mbarkhau/straitjacket/
|
||||||
|
|
||||||
[downloads_img]: https://pepy.tech/badge/pycalver
|
[downloads_img]: https://pepy.tech/badge/pycalver/month
|
||||||
[downloads_ref]: https://pepy.tech/project/pycalver
|
[downloads_ref]: https://pepy.tech/project/pycalver
|
||||||
|
|
||||||
[version_img]: https://img.shields.io/badge/PyCalVer-v201812.0011--beta-blue.svg
|
[version_img]: https://img.shields.io/badge/PyCalVer-v201812.0011--beta-blue.svg
|
||||||
|
|
|
||||||
|
|
@ -87,3 +87,5 @@ src/pycalver/__main__.py =
|
||||||
README.md =
|
README.md =
|
||||||
[PyCalVer {version}]
|
[PyCalVer {version}]
|
||||||
https://img.shields.io/badge/PyCalVer-{calver}{build}-{release}-blue.svg
|
https://img.shields.io/badge/PyCalVer-{calver}{build}-{release}-blue.svg
|
||||||
|
Successfully installed pycalver-{pep440_version}
|
||||||
|
pycalver, version {version}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue