update readme

This commit is contained in:
Manuel Barkhau 2018-12-22 00:24:04 +01:00
parent fe019fa546
commit 1cec699750
2 changed files with 426 additions and 432 deletions

848
README.md
View file

@ -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
```
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
$ pycalver test v201809.0034-beta --release=final
PyCalVer Version: v201809.0035
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 |
|--------------------|--------------------|
| `{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

View file

@ -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}