mirror of
https://github.com/TECHNOFAB11/bumpver.git
synced 2025-12-12 06:20:08 +01:00
update readme
This commit is contained in:
parent
fe019fa546
commit
1cec699750
2 changed files with 426 additions and 432 deletions
856
README.md
856
README.md
|
|
@ -1,15 +1,10 @@
|
|||
# [PyCalVer: Automatic CalVer Versioning for Python Packages][repo_ref]
|
||||
|
||||
PyCalVer is a simple calendar based versioning system. With a single
|
||||
`pycalver bump` command it will:
|
||||
|
||||
- Automatically update version strings across files in your project.
|
||||
- Commit those changes and tag the commit with the new version.
|
||||
|
||||
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/).
|
||||
PyCalVer is for projects that only have one semantic: newer ==
|
||||
better. PyCalVer version strings are compatible with python
|
||||
packaging software [setuptools][setuptools_ref] and
|
||||
[PEP440][pep_440_ref], but can in principle be used with any
|
||||
project.
|
||||
|
||||
|
||||
Project/Repo:
|
||||
|
|
@ -43,25 +38,17 @@ Code Quality/CI:
|
|||
[](TOC)
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Format](#format)
|
||||
- [Versioning Behaviour](#versioning-behaviour)
|
||||
- [Lexical Ids](#lexical-ids)
|
||||
- [Semantics of PyCalVer](#semantics-of-pycalver)
|
||||
- [Breaking Changes](#breaking-changes)
|
||||
- [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)
|
||||
- [Configuration](#configuration)
|
||||
- [Bump It Up](#bump-it-up)
|
||||
- [Pattern Search and Replacement](#pattern-search-and-replacement)
|
||||
- [Rational](#rational)
|
||||
- [Other Versioning Software](#other-versioning-software)
|
||||
- [Some Details](#some-details)
|
||||
- [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)
|
||||
- [Bump It Up](#bump-it-up)
|
||||
- [Version State](#version-state)
|
||||
- [Lexical Ids](#lexical-ids)
|
||||
|
||||
[](TOC)
|
||||
|
||||
|
|
@ -95,6 +82,197 @@ v202207.18133
|
|||
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
|
||||
|
||||
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
|
||||
`pycalver incr`:
|
||||
`pycalver test`:
|
||||
|
||||
```shell
|
||||
$ 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
|
||||
PEP440 Version: 201809.34b0
|
||||
PEP440 Version : 201809.34b0
|
||||
```
|
||||
|
||||
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 optional release tag is preserved as is.
|
||||
|
||||
|
|
@ -172,88 +354,17 @@ You can explicitly update the release tag using the
|
|||
`--release=<tag>` argument:
|
||||
|
||||
```shell
|
||||
$ pycalver incr v201801.0033-alpha --release=beta
|
||||
$ pycalver test v201801.0033-alpha --release=beta
|
||||
PyCalVer Version: v201809.0034-beta
|
||||
PEP440 Version: 201809.34b0
|
||||
$ pycalver incr v201809.0034-beta --release=final
|
||||
PEP440 Version : 201809.34b0
|
||||
$ pycalver test v201809.0034-beta --release=final
|
||||
PyCalVer Version: v201809.0035
|
||||
PEP440 Version: 201809.35
|
||||
PEP440 Version : 201809.35
|
||||
```
|
||||
|
||||
The version number is padded with extra zeros, to maintain the
|
||||
lexical ordering of version numbers. What happens when the
|
||||
padding is exhausted?
|
||||
|
||||
```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.
|
||||
To maintain lexical ordering of version numbers, the version
|
||||
number is padded with extra zeros (see [Lexical
|
||||
Ids](#lexical-ids) ).
|
||||
|
||||
|
||||
## Usage
|
||||
|
|
@ -289,8 +400,8 @@ README.md =
|
|||
{pep440_version}
|
||||
```
|
||||
|
||||
This may or may not cover all version numbers across your
|
||||
repository. Something like the following may illustrate
|
||||
This probably won't cover all instances of version numbers across
|
||||
your repository. Something like the following may illustrate
|
||||
additional changes you might need to make.
|
||||
|
||||
```ini
|
||||
|
|
@ -305,368 +416,249 @@ setup.cfg =
|
|||
current_version = {version}
|
||||
setup.py =
|
||||
version="{pep440_version}"
|
||||
src/myproject.py =
|
||||
myproject/__init__.py =
|
||||
__version__ = "{version}"
|
||||
README.md =
|
||||
[PyCalVer {calver}{build}-{release}]
|
||||
img.shields.io/badge/PyCalVer-{calver}{build}--{release}-blue
|
||||
[PyCalVer {calver}{build}{release}]
|
||||
img.shields.io/badge/PyCalVer-{calver}{build}-{release}-blue
|
||||
```
|
||||
|
||||
### Bump It Up
|
||||
|
||||
To increment and publish a new version, you can use the
|
||||
`pycalver bump` command:
|
||||
|
||||
```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`.
|
||||
To see if a pattern is found, you can use the `--dry` flag, which
|
||||
will leave your repository untouched and only show you a diff.
|
||||
|
||||
|
||||
```shell
|
||||
$ pycalver show
|
||||
Current Version: v201809.0001-beta
|
||||
PEP440 Version: 201809.1b0
|
||||
$ pycalver bump --dry --no-fetch --release rc
|
||||
INFO - Old Version: v201809.0001-beta
|
||||
INFO - New Version: v201809.0002-rc
|
||||
--- README.md
|
||||
+++ README.md
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
$ pycalver bump --dry
|
||||
TODO: diff output
|
||||
Don't forget to do $ git push --tags
|
||||
[![Supported Python Versions][pyversions_img]][pyversions_ref]
|
||||
-[![PyCalVer v201809.0001-beta][version_img]][version_ref]
|
||||
+[![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
|
||||
|
||||
`patterns` is used both to search for version strings and to
|
||||
generate the replacement strings. The following placeholders are
|
||||
available for use, everything else in a pattern is treated as
|
||||
literal text.
|
||||
The `pycalver:file_patterns` section of the configuration is
|
||||
used both to search and also to replace version strings in your
|
||||
projects files. The following placeholders are available for
|
||||
use, everything else in a pattern is treated as literal text.
|
||||
|
||||
|
||||
| placeholder | example |
|
||||
|------------------|--------------------|
|
||||
| `pep440_version` | 201809.1b0 |
|
||||
| `version` | v201809.0001-alpha |
|
||||
| `calver` | v201809 |
|
||||
| `year` | 2018 |
|
||||
| `month` | 09 |
|
||||
| `build` | .0001 |
|
||||
| `release` | -alpha |
|
||||
| placeholder | example |
|
||||
|--------------------|--------------------|
|
||||
| `{pep440_version}` | 201809.1b0 |
|
||||
| `{version}` | v201809.0001-alpha |
|
||||
| `{calver}` | v201809 |
|
||||
| `{year}` | 2018 |
|
||||
| `{month}` | 09 |
|
||||
| `{build}` | .0001 |
|
||||
| `{build_no}` | 0001 |
|
||||
| `{release}` | -alpha |
|
||||
| `{release_tag}` | alpha |
|
||||
|
||||
|
||||
Note that the separator/prefix characters are part of what is
|
||||
matched and generated for a given placeholder, and they should
|
||||
not be included in your patterns.
|
||||
|
||||
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.
|
||||
|
||||
Note that the separator/prefix characters can be part of what is
|
||||
matched and generated for a given placeholder. In other words,
|
||||
assuming you have the following text in your README.md (note the
|
||||
two dashes before alpha):
|
||||
|
||||
```
|
||||
version_str = "v201712.0027-beta"
|
||||
version_dict = pycalver_re.match("v201712.0027-beta").groupdict()
|
||||
import pkg_resources # from setuptools
|
||||
version = pkg_resources.parse_version(version_str)
|
||||
--
|
||||
https://img.shields.io/badge/PyCalVer-v201809.0001--alpha-blue.svg
|
||||
```
|
||||
|
||||
In [2]: version_dict
|
||||
{'year': '2017', 'month': '12', 'build_nr': '0027', 'tag': 'beta'}
|
||||
>>> str(version)
|
||||
201712.27b0
|
||||
An appropriate pattern would be:
|
||||
|
||||
```ini
|
||||
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
|
||||
|
||||
Nobody knows what the semantics of a version number are, because
|
||||
nobody can guarantee that a given release adheres to whatever
|
||||
convention one would like to imbibe it with. Lets just keep things
|
||||
simple.
|
||||
|
||||
- Version numbers should be recognizable as such, that's what
|
||||
the "v" prefix does.
|
||||
- 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
|
||||
0. Check that your repo doesn't have any local changes.
|
||||
1. *Fetch* the most recent global VCS tags from origin
|
||||
(--no-fetch to disable).
|
||||
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 and tag.
|
||||
|
||||
```
|
||||
mylib v201711.001-alpha # birth of a project (in alpha)
|
||||
mylib v201711.002-alpha # new features (in alpha)
|
||||
mylib v201712.003-beta # bugfix release (in beta)
|
||||
mylib v201712.004-rc # release candidate
|
||||
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 bump --dry
|
||||
--- setup.cfg
|
||||
+++ setup.cfg
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
[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
|
||||
how up their dependency is, whether or not a project is still
|
||||
being maintained.
|
||||
### Version State
|
||||
|
||||
The build number, gives the user an idea of the maturity of the
|
||||
project. A project which has been around long enough to produce
|
||||
hundreds of builds, might be considered mature, or at least a
|
||||
project that is only on build number 10, is probably still in
|
||||
early development.
|
||||
The "current version" is considered global state that needs to
|
||||
be stored somewhere. Typically this might be stored in a
|
||||
`VERSION` file, or some other file which is part of the
|
||||
repository. This creates the possibility that different parallel
|
||||
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
|
||||
package every time I introduce a introduce a breaking change?!".
|
||||
The build number padding may eventually be exhausted. In order
|
||||
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!
|
||||
Let's assume your little package has even just 100 users. Do you
|
||||
have any idea about the total effort that will be expended
|
||||
because you decided it would be nice to change the name of a
|
||||
function? It is completely reasonable introduce that the
|
||||
friction for the package author when the price to users is
|
||||
orders of magnitude larger.
|
||||
|
||||
|
||||
1801
|
||||
|
||||
https://calver.org/
|
||||
|
||||
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
|
||||
is aware of how a given change needs to be reflected in a
|
||||
version number and 2. that users and packaging software correctly
|
||||
parse that meaning. When I used semantic versioning, I realized that
|
||||
the major version number of my packages would never change,
|
||||
because I don't think breaking changes should ever be
|
||||
```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: `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
|
||||
|
||||
[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_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_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
|
||||
|
||||
[version_img]: https://img.shields.io/badge/PyCalVer-v201812.0011--beta-blue.svg
|
||||
|
|
|
|||
|
|
@ -87,3 +87,5 @@ src/pycalver/__main__.py =
|
|||
README.md =
|
||||
[PyCalVer {version}]
|
||||
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