Skip to content

Commit

Permalink
Added bumpversion command.
Browse files Browse the repository at this point in the history
Issue #160.
  • Loading branch information
mauritsvanrees committed Jan 29, 2016
1 parent ee09586 commit beef14b
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 9 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Changelog for zest.releaser
6.6 (unreleased)
----------------

- Added ``bumpversion`` command. Options ``--feature`` and
``--breaking``. Issue #160. The exact behavior might change in
future versions after more practical experience. Try it out and
report any issues you find. [maurits]

- Fixed possible encoding problems when writing files. This is
especially for an ascii file to which we add non ascii characters,
like in the ``addchangelogentry`` command. [maurits]
Expand Down
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,8 @@ There are some additional tools:
is indented and the first line is started with a dash. The command
detects it if you use for example a star as first character of an
entry.

- **bumpversion**: do not release, only bump the version. A
development marker is kept when it is there. With ``--feature`` we
update the minor version. With option ``--breaking`` we update the
major version.
48 changes: 48 additions & 0 deletions doc/source/entrypoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,51 @@ reporoot

workingdir
Original working directory

Bumpversion data dict items
---------------------------

breaking
True if we handle a breaking (major) change

commit_msg
Message template used when committing.

feature
True if we handle a feature (minor) change

headings
Extracted headings from the history file

history_encoding
The detected encoding of the history file

history_file
Filename of history/changelog file (when found)

history_header
Header template used for 1st history header

history_insert_line_here
Line number where an extra changelog entry can be inserted.

history_last_release
Full text of all history entries of the current release

history_lines
List with all history file lines (when found)

name
Name of the project being released

new_version
New development version (so 1.1)

release
Type of release: breaking, feature, normal

reporoot
Root of the version control repository

workingdir
Original working directory
6 changes: 6 additions & 0 deletions doc/source/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ Or on multiple lines::

This was difficult."

The ``bumpversion`` command accepts two mutually exclusive options:

- With ``--feature`` we update the minor version.

- With option ``--breaking`` we update the major version.


Global options
--------------
Expand Down
4 changes: 4 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def read(filename):
'lasttagdiff = zest.releaser.lasttagdiff:main',
'lasttaglog = zest.releaser.lasttaglog:main',
'addchangelogentry = zest.releaser.addchangelogentry:main',
'bumpversion = zest.releaser.bumpversion:main',
],
# The datachecks are implemented as entry points to be able to check
# our entry point implementation.
Expand All @@ -97,6 +98,9 @@ def read(filename):
'zest.releaser.addchangelogentry.middle': [
'datacheck = zest.releaser.addchangelogentry:datacheck',
],
'zest.releaser.bumpversion.middle': [
'datacheck = zest.releaser.bumpversion:datacheck',
],
# Documentation generation
'zest.releaser.prereleaser.before': [
'preparedocs = ' +
Expand Down
152 changes: 152 additions & 0 deletions zest/releaser/bumpversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""Do the checks and tasks that have to happen after doing a release.
"""
from __future__ import unicode_literals

import logging
import sys

from zest.releaser import baserelease
from zest.releaser import utils

logger = logging.getLogger(__name__)

HISTORY_HEADER = '%(new_version)s (unreleased)'
COMMIT_MSG = 'Bumped version for %(release)s release.'

DATA = {
# Documentation for self.data. You get runtime warnings when something is
# in self.data that is not in this list. Embarrasment-driven
# documentation!
'workingdir': 'Original working directory',
'reporoot': 'Root of the version control repository',
'name': 'Name of the project being released',
'new_version': 'New development version (so 1.1)',
'commit_msg': 'Message template used when committing.',
'headings': 'Extracted headings from the history file',
'history_file': 'Filename of history/changelog file (when found)',
'history_last_release': (
'Full text of all history entries of the current release'),
'history_header': 'Header template used for 1st history header',
'history_lines': 'List with all history file lines (when found)',
'history_encoding': 'The detected encoding of the history file',
'history_insert_line_here': (
'Line number where an extra changelog entry can be inserted.'),
'original_version': 'Version before bump (e.g. 1.0.dev0)',
'breaking': 'True if we handle a breaking (major) change',
'feature': 'True if we handle a feature (minor) change',
'release': 'Type of release: breaking, feature, normal',
}


class BumpVersion(baserelease.Basereleaser):
"""Add a changelog entry.
self.data holds data that can optionally be changed by plugins.
"""

def __init__(self, vcs=None, breaking=False, feature=False):
baserelease.Basereleaser.__init__(self, vcs=vcs)
# Prepare some defaults for potential overriding.
if breaking:
release = 'breaking'
elif feature:
release = 'feature'
else:
release = 'normal'
self.data.update(dict(
history_header=HISTORY_HEADER,
breaking=breaking,
feature=feature,
release=release,
commit_msg=COMMIT_MSG))

def prepare(self):
"""Prepare self.data by asking about new dev version"""
print('Checking version bump for {} release.'.format(
self.data['release']))
if not utils.sanity_check(self.vcs):
logger.critical("Sanity check failed.")
sys.exit(1)
self._grab_version(initial=True)
self._grab_history()
# Grab and set new version.
self._grab_version()

def execute(self):
"""Make the changes and offer a commit"""
self._change_header()
self._write_version()
self._write_history()
self._diff_and_commit()

def _grab_version(self, initial=False):
"""Grab the version.
When initial is False, ask the user for a non-development
version. When initial is True, grab the current suggestion.
"""
original_version = self.vcs.version
logger.debug("Extracted version: %s", original_version)
if original_version is None:
logger.critical('No version found.')
sys.exit(1)
suggestion = new_version = self.data.get('new_version')
if not new_version:
# Get a suggestion.
breaking = self.data['breaking']
feature = self.data['feature']
# Compare the suggestion for the last tag with the current version.
# The wanted version bump may already have been done.
last_tag_version = utils.get_last_tag(self.vcs, allow_missing=True)
if last_tag_version is None:
print("No tag found. No version bump needed.")
sys.exit(0)
else:
print("Last tag: {}".format(last_tag_version))
print("Current version: {}".format(original_version))
minimum_version = utils.suggest_version(
last_tag_version, feature=feature, breaking=breaking)
if minimum_version <= original_version:
print("No version bump needed.")
sys.exit(0)
# A bump is needed. Get suggestion for next version.
suggestion = utils.suggest_version(
original_version, feature=feature, breaking=breaking)
if not initial:
new_version = utils.ask_version(
"Enter version", default=suggestion)
if not new_version:
new_version = suggestion
self.data['original_version'] = original_version
self.data['new_version'] = new_version


def datacheck(data):
"""Entrypoint: ensure that the data dict is fully documented"""
utils.is_data_documented(data, documentation=DATA)


def main():
parser = utils.base_option_parser()
parser.add_argument(
"--feature",
action="store_true",
dest="feature",
default=False,
help="Bump for feature release (increase minor version)")
parser.add_argument(
"--breaking",
action="store_true",
dest="breaking",
default=False,
help="Bump for breaking release (increase major version)")
options = utils.parse_options(parser)
if options.breaking and options.feature:
print('Cannot have both breaking and feature options.')
sys.exit(1)
utils.configure_logging()
bumpversion = BumpVersion(
breaking=options.breaking, feature=options.feature)
bumpversion.run()
2 changes: 2 additions & 0 deletions zest/releaser/preparedocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os

from zest.releaser import addchangelogentry
from zest.releaser import bumpversion
from zest.releaser import prerelease
from zest.releaser import release
from zest.releaser import postrelease
Expand Down Expand Up @@ -34,6 +35,7 @@ def prepare_entrypoint_documentation(data):
('release', release.DATA),
('postrelease', postrelease.DATA),
('addchangelogentry', addchangelogentry.DATA),
('bumpversion', bumpversion.DATA),
):
heading = '%s data dict items' % name.capitalize()
result.append(heading)
Expand Down
2 changes: 1 addition & 1 deletion zest/releaser/prerelease.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
'Text that must be present in the changelog. Can be a string or a '
'list, for example ["New:", "Fixes:"]. For a list, only one of them '
'needs to be present.'),
'original_version': 'Version before prereleasing (e.g. 1.0dev)',
'original_version': 'Version before prereleasing (e.g. 1.0.dev0)',
'commit_msg': 'Message template used when committing',
'history_header': 'Header template used for 1st history header',
}
Expand Down
6 changes: 3 additions & 3 deletions zest/releaser/tests/addchangelogentry.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Detailed tests of prerelease.py
===============================
Detailed tests of addchangelogentry.py
======================================

.. :doctest:
.. :setup: zest.releaser.tests.functional.setup
Expand Down Expand Up @@ -45,7 +45,7 @@ commit::
Question: OK to commit this (Y/n)?
Our reply: <ENTER>

The changelog and setup.py are at 0.1 and indicate a release date:
The changelog and setup.py are at 0.1 and have the message::

>>> contents = (open('CHANGES.txt').read())
>>> print(contents)
Expand Down
94 changes: 94 additions & 0 deletions zest/releaser/tests/bumpversion.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
Detailed tests of bumpversion.py
================================

.. :doctest:
.. :setup: zest.releaser.tests.functional.setup
.. :teardown: zest.releaser.tests.functional.teardown

Several items are prepared for us.

An svn repository:

>>> repo_url
'file://TESTREPO'

An svn checkout of a project:

>>> svnsourcedir
'TESTTEMP/tha.example-svn'
>>> import os
>>> import sys
>>> os.chdir(svnsourcedir)

Asking input on the prompt is not unittestable unless we use the prepared
testing hack in utils.py:

>>> from zest.releaser import utils
>>> utils.TESTMODE = True

Initially there are no tags, and we require them. In the tests the
error is ugly, but in practice it looks fine, saying no bump is needed.

>>> from zest.releaser import bumpversion
>>> bumpversion.main()
Traceback (most recent call last):
...
RuntimeError: SYSTEM EXIT (code=0)

So first run the fullrelease:

>>> from zest.releaser import fullrelease
>>> utils.test_answer_book.set_answers(['', '', '', '2.3.4', '', '', '', '', '', '', ''])
>>> fullrelease.main()
Question...
Question: Enter version [0.1]:
Our reply: 2.3.4
...
>>> svnhead('CHANGES.txt')
Changelog of tha.example
========================
<BLANKLINE>
2.3.5 (unreleased)
------------------
>>> svnhead('setup.py')
from setuptools import setup, find_packages
import os.path
<BLANKLINE>
version = '2.3.5.dev0'

Try bumpversion again. The first time we again get an error because
no version bump is needed: our current version is already higher than
the latest tag, and we have no feature or breaking change. In the
tests it is again ugly, but the exit code is zero, which is good.

>>> utils.test_answer_book.set_answers(['', '', '', '', '', ''])
>>> bumpversion.main()
Traceback (most recent call last):
...
RuntimeError: SYSTEM EXIT (code=0)

Now a feature bump::

>>> sys.argv[1:] = ['--feature']
>>> bumpversion.main()
Checking version bump for feature release.
Last tag: 2.3.4
Current version: 2.3.5.dev0
Question: Enter version [2.4.0.dev0]:
Our reply: <ENTER>
Checking data dict
Question: OK to commit this (Y/n)?
Our reply: <ENTER>

Now a breaking bump::

>>> sys.argv[1:] = ['--breaking']
>>> bumpversion.main()
Checking version bump for breaking release.
Last tag: 2.3.4
Current version: 2.4.0.dev0
Question: Enter version [3.0.0.dev0]:
Our reply: <ENTER>
Checking data dict
Question: OK to commit this (Y/n)?
Our reply: <ENTER>
Loading

0 comments on commit beef14b

Please sign in to comment.