peritus/bumpversion

support prereleases

Opened this issue · 12 comments

Here is the workflow I'm following for my project (assuming last release is 3.11)
'master' branch is for stable releases, 'next' is for development.

On 'next':

  • First, alpha release: 3.12a1 to start implementing exciting new features
  • When all new features are implemented: first release candidate: 3.12rc1
  • Fix bugs found during beta-testing: 3.12rc2
  • (optional: 3.12rc3, 3.12rc4 ... until all blocking bugs are fixed)
  • Final release: 3.12 (merge 'next' into 'master')

On 'master';

  • Bugs found after 3.12 was released, release 3.12.1
  • Optional, 3.12.3, 3.12.4...,

This version scheme is modeled after PEP440

Here's what I've tried:

[bumpversion]
current_version = 3.12a1
files = setup.py cmake/qibuild/version.cmake doc/source/conf.in.py python/qisys/main.py
message = qibuild {new_version}
commit = True
tag = True
parse = (?P<major>\d+)                             # major: 3.12 -> 3
        \.
        (?P<minor>\d+)                             # minor 3.12 -> 12
        (\.(?P<patch>\d+))?                        # either a maintenance release 3.12.1 ...
        ((?P<release>a|rc)(?P<rel_num>\d+))?       # or a rc, or an alpha 3.12a1, 3.12rc2
serialize =
  {major}.{minor}
  {major}.{minor}.{patch}
  {major}.{minor}{release}{rel_num}

[bumpversion:part:release]
values =
  a
  rc

But then I get:

$ bump_version rel_num
Parsing version '3.12a1' using regexp '(?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+))?((?P<release>a|rc)(?P<rel_num>\d+))?'
Parsed the following values: major=3, minor=12, patch=0, rel_num=1, release=a
Attempting to increment part 'rel_num'
Values are now: major=3, minor=12
Did not find key u'patch' in <bumpversion.Version:major=3, minor=12> when serializing version number
Opportunistic finding of new_version failed: Did not find key u'patch' in <bumpversion.Version:major=3, minor=12> when serializing version number
...
bumpversion: error: argument --new-version is required

Digging in the source code, I suspect the problem is somewhere near:

class VersionConfig(object):

    # ....

    def order(self):
        # currently, order depends on the first given serialization format
        # this seems like a good idea because this should be the most complete format
        return self._labels_for_format(self.serialize_formats[0])

But maybe there's something I've missed in the documentation...

twall commented

I've found that this almost works (presumably you could add any number of "release" part values rather than "dev"):

[bumpversion]
current_version = 0.1.0
parse = (?P<major>\d+)
        \.(?P<minor>\d+)
        \.(?P<patch>\d+)
        ((?P<release>[a-z]+)(?P<num>\d+))?
serialize =
        {major}.{minor}.{patch}{release}{num}
        {major}.{minor}.{patch}

[bumpversion:part:release]
optional_value = placeholder
first_value = dev
values =
        dev
        placeholder

The only problem I have ATM is that if you specify "num" as the part to increment when it doesn't already exist, the resulting version is 0.1.001. Not difficult to work around, just ensure you specify "patch" instead of "num" as the first bump after a release.

Yeah but in my use case there's no 'patch' component when I'm making a prerelease

I'm having the same issue (also like to omit "patch" on a prerelease). Is there a workaround?

dacox commented

@twall So I'm doing what you're doing, and it fits my use case quite nicely. However, I am experiencing something weird and I'm not quite sure why.

I took your solution and I added

[bumpversion:part:num]
first_value = 1

so that the numbering would start at 1 instead of 0 (I'm going for 1.0.0rc1, 1.0.0rc2, 1.0.0)

When I do this, however, the text placeholder appears when I run bumpversion release. This only happens when I try to change the first_value for num.

The weird thing is, given

[bumpversion]
current_version = 0.1.0
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<pre>\d+)\.(?P<prenum>\d+))?
serialize =
	{major}.{minor}.{patch}-{pre}.{prenum}
	{major}.{minor}.{patch}

(note the absence of explicit pre and prenum parts declaration, hence both are being implicitly set to default numeric part definitions under the hood), I can

0.1.0
bumpversion pre0.1.0-1.0
bumpversion prenum0.1.0-1.1
bumpversion prenum0.1.0-1.2
bumpversion major1.0.0.

However, whenever custom values are set in any of extra part definitions, -{pre}.{prenum} gets appended no matter what part is bumped.

@webyneter

I ran into this same issue. I was able to get it to work properly by specifying the first_value field to the optional one. I'm not 100% sure why this works, but it seems to:

[bumpversion]
commit = True
tag = True
current_version = 4.1.7
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<releaselevel>[a-z]+)\.(?P<preversion>\d+))?
serialize = 
	{major}.{minor}.{patch}-{releaselevel}.{preversion}
	{major}.{minor}.{patch}

[bumpversion:part:releaselevel]
first_value = final
optional_value = final
values = 
	alpha
	beta
	final

Edit*: I do agree that there should be a better way to do this though. The current way is a bit finicky.

Hi! Does anyone know if bump2version or ADVbumpversion have made improvements in supporting pre-releases?

In case this helps, we've built an alternative called tbump

Prelease support looks like this:

[version]
current = "1.6.0"

regex = '''
  (?P<major>\d+)
  \.
  (?P<minor>\d+)
  \.
  (?P<patch>\d+)
  (
    -
    (?P<lifecycle>alpha|beta|r)
    (?P<release>\d+)
  )?

And then you can use tbump 1.6.1. or tbump 1.6.0-r1

Cons: you have to type the whole version number on the command line instead of the 'part' you want to bump

Pros: the implementation is much simpler and supports many use cases.

@dacox FWIW, the bumpversion docs state that the default value for optional_value is actually The first entry in values =, not the last. I think bumpversion should be updated to handle traversing entries in any order, but I imagine this would get complicated if the user were to unexpectedly put placeholder in a spot not at the beginning or end of a values array of length greater than 2.

Instead, put placeholder at the beginning rather than the end and remove optional_value (or leave it in so you don't forget that that is what placeholder is).

As an example, the following .bumpversion.cfg works well for me:

[bumpversion]
current_version = 0.0.0
tag = False
tag_name = {new_version}
commit = True
parse =
    (?P<major>\d+)
    \.
    (?P<minor>\d+)
    \.
    (?P<patch>\d+)
    (\-(?P<pre>[a-z]+)\.(?P<prenum>\d+))?
serialize =
    {major}.{minor}.{patch}-{pre}.{prenum}
    {major}.{minor}.{patch}

[bumpversion:part:pre]
optional_value = placeholder
values =
    placeholder
    alpha
    beta
    rc

[bumpversion:part:prenum]
first_value = 1

[bumpversion:file:pyproject.toml]

In your case, see if this

[bumpversion]
current_version = 0.1.0
parse = (?P<major>\d+)
        \.(?P<minor>\d+)
        \.(?P<patch>\d+)
        ((?P<release>[a-z]+)(?P<num>\d+))?
serialize =
        {major}.{minor}.{patch}{release}{num}
        {major}.{minor}.{patch}

[bumpversion:part:release]
values =
        placeholder
        dev

or this

[bumpversion]
current_version = 0.1.0
parse = (?P<major>\d+)
        \.(?P<minor>\d+)
        \.(?P<patch>\d+)
        ((?P<release>[a-z]+)(?P<num>\d+))?
serialize =
        {major}.{minor}.{patch}{release}{num}
        {major}.{minor}.{patch}

[bumpversion:part:release]
optional_value = placeholder
values =
        placeholder
        dev

suits your needs (where in the latter case we are just being explicit about the fact that placeholder is in fact an optional_value),

@peritus and @c4urself As a followup to the above, would it be prudent to update bump2version to use list slicing, search for the optional_value, and traverse the array from here through the rest of the values? In addition, adding another flag traverse_reversed = True/False # default: False might help as well.

@dacox As another item, when going from rapid-development (0.x.y) or final major releases (#.x.y) to the next pre-release (#+1.x.y-alpha.1), if you have bumpversion set to commit with each bump, you'll want to override that on the commandline so you can bump both the major part and the pre-release part, like this:

bumpversion --no-comit major
bumpversion pre --message 'Bump version: {0.x.y} → {new_version}'

replacing x and y obviously with the last version (or N.x.y, as appropriate).

I used bumpversion in mostly all my projects, but dealing with prereleases became frustrating. Basically, I wanted something like:

0.1.0 -> 0.2.0-dev.1 -> 0.2.0-dev.2 -> 0.2.0-dev.3 -> 0.2.0

and it required some gymnastics to achieve it and I ended up using custom scripts. Also, I wanted to bump some files only for non-prereleases (e.g. I wanted the CHANGELOG file to be updated only for non-prereleases), and this also required some custom scripting.

I ended up writing semver. The approach that I adopted is to let the user specify a custom JS function in the configuration on how to bump the prerelease part of the version.

You can have a look at the configuration used by semver itself here. In particular, you can see the JS function:

[semver.prerelease]
bump_script = '''
var PREFIX = "dev.";
function bump(version) {
  var counter = !version.prerelease ? 0 : parseInt(version.prerelease.slice(PREFIX.length));
  return `${PREFIX}${counter + 1}`;
}
'''

As an example, let's say that the starting version is 0.1.0:

  • semver bump -c .semver.toml --part minor --new-prerelease -> 0.2.0-dev.1,
  • semver bump -c .semver.toml --part prerelease -> 0.2.0-dev.2,
  • semver bump -c .semver.toml --part prerelease -> 0.2.0-dev.3,
  • semver bump -c .semver.toml --finalize-prerelease -> 0.2.0,
  • semver bump -c .semver.toml --part patch -> 0.2.1,
  • semver bump -c .semver.toml --part minor -> 0.3.0,
  • semver bump -c .semver.toml --part major -> 1.0.0.

Though, semver only works with versions that comply with semver specification.