Repository: zestsoftware/zest.releaser (language: python)

Issue:
Future plans for zest.releaser
For background, a small bit of history up front: me and my brother @reinout both worked for Zest Software about 15 years ago. It is a small software company in The Netherlands, at the moment three people: Jean-Paul, Fred and me. Reinout created zest.releaser, and since then we both did a lot of work on it. According to LinkedIn he left in 2009, but I stayed and am still there. We two are the only maintainers.

Because I am Plone Releaser Manager, I probably use zest.releaser a lot more than he does now. Me and my colleagues use it often, and many people in the Plone community as well.
Latest Plone release 5.2 supports Python 2 and 3. Version 6 is expected within a few months and will only support Python 3.  In the Plone world and the Zest world, git is the only version control system. More broadly, in the Stack Overflow Developer Survey 2021 [over 90% of developers use git](https://insights.stackoverflow.com/survey/2021?_ga=2.36543652.581266192.1629570533-1881696037.1629570533#section-most-popular-technologies-other-tools).

I think development on zest.releaser may be a bit too difficult at the moment, certainly for outsiders who may want to create a pull request. We test subversion, bazaar, mercurial, git. Python 2 and 3 plus PyPy. To run the tests, you need to use buildout. And we have this weird test setup with `z3c.testsetup`, with I think even Grok-like behavior. And doctests. Can we make this simpler?

Some ideas for future simplification:

- Drop support for Python 2.7 (and PyPy2). Pretty obvious.
- Probably drop support for Python 3.5 and 3.6. Python 3.10 is around the corner, so we should start testing it. Keeping PyPy3 would be cool though.
- Switch from Travis to GitHub Actions.
- Drop support for subversion, bazaar and mercurial. Git has won, at least in my backyard.
- Allternatively, since supporting multiple VCSes is not too hard, only the testing of it is hard (and time consuming): change the tests to only do the full tests for git by default. Test git on all Python versions. Test the other version control systems only basically and only on one Python version. That should be doable with tox and GitHub Actions.
- Get rid of buildout in the tests. Just install what we need with pip. We also use buildout to generate the Sphinx documentation I think, so maybe something needs to be left. I don't really know how this is done.
- Get rid of doctests. They have their uses, but as some people like to say: they are neither documentation nor tests.
- Get rid of `z3c.testsetup`. Maybe use `pytest`.

We can continue to do bug fixes for the current 6.x release.

Some stuff is easy to do on short term:

- Drop support for Python 3.6 and older and run pyupgrade.
- Switch to GitHub Actions.
- Maybe also: let one test run only use one VCS, probably via some environment variable. Default to git. Test git on all Python versions, and the others only on one, perhaps 3.8.
- Release this as 7.x.

@reinout What do you think?


Discussion / hints:
I still use it a lot myself. And it is standard for all python packages in my current company, too. There even is a plugin that I wrote that we use regularly: qgispluginreleaser :-)

- Buildout => pip, yes
- pytest: yes, please.
- Drop subversion and bzr: yes. (Note: the test-docker that's included lets me run the tests just fine, btw).
- Drop mercurial: probably yes. (At least, I don't use it anymore and the main mercurial hoster, bitbucket, also dropped it).
- Python: 3.6+, I'd say (ubuntu 18.04)
- Sphinx: buildout isn't needed for that, the buildout sphinx recipe is just a small wrapper. I can fix that quick enough.
- Github actions: yes.

And... I'd love to pour "isort" and "black" over the code. I still haven't done it as there are a few PRs we might want to look at first.
I am in favour of `isort` and `black` as well.
There are only two open PRs and they have been open since 2015 and 2017. And they are both about encodings, so they might be fixed already if we do Python 3 only.

Here is a [Google search popularity comparison](https://trends.google.com/trends/explore?q=git,svn,mercurial,bzr) for the four version control systems. This does not say everything. But git is by far the most popular. Mercurial gets some popularity because there is apparently a shoe with this name from a popular brand.
I thought core Python was still using Mercurial, but the last change on  [hg.python.org](https://hg.python.org/?sort=lastchange) is from 17 months ago, and CPython is developed on [GitHub](https://github.com/python/cpython). So dropping Mercurial seems okay.

Maybe remove the non-git code, but try to keep the possibility of using a different VCS built in. If someone can copy our existing mercurial code and put it in a separate package that this person maintains, that would be nice. Most changes for this would be in [`choose.py`](https://github.com/zestsoftware/zest.releaser/blob/master/zest/releaser/choose.py) where we choose/determine the VCS. In [`vcs.py`](https://github.com/zestsoftware/zest.releaser/blob/master/zest/releaser/vcs.py) we could create a dictionary `VCS_SUPPORT` with a mapping from the internal file/directory name (`.git`, `.hg`) to a class (`Git`, `Mercurial`) that would inherit from our [`BaseVersionControl`](https://github.com/zestsoftware/zest.releaser/blob/master/zest/releaser/vcs.py#L40) class. A third party package could then add its own code to this dictionary (via a small function that we provide). That sounds like something that should not be too hard.

Theoretically, we could let our test setup register a testing VCS here, which does nothing, except some printing ('Committing with comment X', 'Creating tag Y'). That could make testing some corner cases easier. Just a thought.

After we do a few cleanups, we could release a 7.0.0a1/b1/rc1 if we think a non-final release is useful.
+10 for all points.
Better drop old stuff, almost nobody uses abymore, to better support newer stuff.

FYI, recently I started working on fully poetry-based projects (no setup.py/setup.cfg)



Fix patch:
diff --git a/.gitignore b/.gitignore
index d3eda27b..35a64019 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ include
 lib
 /local
 parts
+/pyvenv.cfg
 zest.releaser.egg-info
 .coverage
 htmlcov
diff --git a/.landscape.yml b/.landscape.yml
deleted file mode 100644
index ac2f0b6c..00000000
--- a/.landscape.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-ignore-paths:
-    - bootstrap.py
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f11cd505..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-sudo: false
-language: python
-python:
-  - 2.7
-  - 3.5
-  - 3.6
-  - 3.7
-  - 3.8
-  - pypy
-# Note: pypy3 fails at starting the bin/test script.
-addons:
-  apt:
-    sources:
-      - git-core
-    packages:
-      # git, bzr, subversion and mercurial are already installed by default,
-      # we only need to install git-svn (but from the git-core PPA, as travis
-      # uses that)
-      - git-svn
-    update: true
-
-before_install:
-  # Configure git and bazaar:
-  - git config --global user.name "Temp user"
-  - git config --global user.email "temp@example.com"
-  - bzr whoami "Temp user <temp@example.com>"
-  # Get the versions of the vcs programs, so we have this information in case
-  # something goes wrong:
-  - bzr --version
-  - git --version
-  - hg --version
-  - svn --version
-  - pip install -U setuptools
-notifications:
-  irc:
-    channels: "irc.freenode.org#vanrees"
-    on_success: change
-env: HGUSER=temp
-install:
-  - pip install zc.buildout
-  # Fix PyPy error in test functional-with-hooks.txt:
-  # ContextualVersionConflict: (typing 3.6.6 (/home/travis/virtualenv/pypy2.7-7.1.1/site-packages), Requirement.parse('typing>=3.7.4'), set(['typing-extensions']))
-  - if test $(python --version 2> /dev/stdout | grep -c PyPy) -ge 1; then pip install typing==3.7.4.3 typing-extensions==3.7.4.3; fi
-  - buildout
-script:
-  - bin/test -v
diff --git a/CHANGES.rst b/CHANGES.rst
index 5feae46f..b26b2ef4 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,797 +1,20 @@
 Changelog for zest.releaser
 ===========================
 
-6.22.3 (unreleased)
--------------------
+7.0.0a1 (unreleased)
+--------------------
 
-- Nothing changed yet.
+Big cleanup to ease future development:
 
+- Removed support for Subversion (``svn``), Bazaar (``bzr``), Mercurial (``hg``).
 
-6.22.2 (2021-10-29)
--------------------
+- Removed support for Python 2 and 3.5.
 
-- Migrate to `travis-ci.com <https://travis-ci.com/github/zestsoftware/zest.releaser>`_.
-  [maurits]
+- Added support for Python 3.9 and 3.10.
 
+- Tested with Python 3.6-3.10 plus PyPy3.
 
-6.22.1 (2020-09-22)
--------------------
+- Switched from Travis to GitHub Actions.
 
-- When replacing new version in ``__version__ = "1.0"``, keep the existing quote style.
-  We always replaced double with single quotes, but now we keep them.
-  [graingert]
-
-
-6.22.0 (2020-09-03)
--------------------
-
-- Fixed deadlock when communicating with git-lfs, or anything else that gives back lots of output.
-  [mcdeck]
-
-- Fixed TypeError setting unicode in environment on Python 2.7 on Windows.
-  [mcdeck]
-
-
-6.21.1 (2020-08-07)
--------------------
-
-- Fixed uploading to multiple servers if we do not want to upload to the first server.
-  Fixes `issue #357 <https://github.com/zestsoftware/zest.releaser/issues/357>`_.
-  [maurits]
-
-
-6.21.0 (2020-07-01)
--------------------
-
-- Added support for Twine environment variables.
-  Especially, setting ``TWINE_REPOSITORY`` and ``TWINE_REPOSITORY_URL`` had no effect previously.
-  Fixes `issue #353 <https://github.com/zestsoftware/zest.releaser/issues/353>`_.
-  [mctwynne]
-
-
-6.20.1 (2020-02-21)
--------------------
-
-- Added ``Dockerfile`` for much easier testing. See `the developer
-  documentation
-  <https://zestreleaser.readthedocs.io/en/latest/developing.html>`_. Previously,
-  getting the tests to run reliably locally was hard, now it is easy.
-  [Behoston]
-
-
-6.20.0 (2020-01-07)
--------------------
-
-- Zest.releaser now sets an environment variable ``ZESTRELEASER`` so that
-  tools that we call on the command line can detect us. Don't depend on the
-  variable's textual content, just on the variable's name.
-
-
-6.19.1 (2019-09-03)
--------------------
-
-- Percent signs (%) don't crash addchangelogentry anymore.
-
-
-6.19.0 (2019-06-03)
--------------------
-
-- Do not go to the root of the repo by default.
-  When you were not in the root of a repo, ``zest.releaser`` already asked if you wanted to go there.
-  The default answer has now changed from yes to no.
-  This might help when releasing from monorepos.
-  Issue `#326 <https://github.com/zestsoftware/zest.releaser/issues/326>`_.  [maurits]
-
-
-6.18.2 (2019-04-10)
--------------------
-
-- Remove nothing_changed_yet line from history lines in unreleased section. [kleschenko]
-
-
-6.18.1 (2019-04-04)
--------------------
-
-- Document that we only support underline-style headings for markdown.
-  Fixes `issue 317 <https://github.com/zestsoftware/zest.releaser/issues/317>`_.
-  [reinout]
-
-- Using simply ``git ls-files`` to list files in a git repo instead of an
-  older much longer command. (Suggestion by @mgedmin).
-  [reinout]
-
-
-6.18.0 (2019-04-03)
--------------------
-
-- Calling twine in a more generic way to let it automatically do the right
-  thing. This saves us a lot of code and lets twine do what it's good at.
-  [htgoebel,reinout]
-
-
-6.17.2 (2019-03-25)
--------------------
-
-- When ``bdist_wheel`` is in ``setup.cfg``, release a wheel.
-  No longer check if this should be a universal wheel or not.
-  That is handled automatically when calling ``python setup.py bdist_wheel``.
-  You can still set ``[zest.releaser] create-wheel = no`` to prevent creating a wheel.
-  Fixes `issue 315 <https://github.com/zestsoftware/zest.releaser/issues/315>`_.
-  [maurits]
-
-
-6.17.1 (2019-03-19)
--------------------
-
-- Also accept 201 as valid statuscode when uploading using twine
-  Fixes `issue 318 <https://github.com/zestsoftware/zest.releaser/issues/318>`_
-  [fRiSi]
-
-
-6.17.0 (2019-02-20)
--------------------
-
-- Refuse to edit history header when it looks to be from an already released version.
-  We look for a date in it (like 2019-02-20).  Give a warning when this happens.
-  Fixes `issue 311 <https://github.com/zestsoftware/zest.releaser/issues/311>`_.
-  [maurits]
-
-- Better support for ``zestreleaser.towncrier`` (and similar extensions):
-  the update_history setting is now also respected by the ``bumpversion`` command.
-  Fixes `issue 310 <https://github.com/zestsoftware/zest.releaser/issues/310>`_.
-  [maurits]
-
-
-6.16.0 (2019-01-17)
--------------------
-
-- Fix for `issue #259 <https://github.com/zestsoftware/zest.releaser/issues/259>`_:
-  using zest.releaser on windows no longer can result in accidental extra
-  ``\r`` (carriage return) characters in the changelog and your ``setup.py``.
-  [reinout]
-
-
-6.15.4 (2019-01-11)
--------------------
-
-- We retain the existing quoting style for the ``version='1.0'`` in
-  ``setup.py`` files. The "black" code formatting prefers double quotes and
-  zest.releaser by default wrote single quotes.
-  [reinout]
-
-- Fix for `issue #299 <https://github.com/zestsoftware/zest.releaser/issues/299>`_:
-  bumpversion now also compares versions numerically instead of as a string,
-  so ``2.9 < 2.10`` is now true.
-  [reinout]
-
-
-6.15.3 (2018-12-03)
--------------------
-
-- Fix for `issue #297 <https://github.com/zestsoftware/zest.releaser/issues/297>`_:
-  bytes+int problem on python 3 when detecting encodings.
-  [reinout]
-
-
-6.15.2 (2018-08-30)
--------------------
-
-- If a tag already exists, zest.releaser asks a safety question. The location
-  where the question gets asked was moved slightly to help a program that uses
-  zest.releaser as a library.
-  [reinout]
-
-- Switched our readthedocs urls to https.
-  [reinout]
-
-
-6.15.1 (2018-06-22)
--------------------
-
-- Fix for #286: removed the confusing word "register" from the info message
-  you got when a package wasn't available yet on pypi.
-
-  Registering isn't used anymore on pypi, but it was still in our textual
-  message.
-  [reinout]
-
-
-6.15.0 (2018-05-15)
--------------------
-
-- Use pypi.org, especially when checking if a package is on PyPI.
-  Fixes `issue #281 <https://github.com/zestsoftware/zest.releaser/issues/281>`_.
-  [maurits]
-
-- Added key ``update_history`` in prerelease and postrelease data.
-  Plugins can use this to tell ``zest.releaser`` (and other plugins)
-  to not touch the history, presumably because the plugin handles it.
-  [maurits]
-
-- Declared ``requests`` dependency.
-  Declared ``zope.testing`` test dependency.
-  [maurits]
-
-
-6.14.0 (2018-03-26)
--------------------
-
-- Advertise ``setup.cfg`` option ``[zest.releaser] history-file``.
-  Usually zest.releaser can find the correct history or changelog file on its own.
-  But sometimes it may not find anything, or it finds multiple files and
-  selects the wrong one.
-  Then you can set a path here.
-  A ``history_file`` option with an underscore was already read, but not documented.
-  Now we try both a dash and an underscore for good measure.
-  [maurits]
-
-- Use new ``setup.cfg`` option ``[zest.releaser] encoding``.
-  Set this to, for example, ``utf-8`` when the encoding of your ``CHANGES.rst``
-  file is not determined correctly.
-  Fixes `issue 264 <https://github.com/zestsoftware/zest.releaser/issues/264>`_.
-  [maurits]
-
-- When inserting changelog entry, check that it conforms to the existing encoding.
-  Try to recover if there is a difference, especially when the changelog file
-  was ascii and we insert utf-8.  [maurits]
-
-- When determining encoding, first look for coding hints in the file itself.
-  Only when that fails, we try ``tokenize`` or ``chardet``.
-  Fixes `issue 264 <https://github.com/zestsoftware/zest.releaser/issues/264>`_.
-  [maurits]
-
-- Get PyPI password raw, without interpolation.
-  If you had a password with a percentage sign, you could get an error.
-  Fixes `issue 271 <https://github.com/zestsoftware/zest.releaser/issues/271>`_.
-  [maurits]
-
-- Prevent unclosed files.  Python 3.6 warned about them,
-  and PyPy may have more problems with it.
-  Fixed several other DeprecationWarnings.  [maurits]
-
-- Print commands in a nicer way.
-  You could get ugly output like this, especially on Python 2.7:
-  ``INFO: The '[u'git', u'diff']':`` or worse:
-  ``Command failed: u"t w i n e ' ' u p l o a d"``.
-  [maurits]
-
-- Test compatibility with Python 2.7, 3.4, 3.5, 3.6, PyPy2.  [maurits]
-
-
-6.13.5 (2018-02-16)
--------------------
-
-- Quit in ``postrelease`` when we cannot find a version.
-  Fixes `issue #262 <https://github.com/zestsoftware/zest.releaser/issues/262>`.
-  [maurits]
-
-
-6.13.4 (2018-02-05)
--------------------
-
-- Fixed IOError when ``setup.cfg`` is missing and no version is found.
-  [maurits]
-
-
-6.13.3 (2017-12-19)
--------------------
-
-- Fixed writing of files in original encoding on python3, too. [andreparames]
-
-
-6.13.2 (2017-11-27)
--------------------
-
-- Fixed tests with mercurial 4.4+.  [maurits]
-
-- Fixed writing of files in original encoding. [mgedmin]
-
-
-6.13.1 (2017-11-13)
--------------------
-
-- Add tag message formatting (option ``tag-message``). [htgoebel]
-
-
-6.13.0 (2017-11-10)
--------------------
-
-- Add support for signing tags (option ``tag-signing``). [htgoebel]
-
-
-6.12.5 (2017-09-25)
--------------------
-
-- Sorting uploadable filenames so that wheels are uploaded first. (For most
-  filesystems this happened automatically, but the order on OSX' new
-  filesystem is non-deterministic, so we added sorting.)
-  [reinout]
-
-- Release process will now fail when specified hooks cannot be imported.
-  (`PR #236 <https://github.com/zestsoftware/zest.releaser/pulls/236>`_)
-
-
-6.12.4 (2017-08-30)
--------------------
-
-- Also support version in setup.cfg. [ewjoachim]
-
-
-6.12.3 (2017-08-16)
--------------------
-
-- Allows ``{version}`` format for ``tag-format``.
-  [leorochael]
-
-
-6.12.2 (2017-07-13)
--------------------
-
-- Subversion fix: create tag of entire trunk or branch when not in repo root.
-  If you have ``trunk/pkg1`` and ``trunk/pkg2`` and you make tag 1.0 in directory ``pkg1``,
-  then until now we would create ``tags/1.0`` with the contents of directory ``pkg1``.
-  Checking out the tag and changing to the ``pkg1`` directory then failed.
-  We now make a tag of the entire trunk or branch, just like in the other version control systems.
-  Fixes `issue #213 <https://github.com/zestsoftware/zest.releaser/issues/213>`_.
-  [maurits]
-
-- Do not needlessly run ``svn info``.  [maurits]
-
-
-6.12.1 (2017-07-03)
--------------------
-
-- Quote the path when making a git clone, to fix problems with spaces.  [halkeye]
-
-- Fixed percentage signs in ``date-format`` in ``setup.cfg``.
-  You need double percentages.  [mgedmin]
-
-
-6.12 (2017-06-19)
------------------
-
-- Add date format in the config.  Default is ISO-8601 (%Y-%m-%d).
-  Put ``date-format = format string`` in your ``~/.pypirc`` or ``setup.cfg``.
-  [mgedmin]
-
-
-6.11 (2017-06-09)
------------------
-
-- If the package wants to build universal wheels by setting
-  ``[bdist_wheel] universal = 1``, then the default for
-  ``create-wheel`` is now yes.
-
-
-6.10 (2017-04-18)
------------------
-
-- Corner case fix: a top-level ``version = 1.0`` in your ``setup.py`` is now
-  also allowed to be in uppercase, like ``VERSION = 1.0``.
-  This fixes `issue 216
-  <https://github.com/zestsoftware/zest.releaser/issues/216>`_.
-  [reinout]
-
-
-6.9 (2017-02-17)
-----------------
-
-- Add tag formatter in the config.  This is a formatter that changes the name of the tag.
-  Default is the same as the version.
-  Put ``tag-format = a string`` in your ``~/.pypirc`` or ``setup.cfg``.
-  It needs to contain ``%(version)s``.
-  [tcezard]
-
-
-6.8.1 (2017-01-13)
-------------------
-
-- Catch error when uploading first package file in new PyPI project.
-  This fixes `issue 206
-  <https://github.com/zestsoftware/zest.releaser/issues/206>`_.
-  [maurits]
-
-
-6.8 (2016-12-30)
-----------------
-
-- Before retrying a ``twine`` command, reload the pypi config.  Then
-  when the user fixes his account settings in ``~/.pypirc`` and
-  retries, these changes take effect.  This used to work a while ago,
-  but got broken.  [maurits]
-
-- Added ``development-marker`` config option.  With this can override
-  the default ``.dev0``.  [drucci]
-
-- Added ``version-levels`` and ``less-zeroes`` options.
-  This influences the suggested version.  [maurits]
-
-- Allow ``.pypirc`` with just a ``pypi`` section.  Previously, we
-  required either a ``[server-login]`` section with a ``username``
-  option, or a ``[distutils]`` section with an ``index-servers`` option.
-  Failing this, we gave a warning about a not properly configured
-  file, and happily continued without uploading anything.  Now if
-  there is something missing from the ``pypirc`` file, we give an
-  error and explicitly ask if you want to continue without uploading.
-  Fixes `issue #199 <https://github.com/zestsoftware/zest.releaser/issues/199>`_.
-
-  Note for developers of extensions for ``zest.releaser``: this
-  removes the ``is_old_pypi_config`` and ``is_new_pypi_config``
-  methods, because they made no sense anymore.  If you were using
-  these, see if you can use the ``distutils_server`` method instead.
-  [maurits]
-
-- Added ``push-changes`` config file option.  Default: yes.  When this
-  is false, zest.releaser sets ``no`` as default answer for the
-  question if you want to push the changes to the remote.
-  [newlog]
-
-- By default no longer register a new package, but only upload it.
-  Registering a package is no longer needed on PyPI: uploading a new
-  distribution takes care of this.  If you *do* want to register,
-  maybe because a different package server requires it, then in your
-  ``setup.cfg`` or ``~/.pypirc``, use the following::
-
-    [zest.releaser]
-    register = yes
-
-  Fixes `issue 191 <https://github.com/zestsoftware/zest.releaser/issues/191>`_.
-  [willowmck]
-
-
-6.7.1 (2016-12-22)
-------------------
-
-- Create the list of distributions after the ``before_upload`` hook has fired.
-  This allows the ``before_upload`` hook to create additional distributions,
-  which will then be uploaded.  [t-8ch]
-
-
-6.7 (2016-10-23)
-----------------
-
-- Use the intended API of twine.  This should work with twine 1.6.0
-  and higher, including future versions.  [maurits]
-
-
-6.6.5 (2016-09-12)
-------------------
-
-- Support and require twine 1.8.0 as minimum version.
-  Fixes https://github.com/zestsoftware/zest.releaser/issues/183
-  [maurits]
-
-- Updated the documentation on uploading.  [mgedmin, maurits]
-
-- Replaced http://zestreleaser.readthedocs.org with
-  https://zestreleaser.readthedocs.io.  This is the new canonical
-  domain since 28 April 2016.  [maurits]
-
-
-6.6.4 (2016-02-24)
-------------------
-
-- Really create a shallow git clone when creating a distribution.
-  See issue #169.
-  [maurits]
-
-
-6.6.3 (2016-02-24)
-------------------
-
-- Using a "shallow" git clone when creating a distribution. This speeds up
-  releases, especially on big repositories.
-  See issue #169.
-  [gforcada]
-
-
-6.6.2 (2016-02-11)
-------------------
-
-- Added ``no-input`` option also to global (.pypirc) options.
-  Issue #164.
-  [jcerjak]
-
-
-6.6.1 (2016-02-02)
-------------------
-
-- Fixed version in changelog after bumpversion call.
-  [maurits]
-
-
-6.6.0 (2016-01-29)
-------------------
-
-- 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]
-
-- Added ``addchangelogentry`` command.  Issue #159.  [maurits]
-
-- Moved ``_diff_and_commit``, ``_push`` and ``_grab_version`` to
-  ``baserelease.py``, as the first was duplicated and the second and
-  third may be handy for other code too.  ``_grab_version`` is the
-  basic implementation, and is overridden in the prereleaser.  [maurits]
-
-- Show changelog of current release before asking for the new version
-  number.  Issue #155.  [maurits]
-
-- Moved ``_diff_and_commit``, ``_push`` and ``_grab_version`` to
-  ``baserelease.py``, as the first was duplicated and the second and
-  third may be handy for other code too.  ``_grab_version`` is the
-  basic implementation, and is overridden in the prereleaser.  [maurits]
-
-6.5 (2016-01-05)
-----------------
-
-- Adjusted ``bin/longtest`` for the (necessary) rename of the ``readme``
-  library to ``readme_renderer``.
-  Fixes #153
-
-  Note: the current ``readme`` package on pypi is broken to force an
-  upgrade. If you use an older zest.releaser, you have to pin ``readme`` to
-  ``0.6.0``, it works just fine.
-  [reinout]
-
-
-6.4 (2015-11-13)
-----------------
-
-- Fixed error when retrying twine command.
-  Fixes #148
-  [maurits]
-
-
-6.3 (2015-11-11)
-----------------
-
-- Fixed exception when logging an exception when a twine command
-  fails.
-  [maurits]
-
-
-6.2 (2015-10-29)
-----------------
-
-New:
-
-- Use ``twine`` as library instead of as command.  You no longer need
-  to have ``twine`` on your ``PATH``.
-  Fixes issue #142.
-  [maurits]
-
-
-6.1 (2015-10-29)
-----------------
-
-Fixes:
-
-- Fixed registering on servers other than PyPI.  We forgot to specify
-  the server in that case.
-  [maurits]
-
-
-6.0 (2015-10-27)
-----------------
-
-- Made ``twine`` a core dependency.  We now always use it for
-  registering and uploading.  We require at least version 1.6.0, as
-  this introduces the ``register`` command.
-  [maurits]
-
-- When uploading with ``twine`` first use the ``twine register``
-  command.  On PyPI, when the project is already registered, we do not
-  call it again, but we can only check this for PyPI, not for other
-  servers.
-  Issue #128.
-  [maurits]
-
-- Always exit with error code 1 when we exit explicitly.  In some
-  cases we would exit with success code 0 when we exited based on the
-  answer to a question.  This happened when the user did not want us
-  to create the missing ``tags`` directory in subversion, and also
-  after asking if the user wanted to continue even though 'nothing
-  changed yet' was in the history.
-  [maurits]
-
-- Extensions can now tell zest.releaser to look for specific required
-  words in the history.  Just add ``required_changelog_text`` to the
-  prerelease data.  It can be a string or a list, for example
-  ``["New:", "Fixes:"]``.  For a list, only one of them needs to be
-  present.
-  [maurits]
-
-- Look for the 'Nothing changed yet' text in the complete text of the
-  history entries of the current release, instead of looking at it
-  line by line.  This means that zest releaser extensions can overwrite
-  ``nothing_changed_yet`` in the prerelease data to span multiple lines.
-  [maurits]
-
-- zest.releaser extensions can now look at
-  ``history_insert_line_here`` in the prerelease data.  On this line
-  number in the history file they can add an extra changelog entry if
-  wanted.
-  [maurits]
-
-- Added ``history_last_release`` to the prerelease data.  This is the
-  text with all history entries of the current release.
-  [maurits]
-
-- When using the ``--no-input`` option, show the question and the
-  chosen answer.  Otherwise in case of a problem it is not clear why
-  the command stopped.
-  Fixes issue #136.
-  [maurits]
-
-
-5.7 (2015-10-14)
-----------------
-
-- The history/changelog file is now written back with the originally detected
-  encoding. The functionality was added in 5.2, but only used for writing the
-  ``setup.py``, not the changelog. This is fixed now.
-  [reinout]
-
-
-5.6 (2015-09-23)
-----------------
-
-- Add support for PyPy.
-  [jamadden]
-
-
-5.5 (2015-09-05)
-----------------
-
-- The ``bin/longtest`` command adds the correct utf-8 character encoding hint
-  to the resulting html so that non-ascii long descriptions are properly
-  rendered in all browsers.
-  [reinout]
-
-
-5.4 (2015-08-28)
-----------------
-
-- Requiring at least version 0.6 of the (optional, btw) readme package. The
-  API of readme changed slightly. Only needed when you want to check your
-  package's long description with ``bin/longtest``.
-  [reinout]
-
-
-5.3 (2015-08-21)
-----------------
-
-- Fixed typo in svn command to show the changelog since the last tag.
-  [awello]
-
-
-5.2 (2015-07-27)
-----------------
-
-- When we find no version control in the current directory, look a few
-  directories up.  When looking for version and history files, we look
-  in the current directory and its sub directories, and not in the
-  repository root.  After making a tag checkout, we change directory
-  to the same relative path that we were in before.  You can use this
-  when you want to release a Python package that is in a sub directory
-  of the repository.  When we detect this, we first offer to change to
-  the root directory of the repository.
-  [maurits]
-
-- Write file with the same encoding that we used for reading them.
-  Issue #109.
-  [maurits]
-
-
-5.1 (2015-06-11)
-----------------
-
-- Fix writing history/changelog file with non-ascii.  Issue #109.
-  [maurits]
-
-- Release zest.releaser as universal wheel, so one wheel for Python 2
-  and 3.  As usual, we release it also as a source distribution.
-  [maurits]
-
-- Regard "Skipping installation of __init__.py (namespace package)" as
-  warning, printing it in magenta.  This can happen when creating a
-  wheel.  Issue #108.
-  [maurits]
-
-
-5.0 (2015-06-05)
-----------------
-
-- Python 3 support.
-  [mitchellrj]
-
-- Use the same `readme` library that PyPI uses to parse long
-  descriptions when we test and render them.
-  [mitchellrj]
-
-
-4.0 (2015-05-21)
-----------------
-
-- Try not to treat warnings as errors.
-  [maurits]
-
-- Allow retrying some commands when there is an error.  Currently only
-  for commands that talk to PyPI or another package index.  We ask the
-  user if she wants to retry: Yes, no, quit.
-  [maurits]
-
-- Added support for twine_.  If the ``twine`` command is available, it
-  is used for uploading to PyPI.  It is installed automatically if you
-  use the ``zest.releaser[recommended]`` extra.  Note that if the
-  ``twine`` command is not available, you may need to change your
-  system ``PATH`` or need to install ``twine`` explicitly.  This seems
-  more needed when using ``zc.buildout`` than when using ``pip``.
-  Added ``releaser.before_upload`` entry point.  Issue #59.
-  [maurits]
-
-- Added ``check-manifest`` and ``pyroma`` to the ``recommended``
-  extra.  Issue #49.
-  [maurits]
-
-- Python 2.6 not officially supported anymore.  It may still work, but
-  we are no longer testing against it.
-  [maurits]
-
-- Do not accept ``y`` or ``n`` as answer for a new version.
-  [maurits]
-
-- Use ``colorama`` to output errors in red.
-  Issue #86
-  [maurits]
-
-- Show errors when uploading to PyPI.  They were unintentionally
-  swallowed before, so you did not notice when an upload failed.
-  Issue #84.
-  [maurits]
-
-- Warn when between the last postrelease and a new prerelease no
-  changelog entry has been added.  '- Nothing changed yet' would still
-  be in there.
-  Issue #26.
-  [maurits]
-
-- Remove code for support of collective.sdist.  That package was a backport
-  from distutils for Python 2.5 and earlier, which we do not support.
-  [maurits]
-
-- Add optional support for uploading Python wheels.  Use the new
-  ``zest.releaser[recommended]`` extra, or run ``pip install wheel``
-  yourself next to ``zest.releaser``.  Create or edit ``setup.cfg`` in
-  your project (or globally in your ``~/.pypirc``) and create a section
-  ``[zest.releaser]`` with ``create-wheel = yes`` to create a wheel to
-  upload to PyPI.  See http://pythonwheels.com for deciding whether
-  this is a good idea for your package.  Briefly, if it is a pure
-  Python 2 *or* pure Python 3 package: just do it.
-  Issue #55
-  [maurits]
-
-- Optionally add extra text to commit messages.  This can be used to
-  avoid running Travis Continuous Integration builds.  See
-  http://docs.travis-ci.com/user/how-to-skip-a-build/.  To activate
-  this, add ``extra-message = [ci skip]`` to a ``[zest.releaser]``
-  section in the ``setup.cfg`` of your package, or your global
-  ``~/.pypirc``.  Or add your favorite geeky quotes there.
-  [maurits]
-
-- Fix a random test failure on Travis CI, by resetting ``AUTO_RESPONSE``.
-  [maurits]
-
-- Added clarification to logging: making an sdist/wheel now says that it is
-  being created in a temp folder. Fixes #61.
-  [reinout]
 
 .. # Note: for older changes see ``doc/sources/changelog.rst``.
-
-.. _twine: https://pypi.org/project/twine
diff --git a/Dockerfile b/Dockerfile
index 819caafa..65420710 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,17 +2,8 @@ FROM python:3.8
 WORKDIR /zest.releaser/
 ENV PYTHONPATH=/zest.releaser/
 ENV USER=root
-ENV HGUSER=root
-RUN pip install -U pip setuptools zc.buildout && \
+RUN pip install -U pip setuptools tox && \
     apt-get update && \
-    apt-get -y install git \
-                       subversion \
-                       bzr \
-                       mercurial \
-                       git-svn
-RUN git config --global user.name "Temp user" && \
-    git config --global user.email "temp@example.com" && \
-    bzr whoami "Temp user <temp@example.com>"
+    apt-get -y install git
 COPY . /zest.releaser/
-RUN buildout
-CMD bin/test -v
+CMD tox -e py38
diff --git a/README.rst b/README.rst
index 7f74b40d..8560bc34 100644
--- a/README.rst
+++ b/README.rst
@@ -38,15 +38,6 @@ First the three most important links:
 - The code is at `github.com/zestsoftware/zest.releaser
   <https://github.com/zestsoftware/zest.releaser>`_.
 
-And... we're automatically being tested by Travis and Landscape:
-
-.. image:: https://api.travis-ci.com/zestsoftware/zest.releaser.png?branch=master
-   :target: https://travis-ci.com/github/zestsoftware/zest.releaser
-
-.. image:: https://landscape.io/github/zestsoftware/zest.releaser/master/landscape.svg?style=flat
-   :target: https://landscape.io/github/zestsoftware/zest.releaser/master
-   :alt: Code Health
-
 
 Compatibility / Dependencies
 ----------------------------
@@ -54,9 +45,8 @@ Compatibility / Dependencies
 .. image:: https://img.shields.io/pypi/pyversions/zest.releaser?   :alt: PyPI - Python Version
 .. image:: https://img.shields.io/pypi/implementation/zest.releaser?   :alt: PyPI - Implementation
 
-``zest.releaser`` works on Python 2.7.
-Python 3.5+ is supported.
-PyPy2 is supported.
+``zest.releaser`` works on Python 3.6+, including PyPy3.
+Tested until Python 3.10, but see ``tox.ini`` for the canonical place for that.
 
 To be sure: the packages that you release with ``zest.releaser`` may
 very well work on other Python versions: that totally depends on your
@@ -68,8 +58,6 @@ We depend on:
 
 - ``colorama`` for colorized output (some errors printed in red).
 
-- ``six`` for python2/python3 compatibility.
-
 - ``twine`` for secure uploading via https to pypi. Plain setuptools doesn't
   support this.
 
@@ -135,25 +123,17 @@ project's buildout, by having a buildout configuration such as::
     eggs = zest.releaser[recommended]
 
 
-Version control systems: svn, hg, git, bzr
-------------------------------------------
-
-Of course you must have a version control system installed.  zest.releaser
-currently supports:
-
-- Subversion (svn).
-
-- Mercurial (hg).
-
-- Git (git).
-
-- Git-svn.
+Version control systems: git
+----------------------------
 
-- Bazaar (bzr).
+Of course you must have a version control system installed.
+Since version 7, zest.releaser only supports git.
 
-Others could be added if there are volunteers! Git and mercurial support
-have been contributed years ago when we were working with bzr and subversion,
-for instance.
+If you use Subversion (svn), Mercurial (hg), Git-svn, or Bazaar (bzr), please use version 6.
+If you really want, you can probably copy the relevant parts from the old code to a new package,
+and release this as an add-on package for zest.releaser.
+I suspect that currently it would only work with a monkey patch.
+If you are planning something, please open an issue, and we can see about making this part pluggable.
 
 
 Available commands
diff --git a/TODO.rst b/TODO.rst
deleted file mode 100644
index 1ef2d168..00000000
--- a/TODO.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-To do
-=====
-
-* Add some more tests (test coverage *is* at 95%, btw).
diff --git a/bootstrap.py b/bootstrap.py
deleted file mode 100644
index a4599211..00000000
--- a/bootstrap.py
+++ /dev/null
@@ -1,210 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2006 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Bootstrap a buildout-based project
-
-Simply run this script in a directory containing a buildout.cfg.
-The script accepts buildout command-line options, so you can
-use the -c option to specify an alternate configuration file.
-"""
-
-import os
-import shutil
-import sys
-import tempfile
-
-from optparse import OptionParser
-
-__version__ = '2015-07-01'
-# See zc.buildout's changelog if this version is up to date.
-
-tmpeggs = tempfile.mkdtemp(prefix='bootstrap-')
-
-usage = '''\
-[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
-
-Bootstraps a buildout-based project.
-
-Simply run this script in a directory containing a buildout.cfg, using the
-Python that you want bin/buildout to use.
-
-Note that by using --find-links to point to local resources, you can keep
-this script from going over the network.
-'''
-
-parser = OptionParser(usage=usage)
-parser.add_option("--version",
-                  action="store_true", default=False,
-                  help=("Return bootstrap.py version."))
-parser.add_option("-t", "--accept-buildout-test-releases",
-                  dest='accept_buildout_test_releases',
-                  action="store_true", default=False,
-                  help=("Normally, if you do not specify a --version, the "
-                        "bootstrap script and buildout gets the newest "
-                        "*final* versions of zc.buildout and its recipes and "
-                        "extensions for you.  If you use this flag, "
-                        "bootstrap and buildout will get the newest releases "
-                        "even if they are alphas or betas."))
-parser.add_option("-c", "--config-file",
-                  help=("Specify the path to the buildout configuration "
-                        "file to be used."))
-parser.add_option("-f", "--find-links",
-                  help=("Specify a URL to search for buildout releases"))
-parser.add_option("--allow-site-packages",
-                  action="store_true", default=False,
-                  help=("Let bootstrap.py use existing site packages"))
-parser.add_option("--buildout-version",
-                  help="Use a specific zc.buildout version")
-parser.add_option("--setuptools-version",
-                  help="Use a specific setuptools version")
-parser.add_option("--setuptools-to-dir",
-                  help=("Allow for re-use of existing directory of "
-                        "setuptools versions"))
-
-options, args = parser.parse_args()
-if options.version:
-    print("bootstrap.py version %s" % __version__)
-    sys.exit(0)
-
-
-######################################################################
-# load/install setuptools
-
-try:
-    from urllib.request import urlopen
-except ImportError:
-    from urllib2 import urlopen
-
-ez = {}
-if os.path.exists('ez_setup.py'):
-    exec(open('ez_setup.py').read(), ez)
-else:
-    exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez)
-
-if not options.allow_site_packages:
-    # ez_setup imports site, which adds site packages
-    # this will remove them from the path to ensure that incompatible versions
-    # of setuptools are not in the path
-    import site
-    # inside a virtualenv, there is no 'getsitepackages'.
-    # We can't remove these reliably
-    if hasattr(site, 'getsitepackages'):
-        for sitepackage_path in site.getsitepackages():
-            # Strip all site-packages directories from sys.path that
-            # are not sys.prefix; this is because on Windows
-            # sys.prefix is a site-package directory.
-            if sitepackage_path != sys.prefix:
-                sys.path[:] = [x for x in sys.path
-                               if sitepackage_path not in x]
-
-setup_args = dict(to_dir=tmpeggs, download_delay=0)
-
-if options.setuptools_version is not None:
-    setup_args['version'] = options.setuptools_version
-if options.setuptools_to_dir is not None:
-    setup_args['to_dir'] = options.setuptools_to_dir
-
-ez['use_setuptools'](**setup_args)
-import setuptools
-import pkg_resources
-
-# This does not (always?) update the default working set.  We will
-# do it.
-for path in sys.path:
-    if path not in pkg_resources.working_set.entries:
-        pkg_resources.working_set.add_entry(path)
-
-######################################################################
-# Install buildout
-
-ws = pkg_resources.working_set
-
-setuptools_path = ws.find(
-    pkg_resources.Requirement.parse('setuptools')).location
-
-# Fix sys.path here as easy_install.pth added before PYTHONPATH
-cmd = [sys.executable, '-c',
-       'import sys; sys.path[0:0] = [%r]; ' % setuptools_path +
-       'from setuptools.command.easy_install import main; main()',
-       '-mZqNxd', tmpeggs]
-
-find_links = os.environ.get(
-    'bootstrap-testing-find-links',
-    options.find_links or
-    ('http://downloads.buildout.org/'
-     if options.accept_buildout_test_releases else None)
-    )
-if find_links:
-    cmd.extend(['-f', find_links])
-
-requirement = 'zc.buildout'
-version = options.buildout_version
-if version is None and not options.accept_buildout_test_releases:
-    # Figure out the most recent final version of zc.buildout.
-    import setuptools.package_index
-    _final_parts = '*final-', '*final'
-
-    def _final_version(parsed_version):
-        try:
-            return not parsed_version.is_prerelease
-        except AttributeError:
-            # Older setuptools
-            for part in parsed_version:
-                if (part[:1] == '*') and (part not in _final_parts):
-                    return False
-            return True
-
-    index = setuptools.package_index.PackageIndex(
-        search_path=[setuptools_path])
-    if find_links:
-        index.add_find_links((find_links,))
-    req = pkg_resources.Requirement.parse(requirement)
-    if index.obtain(req) is not None:
-        best = []
-        bestv = None
-        for dist in index[req.project_name]:
-            distv = dist.parsed_version
-            if _final_version(distv):
-                if bestv is None or distv > bestv:
-                    best = [dist]
-                    bestv = distv
-                elif distv == bestv:
-                    best.append(dist)
-        if best:
-            best.sort()
-            version = best[-1].version
-if version:
-    requirement = '=='.join((requirement, version))
-cmd.append(requirement)
-
-import subprocess
-if subprocess.call(cmd) != 0:
-    raise Exception(
-        "Failed to execute command:\n%s" % repr(cmd)[1:-1])
-
-######################################################################
-# Import and run buildout
-
-ws.add_entry(tmpeggs)
-ws.require(requirement)
-import zc.buildout.buildout
-
-if not [a for a in args if '=' not in a]:
-    args.append('bootstrap')
-
-# if -c was provided, we push it back into args for buildout' main function
-if options.config_file is not None:
-    args[0:0] = ['-c', options.config_file]
-
-zc.buildout.buildout.main(args)
-shutil.rmtree(tmpeggs)
diff --git a/buildout.cfg b/buildout.cfg
deleted file mode 100644
index 37d0dcf9..00000000
--- a/buildout.cfg
+++ /dev/null
@@ -1,110 +0,0 @@
-[buildout]
-develop = .
-parts =
-    script
-    test
-    sphinx
-versions = versions
-show-picked-versions = true
-
-[versions]
-# Dependencies for which we want the latest versions to find out about new
-# incompatibilities quickly.
-chardet =
-check-manifest =
-pyroma =
-readme_renderer =
-twine =
-wheel =
-
-# For sphinx, old versions seem best.
-Sphinx = 1.7.1
-sphinx-rtd-theme = 0.2.4
-sphinxcontrib-websupport = 1.0.1
-# I have seen failures installing cryptography 3, so use an older one.
-cryptography = 2.8
-# keyring = 21.8.0 gives problems getting entrypoints with ancient importlib-metadata.
-keyring = 21.0.0
-
-# Versions reported by buildout
-alabaster = 0.7.12
-Babel = 2.9.0
-bleach = 3.2.1
-build = 0.1.0
-certifi = 2020.12.5
-collective.recipe.sphinxbuilder = 1.1
-colorama = 0.4.4
-docutils = 0.16
-idna = 2.10
-imagesize = 1.2.0
-importlib-metadata = 4.6.1
-Jinja2 = 2.11.2
-MarkupSafe = 1.1.1
-martian = 1.4
-packaging = 20.8
-pep517 = 0.9.1
-pkginfo = 1.6.1
-Pygments = 2.7.3
-pyparsing = 2.4.7
-pytz = 2020.5
-requests = 2.25.1
-requests-toolbelt = 0.9.1
-rfc3986 = 1.4.0
-six = 1.15.0
-snowballstemmer = 2.0.0
-toml = 0.10.2
-tqdm = 4.55.1
-typing = 3.7.4.3
-typing-extensions = 3.7.4.3
-urllib3 = 1.26.2
-webencodings = 0.5.1
-z3c.testsetup = 0.8.4
-zc.buildout = 2.13.3
-zc.recipe.egg = 2.0.7
-zc.recipe.testrunner = 2.2
-zope.exceptions = 4.4
-zope.interface = 5.2.0
-zope.testing = 4.8
-zope.testrunner = 5.2
-
-[versions:python27]
-# Newer versions are incompatible with Python 2:
-twine = < 2
-check-manifest = 0.41
-Pygments = < 2.6
-configparser = < 5
-zipp = <2.0.0
-contextlib2 = 0.6.0.post1
-pyroma = 2.6
-importlib-metadata = 2.1.1
-
-[versions:python35]
-twine = < 2
-# Newer versions of check-manifest use f-strings, which needs 3.6.
-check-manifest = 0.41
-pyroma = 2.6
-
-[script]
-recipe = zc.recipe.egg
-dependent-scripts = true
-interpreter = py
-eggs =
-    zest.releaser[recommended]
-
-
-[test]
-recipe = zc.recipe.testrunner
-defaults = ['--tests-pattern', '^tests$', '-v', '-c']
-eggs =
-     zest.releaser
-     zest.releaser[recommended]
-     zest.releaser[test]
-
-
-[sphinx]
-recipe = collective.recipe.sphinxbuilder
-source = ${buildout:directory}/doc/source
-build = ${buildout:directory}/doc/build
-eggs =
-    zest.releaser
-    sphinx_rtd_theme
diff --git a/doc/source/assumptions.rst b/doc/source/assumptions.rst
index e3d0d174..9c7713bf 100644
--- a/doc/source/assumptions.rst
+++ b/doc/source/assumptions.rst
@@ -6,10 +6,6 @@ are some assumptions build-in that might or might not fit you.  Lots of people
 are using it in various companies and open source projects, so it'll probably
 fit :-)
 
-- If you are using svn, your svn is structured with /trunk, /tags (or
-  /tag) and optionally /branches (or /branch).  Both a /trunk or a
-  /branches/something checkout is ok.
-
 - We absolutely need a version. There's a ``version.txt`` or ``setup.py`` in
   your project. The ``version.txt`` has a single line with the version number
   (newline optional). The ``setup.py`` should have a single ``version =
diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst
index f76d9c79..3902513b 100644
--- a/doc/source/changelog.rst
+++ b/doc/source/changelog.rst
@@ -2,6 +2,794 @@
 
 .. # Note: CHANGES.rst is the current changelog, the older entries are in here.
 
+6.22.2 (2021-10-29)
+-------------------
+
+- Migrate to `travis-ci.com <https://travis-ci.com/github/zestsoftware/zest.releaser>`_.
+  [maurits]
+
+
+6.22.1 (2020-09-22)
+-------------------
+
+- When replacing new version in ``__version__ = "1.0"``, keep the existing quote style.
+  We always replaced double with single quotes, but now we keep them.
+  [graingert]
+
+
+6.22.0 (2020-09-03)
+-------------------
+
+- Fixed deadlock when communicating with git-lfs, or anything else that gives back lots of output.
+  [mcdeck]
+
+- Fixed TypeError setting unicode in environment on Python 2.7 on Windows.
+  [mcdeck]
+
+
+6.21.1 (2020-08-07)
+-------------------
+
+- Fixed uploading to multiple servers if we do not want to upload to the first server.
+  Fixes `issue #357 <https://github.com/zestsoftware/zest.releaser/issues/357>`_.
+  [maurits]
+
+
+6.21.0 (2020-07-01)
+-------------------
+
+- Added support for Twine environment variables.
+  Especially, setting ``TWINE_REPOSITORY`` and ``TWINE_REPOSITORY_URL`` had no effect previously.
+  Fixes `issue #353 <https://github.com/zestsoftware/zest.releaser/issues/353>`_.
+  [mctwynne]
+
+
+6.20.1 (2020-02-21)
+-------------------
+
+- Added ``Dockerfile`` for much easier testing. See `the developer
+  documentation
+  <https://zestreleaser.readthedocs.io/en/latest/developing.html>`_. Previously,
+  getting the tests to run reliably locally was hard, now it is easy.
+  [Behoston]
+
+
+6.20.0 (2020-01-07)
+-------------------
+
+- Zest.releaser now sets an environment variable ``ZESTRELEASER`` so that
+  tools that we call on the command line can detect us. Don't depend on the
+  variable's textual content, just on the variable's name.
+
+
+6.19.1 (2019-09-03)
+-------------------
+
+- Percent signs (%) don't crash addchangelogentry anymore.
+
+
+6.19.0 (2019-06-03)
+-------------------
+
+- Do not go to the root of the repo by default.
+  When you were not in the root of a repo, ``zest.releaser`` already asked if you wanted to go there.
+  The default answer has now changed from yes to no.
+  This might help when releasing from monorepos.
+  Issue `#326 <https://github.com/zestsoftware/zest.releaser/issues/326>`_.  [maurits]
+
+
+6.18.2 (2019-04-10)
+-------------------
+
+- Remove nothing_changed_yet line from history lines in unreleased section. [kleschenko]
+
+
+6.18.1 (2019-04-04)
+-------------------
+
+- Document that we only support underline-style headings for markdown.
+  Fixes `issue 317 <https://github.com/zestsoftware/zest.releaser/issues/317>`_.
+  [reinout]
+
+- Using simply ``git ls-files`` to list files in a git repo instead of an
+  older much longer command. (Suggestion by @mgedmin).
+  [reinout]
+
+
+6.18.0 (2019-04-03)
+-------------------
+
+- Calling twine in a more generic way to let it automatically do the right
+  thing. This saves us a lot of code and lets twine do what it's good at.
+  [htgoebel,reinout]
+
+
+6.17.2 (2019-03-25)
+-------------------
+
+- When ``bdist_wheel`` is in ``setup.cfg``, release a wheel.
+  No longer check if this should be a universal wheel or not.
+  That is handled automatically when calling ``python setup.py bdist_wheel``.
+  You can still set ``[zest.releaser] create-wheel = no`` to prevent creating a wheel.
+  Fixes `issue 315 <https://github.com/zestsoftware/zest.releaser/issues/315>`_.
+  [maurits]
+
+
+6.17.1 (2019-03-19)
+-------------------
+
+- Also accept 201 as valid statuscode when uploading using twine
+  Fixes `issue 318 <https://github.com/zestsoftware/zest.releaser/issues/318>`_
+  [fRiSi]
+
+
+6.17.0 (2019-02-20)
+-------------------
+
+- Refuse to edit history header when it looks to be from an already released version.
+  We look for a date in it (like 2019-02-20).  Give a warning when this happens.
+  Fixes `issue 311 <https://github.com/zestsoftware/zest.releaser/issues/311>`_.
+  [maurits]
+
+- Better support for ``zestreleaser.towncrier`` (and similar extensions):
+  the update_history setting is now also respected by the ``bumpversion`` command.
+  Fixes `issue 310 <https://github.com/zestsoftware/zest.releaser/issues/310>`_.
+  [maurits]
+
+
+6.16.0 (2019-01-17)
+-------------------
+
+- Fix for `issue #259 <https://github.com/zestsoftware/zest.releaser/issues/259>`_:
+  using zest.releaser on windows no longer can result in accidental extra
+  ``\r`` (carriage return) characters in the changelog and your ``setup.py``.
+  [reinout]
+
+
+6.15.4 (2019-01-11)
+-------------------
+
+- We retain the existing quoting style for the ``version='1.0'`` in
+  ``setup.py`` files. The "black" code formatting prefers double quotes and
+  zest.releaser by default wrote single quotes.
+  [reinout]
+
+- Fix for `issue #299 <https://github.com/zestsoftware/zest.releaser/issues/299>`_:
+  bumpversion now also compares versions numerically instead of as a string,
+  so ``2.9 < 2.10`` is now true.
+  [reinout]
+
+
+6.15.3 (2018-12-03)
+-------------------
+
+- Fix for `issue #297 <https://github.com/zestsoftware/zest.releaser/issues/297>`_:
+  bytes+int problem on python 3 when detecting encodings.
+  [reinout]
+
+
+6.15.2 (2018-08-30)
+-------------------
+
+- If a tag already exists, zest.releaser asks a safety question. The location
+  where the question gets asked was moved slightly to help a program that uses
+  zest.releaser as a library.
+  [reinout]
+
+- Switched our readthedocs urls to https.
+  [reinout]
+
+
+6.15.1 (2018-06-22)
+-------------------
+
+- Fix for #286: removed the confusing word "register" from the info message
+  you got when a package wasn't available yet on pypi.
+
+  Registering isn't used anymore on pypi, but it was still in our textual
+  message.
+  [reinout]
+
+
+6.15.0 (2018-05-15)
+-------------------
+
+- Use pypi.org, especially when checking if a package is on PyPI.
+  Fixes `issue #281 <https://github.com/zestsoftware/zest.releaser/issues/281>`_.
+  [maurits]
+
+- Added key ``update_history`` in prerelease and postrelease data.
+  Plugins can use this to tell ``zest.releaser`` (and other plugins)
+  to not touch the history, presumably because the plugin handles it.
+  [maurits]
+
+- Declared ``requests`` dependency.
+  Declared ``zope.testing`` test dependency.
+  [maurits]
+
+
+6.14.0 (2018-03-26)
+-------------------
+
+- Advertise ``setup.cfg`` option ``[zest.releaser] history-file``.
+  Usually zest.releaser can find the correct history or changelog file on its own.
+  But sometimes it may not find anything, or it finds multiple files and
+  selects the wrong one.
+  Then you can set a path here.
+  A ``history_file`` option with an underscore was already read, but not documented.
+  Now we try both a dash and an underscore for good measure.
+  [maurits]
+
+- Use new ``setup.cfg`` option ``[zest.releaser] encoding``.
+  Set this to, for example, ``utf-8`` when the encoding of your ``CHANGES.rst``
+  file is not determined correctly.
+  Fixes `issue 264 <https://github.com/zestsoftware/zest.releaser/issues/264>`_.
+  [maurits]
+
+- When inserting changelog entry, check that it conforms to the existing encoding.
+  Try to recover if there is a difference, especially when the changelog file
+  was ascii and we insert utf-8.  [maurits]
+
+- When determining encoding, first look for coding hints in the file itself.
+  Only when that fails, we try ``tokenize`` or ``chardet``.
+  Fixes `issue 264 <https://github.com/zestsoftware/zest.releaser/issues/264>`_.
+  [maurits]
+
+- Get PyPI password raw, without interpolation.
+  If you had a password with a percentage sign, you could get an error.
+  Fixes `issue 271 <https://github.com/zestsoftware/zest.releaser/issues/271>`_.
+  [maurits]
+
+- Prevent unclosed files.  Python 3.6 warned about them,
+  and PyPy may have more problems with it.
+  Fixed several other DeprecationWarnings.  [maurits]
+
+- Print commands in a nicer way.
+  You could get ugly output like this, especially on Python 2.7:
+  ``INFO: The '[u'git', u'diff']':`` or worse:
+  ``Command failed: u"t w i n e ' ' u p l o a d"``.
+  [maurits]
+
+- Test compatibility with Python 2.7, 3.4, 3.5, 3.6, PyPy2.  [maurits]
+
+
+6.13.5 (2018-02-16)
+-------------------
+
+- Quit in ``postrelease`` when we cannot find a version.
+  Fixes `issue #262 <https://github.com/zestsoftware/zest.releaser/issues/262>`.
+  [maurits]
+
+
+6.13.4 (2018-02-05)
+-------------------
+
+- Fixed IOError when ``setup.cfg`` is missing and no version is found.
+  [maurits]
+
+
+6.13.3 (2017-12-19)
+-------------------
+
+- Fixed writing of files in original encoding on python3, too. [andreparames]
+
+
+6.13.2 (2017-11-27)
+-------------------
+
+- Fixed tests with mercurial 4.4+.  [maurits]
+
+- Fixed writing of files in original encoding. [mgedmin]
+
+
+6.13.1 (2017-11-13)
+-------------------
+
+- Add tag message formatting (option ``tag-message``). [htgoebel]
+
+
+6.13.0 (2017-11-10)
+-------------------
+
+- Add support for signing tags (option ``tag-signing``). [htgoebel]
+
+
+6.12.5 (2017-09-25)
+-------------------
+
+- Sorting uploadable filenames so that wheels are uploaded first. (For most
+  filesystems this happened automatically, but the order on OSX' new
+  filesystem is non-deterministic, so we added sorting.)
+  [reinout]
+
+- Release process will now fail when specified hooks cannot be imported.
+  (`PR #236 <https://github.com/zestsoftware/zest.releaser/pulls/236>`_)
+
+
+6.12.4 (2017-08-30)
+-------------------
+
+- Also support version in setup.cfg. [ewjoachim]
+
+
+6.12.3 (2017-08-16)
+-------------------
+
+- Allows ``{version}`` format for ``tag-format``.
+  [leorochael]
+
+
+6.12.2 (2017-07-13)
+-------------------
+
+- Subversion fix: create tag of entire trunk or branch when not in repo root.
+  If you have ``trunk/pkg1`` and ``trunk/pkg2`` and you make tag 1.0 in directory ``pkg1``,
+  then until now we would create ``tags/1.0`` with the contents of directory ``pkg1``.
+  Checking out the tag and changing to the ``pkg1`` directory then failed.
+  We now make a tag of the entire trunk or branch, just like in the other version control systems.
+  Fixes `issue #213 <https://github.com/zestsoftware/zest.releaser/issues/213>`_.
+  [maurits]
+
+- Do not needlessly run ``svn info``.  [maurits]
+
+
+6.12.1 (2017-07-03)
+-------------------
+
+- Quote the path when making a git clone, to fix problems with spaces.  [halkeye]
+
+- Fixed percentage signs in ``date-format`` in ``setup.cfg``.
+  You need double percentages.  [mgedmin]
+
+
+6.12 (2017-06-19)
+-----------------
+
+- Add date format in the config.  Default is ISO-8601 (%Y-%m-%d).
+  Put ``date-format = format string`` in your ``~/.pypirc`` or ``setup.cfg``.
+  [mgedmin]
+
+
+6.11 (2017-06-09)
+-----------------
+
+- If the package wants to build universal wheels by setting
+  ``[bdist_wheel] universal = 1``, then the default for
+  ``create-wheel`` is now yes.
+
+
+6.10 (2017-04-18)
+-----------------
+
+- Corner case fix: a top-level ``version = 1.0`` in your ``setup.py`` is now
+  also allowed to be in uppercase, like ``VERSION = 1.0``.
+  This fixes `issue 216
+  <https://github.com/zestsoftware/zest.releaser/issues/216>`_.
+  [reinout]
+
+
+6.9 (2017-02-17)
+----------------
+
+- Add tag formatter in the config.  This is a formatter that changes the name of the tag.
+  Default is the same as the version.
+  Put ``tag-format = a string`` in your ``~/.pypirc`` or ``setup.cfg``.
+  It needs to contain ``%(version)s``.
+  [tcezard]
+
+
+6.8.1 (2017-01-13)
+------------------
+
+- Catch error when uploading first package file in new PyPI project.
+  This fixes `issue 206
+  <https://github.com/zestsoftware/zest.releaser/issues/206>`_.
+  [maurits]
+
+
+6.8 (2016-12-30)
+----------------
+
+- Before retrying a ``twine`` command, reload the pypi config.  Then
+  when the user fixes his account settings in ``~/.pypirc`` and
+  retries, these changes take effect.  This used to work a while ago,
+  but got broken.  [maurits]
+
+- Added ``development-marker`` config option.  With this can override
+  the default ``.dev0``.  [drucci]
+
+- Added ``version-levels`` and ``less-zeroes`` options.
+  This influences the suggested version.  [maurits]
+
+- Allow ``.pypirc`` with just a ``pypi`` section.  Previously, we
+  required either a ``[server-login]`` section with a ``username``
+  option, or a ``[distutils]`` section with an ``index-servers`` option.
+  Failing this, we gave a warning about a not properly configured
+  file, and happily continued without uploading anything.  Now if
+  there is something missing from the ``pypirc`` file, we give an
+  error and explicitly ask if you want to continue without uploading.
+  Fixes `issue #199 <https://github.com/zestsoftware/zest.releaser/issues/199>`_.
+
+  Note for developers of extensions for ``zest.releaser``: this
+  removes the ``is_old_pypi_config`` and ``is_new_pypi_config``
+  methods, because they made no sense anymore.  If you were using
+  these, see if you can use the ``distutils_server`` method instead.
+  [maurits]
+
+- Added ``push-changes`` config file option.  Default: yes.  When this
+  is false, zest.releaser sets ``no`` as default answer for the
+  question if you want to push the changes to the remote.
+  [newlog]
+
+- By default no longer register a new package, but only upload it.
+  Registering a package is no longer needed on PyPI: uploading a new
+  distribution takes care of this.  If you *do* want to register,
+  maybe because a different package server requires it, then in your
+  ``setup.cfg`` or ``~/.pypirc``, use the following::
+
+    [zest.releaser]
+    register = yes
+
+  Fixes `issue 191 <https://github.com/zestsoftware/zest.releaser/issues/191>`_.
+  [willowmck]
+
+
+6.7.1 (2016-12-22)
+------------------
+
+- Create the list of distributions after the ``before_upload`` hook has fired.
+  This allows the ``before_upload`` hook to create additional distributions,
+  which will then be uploaded.  [t-8ch]
+
+
+6.7 (2016-10-23)
+----------------
+
+- Use the intended API of twine.  This should work with twine 1.6.0
+  and higher, including future versions.  [maurits]
+
+
+6.6.5 (2016-09-12)
+------------------
+
+- Support and require twine 1.8.0 as minimum version.
+  Fixes https://github.com/zestsoftware/zest.releaser/issues/183
+  [maurits]
+
+- Updated the documentation on uploading.  [mgedmin, maurits]
+
+- Replaced http://zestreleaser.readthedocs.org with
+  https://zestreleaser.readthedocs.io.  This is the new canonical
+  domain since 28 April 2016.  [maurits]
+
+
+6.6.4 (2016-02-24)
+------------------
+
+- Really create a shallow git clone when creating a distribution.
+  See issue #169.
+  [maurits]
+
+
+6.6.3 (2016-02-24)
+------------------
+
+- Using a "shallow" git clone when creating a distribution. This speeds up
+  releases, especially on big repositories.
+  See issue #169.
+  [gforcada]
+
+
+6.6.2 (2016-02-11)
+------------------
+
+- Added ``no-input`` option also to global (.pypirc) options.
+  Issue #164.
+  [jcerjak]
+
+
+6.6.1 (2016-02-02)
+------------------
+
+- Fixed version in changelog after bumpversion call.
+  [maurits]
+
+
+6.6.0 (2016-01-29)
+------------------
+
+- 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]
+
+- Added ``addchangelogentry`` command.  Issue #159.  [maurits]
+
+- Moved ``_diff_and_commit``, ``_push`` and ``_grab_version`` to
+  ``baserelease.py``, as the first was duplicated and the second and
+  third may be handy for other code too.  ``_grab_version`` is the
+  basic implementation, and is overridden in the prereleaser.  [maurits]
+
+- Show changelog of current release before asking for the new version
+  number.  Issue #155.  [maurits]
+
+- Moved ``_diff_and_commit``, ``_push`` and ``_grab_version`` to
+  ``baserelease.py``, as the first was duplicated and the second and
+  third may be handy for other code too.  ``_grab_version`` is the
+  basic implementation, and is overridden in the prereleaser.  [maurits]
+
+6.5 (2016-01-05)
+----------------
+
+- Adjusted ``bin/longtest`` for the (necessary) rename of the ``readme``
+  library to ``readme_renderer``.
+  Fixes #153
+
+  Note: the current ``readme`` package on pypi is broken to force an
+  upgrade. If you use an older zest.releaser, you have to pin ``readme`` to
+  ``0.6.0``, it works just fine.
+  [reinout]
+
+
+6.4 (2015-11-13)
+----------------
+
+- Fixed error when retrying twine command.
+  Fixes #148
+  [maurits]
+
+
+6.3 (2015-11-11)
+----------------
+
+- Fixed exception when logging an exception when a twine command
+  fails.
+  [maurits]
+
+
+6.2 (2015-10-29)
+----------------
+
+New:
+
+- Use ``twine`` as library instead of as command.  You no longer need
+  to have ``twine`` on your ``PATH``.
+  Fixes issue #142.
+  [maurits]
+
+
+6.1 (2015-10-29)
+----------------
+
+Fixes:
+
+- Fixed registering on servers other than PyPI.  We forgot to specify
+  the server in that case.
+  [maurits]
+
+
+6.0 (2015-10-27)
+----------------
+
+- Made ``twine`` a core dependency.  We now always use it for
+  registering and uploading.  We require at least version 1.6.0, as
+  this introduces the ``register`` command.
+  [maurits]
+
+- When uploading with ``twine`` first use the ``twine register``
+  command.  On PyPI, when the project is already registered, we do not
+  call it again, but we can only check this for PyPI, not for other
+  servers.
+  Issue #128.
+  [maurits]
+
+- Always exit with error code 1 when we exit explicitly.  In some
+  cases we would exit with success code 0 when we exited based on the
+  answer to a question.  This happened when the user did not want us
+  to create the missing ``tags`` directory in subversion, and also
+  after asking if the user wanted to continue even though 'nothing
+  changed yet' was in the history.
+  [maurits]
+
+- Extensions can now tell zest.releaser to look for specific required
+  words in the history.  Just add ``required_changelog_text`` to the
+  prerelease data.  It can be a string or a list, for example
+  ``["New:", "Fixes:"]``.  For a list, only one of them needs to be
+  present.
+  [maurits]
+
+- Look for the 'Nothing changed yet' text in the complete text of the
+  history entries of the current release, instead of looking at it
+  line by line.  This means that zest releaser extensions can overwrite
+  ``nothing_changed_yet`` in the prerelease data to span multiple lines.
+  [maurits]
+
+- zest.releaser extensions can now look at
+  ``history_insert_line_here`` in the prerelease data.  On this line
+  number in the history file they can add an extra changelog entry if
+  wanted.
+  [maurits]
+
+- Added ``history_last_release`` to the prerelease data.  This is the
+  text with all history entries of the current release.
+  [maurits]
+
+- When using the ``--no-input`` option, show the question and the
+  chosen answer.  Otherwise in case of a problem it is not clear why
+  the command stopped.
+  Fixes issue #136.
+  [maurits]
+
+
+5.7 (2015-10-14)
+----------------
+
+- The history/changelog file is now written back with the originally detected
+  encoding. The functionality was added in 5.2, but only used for writing the
+  ``setup.py``, not the changelog. This is fixed now.
+  [reinout]
+
+
+5.6 (2015-09-23)
+----------------
+
+- Add support for PyPy.
+  [jamadden]
+
+
+5.5 (2015-09-05)
+----------------
+
+- The ``bin/longtest`` command adds the correct utf-8 character encoding hint
+  to the resulting html so that non-ascii long descriptions are properly
+  rendered in all browsers.
+  [reinout]
+
+
+5.4 (2015-08-28)
+----------------
+
+- Requiring at least version 0.6 of the (optional, btw) readme package. The
+  API of readme changed slightly. Only needed when you want to check your
+  package's long description with ``bin/longtest``.
+  [reinout]
+
+
+5.3 (2015-08-21)
+----------------
+
+- Fixed typo in svn command to show the changelog since the last tag.
+  [awello]
+
+
+5.2 (2015-07-27)
+----------------
+
+- When we find no version control in the current directory, look a few
+  directories up.  When looking for version and history files, we look
+  in the current directory and its sub directories, and not in the
+  repository root.  After making a tag checkout, we change directory
+  to the same relative path that we were in before.  You can use this
+  when you want to release a Python package that is in a sub directory
+  of the repository.  When we detect this, we first offer to change to
+  the root directory of the repository.
+  [maurits]
+
+- Write file with the same encoding that we used for reading them.
+  Issue #109.
+  [maurits]
+
+
+5.1 (2015-06-11)
+----------------
+
+- Fix writing history/changelog file with non-ascii.  Issue #109.
+  [maurits]
+
+- Release zest.releaser as universal wheel, so one wheel for Python 2
+  and 3.  As usual, we release it also as a source distribution.
+  [maurits]
+
+- Regard "Skipping installation of __init__.py (namespace package)" as
+  warning, printing it in magenta.  This can happen when creating a
+  wheel.  Issue #108.
+  [maurits]
+
+
+5.0 (2015-06-05)
+----------------
+
+- Python 3 support.
+  [mitchellrj]
+
+- Use the same `readme` library that PyPI uses to parse long
+  descriptions when we test and render them.
+  [mitchellrj]
+
+
+4.0 (2015-05-21)
+----------------
+
+- Try not to treat warnings as errors.
+  [maurits]
+
+- Allow retrying some commands when there is an error.  Currently only
+  for commands that talk to PyPI or another package index.  We ask the
+  user if she wants to retry: Yes, no, quit.
+  [maurits]
+
+- Added support for twine_.  If the ``twine`` command is available, it
+  is used for uploading to PyPI.  It is installed automatically if you
+  use the ``zest.releaser[recommended]`` extra.  Note that if the
+  ``twine`` command is not available, you may need to change your
+  system ``PATH`` or need to install ``twine`` explicitly.  This seems
+  more needed when using ``zc.buildout`` than when using ``pip``.
+  Added ``releaser.before_upload`` entry point.  Issue #59.
+  [maurits]
+
+- Added ``check-manifest`` and ``pyroma`` to the ``recommended``
+  extra.  Issue #49.
+  [maurits]
+
+- Python 2.6 not officially supported anymore.  It may still work, but
+  we are no longer testing against it.
+  [maurits]
+
+- Do not accept ``y`` or ``n`` as answer for a new version.
+  [maurits]
+
+- Use ``colorama`` to output errors in red.
+  Issue #86
+  [maurits]
+
+- Show errors when uploading to PyPI.  They were unintentionally
+  swallowed before, so you did not notice when an upload failed.
+  Issue #84.
+  [maurits]
+
+- Warn when between the last postrelease and a new prerelease no
+  changelog entry has been added.  '- Nothing changed yet' would still
+  be in there.
+  Issue #26.
+  [maurits]
+
+- Remove code for support of collective.sdist.  That package was a backport
+  from distutils for Python 2.5 and earlier, which we do not support.
+  [maurits]
+
+- Add optional support for uploading Python wheels.  Use the new
+  ``zest.releaser[recommended]`` extra, or run ``pip install wheel``
+  yourself next to ``zest.releaser``.  Create or edit ``setup.cfg`` in
+  your project (or globally in your ``~/.pypirc``) and create a section
+  ``[zest.releaser]`` with ``create-wheel = yes`` to create a wheel to
+  upload to PyPI.  See http://pythonwheels.com for deciding whether
+  this is a good idea for your package.  Briefly, if it is a pure
+  Python 2 *or* pure Python 3 package: just do it.
+  Issue #55
+  [maurits]
+
+- Optionally add extra text to commit messages.  This can be used to
+  avoid running Travis Continuous Integration builds.  See
+  http://docs.travis-ci.com/user/how-to-skip-a-build/.  To activate
+  this, add ``extra-message = [ci skip]`` to a ``[zest.releaser]``
+  section in the ``setup.cfg`` of your package, or your global
+  ``~/.pypirc``.  Or add your favorite geeky quotes there.
+  [maurits]
+
+- Fix a random test failure on Travis CI, by resetting ``AUTO_RESPONSE``.
+  [maurits]
+
+- Added clarification to logging: making an sdist/wheel now says that it is
+  being created in a temp folder. Fixes #61.
+  [reinout]
+
+.. _twine: https://pypi.org/project/twine
+
+
 3.56 (2015-03-18)
 -----------------
 
diff --git a/doc/source/developing.rst b/doc/source/developing.rst
index 468c7aa1..fc998f3d 100644
--- a/doc/source/developing.rst
+++ b/doc/source/developing.rst
@@ -1,14 +1,18 @@
 Information for developers of zest.releaser itself
 ===================================================
 
-**Note:** the whole test setup is quite elaborate and hard to get right.
-There is a solution however: docker. See the next section on this page. It is
-still work-in-progress, we'll probably add a docker-compose file, for
+**Note:** the whole test setup used to be quite elaborate and hard to get right.
+Since version 7, this is much less so.
+You just need ``git`` and ``tox``.
+
+If you still run into problems, there is a solution however: docker.
+See the next section on this page.
+It is still work-in-progress, we'll probably add a docker-compose file, for
 example. And support for testing different python versions with docker. (3.8
 is used now).
 
-So: for easy testing, use the docker commands . The rest of the document
-explains the original test setup requirements.
+So: for easy testing, use the docker commands, described next.
+The rest of the document explains the original test setup requirements.
 
 
 Testing with docker
@@ -22,6 +26,9 @@ If you have docker installed, all you need to do to run the tests is::
 The "build" step runs bootstrap and buildout. Re-run it periodically to get
 the latest versions of if you've changed the buildout config.
 
+Note: buildout should not be needed anymore, because we use ``tox``.
+So the Dockerfile was updated, but not tested.
+
 The "run" command runs the tests. It uses the code copied into the dockerfile
 in the build step, but you probably want to test your current version. For
 that, mount the code directory into the docker::
@@ -32,80 +39,53 @@ that, mount the code directory into the docker::
 Running tests
 -------------
 
-We like to use zc.buildout to get a test environment with any versions of
-needed python packages pinned.  When you are in the root folder of your
-zest.releaser checkout, do this::
+Actually, this should be easy now, because we use tox.
+So ``pip install tox`` somewhere, probably in a virtualenv, maybe the current directory,
+and call it:
+
+    tox
+
+You probably want to run the tests for all environments in parallel::
 
-  $ python bootstrap.py  # Or a different python version.
-  $ bin/buildout
-  $ export LANG=en_US.UTF-8
-  $ export LC_ALL=$LANG
-  $ export LANGUAGE=$LANG
-  $ bin/test
+    tox -p auto
 
-Do note the "necessary programs" and "configuration" sections below as you'll
-get test errors otherwise.
+To run a specific environment and a specific test file::
+
+    tox -e py38 -- utils.txt
 
 
 Python versions
 ---------------
 
-The tests currently pass on python 2.7, 3.3, 3.4 & 3.5. Travis continuous
-integration tests 2.7, 3.3, 3.4 & 3.5 for us automatically.
+The tests currently pass on python 3.6-3.10 and PyPy3.
 
 
 Necessary programs
 ------------------
 
-To run the tests, you need to have the supported versioning systems
-installed: svn, hg (mercurial), git and git-svn. On ubuntu::
+To run the tests, you need to have the supported versioning systems installed.
+Since version 7, we only support ``git``.
+On ubuntu::
 
-  $ sudo apt-get install subversion git bzr mercurial git-svn
+  $ sudo apt-get install git
 
 There may be test failures when you have different versions of these programs.
 In that case, please investigate as these may be genuine errors.
+In the past, ``git`` commands would give slightly different output.
+If the output of a command changes again, we may need extra compatibility code in ``test_setup.py``
 
 
 Setuptools is needed
 --------------------
 
-You also need distribute (or setuptools) in your system path.  This is because
+You also need ``setuptools`` in your system path.  This is because
 we basically call 'sys.executable setup.py egg_info' directly (in the tests
 and in the actual code), which will give an import error on setuptools
 otherwise.  There is a branch with a hack that solves this but it sounds too
 hacky.
 
-
-Configuration for mercurial, bzr and git
-----------------------------------------
-
-For mercurial you may get test failures because of extra output like
-this::
-
-  No username found, using 'name@domain' instead.
-
-To avoid this, create a file ~/.hgrc with something like this in it::
-
-  [ui]
-  username = Author Name <email@address.domain>
-
-If you keep having problems, `Ubuntu explains it
-<https://help.ubuntu.com/community/Mercurial>`_ quite good.
-
-There is a similar problem with bazaar.  Since bzr version 2.2, it
-refuses to guess what your username and email is, so you have to
-set it once, like this, otherwise you get test failures::
-
-  $ bzr whoami "Author Name <email@address.domain>"
-
-And the same for git, so you should do::
-
-  $ git config --global user.name "Your Name"
-  $ git config --global user.email you@example.com
-
-For release testing we expected a functioning .pypirc file in your
-home dir, with an old-style configuration, but the tests now use an
-own config file.
+TODO: calling ``setup.py`` from the command line is not recommended anymore.
+We should upgrade the code to no longer do that.
 
 
 Building the documentation locally
@@ -115,9 +95,12 @@ If you worked on the documentation, we suggest you verify the markup
 and the result by building the documentation locally and view your
 results.
 
-For building the documentation simply run::
+For building the documentation::
 
-    $ bin/sphinx
+    $ python3.9 -m venv .
+    $ bin/pip install sphinx sphinx_rtd_theme
+    $ bin/pip install -e .
+    $ bin/sphinx-build doc/source/ doc/build/
 
 For viewing the documentation open :file:`doc/build/html/index.html`
 in your browser, e.g. by running::
diff --git a/doc/source/index.rst b/doc/source/index.rst
index dc235bf8..1341e300 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -3,7 +3,7 @@ Zest.releaser: easy releasing and tagging for Python packages
 
 Make easy, quick and neat releases of your Python packages. You need to change
 the version number, add a new heading in your changelog, record the release
-date, svn/git/bzr/hg tag your project, perhaps upload it to
+date, git tag your project, perhaps upload it to
 pypi... *zest.releaser* takes care of the boring bits for you.
 
 Here's an overview of the documentation we have for you. First the
diff --git a/doc/source/options.rst b/doc/source/options.rst
index ff2645cf..691018eb 100644
--- a/doc/source/options.rst
+++ b/doc/source/options.rst
@@ -146,8 +146,6 @@ tag-message = a string
     Default: ``Tagging {version}``
     This formatter defines the commit message passed to the ``tag``
     command of the VCS.
-    Please note that not all VCSes (e.g. `bzr`) support passing
-    messages when tagging.
     It must contain ``{version}``.
 
 tag-signing = yes / no
diff --git a/doc/source/uploading.rst b/doc/source/uploading.rst
index 6f264f2a..37b147f0 100644
--- a/doc/source/uploading.rst
+++ b/doc/source/uploading.rst
@@ -193,29 +193,16 @@ files:
   you if you don't already have it. The default is often good enough.
 
 - Setuptools *can* detect which files are included in your version control
-  system (svn, git, etc.) which it'll then automatically include.
+  system (git) which it'll then automatically include.
 
 The last approch has a problem: not every version control system is supported
 out of the box. So you might need to install extra packages to get it to
 work. So: use a ``MANIFEST.in`` file to spare you the trouble. If not, here
-are some extra packages:
+is an extra package:
 
 - setuptools-git (Setuptools plugin for finding files under Git
   version control)
 
-- setuptools_hg (Setuptools plugin for finding files under Mercurial
-  version control)
-
-- setuptools_bzr (Setuptools plugin for finding files under Bazaar
-  version control)
-
-- setuptools_subversion (Setuptools plugin for finding files under
-  Subversion version control.)  You probably need this when you
-  upgrade to the recent subversion 1.7.  If you suddenly start missing
-  files in the sdists you upload to PyPI you definitely need it.
-  Alternatively: set up a proper MANIFEST.in as that method works with
-  any version control system.
-
 In general, if you are missing files in the uploaded package, the best
 is to put a proper ``MANIFEST.in`` file next to your ``setup.py``.
 See `zest.pocompile`_ for an example.
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..dcd753ac
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,2 @@
+[tool.isort]
+profile = "plone"
diff --git a/setup.cfg b/setup.cfg
index 163ae672..96912103 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -16,4 +16,5 @@ ignore =
     tox.ini
 
 [bdist_wheel]
-universal = 1
+# Python 3 only
+universal = 0
diff --git a/setup.py b/setup.py
index 60e02ae8..785cb475 100644
--- a/setup.py
+++ b/setup.py
@@ -1,115 +1,112 @@
-import codecs
-import sys
-
 from setuptools import find_packages
 from setuptools import setup
 
+import codecs
+import sys
 
-version = '6.22.3.dev0'
+
+version = "7.0.0a1.dev0"
 
 
 def read(filename):
     try:
-        with codecs.open(filename, encoding='utf-8') as f:
-            return unicode(f.read())
+        with codecs.open(filename, encoding="utf-8") as f:
+            return f.read()
     except NameError:
-        with open(filename, 'r', encoding='utf-8') as f:
+        with open(filename, encoding="utf-8") as f:
             return f.read()
 
 
-
-long_description = u'\n\n'.join([read('README.rst'),
-                                 read('CREDITS.rst'),
-                                 read('CHANGES.rst')])
+long_description = "\n\n".join(
+    [read("README.rst"), read("CREDITS.rst"), read("CHANGES.rst")]
+)
 
 if sys.version_info < (3,):
-    long_description = long_description.encode('utf-8')
-
+    long_description = long_description.encode("utf-8")
 
-setup(name='zest.releaser',
-      version=version,
-      description="Software releasing made easy and repeatable",
-      long_description=long_description,
-      classifiers=[
-          "Development Status :: 6 - Mature",
-          "Intended Audience :: Developers",
-          "License :: OSI Approved :: GNU General Public License (GPL)",
-          "Programming Language :: Python",
-          "Programming Language :: Python :: 2",
-          "Programming Language :: Python :: 2.7",
-          "Programming Language :: Python :: 3",
-          "Programming Language :: Python :: 3.5",
-          "Programming Language :: Python :: 3.6",
-          "Programming Language :: Python :: 3.7",
-          "Programming Language :: Python :: 3.8",
-          "Programming Language :: Python :: Implementation :: CPython",
-          "Programming Language :: Python :: Implementation :: PyPy",
-          "Topic :: Software Development :: Libraries :: Python Modules",
-          ],
-      keywords=['releasing', 'packaging', 'pypi'],
-      author='Reinout van Rees',
-      author_email='reinout@vanrees.org',
-      url='https://zestreleaser.readthedocs.io',
-      license='GPL',
-      packages=find_packages(exclude=['ez_setup']),
-      namespace_packages=['zest'],
-      include_package_data=True,
-      zip_safe=False,
-      install_requires=[
-          'setuptools',
-          'colorama',
-          'requests',
-          'six',
-          'twine >= 1.6.0',
-      ],
-      extras_require={
-          'recommended': [
-              'chardet',
-              'check-manifest',
-              'pyroma',
-              'readme_renderer',
-              'wheel',
-              ],
-          'test': [
-              'z3c.testsetup >= 0.8.4',
-              'zope.testing',
-              'zope.testrunner',
-              'wheel',
-              ]},
-      entry_points={
-          'console_scripts': [
-              'release = zest.releaser.release:main',
-              'prerelease = zest.releaser.prerelease:main',
-              'postrelease = zest.releaser.postrelease:main',
-              'fullrelease = zest.releaser.fullrelease:main',
-              'longtest = zest.releaser.longtest:main',
-              '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.
-          'zest.releaser.prereleaser.middle': [
-              'datacheck = zest.releaser.prerelease:datacheck',
-              ],
-          'zest.releaser.releaser.middle': [
-              'datacheck = zest.releaser.release:datacheck',
-              ],
-          'zest.releaser.postreleaser.middle': [
-              'datacheck = zest.releaser.postrelease:datacheck',
-              ],
-          '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 = ' +
-              'zest.releaser.preparedocs:prepare_entrypoint_documentation',
-              ],
 
-          },
-      )
+setup(
+    name="zest.releaser",
+    version=version,
+    description="Software releasing made easy and repeatable",
+    long_description=long_description,
+    classifiers=[
+        "Development Status :: 6 - Mature",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: GNU General Public License (GPL)",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3 :: Only",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: Implementation :: CPython",
+        "Programming Language :: Python :: Implementation :: PyPy",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+    ],
+    keywords=["releasing", "packaging", "pypi"],
+    author="Reinout van Rees",
+    author_email="reinout@vanrees.org",
+    url="https://zestreleaser.readthedocs.io",
+    license="GPL",
+    packages=find_packages(exclude=["ez_setup"]),
+    namespace_packages=["zest"],
+    include_package_data=True,
+    zip_safe=False,
+    install_requires=[
+        "setuptools",
+        "colorama",
+        "requests",
+        "twine >= 1.6.0",
+    ],
+    extras_require={
+        "recommended": [
+            "chardet",
+            "check-manifest",
+            "pyroma",
+            "readme_renderer",
+            "wheel",
+        ],
+        "test": [
+            "z3c.testsetup >= 0.8.4",
+            "zope.testing",
+            "zope.testrunner",
+            "wheel",
+        ],
+    },
+    entry_points={
+        "console_scripts": [
+            "release = zest.releaser.release:main",
+            "prerelease = zest.releaser.prerelease:main",
+            "postrelease = zest.releaser.postrelease:main",
+            "fullrelease = zest.releaser.fullrelease:main",
+            "longtest = zest.releaser.longtest:main",
+            "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.
+        "zest.releaser.prereleaser.middle": [
+            "datacheck = zest.releaser.prerelease:datacheck",
+        ],
+        "zest.releaser.releaser.middle": [
+            "datacheck = zest.releaser.release:datacheck",
+        ],
+        "zest.releaser.postreleaser.middle": [
+            "datacheck = zest.releaser.postrelease:datacheck",
+        ],
+        "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 = "
+            + "zest.releaser.preparedocs:prepare_entrypoint_documentation",
+        ],
+    },
+)
diff --git a/tox.ini b/tox.ini
index 98421722..a3ff8be3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,20 +1,11 @@
 [tox]
 envlist =
-    py27,py35,py36,py37,py38,pypy,pypy3
+    py36,py37,py38,py39,py310,pypy3
 
 [testenv]
 usedevelop = true
 deps =
-    zope.testrunner
-    z3c.testsetup
-    check-manifest
-    pyroma
-    twine
-    wheel
-    setuptools >= 8
+    zest.releaser[test]
+    zest.releaser[recommended]
 commands =
     zope-testrunner --test-path=. --tests-pattern=^tests$ {posargs:-v -c}
-# Tox 2.0+: need to pass HOME from the calling env, otherwise
-# HOME/.gitconfig cannot be found, so git cannot commit because it
-# wants to know who you are first.
-passenv = HOME
diff --git a/zest/releaser/__init__.py b/zest/releaser/__init__.py
index 3847cb70..848a6675 100644
--- a/zest/releaser/__init__.py
+++ b/zest/releaser/__init__.py
@@ -1,6 +1,8 @@
-import pkg_resources
 from colorama import init
 
+import pkg_resources
+
+
 # Initialize colorized output.  Set it to reset after each print, so
 # for example a foreground color does not linger into the next print.
 # Note that the colorama docs say you should call `deinit` at exit,
diff --git a/zest/releaser/addchangelogentry.py b/zest/releaser/addchangelogentry.py
index b2cbe7ae..67d1fcf5 100644
--- a/zest/releaser/addchangelogentry.py
+++ b/zest/releaser/addchangelogentry.py
@@ -1,12 +1,12 @@
 """Add a changelog entry.
 """
-from __future__ import unicode_literals
+
+from zest.releaser import baserelease
+from zest.releaser import utils
 
 import logging
 import sys
 
-from zest.releaser import baserelease
-from zest.releaser import utils
 
 logger = logging.getLogger(__name__)
 
diff --git a/zest/releaser/baserelease.py b/zest/releaser/baserelease.py
index 4fc9ff87..bce8d43f 100644
--- a/zest/releaser/baserelease.py
+++ b/zest/releaser/baserelease.py
@@ -1,20 +1,19 @@
 """Provide a base for the three releasers"""
 
-from __future__ import unicode_literals
+
+from zest.releaser import choose
+from zest.releaser import pypi
+from zest.releaser import utils
+from zest.releaser.utils import execute_command
+from zest.releaser.utils import read_text_file
+from zest.releaser.utils import write_text_file
 
 import logging
 import os
 import pkg_resources
 import re
-import six
 import sys
 
-from zest.releaser import utils
-from zest.releaser import choose
-from zest.releaser import pypi
-from zest.releaser.utils import execute_command
-from zest.releaser.utils import read_text_file
-from zest.releaser.utils import write_text_file
 
 logger = logging.getLogger(__name__)
 DATE_PATTERN = re.compile(r'^\d{4}-\d{2}-\d{2}$')
@@ -54,10 +53,10 @@
 NOTHING_CHANGED_YET = '- Nothing changed yet.'
 
 
-class Basereleaser(object):
+class Basereleaser:
 
     def __init__(self, vcs=None):
-        os.environ[str("ZESTRELEASER")] = str("We are called from within zest.releaser")
+        os.environ["ZESTRELEASER"] = "We are called from within zest.releaser"
         # ^^^ Env variable so called tools can detect us. Don't depend on the
         # actual text, just on the variable's name.
         if vcs is None:
@@ -91,10 +90,6 @@ def _grab_version(self, initial=False):
         between initially getting the current version, and later getting
         a suggested version or asking the user.
         """
-        # 'initial' is not used in this method, but may be used by sub classes.
-        # Mention it here so a check on landscape.io does not complain about an
-        # unused argument.
-        initial  # noqa
         version = self.vcs.version
         if not version:
             logger.critical("No version detected, so we can't do anything.")
@@ -291,7 +286,7 @@ def _insert_changelog_entry(self, message):
         prefix = utils.get_list_item(self.data['history_lines'])
         for index, line in enumerate(message.splitlines()):
             if index == 0:
-                line = '{} {}'.format(prefix, line)
+                line = f'{prefix} {line}'
             else:
                 line = '{}  {}'.format(' ' * len(prefix), line)
             lines.append(line)
@@ -311,7 +306,7 @@ def _check_nothing_changed(self):
             return
         # We want quotes around the text, but also want to avoid
         # printing text with a u'unicode marker' in front...
-        pretty_nothing_changed = '"{}"'.format(nothing_yet)
+        pretty_nothing_changed = f'"{nothing_yet}"'
         if not utils.ask(
                 "WARNING: Changelog contains {}. Are you sure you "
                 "want to release?".format(pretty_nothing_changed),
@@ -331,7 +326,7 @@ def _check_required(self):
         required = self.data.get('required_changelog_text')
         if not required:
             return
-        if isinstance(required, six.string_types):
+        if isinstance(required, str):
             required = [required]
         history_last_release = self.data['history_last_release']
         for text in required:
diff --git a/zest/releaser/bumpversion.py b/zest/releaser/bumpversion.py
index 4e742e3a..de4d782f 100644
--- a/zest/releaser/bumpversion.py
+++ b/zest/releaser/bumpversion.py
@@ -1,14 +1,14 @@
 """Do the checks and tasks that have to happen after doing a release.
 """
-from __future__ import unicode_literals
-
-import logging
-import sys
 
 from pkg_resources import parse_version
 from zest.releaser import baserelease
 from zest.releaser import utils
 
+import logging
+import sys
+
+
 logger = logging.getLogger(__name__)
 
 HISTORY_HEADER = '%(clean_new_version)s (unreleased)'
@@ -95,8 +95,8 @@ def _grab_version(self, initial=False):
                 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))
+                print(f"Last tag: {last_tag_version}")
+            print(f"Current version: {original_version}")
             params = dict(
                 feature=feature,
                 breaking=breaking,
diff --git a/zest/releaser/bzr.py b/zest/releaser/bzr.py
deleted file mode 100644
index d7106c2d..00000000
--- a/zest/releaser/bzr.py
+++ /dev/null
@@ -1,89 +0,0 @@
-from __future__ import unicode_literals
-
-import logging
-import tempfile
-import os
-import sys
-
-from zest.releaser.utils import execute_command
-from zest.releaser.vcs import BaseVersionControl
-
-logger = logging.getLogger(__name__)
-
-
-class Bzr(BaseVersionControl):
-    """Command proxy for Bazaar"""
-    internal_filename = '.bzr'
-    setuptools_helper_package = 'setuptools_bzr'
-
-    @property
-    def name(self):
-        package_name = self.get_setup_py_name()
-        if package_name:
-            return package_name
-        # No setup.py? With bzr we can probably only fall back to the directory
-        # name as there's no svn-url with a usable name in it.
-        dir_name = os.path.basename(os.getcwd())
-        return dir_name
-
-    def available_tags(self):
-        tag_info = execute_command(['bzr', 'tags'])
-        tags = [line[:line.find(' ')] for line in tag_info.split('\n')]
-        tags = [tag for tag in tags if tag]
-        logger.debug("Available tags: '%s'", ', '.join(tags))
-        return tags
-
-    def prepare_checkout_dir(self, prefix):
-        """Return directory where a tag checkout can be made"""
-        return tempfile.mkdtemp(prefix=prefix)
-
-    def tag_url(self, version):
-        # this doesn't apply to Bazaar, so we just return the
-        # version name given ...
-        return version
-
-    def cmd_diff(self):
-        return ['bzr', 'diff']
-
-    def cmd_commit(self, message):
-        return ['bzr', 'commit', '-v', '-m', message]
-
-    def cmd_diff_last_commit_against_tag(self, version):
-        return ["bzr", "diff", "-r", "tag:%s..-1" % version]
-
-    def cmd_log_since_tag(self, version):
-        return ["bzr", "log", "-r", "tag:%s..-1" % version]
-
-    def cmd_create_tag(self, version, message, sign=False):
-        if sign:
-            logger.error("bzr does not support signing tags, sorry. "
-                         "Please check your configuration in 'setup.cfg'.")
-            sys.exit(20)
-        return ["bzr", "tag", version]
-
-    def cmd_checkout_from_tag(self, version, checkout_dir):
-        source = self.reporoot
-        target = checkout_dir
-        return ["bzr", "checkout", "-r", "tag:%s" % version,
-                source, target]
-
-    def is_clean_checkout(self):
-        """Is this a clean checkout?
-
-        When you try to do commits in bazaar but you are on a tag you
-        will get this error:
-
-        "working tree is out of date, run 'bzr update'"
-
-        That should be clear enough already.  Well, we can run 'bzr
-        status' and see what we get.
-        """
-        # Check for changes to versioned files.
-        if execute_command(['bzr', 'status', '--versioned']):
-            # Local changes.
-            return False
-        return True
-
-    def list_files(self):
-        """List files in version control."""
-        return execute_command(['bzr', 'ls', '--recursive']).splitlines()
diff --git a/zest/releaser/choose.py b/zest/releaser/choose.py
index fb0fe2ac..b0064ad3 100644
--- a/zest/releaser/choose.py
+++ b/zest/releaser/choose.py
@@ -1,14 +1,10 @@
-from __future__ import unicode_literals
+from zest.releaser import git
+from zest.releaser import utils
 
 import logging
 import os
 import sys
 
-from zest.releaser import git
-from zest.releaser import hg
-from zest.releaser import bzr
-from zest.releaser import svn
-from zest.releaser import utils
 
 logger = logging.getLogger(__name__)
 
@@ -29,20 +25,7 @@ def version_control():
     q = "You are NOT in the root of the repository. Do you want to go there?"
     for level in range(8):
         curdir_contents = os.listdir(path)
-        if '.svn' in curdir_contents:
-            # Maybe chdir to the found root.
-            if level != 0 and utils.ask(q, default=False):
-                os.chdir(path)
-            return svn.Subversion(path)
-        elif '.hg' in curdir_contents:
-            if level != 0 and utils.ask(q, default=False):
-                os.chdir(path)
-            return hg.Hg(path)
-        elif '.bzr' in curdir_contents:
-            if level != 0 and utils.ask(q, default=False):
-                os.chdir(path)
-            return bzr.Bzr(path)
-        elif '.git' in curdir_contents:
+        if '.git' in curdir_contents:
             if level != 0 and utils.ask(q, default=False):
                 os.chdir(path)
             return git.Git(path)
diff --git a/zest/releaser/fullrelease.py b/zest/releaser/fullrelease.py
index fe570e99..83942549 100644
--- a/zest/releaser/fullrelease.py
+++ b/zest/releaser/fullrelease.py
@@ -1,15 +1,15 @@
 """Do the prerelease, actual release and post release in one fell swoop!
 """
-from __future__ import unicode_literals
-
-import logging
-import os
 
+from zest.releaser import postrelease
 from zest.releaser import prerelease
 from zest.releaser import release
-from zest.releaser import postrelease
 from zest.releaser import utils
 
+import logging
+import os
+
+
 logger = logging.getLogger(__name__)
 
 
diff --git a/zest/releaser/git.py b/zest/releaser/git.py
index e36f43f9..eab21cb0 100644
--- a/zest/releaser/git.py
+++ b/zest/releaser/git.py
@@ -1,13 +1,12 @@
-from __future__ import unicode_literals
+from zest.releaser.utils import execute_command
+from zest.releaser.utils import fs_to_text
+from zest.releaser.vcs import BaseVersionControl
 
 import logging
-import tempfile
 import os.path
 import sys
+import tempfile
 
-from zest.releaser.utils import fs_to_text
-from zest.releaser.utils import execute_command
-from zest.releaser.vcs import BaseVersionControl
 
 logger = logging.getLogger(__name__)
 
@@ -82,41 +81,6 @@ def cmd_create_tag(self, version, message, sign=False):
         cmd = ['git', 'tag', version, '-m', message]
         if sign:
             cmd.append("--sign")
-        if os.path.isdir('.git/svn'):
-            print("\nEXPERIMENTAL support for git-svn tagging!\n")
-            with open('.git/HEAD') as f:
-                cur_branch = f.read().strip().split('/')[-1]
-            print("You are on branch %s." % (cur_branch,))
-            if cur_branch != 'master':
-                print("Only the master branch is supported for "
-                      "git-svn tagging.")
-                print("Please tag yourself.")
-                print("'git tag' needs to list tag named %s." % (version,))
-                sys.exit(1)
-            cmd = [cmd]
-
-            trunk = None
-            # In Git v2.0, the default prefix will change from "" (no prefix)
-            # to "origin/", try both here.
-            for t in ['.git/refs/remotes/trunk',
-                      '.git/refs/remotes/origin/trunk']:
-                if os.path.isfile(t):
-                    with open(t) as f:
-                        trunk = f.read()
-
-            if not trunk:
-                print('No SVN remote found (only the default svn ' +
-                      'prefixes ("" or "origin/") are supported).')
-                sys.exit(1)
-
-            with open('.git/refs/heads/master') as f:
-                local_head = f.read()
-            if local_head != trunk:
-                print("Your local master diverges from trunk.\n")
-                # dcommit before local tagging
-                cmd.insert(0, ['git', 'svn', 'dcommit'])
-            # create tag in svn
-            cmd.append(['git', 'svn', 'tag', '-m', message, version])
         return cmd
 
     def cmd_checkout_from_tag(self, version, checkout_dir):
diff --git a/zest/releaser/hg.py b/zest/releaser/hg.py
deleted file mode 100644
index 56852830..00000000
--- a/zest/releaser/hg.py
+++ /dev/null
@@ -1,108 +0,0 @@
-from __future__ import unicode_literals
-
-import logging
-import tempfile
-import os
-import sys
-
-from zest.releaser.utils import fs_to_text
-from zest.releaser.utils import execute_command
-from zest.releaser.vcs import BaseVersionControl
-
-logger = logging.getLogger(__name__)
-
-
-class Hg(BaseVersionControl):
-    """Command proxy for Mercurial"""
-    internal_filename = '.hg'
-    setuptools_helper_package = 'setuptools_hg'
-
-    @property
-    def name(self):
-        package_name = self.get_setup_py_name()
-        if package_name:
-            return package_name
-        # No setup.py? With hg we can probably only fall back to the directory
-        # name as there's no svn-url with a usable name in it.
-        dir_name = os.path.basename(os.getcwd())
-        dir_name = fs_to_text(dir_name)
-        return dir_name
-
-    def available_tags(self):
-        tag_info = execute_command(['hg', 'tags'])
-        tags = [line[:line.find(' ')] for line in tag_info.split('\n')]
-        tags = [tag for tag in tags if tag]
-        tags.remove('tip')  # Not functional for us
-        logger.debug("Available tags: '%s'", ', '.join(tags))
-        return tags
-
-    def prepare_checkout_dir(self, prefix):
-        """Return directory where a tag checkout can be made"""
-        return tempfile.mkdtemp(prefix=prefix)
-
-    def tag_url(self, version):
-        # this doesn't apply to Mercurial, so we just return the
-        # version name given ...
-        return version
-
-    def cmd_diff(self):
-        return ['hg', 'diff']
-
-    def cmd_commit(self, message):
-        return ['hg', 'commit', '-v', '-m', message]
-
-    def cmd_diff_last_commit_against_tag(self, version):
-        current_revision = execute_command(['hg', 'identify'])
-        current_revision = current_revision.split(' ')[0]
-        # + at the end of the revision denotes uncommitted changes
-        current_revision = current_revision.rstrip('+')
-        return ['hg', 'diff', '-r', version, '-r', current_revision]
-
-    def cmd_log_since_tag(self, version):
-        current_revision = execute_command(['hg', 'identify'])
-        current_revision = current_revision.split(' ')[0]
-        # + at the end of the revision denotes uncommitted changes
-        current_revision = current_revision.rstrip('+')
-        return ['hg', 'log', '-r', version, '-r', current_revision]
-
-    def cmd_create_tag(self, version, message, sign=False):
-        if sign:
-            logger.error(
-                "Signing tags with mercurial is not yet implemented, sorry. "
-                "Please check your configuration in 'setup.cfg'.")
-            sys.exit(21)
-        # Note: place the '-m' before the argument for hg 1.1 support.
-        return ['hg', 'tag', '-m', message, version]
-
-    def cmd_checkout_from_tag(self, version, checkout_dir):
-        source = self.reporoot
-        target = checkout_dir
-        return ['hg', 'clone', '-r', version, source, target]
-
-    def checkout_from_tag(self, version):
-        package = self.name
-        prefix = '%s-%s-' % (package, version)
-        # Not all hg versions can do a checkout in an existing or even
-        # just in the current directory.
-        tagdir = tempfile.mktemp(prefix=prefix)
-        cmd = self.cmd_checkout_from_tag(version, tagdir)
-        print(execute_command(cmd))
-        os.chdir(tagdir)
-
-    def is_clean_checkout(self):
-        """Is this a clean checkout?
-        """
-        # The --quiet option ignores untracked (unknown and ignored)
-        # files, which seems reasonable.
-        if execute_command(['hg', 'status', '--quiet']):
-            # Local changes.
-            return False
-        return True
-
-    def push_commands(self):
-        """Return commands to push changes to the server."""
-        return [['hg', 'push']]
-
-    def list_files(self):
-        """List files in version control."""
-        return execute_command(['hg', 'locate', '--fullpath']).splitlines()
diff --git a/zest/releaser/lasttagdiff.py b/zest/releaser/lasttagdiff.py
index 009721bd..e5eaffea 100644
--- a/zest/releaser/lasttagdiff.py
+++ b/zest/releaser/lasttagdiff.py
@@ -1,14 +1,14 @@
 # GPL, (c) Reinout van Rees
 #
 # Script to show the diff with the last relevant tag.
-from __future__ import unicode_literals
+
+from zest.releaser import utils
+from zest.releaser.utils import execute_command
 
 import logging
 import sys
-
 import zest.releaser.choose
-from zest.releaser.utils import execute_command
-from zest.releaser import utils
+
 
 logger = logging.getLogger(__name__)
 
diff --git a/zest/releaser/lasttaglog.py b/zest/releaser/lasttaglog.py
index 3fdbed5b..350f9f2a 100644
--- a/zest/releaser/lasttaglog.py
+++ b/zest/releaser/lasttaglog.py
@@ -1,14 +1,14 @@
 # GPL, (c) Reinout van Rees
 #
 # Script to show the log from the last relevant tag till now.
-from __future__ import unicode_literals
+
+from zest.releaser import utils
+from zest.releaser.utils import execute_command
 
 import logging
 import sys
-
 import zest.releaser.choose
-from zest.releaser.utils import execute_command
-from zest.releaser import utils
+
 
 logger = logging.getLogger(__name__)
 
diff --git a/zest/releaser/postrelease.py b/zest/releaser/postrelease.py
index 715b0664..0e2eab4b 100644
--- a/zest/releaser/postrelease.py
+++ b/zest/releaser/postrelease.py
@@ -1,12 +1,12 @@
 """Do the checks and tasks that have to happen after doing a release.
 """
-from __future__ import unicode_literals
+
+from zest.releaser import baserelease
+from zest.releaser import utils
 
 import logging
 import sys
 
-from zest.releaser import baserelease
-from zest.releaser import utils
 
 logger = logging.getLogger(__name__)
 
diff --git a/zest/releaser/preparedocs.py b/zest/releaser/preparedocs.py
index c5ead0ba..81c44a41 100644
--- a/zest/releaser/preparedocs.py
+++ b/zest/releaser/preparedocs.py
@@ -1,16 +1,14 @@
-from __future__ import unicode_literals
-
-import os
-
 from zest.releaser import addchangelogentry
 from zest.releaser import baserelease
 from zest.releaser import bumpversion
+from zest.releaser import postrelease
 from zest.releaser import prerelease
 from zest.releaser import release
-from zest.releaser import postrelease
 from zest.releaser.utils import read_text_file
 from zest.releaser.utils import write_text_file
 
+import os
+
 
 def prepare_entrypoint_documentation(data):
     """Place the generated entrypoint doc in the source structure."""
diff --git a/zest/releaser/prerelease.py b/zest/releaser/prerelease.py
index 74340111..26494899 100644
--- a/zest/releaser/prerelease.py
+++ b/zest/releaser/prerelease.py
@@ -1,13 +1,13 @@
 """Do the checks and tasks that have to happen before doing a release.
 """
-from __future__ import unicode_literals
+
+from zest.releaser import baserelease
+from zest.releaser import utils
 
 import datetime
 import logging
 import sys
 
-from zest.releaser import baserelease
-from zest.releaser import utils
 
 logger = logging.getLogger(__name__)
 
@@ -55,7 +55,7 @@ def prepare(self):
         self._grab_history()
         if self.data['update_history']:
             # Print changelog for this release.
-            print("Changelog entries for version {0}:\n".format(
+            print("Changelog entries for version {}:\n".format(
                 self.data['new_version']))
             print(self.data.get('history_last_release'))
         # Grab and set new version.
diff --git a/zest/releaser/pypi.py b/zest/releaser/pypi.py
index b9c00b1b..2ae44b7b 100644
--- a/zest/releaser/pypi.py
+++ b/zest/releaser/pypi.py
@@ -1,14 +1,12 @@
-from __future__ import unicode_literals
+from configparser import ConfigParser
+from configparser import NoOptionError
+from configparser import NoSectionError
 
 import logging
 import os
+import pkg_resources
 import sys
 
-import pkg_resources
-from six import text_type
-from six.moves.configparser import ConfigParser
-from six.moves.configparser import NoSectionError
-from six.moves.configparser import NoOptionError
 
 try:
     pkg_resources.get_distribution('wheel')
@@ -23,7 +21,7 @@
 logger = logging.getLogger(__name__)
 
 
-class BaseConfig(object):
+class BaseConfig:
     """Base config class with a few helper methods."""
 
     def __init__(self):
@@ -56,7 +54,7 @@ def _get_text(self, section, key, default=None, raw=False):
                 result = self.config.get(section, key, raw=raw)
             except (NoSectionError, NoOptionError, ValueError):
                 return result
-            if not isinstance(result, text_type):
+            if not isinstance(result, str):
                 result = result.decode('utf-8')
         return result
 
diff --git a/zest/releaser/release.py b/zest/releaser/release.py
index 632a397e..2741fc7b 100644
--- a/zest/releaser/release.py
+++ b/zest/releaser/release.py
@@ -1,14 +1,14 @@
 # GPL, (c) Reinout van Rees
-from __future__ import unicode_literals
+
+from colorama import Fore
+from urllib import request
+from urllib.error import HTTPError
 
 import logging
 import os
+import requests
 import sys
 
-from colorama import Fore
-from six.moves.urllib.error import HTTPError
-from six.moves.urllib import request as urllib2
-import requests
 
 try:
     from twine.cli import dispatch as twine_dispatch
@@ -49,7 +49,7 @@ def package_in_pypi(package):
     """Check whether the package is registered on pypi"""
     url = 'https://pypi.org/simple/%s' % package
     try:
-        urllib2.urlopen(url)
+        request.urlopen(url)
         return True
     except HTTPError as e:
         logger.debug("Package not found on pypi: %s", e)
@@ -116,7 +116,7 @@ def _make_tag(self):
                       (tag, version))
                 sys.exit(1)
         if not self.vcs.tag_exists(tag):
-            print("\nFailed to create tag %s!" % (tag,))
+            print(f"\nFailed to create tag {tag}!")
             sys.exit(1)
 
     def _upload_distributions(self, package):
@@ -147,8 +147,8 @@ def _upload_distributions(self, package):
         self._run_hooks('before_upload')
 
         # Get list of all files to upload.
-        files_in_dist = sorted([
-            os.path.join('dist', filename) for filename in os.listdir('dist')]
+        files_in_dist = sorted(
+            os.path.join('dist', filename) for filename in os.listdir('dist')
         )
 
         register = self.pypiconfig.register_package()
@@ -200,7 +200,7 @@ def _ask_upload(self, package, server, register):
         question = "Upload"
         if register:
             question = "Register and upload"
-        return utils.ask("%s to %s" % (question, server),
+        return utils.ask(f"{question} to {server}",
                      default=default, exact=exact)
 
     def _retry_twine(self, twine_command, server, filenames):
@@ -298,32 +298,6 @@ def _release(self):
                 # Fix the setup.cfg in the current working directory
                 # so the current release works well.
                 self.setup_cfg.fix_config()
-                # Now we may want to commit this.  Note that this is
-                # only really useful for subversion, as for example in
-                # git you are in a detached HEAD state, which is a
-                # place where a commit will be lost.
-                #
-                # Ah, in the case of bazaar doing a commit is actually
-                # harmful, as the commit ends up on the tip, instead
-                # of only being done on a tag or branch.
-                #
-                # So the summary is:
-                #
-                # - svn: NEEDED, not harmful
-                # - git: not needed, not harmful
-                # - hg: not needed, not harmful
-                # - bzr: not needed, HARMFUL
-                #
-                # So for clarity and safety we should only do this for
-                # subversion.
-                if self.vcs.internal_filename == '.svn':
-                    command = self.vcs.cmd_commit(
-                        "Fixed %s on tag for release" %
-                        self.setup_cfg.config_filename)
-                    print(execute_command(command))
-                else:
-                    logger.debug("Not committing in non-svn repository as "
-                                 "this is not needed or may be harmful.")
 
         # Run extra entry point
         self._run_hooks('after_checkout')
diff --git a/zest/releaser/svn.py b/zest/releaser/svn.py
deleted file mode 100644
index e6672239..00000000
--- a/zest/releaser/svn.py
+++ /dev/null
@@ -1,219 +0,0 @@
-from __future__ import unicode_literals
-
-import tempfile
-import logging
-import sys
-
-from zest.releaser import utils
-from zest.releaser.utils import execute_command
-from zest.releaser.vcs import BaseVersionControl
-
-logger = logging.getLogger(__name__)
-
-
-class Subversion(BaseVersionControl):
-    """Command proxy for Subversion"""
-    internal_filename = '.svn'
-    setuptools_helper_package = 'setuptools_subversion'
-
-    def _svn_info(self):
-        """Return svn url"""
-        if not hasattr(self, '_cached_url'):
-            our_info = execute_command(['svn', 'info'])
-            url = [line for line in our_info.split('\n')
-                   if line.startswith('URL')][0]
-            # In English, you have 'URL:', in French 'URL :'
-            self._cached_url = url.split(':', 1)[1].strip()
-        return self._cached_url
-
-    def _base_from_svn(self):
-        # Get repository root url without trunk/tags/branches.
-        base = self._svn_info()
-        # Split and reverse, then look for the first trunk, tag or branch.
-        split_url = list(reversed(base.split('/')))
-        for remove in ['trunk', 'tags', 'branches', 'tag', 'branch']:
-            if remove in split_url:
-                split_url = split_url[split_url.index(remove) + 1:]
-                base = '/'.join(reversed(split_url))
-                # One trunk/tag/branch is quite enough.
-                break
-        if not base.endswith('/'):
-            base += '/'
-        logger.debug("Base url is %s", base)
-        return base
-
-    def _branch_url_from_svn(self):
-        # Get trunk url, or branches/something, or tags/1.0.
-        # This is usually the same as self._svn_info(),
-        # except when you are not in the repo root.
-        # For example, if you are in trunk/my-pkg1, and don't go
-        # to the repo root, this returns 'trunk' without 'my-pkg1'.
-        # Needed for https://github.com/zestsoftware/zest.releaser/issues/213
-        base = self._svn_info()
-        split_url = base.split('/')
-        if 'trunk' in split_url:
-            # Get url including trunk.
-            base = '/'.join(split_url[:split_url.index('trunk') + 1])
-        else:
-            # Get url including branch name or tag id.
-            # Most logical is that this is called for branches,
-            # so first look for those.
-            for flag in ['branches', 'branch', 'tags', 'tag']:
-                if flag in split_url:
-                    base = '/'.join(split_url[:split_url.index(flag) + 2])
-                    break
-        logger.debug("Branch url is %s", base)
-        return base
-
-    def _name_from_svn(self):
-        base = self._base_from_svn()
-        parts = base.split('/')
-        parts = [p for p in parts if p]
-        name = parts[-1]
-        logger.debug("Name is %s", name)
-        return name
-
-    @property
-    def _tags_name(self):
-        """Return name for tags dir
-
-        Normally the plural /tags, but some projects have the singular /tag.
-
-        """
-        default_plural = 'tags'
-        fallback_singular = 'tag'
-        # svn 1.7 introduced a slightly different message and a warning code.
-        failure_messages = ["non-existent in that revision",
-                            "W160013",
-                            ]
-        base = self._base_from_svn()
-        tag_info = execute_command(
-            ['svn', 'list', '%s%s' % (base, default_plural)])
-        # Look for one of the failure messages:
-        found = [1 for mess in failure_messages if mess in tag_info]
-        if not found:
-            return default_plural
-        logger.debug("tags dir does not exist at %s%s", base, default_plural)
-
-        tag_info = execute_command(
-            ['svn', 'list', '%s%s' % (base, fallback_singular)])
-        # Look for one of the failure messages:
-        found = [1 for mess in failure_messages if mess in tag_info]
-        if not found:
-            return fallback_singular
-        logger.debug("tags dir does not exist at %s%s, either", base,
-                     fallback_singular)
-        return None
-
-    @property
-    def name(self):
-        package_name = self.get_setup_py_name()
-        if package_name:
-            return package_name
-        else:
-            return self._name_from_svn()
-
-    def available_tags(self):
-        base = self._base_from_svn()
-        tags_name = self._tags_name
-        if tags_name is None:
-            # Suggest to create a tags dir with the default plural /tags name.
-            print("tags dir does not exist at %s" % base + 'tags')
-            if utils.ask("Shall I create it"):
-                cmd = ['svn', 'mkdir', base + 'tags',
-                       '-m', "Creating tags directory."]
-                logger.info("Running '%s'", utils.format_command(cmd))
-                print(execute_command(cmd))
-                tags_name = self._tags_name
-                assert tags_name == 'tags'
-            else:
-                sys.exit(1)
-
-        tag_info = execute_command(
-            ['svn', 'list', '--non-interactive', base + tags_name])
-        network_errors = [
-            'Could not resolve hostname',
-            'E670008',
-            'Repository moved',
-            'Unable to connect',
-        ]
-        found_errors = [1 for network_error in network_errors
-                        if network_error in tag_info]
-        if found_errors:
-            logger.error('Network problem: %s', tag_info)
-            sys.exit(1)
-        tags = [line.replace('/', '').strip()
-                for line in tag_info.split('\n')]
-        tags = [tag for tag in tags if tag]  # filter empty ones
-        logger.debug("Available tags: '%s'", ', '.join(tags))
-        return tags
-
-    def prepare_checkout_dir(self, prefix):
-        """Return directory where a tag checkout can be made"""
-        return tempfile.mkdtemp(prefix=prefix)
-
-    def tag_url(self, version):
-        base = self._base_from_svn()
-        return base + self._tags_name + '/' + version
-
-    def cmd_diff(self):
-        return ['svn', 'diff', '--non-interactive']
-
-    def cmd_commit(self, message):
-        return ['svn', 'commit', '--non-interactive', '-m', message]
-
-    def cmd_diff_last_commit_against_tag(self, version):
-        url = self._svn_info()
-        tag_url = self.tag_url(version)
-        return ['svn', '--non-interactive', 'diff', tag_url, url]
-
-    def cmd_log_since_tag(self, version):
-        """Return log since a tagged version till the last commit of
-        the working copy.
-        """
-        url = self._svn_info()
-        tag_url = self.tag_url(version)
-        tag_info = execute_command(
-            ['svn', 'info', '--non-interactive', tag_url])
-        # Search for: Last Changed Rev: 42761
-        revision = None
-        for line in tag_info.split('\n'):
-            line = line.lower()
-            if len(line.split(':')) == 2:
-                revision = line.split(':')[-1].strip()
-        if not revision:
-            logger.error('Could not find revision when tag was made: %s',
-                         tag_info)
-            sys.exit(1)
-        return ['svn', '--non-interactive', 'log', '-r%s:HEAD' % revision, url]
-
-    def cmd_create_tag(self, version, message, sign=False):
-        if sign:
-            logger.error("svn does not support signing tags, sorry. "
-                         "Please check your configuration in 'setup.cfg'.")
-            sys.exit(20)
-        url = self._branch_url_from_svn()
-        tag_url = self.tag_url(version)
-        return ['svn', 'cp', '--non-interactive', url, tag_url,
-                '-m', message]
-
-    def cmd_checkout_from_tag(self, version, checkout_dir):
-        tag_url = self.tag_url(version)
-        return ['svn', 'co', '--non-interactive', tag_url, checkout_dir]
-
-    def is_clean_checkout(self):
-        """Is this a clean checkout?
-        """
-        if self._svn_info().startswith(self._base_from_svn() + 'tag'):
-            # We should not commit on a tag.
-            return False
-        # We could do 'svn status --ignore-externals --quiet' and
-        # regard any output as a non-clean checkout, but when there
-        # are externals this still always prints some lines, which
-        # would give false negatives.  So we ignore it.
-        return True
-
-    def list_files(self):
-        """List files in version control."""
-        return execute_command(
-            ['svn', 'ls', '--non-interactive', '--recursive']).splitlines()
diff --git a/zest/releaser/utils.py b/zest/releaser/utils.py
index 5973b366..98928521 100644
--- a/zest/releaser/utils.py
+++ b/zest/releaser/utils.py
@@ -1,7 +1,7 @@
 # Small utility methods.
-from __future__ import unicode_literals
 
 from argparse import ArgumentParser
+
 import logging
 import os
 import re
@@ -9,6 +9,7 @@
 import sys
 import textwrap
 
+
 try:
     from tokenize import detect_encoding
 except ImportError:
@@ -19,10 +20,10 @@
 except ImportError:
     HAVE_CHARDET = False
 from colorama import Fore
-import pkg_resources
 from pkg_resources import parse_version
-import six
-from six.moves import input  # noqa
+
+import pkg_resources
+
 
 logger = logging.getLogger(__name__)
 
@@ -42,7 +43,7 @@
 
 
 def fs_to_text(fs_name):
-    if not isinstance(fs_name, six.text_type):
+    if not isinstance(fs_name, str):
         fs_name = fs_name.decode(sys.getfilesystemencoding(),
                                  'surrogateescape')
     return fs_name
@@ -68,37 +69,8 @@ def splitlines_with_trailing(content):
 
 
 def write_text_file(filename, contents, encoding=None):
-    if six.PY2:
-        if isinstance(contents, six.text_type):
-            # Python 2 unicode needs to be encoded.
-            if encoding is None:
-                encoding = OUTPUT_ENCODING
-                logger.debug(
-                    "Writing to %s with the default output encoding %s",
-                    filename, encoding)
-            else:
-                logger.debug("Writing to %s with its original encoding %s",
-                             filename, encoding)
-            # We might have added something to the contents (a changelog entry)
-            # that does not fit the detected encoding.
-            # So we try a few encodings.
-            orig_encoding = encoding
-            encodings = [orig_encoding, OUTPUT_ENCODING, 'utf-8']
-            for encoding in encodings:
-                try:
-                    contents = contents.encode(encoding)
-                    logger.debug("Encoding we actually used: %s", encoding)
-                    break
-                except UnicodeEncodeError:
-                    pass
-            else:
-                logger.error("Could not write to file %s with any of these "
-                             "encodings: %s", filename, encodings)
-        with open(filename, 'w') as f:
-            f.write(contents)
-    else:
-        with open(filename, 'w', encoding=encoding) as f:
-            f.write(contents)
+    with open(filename, 'w', encoding=encoding) as f:
+        f.write(contents)
 
 
 def read_text_file(filename, encoding=None, fallback_encoding=None):
@@ -364,7 +336,7 @@ def parse_options(parser=None):
 TESTMODE = False
 
 
-class AnswerBook(object):
+class AnswerBook:
 
     def __init__(self):
         self.answers = []
@@ -385,7 +357,7 @@ def get_input(question):
     if not TESTMODE:
         # Normal operation.
         result = input(question)
-        if not isinstance(result, six.text_type):
+        if not isinstance(result, str):
             result = result.decode(INPUT_ENCODING)
         return result.strip()
     # Testing means no interactive input. Get it from answers_for_testing.
@@ -644,7 +616,7 @@ def run_hooks(setup_cfg, which_releaser, when, data):
     when can be before, middle, after.
 
     """
-    hook_group = '%s.%s' % (which_releaser, when)
+    hook_group = f'{which_releaser}.{when}'
     config = setup_cfg.config
 
     if config is not None and config.has_option('zest.releaser', hook_group):
@@ -693,7 +665,7 @@ def run_entry_points(which_releaser, when, data):
     when can be before, middle, after.
 
     """
-    group = 'zest.releaser.%s.%s' % (which_releaser, when)
+    group = f'zest.releaser.{which_releaser}.{when}'
     for entrypoint in pkg_resources.iter_entry_points(group=group):
         # Grab the function that is the actual plugin.
         plugin = entrypoint.load()
@@ -728,7 +700,7 @@ def format_command(command):
     args = []
     for arg in command:
         if " " in arg:
-            arg = "'{}'".format(arg)
+            arg = f"'{arg}'"
         args.append(arg)
     return " ".join(args)
 
@@ -746,9 +718,9 @@ def _subprocess_open(p, command, input_value, show_stderr):
     else:
         (stdout_output, stderr_output) = p.communicate()
     # We assume that the output from commands we're running is text.
-    if not isinstance(stdout_output, six.text_type):
+    if not isinstance(stdout_output, str):
         stdout_output = stdout_output.decode(OUTPUT_ENCODING)
-    if not isinstance(stderr_output, six.text_type):
+    if not isinstance(stderr_output, str):
         stderr_output = stderr_output.decode(OUTPUT_ENCODING)
     # TODO.  Note that the returncode is always None, also after
     # running p.kill().  The shell=True may be tripping us up.  For
diff --git a/zest/releaser/vcs.py b/zest/releaser/vcs.py
index b2b512fe..2ba67cf3 100644
--- a/zest/releaser/vcs.py
+++ b/zest/releaser/vcs.py
@@ -1,15 +1,11 @@
-from __future__ import unicode_literals
+from zest.releaser import pypi
+from zest.releaser import utils
 
 import logging
 import os
 import re
 import sys
 
-import six
-
-from zest.releaser import pypi
-from zest.releaser import utils
-
 
 VERSION_PATTERN = re.compile(r"""
 ^                # Start of line
@@ -37,7 +33,7 @@
 logger = logging.getLogger(__name__)
 
 
-class BaseVersionControl(object):
+class BaseVersionControl:
     "Shared implementation between all version control systems"
 
     internal_filename = ''  # e.g. '.svn' or '.hg'
@@ -58,7 +54,7 @@ def __init__(self, reporoot=None):
         self.fallback_encoding = pypi_cfg.encoding()
 
     def __repr__(self):
-        return '<{0} at {1} {2}>'.format(
+        return '<{} at {} {}>'.format(
             self.__class__.__name__, self.reporoot, self.relative_path_in_repo)
 
     def is_setuptools_helper_package_installed(self):
@@ -132,7 +128,7 @@ def filefind(self, names):
         a CHANGES.txt and a docs/HISTORY.txt, you want the top level
         CHANGES.txt to be found first.
         """
-        if isinstance(names, six.string_types):
+        if isinstance(names, str):
             names = [names]
         names = [name.lower() for name in names]
         files = self.list_files()
@@ -365,7 +361,7 @@ def cmd_create_tag(self, version, message, sign=False):
 
     def checkout_from_tag(self, version):
         package = self.name
-        prefix = '%s-%s-' % (package, version)
+        prefix = f'{package}-{version}-'
         tagdir = self.prepare_checkout_dir(prefix)
         os.chdir(tagdir)
         cmd = self.cmd_checkout_from_tag(version, tagdir)


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..7a9003d1
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,44 @@
+name: tests
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+  schedule:
+    - cron: '13 7 * * 0'  # run once a week on Sunday
+  # Allow to run this workflow manually from the Actions tab
+  workflow_dispatch:
+jobs:
+  build:
+    strategy:
+      matrix:
+        config:
+        # [Python version, tox env]
+        - ["3.6",   "py36"]
+        - ["3.7",   "py37"]
+        - ["3.8",   "py38"]
+        - ["3.9",   "py39"]
+        - ["3.10",   "py310"]
+        - ["pypy3", "pypy3"]
+
+    runs-on: ubuntu-latest
+    name: ${{ matrix.config[1] }}
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python
+      uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.config[0] }}
+    - name: Pip cache
+      uses: actions/cache@v2
+      with:
+        path: ~/.cache/pip
+        key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }}
+        restore-keys: |
+          ${{ runner.os }}-pip-${{ matrix.config[0] }}-
+          ${{ runner.os }}-pip-
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install tox
+    - name: Test
+      run: tox -e ${{ matrix.config[1] }}
diff --git a/zest/releaser/longtest.py b/zest/releaser/longtest.py
index 860fb560..28849375 100644
--- a/zest/releaser/longtest.py
+++ b/zest/releaser/longtest.py
@@ -1,6 +1,5 @@
 """Do the checks and tasks that have to happen before doing a release.
 """
-from __future__ import unicode_literals
 
 import io
 import logging
@@ -8,6 +7,7 @@
 import tempfile
 import webbrowser
 
+
 try:
     from readme_renderer.rst import render
     HAVE_README = True
@@ -17,6 +17,7 @@
 from zest.releaser import utils
 from zest.releaser.utils import _execute_command
 
+
 HTML_PREFIX = '''<html>
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
diff --git a/zest/releaser/tests/addchangelogentry.txt b/zest/releaser/tests/addchangelogentry.txt
index 966614ee..ef01684c 100644
--- a/zest/releaser/tests/addchangelogentry.txt
+++ b/zest/releaser/tests/addchangelogentry.txt
@@ -7,18 +7,13 @@ Detailed tests of addchangelogentry.py
 
 Several items are prepared for us.
 
-An svn repository:
+A git checkout of a project:
 
-    >>> repo_url
-    'file://TESTREPO'
-
-An svn checkout of a project:
-
-    >>> svnsourcedir
-    'TESTTEMP/tha.example-svn'
+    >>> gitsourcedir
+    'TESTTEMP/tha.example-git'
     >>> import os
     >>> import sys
-    >>> os.chdir(svnsourcedir)
+    >>> os.chdir(gitsourcedir)
 
 Asking input on the prompt is not unittestable unless we use the prepared
 testing hack in utils.py:
@@ -63,9 +58,13 @@ The changelog and setup.py are at 0.1 and have the message::
 
 Add some white space in front of the list items, to show that this is
 kept when adding a message.  Make the list items stars instead of
-dashed.  Add multiple lines in one go.
+dashes.  Commit the change so we have a clean checkout.
 
     >>> utils.write_text_file('CHANGES.txt', contents.replace('- ', '  * '))
+    >>> commit_all_changes()
+
+Add multiple lines in one go.
+
     >>> sys.argv[1:] = ['My longer message.\nOn two lines.']
     >>> addchangelogentry.main()
     Checking data dict
diff --git a/zest/releaser/tests/baserelease.txt b/zest/releaser/tests/baserelease.txt
index 9ecdcae6..57e0a6c4 100644
--- a/zest/releaser/tests/baserelease.txt
+++ b/zest/releaser/tests/baserelease.txt
@@ -5,12 +5,12 @@ Detailed tests of baserelease.py
 .. :setup: zest.releaser.tests.functional.setup
 .. :teardown: zest.releaser.tests.functional.teardown
 
-Change to an svn dir:
+Change to a git dir:
 
-    >>> svnsourcedir
-    'TESTTEMP/tha.example-svn'
+    >>> gitsourcedir
+    'TESTTEMP/tha.example-git'
     >>> import os
-    >>> os.chdir(svnsourcedir)
+    >>> os.chdir(gitsourcedir)
 
 Init the Basereleaser, which is otherwise only used as a base class.
 
@@ -20,11 +20,11 @@ Init the Basereleaser, which is otherwise only used as a base class.
 The data dict is initialized.  And a vcs is chosen:
 
     >>> base.data['workingdir']
-    'TESTTEMP/tha.example-svn'
+    'TESTTEMP/tha.example-git'
     >>> base.data['name']
     'tha.example'
     >>> base.vcs
-    <Subversion at TESTTEMP/tha.example-svn .>
+    <Git at TESTTEMP/tha.example-git .>
 
 Two methods are unimplemented:
 
diff --git a/zest/releaser/tests/bumpversion.txt b/zest/releaser/tests/bumpversion.txt
index 8b2592af..bf6decac 100644
--- a/zest/releaser/tests/bumpversion.txt
+++ b/zest/releaser/tests/bumpversion.txt
@@ -7,18 +7,13 @@ Detailed tests of bumpversion.py
 
 Several items are prepared for us.
 
-An svn repository:
+A git checkout of a project:
 
-    >>> repo_url
-    'file://TESTREPO'
-
-An svn checkout of a project:
-
-    >>> svnsourcedir
-    'TESTTEMP/tha.example-svn'
+    >>> gitsourcedir
+    'TESTTEMP/tha.example-git'
     >>> import os
     >>> import sys
-    >>> os.chdir(svnsourcedir)
+    >>> os.chdir(gitsourcedir)
 
 Asking input on the prompt is not unittestable unless we use the prepared
 testing hack in utils.py:
@@ -38,22 +33,20 @@ error is ugly, but in practice it looks fine, saying no bump is needed.
 So first run the fullrelease:
 
     >>> from zest.releaser import fullrelease
-    >>> utils.test_answer_book.set_answers(['', '', '', '2.9.4', '', '', '', '', '', '', ''])
+    >>> utils.test_answer_book.set_answers(['', '', '2.9.4', '', '', '', '', '', '', '', 'n'])
     >>> fullrelease.main()
     Question...
     Question: Enter version [0.1]:
     Our reply: 2.9.4
     ...
-    >>> svnhead('CHANGES.txt')
+    >>> githead('CHANGES.txt')
     Changelog of tha.example
     ========================
     <BLANKLINE>
     2.9.5 (unreleased)
     ------------------
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '2.9.5.dev0'
 
 Try bumpversion again.  The first time we again get an error because
@@ -79,12 +72,10 @@ Now a feature bump::
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '2.10.0.dev0'
-    >>> svnhead('CHANGES.txt')
+    >>> githead('CHANGES.txt')
     Changelog of tha.example
     ========================
     <BLANKLINE>
@@ -104,12 +95,10 @@ Now a breaking bump, and for this test we explicitly remove the dev marker::
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '3.0.0'
-    >>> svnhead('CHANGES.txt')
+    >>> githead('CHANGES.txt')
     Changelog of tha.example
     ========================
     <BLANKLINE>
diff --git a/zest/releaser/tests/bzr.txt b/zest/releaser/tests/bzr.txt
deleted file mode 100644
index 3856e014..00000000
--- a/zest/releaser/tests/bzr.txt
+++ /dev/null
@@ -1,144 +0,0 @@
-Detailed tests of bzr.py
-=======================
-
-.. :doctest:
-.. :setup: zest.releaser.tests.functional.setup
-.. :teardown: zest.releaser.tests.functional.teardown
-
-Some initial imports:
-
-    >>> from zest.releaser import bzr
-    >>> from zest.releaser.utils import execute_command
-    >>> import os
-
-Project name
-------------
-
-The prepared bzr project has a setup.py, so the name in there is used:
-
-    >>> os.chdir(bzrsourcedir)
-    >>> checkout = bzr.Bzr()
-    >>> checkout.name
-    'tha.example'
-
-When the setup.py doesn't exist or doesn't return a proper name, we fall back
-to the directory name.
-
-    >>> orig = checkout.get_setup_py_name
-    >>> checkout.get_setup_py_name= lambda: None  # Hack
-    >>> checkout.name
-    'tha.example-bzr'
-    >>> checkout.get_setup_py_name = orig  # Restore hack
-
-
-Diff and commit
----------------
-
-Make a change:
-
-    >>> setup_py = os.path.join(bzrsourcedir, 'setup.py')
-    >>> with open(setup_py, 'a') as f:
-    ...    _ = f.write('\na = 2\n')
-    >>> cmd = checkout.cmd_diff()
-    >>> cmd
-    ['bzr', 'diff']
-    >>> print(execute_command(cmd))
-    === modified file 'setup.py'
-    --- setup.py	...
-    +++ setup.py	...
-    @@ -42,3 +42,5 @@
-               'console_scripts': [
-               ]},
-           )
-    +
-    +a = 2
-
-Commit it:
-
-    >>> cmd = checkout.cmd_commit('small tweak')
-    >>> cmd
-    ['bzr', 'commit', '-v', '-m', 'small tweak']
-    >>> print(execute_command(cmd))
-    RED Committing to: TESTTEMP/tha.example-bzr/
-    RED modified setup.py
-    RED Committed revision 2.
-
-Note the red color: bzr prints everything to stderr...
-
-
-Tags
-----
-
-Originally there are no tags (the default 'tip' tag is filtered out):
-
-    >>> checkout.available_tags()
-    []
-
-Create a tag and it will show up:
-
-    >>> cmd = checkout.cmd_create_tag('0.1', 'Creating tag 0.1')
-    >>> cmd
-    ['bzr', 'tag', '0.1']
-    >>> dont_care = execute_command(cmd)
-    >>> checkout.available_tags()
-    ['0.1']
-
-A specific tag url is important for subversion, but nonsensical for
-bazaar.  We just return the version as-is:
-
-    >>> checkout.tag_url('holadijee')
-    'holadijee'
-
-Make and commit a small change:
-
-    >>> with open(setup_py, 'a') as f:
-    ...     _ = f.write('\nb = 3\n')
-    >>> cmd = checkout.cmd_commit('small tweak')
-    >>> print(execute_command(cmd))
-    RED Committing to: TESTTEMP/tha.example-bzr/
-    RED modified setup.py
-    RED Committed revision 3.
-
-Now we can request the changes since a specific tag:
-
-    >>> cmd = checkout.cmd_diff_last_commit_against_tag('0.1')
-    >>> cmd
-    ['bzr', 'diff', '-r', 'tag:0.1..-1']
-    >>> print(execute_command(cmd))
-    === modified file 'setup.py'
-    --- setup.py	...
-    +++ setup.py	...
-    @@ -44,3 +44,5 @@
-           )
-    <BLANKLINE>
-     a = 2
-    +
-    +b = 3
-
-
-Making a tag checkout
----------------------
-
-Some bzr versions cannot checkout in an existing directory, so contrary
-svn we will not create an empty tempdir before doing a checkout.
-The tag checkout command makes a checkout in a fresh dir:
-
-    >>> checkout.checkout_from_tag('0.1')
-
-At the end of the command we are in the created directory.  The
-checkout is done directly in that temporary directory, so not in a
-newly created subdirectory of it:
-
-    >>> os.getcwd()
-    '.../tha.example-0.1-...'
-    >>> sorted(os.listdir('.'))
-    ['.bzr', '.bzrignore', 'CHANGES.txt', ...]
-
-Pushing changes
----------------
-
-For bzr, committing is normally enough. We assume you've set it up that way,
-though you might not have done so. There's no handy way to find out, though.
-
-    >>> checkout.push_commands()
-    []
diff --git a/zest/releaser/tests/choose.txt b/zest/releaser/tests/choose.txt
index 2daba1b3..8615ddf0 100644
--- a/zest/releaser/tests/choose.txt
+++ b/zest/releaser/tests/choose.txt
@@ -14,24 +14,6 @@ Some initial imports:
 
 Choose makes the choice between version control systems.
 
-Subversion:
-
-    >>> os.chdir(svnsourcedir)
-    >>> choose.version_control()
-    <Subversion at TESTTEMP/tha.example-svn .>
-
-Mercurial:
-
-    >>> os.chdir(hgsourcedir)
-    >>> choose.version_control()
-    <Hg at TESTTEMP/tha.example-hg .>
-
-Bazaar:
-
-    >>> os.chdir(bzrsourcedir)
-    >>> choose.version_control()
-    <Bzr at TESTTEMP/tha.example-bzr .>
-
 Git:
 
     >>> os.chdir(gitsourcedir)
diff --git a/zest/releaser/tests/example.tar b/zest/releaser/tests/example.tar
index 1c0a3b45..088ca90a 100644
Binary files a/zest/releaser/tests/example.tar and b/zest/releaser/tests/example.tar differ
diff --git a/zest/releaser/tests/fullrelease.txt b/zest/releaser/tests/fullrelease.txt
index 97df9b3a..9aa7a08c 100644
--- a/zest/releaser/tests/fullrelease.txt
+++ b/zest/releaser/tests/fullrelease.txt
@@ -7,29 +7,17 @@ Fullrelease process with --no-input
 
 Several items are prepared for us.
 
-An svn repository:
+A git checkout of a project:
 
-    >>> repo_url
-    'file://TESTREPO'
-
-An svn checkout of a project:
-
-    >>> svnsourcedir
-    'TESTTEMP/tha.example-svn'
+    >>> gitsourcedir
+    'TESTTEMP/tha.example-git'
     >>> import os
-    >>> os.chdir(svnsourcedir)
-    >>> from zest.releaser.utils import execute_command
-    >>> print(execute_command(["svn", "info", svnsourcedir]))
-    Path: ...
-    URL: file://TESTREPO/tha.example/trunk
-    ...
+    >>> os.chdir(gitsourcedir)
 
 The version is at 0.1.dev0:
 
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.1.dev0'
 
 Asking input on the prompt is not unittestable unless we use the prepared
@@ -66,16 +54,14 @@ Now run the fullrelease:
 
 The changelog and setup.py are at 0.2.dev0:
 
-    >>> svnhead('CHANGES.txt')
+    >>> githead('CHANGES.txt')
     Changelog of tha.example
     ========================
     <BLANKLINE>
     0.2 (unreleased)
     ----------------
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.2.dev0'
 
 An alternative is to set a ``no-input`` option in the ``.pypirc`` or
@@ -125,16 +111,14 @@ Yes, the no-input was detected:
 
 The changelog and setup.py are at 0.3.dev0 now:
 
-    >>> svnhead('CHANGES.txt')
+    >>> githead('CHANGES.txt')
     Changelog of tha.example
     ========================
     <BLANKLINE>
     0.3 (unreleased)
     ----------------
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.3.dev0'
 
 When you do some commits, but forget to update the changelog,
diff --git a/zest/releaser/tests/functional-bzr.txt b/zest/releaser/tests/functional-bzr.txt
deleted file mode 100644
index 813945c4..00000000
--- a/zest/releaser/tests/functional-bzr.txt
+++ /dev/null
@@ -1,150 +0,0 @@
-Integration test
-================
-
-.. :doctest:
-.. :setup: zest.releaser.tests.functional.setup
-.. :teardown: zest.releaser.tests.functional.teardown
-
-Several items are prepared for us.
-
-A Bazaar directory (repository and checkout in one):
-
-    >>> bzrsourcedir
-    'TESTTEMP/tha.example-bzr'
-    >>> import os
-    >>> os.chdir(bzrsourcedir)
-
-There are no tags yet:
-
-    >>> from zest.releaser.utils import execute_command
-    >>> print(execute_command(["bzr", "tags"]))
-
-The changelog is unreleased:
-
-    >>> bzrhead('CHANGES.txt')
-    Changelog of tha.example
-    ========================
-    <BLANKLINE>
-    0.1 (unreleased)
-    ----------------
-
-The version is at 0.1.dev0:
-
-    >>> bzrhead('setup.py')
-    from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
-    version = '0.1.dev0'
-
-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
-
-Run the prerelease script:
-
-    >>> from zest.releaser import prerelease
-    >>> utils.test_answer_book.set_answers(['', '', '', '', ''])
-    >>> prerelease.main()
-    Question...
-    Question: Enter version [0.1]:
-    Our reply: <ENTER>
-    Checking data dict
-    Question: OK to commit this (Y/n)?
-    Our reply: <ENTER>
-
-The changelog now has a release date instead of ``(unreleased)``:
-
-    >>> bzrhead('CHANGES.txt')
-    Changelog of tha.example
-    ========================
-    <BLANKLINE>
-    0.1 (2007-01-14)
-    ----------------
-
-And the version number is just 0.1 and has lost its dev marker:
-
-    >>> bzrhead('setup.py')
-    from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
-    version = '0.1'
-
-The release script tags the release and uploads it:
-
-    >>> utils.test_answer_book.set_answers(['y', 'y', 'y', 'y', 'y', 'y', 'y', 'y'])
-    >>> mock_pypi_available.append('tha.example')
-    >>> from zest.releaser import release
-    >>> release.main()
-    Checking data dict
-    Tag needed to proceed, you can use the following command:
-    bzr tag 0.1
-    Question: Run this command (Y/n)?
-    Our reply: y
-    RED Created tag 0.1.
-    <BLANKLINE>
-    Question: Check out the tag
-        (for tweaks or pypi/distutils server upload) (Y/n)?
-    Our reply: y
-    <BLANKLINE>
-    Question: Fix setup.cfg (and commit to tag if possible) (Y/n)?
-    Our reply: y
-    [egg_info]
-    tag_build =
-    tag_svn_revision = false
-    <BLANKLINE>
-    <BLANKLINE>
-    Showing first few lines...
-    running sdist
-    running egg_info
-    creating src/tha.example.egg-info
-    ...
-    creating dist
-    Creating ...
-    removing 'tha.example-0.1' ...
-    Question: Upload to pypi (Y/n)?
-    Our reply: y
-    MOCK twine dispatch upload ... dist/tha.example-0.1.tar.gz
-
-There is now a tag:
-
-    >>> print(execute_command(["bzr", "tags"]))
-    0.1                  2
-
-And the postrelease script ups the version:
-
-    >>> utils.test_answer_book.set_answers(['', ''])
-    >>> from zest.releaser import postrelease
-    >>> postrelease.main()
-    Current version is 0.1
-    Question: Enter new development version ('.dev0' will be appended) [0.2]:
-    Our reply: <ENTER>
-    Checking data dict
-    Question: OK to commit this (Y/n)?
-    Our reply: <ENTER>
-
-The commit will contain an extra message with in this case a hint for
-Travis to skip the Continuous Integration build, because our pypirc
-has asked this with the extra-message option::
-
-    >>> from zest.releaser import lasttaglog
-    >>> lasttaglog.main()
-    bzr log...
-        Back to development: 0.2
-    <BLANKLINE>
-        [ci skip]...
-
-The changelog and setup.py are at 0.2 and indicate dev mode:
-
-    >>> bzrhead('CHANGES.txt')
-    Changelog of tha.example
-    ========================
-    <BLANKLINE>
-    0.2 (unreleased)
-    ----------------
-    >>> bzrhead('setup.py')
-    from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
-    version = '0.2.dev0'
diff --git a/zest/releaser/tests/functional-git.txt b/zest/releaser/tests/functional-git.txt
index 79cef506..b7c5d064 100644
--- a/zest/releaser/tests/functional-git.txt
+++ b/zest/releaser/tests/functional-git.txt
@@ -33,8 +33,6 @@ The version is at 0.1.dev0:
 
     >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.1.dev0'
 
 Asking input on the prompt is not unittestable unless we use the prepared
@@ -68,8 +66,6 @@ And the version number is just 0.1 and has lost its dev marker:
 
     >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.1'
 
 The release script tags the release and uploads it.  Note that in the
@@ -153,8 +149,6 @@ The changelog and setup.py are at 0.2 and indicate dev mode:
     ----------------
     >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.2.dev0'
 
 And there are no uncommitted changes:
diff --git a/zest/releaser/tests/functional-mercurial.txt b/zest/releaser/tests/functional-mercurial.txt
deleted file mode 100644
index 54c233bd..00000000
--- a/zest/releaser/tests/functional-mercurial.txt
+++ /dev/null
@@ -1,142 +0,0 @@
-Integration test
-================
-
-.. :doctest:
-.. :setup: zest.releaser.tests.functional.setup
-.. :teardown: zest.releaser.tests.functional.teardown
-
-Several items are prepared for us.
-
-A mercurial directory (repository and checkout in one):
-
-    >>> hgsourcedir
-    'TESTTEMP/tha.example-hg'
-    >>> import os
-    >>> os.chdir(hgsourcedir)
-
-There are no tags yet:
-
-    >>> from zest.releaser.utils import execute_command
-    >>> print(execute_command(["hg", "tags"]))
-    tip 1:234567890abc
-
-The changelog is unreleased:
-
-    >>> hghead('CHANGES.txt')
-    Changelog of tha.example
-    ========================
-    <BLANKLINE>
-    0.1 (unreleased)
-    ----------------
-
-The version is at 0.1.dev0:
-
-    >>> hghead('setup.py')
-    from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
-    version = '0.1.dev0'
-
-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
-
-Run the prerelease script:
-
-    >>> from zest.releaser import prerelease
-    >>> utils.test_answer_book.set_answers(['', '', '', '', ''])
-    >>> prerelease.main()
-    Question...
-    Question: Enter version [0.1]:
-    Our reply: <ENTER>
-    Checking data dict
-    Question: OK to commit this (Y/n)?
-    Our reply: <ENTER>
-
-The changelog now has a release date instead of ``(unreleased)``:
-
-    >>> hghead('CHANGES.txt')
-    Changelog of tha.example
-    ========================
-    <BLANKLINE>
-    0.1 (2007-01-14)
-    ----------------
-
-And the version number is just 0.1 and has lost its dev marker:
-
-    >>> hghead('setup.py')
-    from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
-    version = '0.1'
-
-The release script tags the release and uploads it:
-
-    >>> utils.test_answer_book.set_answers(['y', 'y', 'y', 'y', 'y', 'y', 'y', 'y'])
-    >>> mock_pypi_available.append('tha.example')
-    >>> from zest.releaser import release
-    >>> release.main()
-    Checking data dict
-    Tag needed to proceed, you can use the following command:
-    hg tag -m 'Tagging 0.1' 0.1
-    Question: Run this command (Y/n)?
-    Our reply: y
-    <BLANKLINE>
-    Question: Check out the tag
-        (for tweaks or pypi/distutils server upload) (Y/n)?
-    Our reply: y
-    ...
-    Question: Fix setup.cfg (and commit to tag if possible) (Y/n)?
-    Our reply: y
-    [egg_info]
-    tag_build =
-    tag_svn_revision = false
-    <BLANKLINE>
-    <BLANKLINE>
-    Showing first few lines...
-    running sdist
-    running egg_info
-    creating src/tha.example.egg-info
-    ...
-    creating dist
-    Creating ...
-    removing 'tha.example-0.1' ...
-    Question: Upload to pypi (Y/n)?
-    Our reply: y
-    MOCK twine dispatch upload ... dist/tha.example-0.1.tar.gz
-
-There is now a tag:
-
-    >>> print(execute_command(["hg", "tags"]))
-    tip  1:234567890abc
-    0.1  1:234567890abc
-
-And the postrelease script ups the version:
-
-    >>> utils.test_answer_book.set_answers(['', '', 'n'])
-    >>> from zest.releaser import postrelease
-    >>> postrelease.main()
-    Current version is 0.1
-    Question: Enter new development version ('.dev0' will be appended) [0.2]:
-    Our reply: <ENTER>
-    Checking data dict
-    Question: OK to commit this (Y/n)?
-    Our reply: <ENTER>
-    Question: OK to push commits to the server? (Y/n)?
-    Our reply: n
-
-The changelog and setup.py are at 0.2 and indicate dev mode:
-
-    >>> hghead('CHANGES.txt')
-    Changelog of tha.example
-    ========================
-    <BLANKLINE>
-    0.2 (unreleased)
-    ----------------
-    >>> hghead('setup.py')
-    from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
-    version = '0.2.dev0'
diff --git a/zest/releaser/tests/functional-svn.txt b/zest/releaser/tests/functional-svn.txt
deleted file mode 100644
index f633cc09..00000000
--- a/zest/releaser/tests/functional-svn.txt
+++ /dev/null
@@ -1,156 +0,0 @@
-Integration test
-================
-
-.. :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
-    >>> os.chdir(svnsourcedir)
-    >>> from zest.releaser.utils import execute_command
-    >>> print(execute_command(["svn", "info", svnsourcedir]))
-    Path: ...tha.example-svn...
-    file://TESTREPO/tha.example/trunk
-    ...
-
-There are no tags yet:
-
-    >>> print(execute_command(["svn", "list", "%s/tha.example/tags" % repo_url]))
-
-The changelog is unreleased:
-
-    >>> svnhead('CHANGES.txt')
-    Changelog of tha.example
-    ========================
-    <BLANKLINE>
-    0.1 (unreleased)
-    ----------------
-
-The version is at 0.1.dev0:
-
-    >>> svnhead('setup.py')
-    from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
-    version = '0.1.dev0'
-
-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
-
-Run the prerelease script:
-
-    >>> from zest.releaser import prerelease
-    >>> utils.test_answer_book.set_answers(['', '', '', '', ''])
-    >>> prerelease.main()
-    Question...
-    Question: Enter version [0.1]:
-    Our reply: <ENTER>
-    Checking data dict
-    Question: OK to commit this (Y/n)?
-    Our reply: <ENTER>
-
-The changelog now has a release date instead of ``(unreleased)``:
-
-    >>> svnhead('CHANGES.txt')
-    Changelog of tha.example
-    ========================
-    <BLANKLINE>
-    0.1 (1996-08-24)
-    ----------------
-
-And the version number is just 0.1 and has lost its dev marker:
-
-    >>> svnhead('setup.py')
-    from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
-    version = '0.1'
-
-The release script tags the release and uploads it:
-
-    >>> utils.test_answer_book.set_answers(['y', 'y', 'y', 'y', 'y', 'y', 'y', 'y'])
-    >>> mock_pypi_available.append('tha.example')
-    >>> from zest.releaser import release
-    >>> release.main()
-    Checking data dict
-    Tag needed to proceed, you can use the following command:
-    svn cp --non-interactive file://TESTREPO/tha.example/trunk file://TESTREPO/tha.example/tags/0.1 -m 'Tagging 0.1'
-    Question: Run this command (Y/n)?
-    Our reply: y
-    <BLANKLINE>
-    Committed revision 6...
-    Question: Check out the tag (for tweaks or pypi/distutils server upload) (Y/n)?
-    Our reply: y
-    ...CHANGES.txt...
-    Checked out revision 6.
-    <BLANKLINE>
-    Question: Fix setup.cfg (and commit to tag if possible) (Y/n)?
-    Our reply: y
-    [egg_info]
-    tag_build =
-    tag_svn_revision = false
-    <BLANKLINE>
-    <BLANKLINE>
-    Sending        setup.cfg
-    Transmitting file data...
-    Committed revision 7.
-    <BLANKLINE>
-    <BLANKLINE>
-    Showing first few lines...
-    running sdist
-    running egg_info
-    ...
-    creating dist
-    Creating ...
-    removing 'tha.example-0.1' ...
-    Question: Upload to PyPI (Y/n)?
-    Our reply: y
-    MOCK twine dispatch upload ... dist/tha.example-0.1.tar.gz...
-
-Note that the last dots above could be saying
-that we upload to alternative pypis as well.
-
-There is now a tag:
-
-    >>> print(execute_command(["svn", "list", "%s/tha.example/tags" % repo_url]))
-    0.1/
-
-And the postrelease script ups the version:
-
-    >>> utils.test_answer_book.set_answers(['', ''])
-    >>> from zest.releaser import postrelease
-    >>> postrelease.main()
-    Current version is 0.1
-    Question: Enter new development version ('.dev0' will be appended) [0.2]:
-    Our reply: <ENTER>
-    Checking data dict
-    Question: OK to commit this (Y/n)?
-    Our reply: <ENTER>
-
-The changelog and setup.py are at 0.2 and indicate dev mode:
-
-    >>> svnhead('CHANGES.txt')
-    Changelog of tha.example
-    ========================
-    <BLANKLINE>
-    0.2 (unreleased)
-    ----------------
-    >>> svnhead('setup.py')
-    from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
-    version = '0.2.dev0'
diff --git a/zest/releaser/tests/functional.py b/zest/releaser/tests/functional.py
index 8d6f528f..5a67d53c 100644
--- a/zest/releaser/tests/functional.py
+++ b/zest/releaser/tests/functional.py
@@ -1,22 +1,21 @@
 """Set up functional test fixtures"""
-from __future__ import unicode_literals
+
+from colorama import Fore
+from io import StringIO
+from urllib import request
+from urllib.error import HTTPError
+from zest.releaser import choose
+from zest.releaser import utils
+from zest.releaser.baserelease import NOTHING_CHANGED_YET
+from zest.releaser.utils import execute_command
 
 import os
+import pkg_resources
 import shutil
 import sys
 import tarfile
 import tempfile
 
-import pkg_resources
-from colorama import Fore
-from six import StringIO
-from six.moves.urllib import request as urllib2
-from six.moves.urllib.error import HTTPError
-
-from zest.releaser import utils
-from zest.releaser.baserelease import NOTHING_CHANGED_YET
-from zest.releaser.utils import execute_command
-
 
 def setup(test):
     # Reset constants to original settings:
@@ -38,7 +37,7 @@ def _exit(code=None):
     sys.exit = _exit
 
     # Monkey patch urllib for pypi access mocking.
-    test.orig_urlopen = urllib2.urlopen
+    test.orig_urlopen = request.urlopen
     test.mock_pypi_available = []
 
     def _make_mock_urlopen(mock_pypi_available):
@@ -56,7 +55,7 @@ def _mock_urlopen(url):
 
         return _mock_urlopen
 
-    urllib2.urlopen = _make_mock_urlopen(test.mock_pypi_available)
+    request.urlopen = _make_mock_urlopen(test.mock_pypi_available)
 
     # Extract example project
     example_tar = pkg_resources.resource_filename(
@@ -65,102 +64,33 @@ def _mock_urlopen(url):
         tf.extractall(path=test.tempdir)
     sourcedir = os.path.join(test.tempdir, 'tha.example')
 
-    # Init svn repo.
-    repodir = os.path.join(test.tempdir, 'svnrepo')
-    # With newer svn versions (1.8), we need to add the --compatible-version
-    # argument, because some 'git svn' versions cannot handle higher versions.
-    # You get this error when doing 'git svn clone':
-    # "Expected FS format between '1' and '4'; found format '6'"
-    # But on svn 1.6, this option is not available so it crashes.  So we try.
-    result = execute_command(
-        ['svnadmin', 'create', '--compatible-version=1.6', repodir])
-    if Fore.RED in result:
-        execute_command(['svnadmin', 'create', repodir])
-
-    repo_url = 'file://' + repodir  # TODO: urllib or so for windows
-    # Import example project
-    execute_command(
-        ['svn', 'mkdir', '%s/tha.example' % repo_url, '-m', 'mkdir'])
-    execute_command(
-        ['svn', 'mkdir', '%s/tha.example/tags' % repo_url, '-m', 'mkdir'])
-    execute_command(
-        ['svn', 'import', sourcedir, '%s/tha.example/trunk' % repo_url,
-         '-m', 'import'])
-    # Subversion checkout
-    svnsourcedir = os.path.join(test.tempdir, 'tha.example-svn')
-    execute_command(
-        ['svn', 'co', '%s/tha.example/trunk' % repo_url, svnsourcedir])
-    execute_command(
-        ['svn', 'propset', 'svn:ignore', 'tha.example.egg-info *.pyc',
-         '%s/src' % svnsourcedir])
-    execute_command(['svn', 'up', svnsourcedir])
-    execute_command(['svn', 'commit', svnsourcedir, '-m', 'ignoring egginfo'])
-
-    # Mercurial initialization
-    hgsourcedir = os.path.join(test.tempdir, 'tha.example-hg')
-    shutil.copytree(sourcedir, hgsourcedir)
-    execute_command(['hg', 'init', hgsourcedir])
-    with open(os.path.join(hgsourcedir, '.hgignore'), 'wb') as f:
-        f.write('tha.example.egg-info\n\\.pyc$\n'.encode('utf-8'))
-    execute_command(["hg", "add", hgsourcedir])
-    execute_command(["hg", "commit", "-m", "init", hgsourcedir])
-
-    # Bazaar initialization
-    bzrsourcedir = os.path.join(test.tempdir, 'tha.example-bzr')
-    shutil.copytree(sourcedir, bzrsourcedir)
-    execute_command(["bzr", "init", bzrsourcedir])
-    with open(os.path.join(bzrsourcedir, '.bzrignore'), 'w') as f:
-        f.write('tha.example.egg-info\n*.pyc\n')
-    execute_command(["bzr", "add", bzrsourcedir])
-    execute_command(["bzr", "commit", "-m", "init", bzrsourcedir])
-
     # Git initialization
     gitsourcedir = os.path.join(test.tempdir, 'tha.example-git')
     shutil.copytree(sourcedir, gitsourcedir)
     os.chdir(gitsourcedir)
     execute_command(["git", "init"])
-    with open(os.path.join(gitsourcedir, '.gitignore'), 'w') as f:
-        f.write('tha.example.egg-info\n*.pyc\n')
+    # Configure local git.
+    execute_command(["git", "config", "--local", "user.name", "Temp user"])
+    execute_command(["git", "config", "--local", "user.email", "temp@example.com"])
+    execute_command(["git", "config", "--local", "commit.gpgsign", "false"])
+    execute_command(["git", "config", "--local", "tag.gpgsign", "false"])
     execute_command(["git", "add", "."])
     execute_command(["git", "commit", "-a", "-m", "init"])
     os.chdir(test.orig_dir)
 
-    # Git svn initialization
-    gitsvnsourcedir = os.path.join(test.tempdir, 'tha.example-gitsvn')
-    execute_command(
-        ["git", "svn", "clone", "-s", "%s/tha.example" % repo_url,
-         gitsvnsourcedir])
-    os.chdir(test.orig_dir)
-    if test.name == 'gitsvn.txt' and not os.path.exists(gitsvnsourcedir):
-        raise AssertionError("Setting up git svn tests failed. Is git-svn not installed?")
-
-    def svnhead(*filename_parts):
-        filename = os.path.join(svnsourcedir, *filename_parts)
-        with open(filename) as f:
-            lines = f.readlines()
-        for line in lines[:5]:
-            print(line.strip())
-
-    def hghead(*filename_parts):
-        filename = os.path.join(hgsourcedir, *filename_parts)
-        with open(filename) as f:
-            lines = f.readlines()
-        for line in lines[:5]:
-            print(line.strip())
-
-    def bzrhead(*filename_parts):
-        filename = os.path.join(bzrsourcedir, *filename_parts)
-        with open(filename) as f:
-            lines = f.readlines()
-        for line in lines[:5]:
-            print(line.strip())
-
     def githead(*filename_parts):
         filename = os.path.join(gitsourcedir, *filename_parts)
         with open(filename) as f:
             lines = f.readlines()
         for line in lines[:5]:
-            print(line.strip())
+            line = line.strip()
+            if line:
+                print(line)
+
+    def commit_all_changes(message="Committing all changes"):
+        # Get a clean checkout.
+        vcs = choose.version_control()
+        execute_command(vcs.cmd_commit(message))
 
     def add_changelog_entry():
         # Replace '- Nothing changed yet.'  by a different entry.
@@ -170,27 +100,20 @@ def add_changelog_entry():
             NOTHING_CHANGED_YET, '- Brown bag release.')
         with open('CHANGES.txt', 'w') as f:
             f.write(new_changes)
+        commit_all_changes()
 
-    test.globs.update({'unicode_literals': unicode_literals,
-                       'tempdir': test.tempdir,
-                       'repo_url': repo_url,
-                       'svnsourcedir': svnsourcedir,
-                       'hgsourcedir': hgsourcedir,
-                       'bzrsourcedir': bzrsourcedir,
+    test.globs.update({'tempdir': test.tempdir,
                        'gitsourcedir': gitsourcedir,
-                       'gitsvnsourcedir': gitsvnsourcedir,
-                       'svnhead': svnhead,
-                       'hghead': hghead,
-                       'bzrhead': bzrhead,
                        'githead': githead,
                        'mock_pypi_available': test.mock_pypi_available,
                        'add_changelog_entry': add_changelog_entry,
+                       'commit_all_changes': commit_all_changes,
                        })
 
 
 def teardown(test):
     sys.exit = test.orig_exit
-    urllib2.urlopen = test.orig_urlopen
+    request.urlopen = test.orig_urlopen
     os.chdir(test.orig_dir)
     sys.argv[1:] = test.orig_argv
     shutil.rmtree(test.tempdir)
diff --git a/zest/releaser/tests/git.txt b/zest/releaser/tests/git.txt
index b3b3739b..3f9dd98a 100644
--- a/zest/releaser/tests/git.txt
+++ b/zest/releaser/tests/git.txt
@@ -47,7 +47,7 @@ Make a change:
     index 9c14143..54fa3b9 100644
     --- a/setup.py
     +++ b/setup.py
-    @@ -42,3 +42,5 @@ setup(name='tha.example',
+    @@ -40,3 +40,5 @@ setup(name='tha.example',
                'console_scripts': [
                ]},
            )
@@ -67,7 +67,8 @@ and in other this:
 
     >>> print('dummy %s' % execute_command(cmd))
     dummy...small tweak
-     1 files changed, 2 insertions(+), 0 deletions(-)
+     1 file changed, 2 insertions(+)
+
 
 
 Tags
@@ -108,7 +109,7 @@ Make and commit a small change:
     >>> cmd = checkout.cmd_commit('small second tweak')
     >>> print('dummy %s' % execute_command(cmd))
     dummy...small second tweak
-     1 files changed, 2 insertions(+), 0 deletions(-)
+     1 file changed, 2 insertions(+)
 
 Now we can request the changes since a specific tag:
 
@@ -120,7 +121,7 @@ Now we can request the changes since a specific tag:
     index 9c14143..54fa3b9 100644
     --- a/setup.py
     +++ b/setup.py
-    @@ -44,3 +44,5 @@ setup(name='tha.example',
+    @@ -42,3 +42,5 @@ setup(name='tha.example',
            )
     <BLANKLINE>
      a = 2
@@ -131,10 +132,8 @@ Now we can request the changes since a specific tag:
 Making a tag checkout
 ---------------------
 
-For checking out a tag with subversion or mercurial, we first create a temp
-dir and afterwards do a checkout/clone of a tag.  With git, we immediately
-make a clone of the repository in a tempdir and afterwards switch ("checkout")
-that directory to the tag.
+With git, we make a clone of the repository in a tempdir and
+afterwards switch ("checkout") that directory to the tag.
 
 Since version 6.6.3 we create a shallow clone, which only contains the
 last commit.  And since 6.6.4 this works properly. :-) For that to
diff --git a/zest/releaser/tests/gitsvn.txt b/zest/releaser/tests/gitsvn.txt
deleted file mode 100644
index 4f276a16..00000000
--- a/zest/releaser/tests/gitsvn.txt
+++ /dev/null
@@ -1,122 +0,0 @@
-Detailed tests of gitsvn aspects of git.py
-==========================================
-
-.. :doctest:
-.. :setup: zest.releaser.tests.functional.setup
-.. :teardown: zest.releaser.tests.functional.teardown
-
-Some initial imports:
-
-    >>> from zest.releaser import git
-    >>> from zest.releaser.utils import execute_command
-    >>> import os
-
-Project name
-------------
-
-The prepared git project has a setup.py, so the name in there is used:
-
-    >>> os.chdir(gitsvnsourcedir)
-    >>> checkout = git.Git()
-    >>> checkout.name
-    'tha.example'
-
-When the setup.py doesn't exist or doesn't return a proper name, we fall back
-to the directory name.
-
-    >>> orig = checkout.get_setup_py_name
-    >>> checkout.get_setup_py_name= lambda: None  # Hack
-    >>> checkout.name
-    'tha.example-gitsvn'
-    >>> checkout.get_setup_py_name = orig  # Restore hack
-
-
-Diff and commit
----------------
-
-Make a change:
-
-    >>> setup_py = os.path.join(gitsvnsourcedir, 'setup.py')
-    >>> with open(setup_py, 'a') as f:
-    ...     _ = f.write('\na = 2\n')
-    >>> cmd = checkout.cmd_diff()
-    >>> cmd
-    ['git', 'diff']
-    >>> print(execute_command(cmd))
-    diff --git a/setup.py b/setup.py
-    index 9c14143..54fa3b9 100644
-    --- a/setup.py
-    +++ b/setup.py
-    @@ -42,3 +42,5 @@ setup(name='tha.example',
-               'console_scripts': [
-               ]},
-           )
-    +
-    +a = 2
-
-Commit it:
-
-    >>> cmd = checkout.cmd_commit('small tweak')
-    >>> cmd
-    ['git', 'commit', '-a', '-m', 'small tweak']
-
-In some cases we get this output:
-``[master ...] small tweak``
-and in other this:
-``Created commit ...: small tweak``
-
-    >>> print('dummy %s' % execute_command(cmd))
-    dummy...small tweak
-     1 files changed, 2 insertions(+), 0 deletions(-)
-
-
-Tags
-----
-
-Originally there are no tags:
-
-    >>> checkout.available_tags()
-    []
-
-Create a tag and it will show up (master diverges from trunk):
-
-    >>> cmds = checkout.cmd_create_tag('0.1', 'Creating tag 0.1')
-    <BLANKLINE>
-    EXPERIMENTAL support for git-svn tagging!
-    <BLANKLINE>
-    You are on branch master.
-    Your local master diverges from trunk.
-    <BLANKLINE>
-    >>> cmds
-    [['git', 'svn', 'dcommit'],
-     ['git', 'tag', '0.1', '-m', 'Creating tag 0.1'],
-     ['git', 'svn', 'tag', '-m', 'Creating tag 0.1', '0.1']]
-    >>> for cmd in cmds:
-    ...     dont_care = execute_command(cmd)
-    >>> checkout.available_tags()
-    ['0.1']
-
-Create a tag and it will show up (master == trunk):
-
-    >>> cmds = checkout.cmd_create_tag('0.2', 'Creating tag 0.2')
-    <BLANKLINE>
-    EXPERIMENTAL support for git-svn tagging!
-    <BLANKLINE>
-    You are on branch master.
-    >>> cmds
-    [['git', 'tag', '0.2', '-m', 'Creating tag 0.2'],
-     ['git', 'svn', 'tag', '-m', 'Creating tag 0.2', '0.2']]
-    >>> for cmd in cmds:
-    ...     dont_care = execute_command(cmd)
-    >>> checkout.available_tags()
-    ['0.1', '0.2']
-
-Work on a branch different from master (unsupported):
-
-    >>> dontcare = execute_command(['git', 'checkout', '-b', 'foo'])
-    >>> cmds = checkout.cmd_create_tag('0.3', 'Creating tag 0.3')
-    Traceback (most recent call last):
-    ...
-    RuntimeError: SYSTEM EXIT (code=1)
-    >>> checkout.available_tags()
-    ['0.1', '0.2']
diff --git a/zest/releaser/tests/hg.txt b/zest/releaser/tests/hg.txt
deleted file mode 100644
index 6aa515e8..00000000
--- a/zest/releaser/tests/hg.txt
+++ /dev/null
@@ -1,152 +0,0 @@
-Detailed tests of hg.py
-=======================
-
-.. :doctest:
-.. :setup: zest.releaser.tests.functional.setup
-.. :teardown: zest.releaser.tests.functional.teardown
-
-Some initial imports:
-
-    >>> from zest.releaser import hg
-    >>> from zest.releaser.utils import execute_command
-    >>> import os
-
-Project name
-------------
-
-The prepared hg project has a setup.py, so the name in there is used:
-
-    >>> os.chdir(hgsourcedir)
-    >>> checkout = hg.Hg()
-    >>> checkout.name
-    'tha.example'
-
-When the setup.py doesn't exist or doesn't return a proper name, we fall back
-to the directory name.
-
-    >>> orig = checkout.get_setup_py_name
-    >>> checkout.get_setup_py_name= lambda: None  # Hack
-    >>> checkout.name
-    'tha.example-hg'
-    >>> checkout.get_setup_py_name = orig  # Restore hack
-
-
-Diff and commit
----------------
-
-Make a change:
-
-    >>> setup_py = os.path.join(hgsourcedir, 'setup.py')
-    >>> with open(setup_py, 'a') as f:
-    ...    _ = f.write('\na = 2\n')
-    >>> cmd = checkout.cmd_diff()
-    >>> cmd
-    ['hg', 'diff']
-    >>> print(execute_command(cmd))
-    diff -r 234567890abc setup.py
-    --- a/setup.py    ...
-    +++ b/setup.py    ...
-    @@ -42,3 +42,5 @@
-               'console_scripts': [
-               ]},
-           )
-    +
-    +a = 2
-
-Commit it:
-
-    >>> cmd = checkout.cmd_commit('small tweak')
-    >>> cmd
-    ['hg', 'commit', '-v', '-m', 'small tweak']
-    >>> print(execute_command(cmd))
-    setup.py...
-    committed changeset 1:234567890abc
-
-
-Tags
-----
-
-Originally there are no tags (the default 'tip' tag is filtered out):
-
-    >>> checkout.available_tags()
-    []
-
-Create a tag and it will show up:
-
-    >>> cmd = checkout.cmd_create_tag('0.1', 'Creating tag 0.1')
-    >>> cmd
-    ['hg', 'tag', '-m', 'Creating tag 0.1', '0.1']
-    >>> dont_care = execute_command(cmd)
-    >>> checkout.available_tags()
-    ['0.1']
-
-A specific tag url is important for subversion, but nonsensical for
-mercurial.  We just return the version as-is:
-
-    >>> checkout.tag_url('holadijee')
-    'holadijee'
-
-Make and commit a small change:
-
-    >>> with open(setup_py, 'a') as f:
-    ...     _ = f.write('\nb = 3\n')
-    >>> cmd = checkout.cmd_commit('small tweak')
-    >>> print(execute_command(cmd))
-    setup.py...
-    committed changeset 1:234567890abc
-
-Now we can request the changes since a specific tag:
-
-    >>> cmd = checkout.cmd_diff_last_commit_against_tag('0.1')
-    >>> cmd
-    ['hg', 'diff', '-r', '0.1', '-r', '234567890abc']
-    >>> print(execute_command(cmd))
-    diff -r 234567890abc -r 234567890abc .hgtags
-    --- /dev/null     ...
-    +++ b/.hgtags     ...
-    @@ -0,0 +1,1 @@
-    +... 0.1
-    diff -r 234567890abc -r 234567890abc setup.py
-    --- a/setup.py    ...
-    +++ b/setup.py    ...
-    @@ -44,3 +44,5 @@
-           )
-    <BLANKLINE>
-     a = 2
-    +
-    +b = 3
-
-
-Making a tag checkout
----------------------
-
-Some hg versions cannot checkout in an existing directory, so contrary
-svn we will not create an empty tempdir before doing a checkout.
-The tag checkout command makes a checkout in a fresh dir:
-
-    >>> checkout.checkout_from_tag('0.1')
-    requesting all changes
-    adding changesets
-    adding manifests
-    adding file changes
-    added 2 changesets with ... changes to 17 files
-    updating working directory
-    17 files updated, 0 files merged, 0 files removed, 0 files unresolved
-
-At the end of the command we are in the created directory.  The
-checkout is done directly in that temporary directory, so not in a
-newly created subdirectory of it:
-
-    >>> os.getcwd()
-    '.../tha.example-0.1-...'
-    >>> sorted(os.listdir('.'))
-    ['.hg', '.hgignore', 'CHANGES.txt', ...]
-
-
-Pushing changes
----------------
-
-For hg, committing isn't enough. We need to push changes to the server:
-
-    >>> checkout.push_commands()
-    [['hg', 'push']]
diff --git a/zest/releaser/tests/postrelease.txt b/zest/releaser/tests/postrelease.txt
index 4b669eb7..e8652bbc 100644
--- a/zest/releaser/tests/postrelease.txt
+++ b/zest/releaser/tests/postrelease.txt
@@ -7,29 +7,17 @@ Detailed tests of postrelease.py
 
 Several items are prepared for us.
 
-An svn repository:
+A git checkout of a project:
 
-    >>> repo_url
-    'file://TESTREPO'
-
-An svn checkout of a project:
-
-    >>> svnsourcedir
-    'TESTTEMP/tha.example-svn'
+    >>> gitsourcedir
+    'TESTTEMP/tha.example-git'
     >>> import os
-    >>> os.chdir(svnsourcedir)
-    >>> from zest.releaser.utils import execute_command
-    >>> print(execute_command(["svn", "info", svnsourcedir]))
-    Path: ...
-    URL: file://TESTREPO/tha.example/trunk
-    ...
+    >>> os.chdir(gitsourcedir)
 
 The version is at 0.1.dev0:
 
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.1.dev0'
 
 Asking input on the prompt is not unittestable unless we use the prepared
@@ -40,7 +28,7 @@ testing hack in utils.py:
 
 Run the postrelease script:
 
-    >>> utils.test_answer_book.set_answers(['', ''])
+    >>> utils.test_answer_book.set_answers(['', '', 'n'])
     >>> from zest.releaser import postrelease
     >>> postrelease.main()
     Current version is 0.1
@@ -49,38 +37,39 @@ Run the postrelease script:
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
+    Question: OK to push commits to the server? (Y/n)?
+    Our reply: n
 
 The changelog and setup.py are at 0.2 and indicate dev mode:
 
-    >>> svnhead('CHANGES.txt')
+    >>> githead('CHANGES.txt')
     Changelog of tha.example
     ========================
     <BLANKLINE>
     0.2 (unreleased)
     ----------------
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.2.dev0'
 
 Now we set the version to something that does not end in a number and
 is not recognized as development version (with setuptools 8 or higher
 the version is actually reported with a zero at the end):
 
-    >>> from zest.releaser.svn import Subversion
-    >>> vcs = Subversion()
+    >>> from zest.releaser.git import Git
+    >>> vcs = Git()
     >>> vcs.version
     '0.2.dev0'
     >>> vcs.version = '0.1b'
     >>> vcs.version
     '0.1b0'
+    >>> commit_all_changes()
 
 Run the postrelease script.  Since the version number does not end
 with a number, the script cannot make a suggestion, except when the
 number is normalized by setuptools already:
 
-    >>> utils.test_answer_book.set_answers(['0.2', ''])
+    >>> utils.test_answer_book.set_answers(['0.2', '', 'n'])
     >>> from zest.releaser import postrelease
     >>> postrelease.main()
     Current version is 0.1b0
@@ -89,19 +78,19 @@ number is normalized by setuptools already:
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
+    Question: OK to push commits to the server? (Y/n)?
+    Our reply: n
 
 The changelog and setup.py are at 0.2 and indicate dev mode:
 
-    >>> svnhead('CHANGES.txt')
+    >>> githead('CHANGES.txt')
     Changelog of tha.example
     ========================
     <BLANKLINE>
     0.2 (unreleased)
     ----------------
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.2.dev0'
 
 The prerelease part would complain when the changelog still contains
@@ -114,7 +103,7 @@ and postrelease.  The next version after 0.2.19 should not be 0.2.110
 but 0.2.20:
 
     >>> from zest.releaser import prerelease
-    >>> utils.test_answer_book.set_answers(['', '', '', '0.2.19', ''])
+    >>> utils.test_answer_book.set_answers(['', '', '0.2.19', ''])
     >>> prerelease.main()
     Question...
     Question: Enter version [0.2]:
@@ -122,7 +111,7 @@ but 0.2.20:
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
-    >>> utils.test_answer_book.set_answers(['', ''])
+    >>> utils.test_answer_book.set_answers(['', '', 'n'])
     >>> postrelease.main()
     Current version is 0.2.19
     Question: Enter new development version ('.dev0' will be appended) [0.2.20]:
@@ -130,12 +119,14 @@ but 0.2.20:
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
+    Question: OK to push commits to the server? (Y/n)?
+    Our reply: n
 
 Releases without numbers at the end should not fluster us even when we
 cannot suggest a reasonable number. We'll ask for a version until we get one:
 
     >>> add_changelog_entry()
-    >>> utils.test_answer_book.set_answers(['', '', '', '0.3beta', ''])
+    >>> utils.test_answer_book.set_answers(['', '', '0.3beta', ''])
     >>> prerelease.main()
     Question...
     Question: Enter version [0.2.20]:
@@ -143,7 +134,7 @@ cannot suggest a reasonable number. We'll ask for a version until we get one:
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
-    >>> utils.test_answer_book.set_answers(['0.3rc0', ''])
+    >>> utils.test_answer_book.set_answers(['0.3rc0', '', 'n'])
     >>> postrelease.main()
     Current version is 0.3b0
     Question: Enter new development version ('.dev0' will be appended) [0.3b1]:
@@ -151,11 +142,13 @@ cannot suggest a reasonable number. We'll ask for a version until we get one:
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
+    Question: OK to push commits to the server? (Y/n)?
+    Our reply: n
 
 Numbers and characters can be combined:
 
     >>> add_changelog_entry()
-    >>> utils.test_answer_book.set_answers(['', '', '', '1.0a1', ''])
+    >>> utils.test_answer_book.set_answers(['', '', '1.0a1', ''])
     >>> prerelease.main()
     Question...
     Question: Enter version [0.3rc0]:
@@ -163,7 +156,7 @@ Numbers and characters can be combined:
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
-    >>> utils.test_answer_book.set_answers(['', ''])
+    >>> utils.test_answer_book.set_answers(['', '', 'n'])
     >>> postrelease.main()
     Current version is 1.0a1
     Question: Enter new development version ('.dev0' will be appended) [1.0a2]:
@@ -171,16 +164,19 @@ Numbers and characters can be combined:
     Checking data dict
     Question: OK to commit this (Y/n)?
     Our reply: <ENTER>
+    Question: OK to push commits to the server? (Y/n)?
+    Our reply: n
 
 If there's an empty history file, it gets a fresh header.
 
     >>> add_changelog_entry()
-    >>> utils.test_answer_book.set_answers(['', '', '', '1.0', ''])
+    >>> utils.test_answer_book.set_answers(['', '', '1.0', ''])
     >>> prerelease.main()
     Question: ...
     >>> with open('CHANGES.txt', 'w') as f:
     ...     _ = f.write('')
-    >>> utils.test_answer_book.set_answers(['', ''])
+    >>> commit_all_changes()
+    >>> utils.test_answer_book.set_answers(['', '', 'n'])
     >>> postrelease.main()
     Current ...
     >>> with open('CHANGES.txt') as f:
@@ -194,7 +190,7 @@ If there is no history file, we get no errors and a new history file is not
 created:
 
     >>> add_changelog_entry()
-    >>> utils.test_answer_book.set_answers(['', '', '', '', ''])
+    >>> utils.test_answer_book.set_answers(['', '', '', ''])
     >>> prerelease.main()
     Question: ...
     >>> os.remove('CHANGES.txt')
@@ -213,10 +209,11 @@ Re-instate the history file again, but omit the restructuredtext header line:
 
     >>> with open('CHANGES.txt', 'w') as f:
     ...    _ = f.write('1.0 (1972-12-25)\n\n- hello\n')
-    >>> utils.test_answer_book.set_answers(['', '', '', '1.3', ''])
+    >>> commit_all_changes()
+    >>> utils.test_answer_book.set_answers(['', '', '1.3', ''])
     >>> prerelease.main()
     Question: ...
-    >>> utils.test_answer_book.set_answers(['', ''])
+    >>> utils.test_answer_book.set_answers(['', '', 'n'])
     >>> postrelease.main()
     Current ...
 
diff --git a/zest/releaser/tests/prerelease.txt b/zest/releaser/tests/prerelease.txt
index a47ac903..5b1c0a4f 100644
--- a/zest/releaser/tests/prerelease.txt
+++ b/zest/releaser/tests/prerelease.txt
@@ -7,29 +7,17 @@ Detailed tests of prerelease.py
 
 Several items are prepared for us.
 
-An svn repository:
+A git checkout of a project:
 
-    >>> repo_url
-    'file://TESTREPO'
-
-An svn checkout of a project:
-
-    >>> svnsourcedir
-    'TESTTEMP/tha.example-svn'
+    >>> gitsourcedir
+    'TESTTEMP/tha.example-git'
     >>> import os
-    >>> os.chdir(svnsourcedir)
-    >>> from zest.releaser.utils import execute_command
-    >>> print(execute_command(["svn", "info", svnsourcedir]))
-    Path: ...
-    URL: file://TESTREPO/tha.example/trunk
-    ...
+    >>> os.chdir(gitsourcedir)
 
 The version is at 0.1.dev0:
 
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.1.dev0'
 
 Asking input on the prompt is not unittestable unless we use the prepared
@@ -52,14 +40,12 @@ Run the prerelease script:
 
 The changelog and setup.py are at 0.1 and indicate a release date:
 
-    >>> svnhead('CHANGES.txt')
+    >>> githead('CHANGES.txt')
     Changelog of tha.example
     ========================
     <BLANKLINE>
     0.1 (...-...-...)
     ----------------
-    >>> svnhead('setup.py')
+    >>> githead('setup.py')
     from setuptools import setup, find_packages
-    import os.path
-    <BLANKLINE>
     version = '0.1'
diff --git a/zest/releaser/tests/pypi.txt b/zest/releaser/tests/pypi.txt
index c2888950..d317ffb5 100644
--- a/zest/releaser/tests/pypi.txt
+++ b/zest/releaser/tests/pypi.txt
@@ -122,7 +122,7 @@ Asking for making a release or not
 ----------------------------------
 
 Some people hardly ever want to make a full release of a package to
-pypi; a subversion tag may be enough.  They can tell zest.releaser to
+pypi; a git tag may be enough.  They can tell zest.releaser to
 use a different default answer when it asks to make a checkout for a
 release.  This means you can usually just press Enter on all questions
 that zest.releaser asks.
diff --git a/zest/releaser/tests/release.txt b/zest/releaser/tests/release.txt
index 3aeaa41b..052a577d 100644
--- a/zest/releaser/tests/release.txt
+++ b/zest/releaser/tests/release.txt
@@ -10,7 +10,6 @@ Some initial imports:
     >>> from zest.releaser import release
     >>> from zest.releaser import utils
     >>> import os
-    >>> from zest.releaser import utils
     >>> utils.TESTMODE = True
 
 
@@ -20,8 +19,8 @@ Check availability on pypi
 "Testing" means "don't really poll pypi", so the test setup does some
 monkeypatching for us:
 
-    >>> from six.moves.urllib import request as urllib2
-    >>> urllib2.urlopen
+    >>> from urllib import request
+    >>> request.urlopen
     <function ..._mock_urlopen at ...>
 
 There's a mock list of packages that our mock pypi provides:
@@ -44,7 +43,7 @@ Now search for an "existing" package:
 Version grabbing
 ----------------
 
-    >>> os.chdir(svnsourcedir)
+    >>> os.chdir(gitsourcedir)
     >>> releaser = release.Releaser()
 
 Grab the version:
@@ -90,9 +89,11 @@ Mock that the tag exists and we get a question:
     >>> releaser._info_if_tag_already_exists()
     Question: There is already a tag 0.1, show if there are differences? (Y/n)?
     Our reply: y
-    svn --non-interactive diff file://TESTREPO/tha.example/tags/0.1 file://TESTREPO/tha.example/trunk
+    git diff 0.1
+    RED fatal: ambiguous argument '0.1': unknown revision or path not in the working tree.
     ...
-    >>> # ^^^ The diff itself fails as we mocked its existence
+
+Note: the diff itself fails as we mocked its existence.
 
 
 Making tags
diff --git a/zest/releaser/tests/svn.txt b/zest/releaser/tests/svn.txt
deleted file mode 100644
index 1171614c..00000000
--- a/zest/releaser/tests/svn.txt
+++ /dev/null
@@ -1,419 +0,0 @@
-Detailed tests of svn.py
-========================
-
-.. :doctest:
-.. :setup: zest.releaser.tests.functional.setup
-.. :teardown: zest.releaser.tests.functional.teardown
-
-Some initial imports:
-
-    >>> from zest.releaser import svn
-    >>> from zest.releaser.utils import execute_command
-    >>> import os
-
-Project name
-------------
-
-The prepared svn project has a setup.py, so the name in there is used:
-
-    >>> os.chdir(svnsourcedir)
-    >>> checkout = svn.Subversion()
-    >>> checkout.name
-    'tha.example'
-
-When the setup.py doesn't exist or doesn't return a proper name, we fall back
-to the name in the svn url before trunk/tags:
-
-    >>> orig = checkout.get_setup_py_name
-    >>> checkout.get_setup_py_name= lambda: None  # Hack
-    >>> checkout.name
-    'tha.example'
-    >>> checkout.get_setup_py_name = orig  # Restore hack
-
-
-Diff and commit
----------------
-
-Make a change:
-
-    >>> setup_py = os.path.join(svnsourcedir, 'setup.py')
-    >>> with open(setup_py, 'a') as f:
-    ...     _ = f.write('\na = 2\n')
-    >>> cmd = checkout.cmd_diff()
-    >>> cmd
-    ['svn', 'diff', '--non-interactive']
-    >>> print(execute_command(cmd))
-    Index: ...
-    --- setup.py      (revision 3)
-    +++ setup.py      (working copy)
-    @@ -42,3 +42,5 @@
-               'console_scripts': [
-               ]},
-           )
-    +
-    +a = 2
-
-Commit it:
-
-    >>> cmd = checkout.cmd_commit('small tweak')
-    >>> cmd
-    ['svn', 'commit', '--non-interactive', '-m', 'small tweak']
-    >>> print(execute_command(cmd))
-    Sending        setup.py
-    Transmitting file data...
-    Committed revision 5.
-
-Tags
-----
-
-Originally there are no tags:
-
-    >>> checkout.available_tags()
-    []
-
-The name of the /tags directory (so: a plural) is succesfully detected:
-
-    >>> checkout._tags_name
-    'tags'
-
-Create a tag and it will show up:
-
-    >>> cmd = checkout.cmd_create_tag('0.1', 'Creating tag 0.1')
-    >>> cmd
-    ['svn', 'cp', '--non-interactive',
-     'file://TESTREPO/tha.example/trunk',
-     'file://TESTREPO/tha.example/tags/0.1', '-m', 'Creating tag 0.1']
-    >>> dont_care = execute_command(cmd)
-    >>> checkout.available_tags()
-    ['0.1']
-
-A tag url is important for subversion:
-
-    >>> checkout.tag_url('holadijee')
-    'file://TESTREPO/tha.example/tags/holadijee'
-
-Make and commit a small change:
-
-    >>> with open(setup_py, 'a') as f:
-    ...     _ = f.write('\nb = 3\n')
-    >>> cmd = checkout.cmd_commit('small tweak')
-    >>> print(execute_command(cmd))
-    Sending        setup.py
-    Transmitting file data...
-    Committed revision 7.
-
-Now we can request the changes since a specific tag.  Note that we use the
-``--non-interactive`` option to make sure it can be run from cronjobs or
-post-commit hooks:
-
-    >>> cmd = checkout.cmd_diff_last_commit_against_tag('0.1')
-    >>> cmd
-    ['svn', '--non-interactive', 'diff',
-     'file://TESTREPO/tha.example/tags/0.1',
-     'file://TESTREPO/tha.example/trunk']
-    >>> print(execute_command(cmd))
-    Index: setup.py
-    ===================================================================
-    --- setup.py      (.../tags/0.1)  (revision 7)
-    +++ setup.py      (.../trunk)     (revision 7)
-    @@ -44,3 +44,5 @@
-           )
-    <BLANKLINE>
-     a = 2
-    +
-    +b = 3
-
-
-Making a tag checkout
----------------------
-
-For checking out a tag, we first need a temp folder to do the checkout in:
-
-    >>> temp = checkout.prepare_checkout_dir('somename')
-    >>> temp
-    'TMPDIR/somename...'
-    >>> os.path.isdir(temp)
-    True
-
-The tag checkout command makes a checkout in that tempdir:
-
-    >>> cmd = checkout.cmd_checkout_from_tag('0.1', temp)
-    >>> cmd
-    ['svn', 'co', '--non-interactive',
-     'file://TESTREPO/tha.example/tags/0.1',
-      'TMPDIR/somename...']
-    >>> print('\n'.join(sorted(execute_command(cmd).splitlines())))
-    A    TMPDIR/somename.../CHANGES.txt
-    A    TMPDIR/somename.../README.txt
-    A    TMPDIR/somename.../TODO.txt
-    A    TMPDIR/somename.../bootstrap.py
-    A    TMPDIR/somename.../buildout.cfg
-    A    TMPDIR/somename.../grok-1.0a4.cfg
-    A    TMPDIR/somename.../setup.cfg
-    A    TMPDIR/somename.../setup.py
-    A    TMPDIR/somename.../src
-    A    TMPDIR/somename.../src/tha
-    A    TMPDIR/somename.../src/tha/__init__.py
-    A    TMPDIR/somename.../src/tha/example
-    A    TMPDIR/somename.../src/tha/example/USAGE.txt
-    A    TMPDIR/somename.../src/tha/example/__init__.py
-    A    TMPDIR/somename.../src/tha/example/tests
-    A    TMPDIR/somename.../src/tha/example/tests/__init__.py
-    A    TMPDIR/somename.../src/tha/example/tests/sample.txt
-    A    TMPDIR/somename.../src/tha/example/tests/test.py
-    Checked out revision 7.
-
-The checkout is done directly in that temporary directory, so not in a newly
-created subdirectory of it:
-
-    >>> sorted(os.listdir(temp))
-    ['.svn', 'CHANGES.txt', ...]
-
-
-Tag corner cases
-----------------
-
-Both corner cases do a ``sys.exit()``.  This method has been monkey patched by
-the test setup.
-
-    >>> import sys
-    >>> sys.exit(1)
-    Traceback (most recent call last):
-    ...
-    RuntimeError: SYSTEM EXIT (code=1)
-
-The information on tags is stored on the server, not locally.  So a network
-connection is needed.  When there's a network problem, it is reported:
-
-    >>> # Internal-detail hack
-    >>> orig = checkout._cached_url
-    >>> checkout._cached_url = 'http://non.existing/nonexisting/trunk'
-    >>> # Actual call
-    >>> checkout.available_tags()
-    Traceback (most recent call last):
-    ...
-    RuntimeError: SYSTEM EXIT (code=1)
-    >>> checkout._cached_url = orig
-
-Subversion stores tags in the /tags directory.  Just a convention.  Sometimes
-projects have only a /trunk set up and no tag directory yet.
-
-Set up a new sample project in the repository:
-
-    >>> repo_url
-    'file://TESTREPO'
-    >>> cmd = ['svn', 'mkdir', '%s/sample' % repo_url, '-m', 'mkdir']
-    >>> dont_care = execute_command(cmd)
-    >>> cmd = ['svn', 'mkdir', '%s/sample/trunk' % repo_url, '-m', 'mkdir']
-    >>> dont_care = execute_command(cmd)
-
-Make a checkout:
-
-    >>> os.chdir(tempdir)
-    >>> cmd = ['svn', 'co', '%s/sample/trunk' % repo_url, 'sample']
-    >>> dont_care = execute_command(cmd)
-    >>> os.chdir('sample')
-
-Now look up the tags.  This asks us to create the tags/ directory.  For the
-answering, set up the answer test mode:
-
-    >>> from zest.releaser import utils
-    >>> utils.TESTMODE = True
-    >>> sample_checkout = svn.Subversion()
-
-There is no tags dir yet, so the tags name detection (/tag or /tags) returns
-None:
-
-    >>> print(sample_checkout._tags_name)
-    None
-
-Answer 'no' the first time we get asked to create the directory.  This means
-zest.releaser has no business with this project, so exit.
-
-    >>> utils.AUTO_RESPONSE
-    False
-    >>> utils.test_answer_book.set_answers(['n'])
-    >>> sample_checkout.available_tags()
-    Traceback (most recent call last):
-    ...
-    RuntimeError: SYSTEM EXIT (code=1)
-
-Answer 'yes' and the tags dir gets created.  The available tags result is
-obviously an empty list:
-
-    >>> print(sample_checkout._tags_name)
-    None
-    >>> utils.test_answer_book.set_answers(['y'])
-    >>> sample_checkout.available_tags()
-    tags dir does not exist at file://TESTREPO/sample/tags
-    Question: Shall I create it (Y/n)?
-    Our reply: y
-    <BLANKLINE>
-    Committed revision 10.
-    []
-    >>> print(sample_checkout._tags_name)
-    tags
-    >>> cmd = ['svn', 'list', '%s/sample' % repo_url]
-    >>> print(execute_command(cmd))
-    tags/
-    trunk/
-
-
-Singular /tag instead of /tags dir
-----------------------------------
-
-Subversion stores tags in the /tags directory.  Just a convention.  But for
-instance on codespeak, the singular /tag (and /branch) is the convention.
-Zest.releaser supports that.
-
-Set up a new sample project in the repository:
-
-    >>> repo_url
-    'file://TESTREPO'
-    >>> cmd = ['svn', 'mkdir', '%s/singular' % repo_url, '-m', 'mkdir']
-    >>> dont_care = execute_command(cmd)
-    >>> cmd = ['svn', 'mkdir', '%s/singular/trunk' % repo_url, '-m', 'mkdir']
-    >>> dont_care = execute_command(cmd)
-
-And make a /tag directory.  We don't support adding one ourselves (we always
-suggest /tags, make a /tag dir yourself if you really really need it), but we
-*do* detect and use one:
-
-    >>> cmd = ['svn', 'mkdir', '%s/singular/tag' % repo_url, '-m', 'mkdir']
-    >>> dont_care = execute_command(cmd)
-
-Make a checkout:
-
-    >>> os.chdir(tempdir)
-    >>> cmd = ['svn', 'co', '%s/singular/trunk' % repo_url, 'singular']
-    >>> dont_care = execute_command(cmd)
-    >>> os.chdir('singular')
-
-Now look up the tags.  This asks us to create the tags/ directory.  For the
-answering, set up the answer test mode:
-
-    >>> from zest.releaser import utils
-    >>> utils.TESTMODE = True
-
-Fire up zest.releaser's subversion support.  The _tags_name() method figures
-out the correct name:
-
-    >>> singular_checkout = svn.Subversion()
-    >>> singular_checkout._tags_name
-    'tag'
-
-There are no tags yet:
-
-    >>> singular_checkout.available_tags()
-    []
-
-Tag urls are formed correctly with the singular name:
-
-    >>> singular_checkout.tag_url('0.1')
-    'file://TESTREPO/singular/tag/0.1'
-
-Tag and checkout commands also use the singular:
-
-    >>> cmd = singular_checkout.cmd_checkout_from_tag('0.1', temp)
-    >>> cmd
-    ['svn', 'co', '--non-interactive',
-     'file://TESTREPO/singular/tag/0.1',
-     'TMPDIR/somename...']
-    >>> cmd = singular_checkout.cmd_create_tag('0.1', 'Creating tag 0.1')
-    >>> cmd
-    ['svn', 'cp', '--non-interactive',
-     'file://TESTREPO/singular/trunk',
-     'file://TESTREPO/singular/tag/0.1', '-m', 'Creating tag 0.1']
-
-If we have branch checkout, the base url is still extracted correctly, even if
-the singular /tag and /branch is used:
-
-    >>> cmd = ['svn', 'mkdir', '%s/singular/branch' % repo_url, '-m', 'mkdir']
-    >>> dont_care = execute_command(cmd)
-    >>> cmd = ['svn', 'mkdir', '%s/singular/branch/mybranch' % repo_url, '-m', 'mkdir']
-    >>> dont_care = execute_command(cmd)
-    >>> cmd = ['svn', 'mkdir', '%s/singular/tag/mytag' % repo_url, '-m', 'mkdir']
-    >>> dont_care = execute_command(cmd)
-
-    >>> os.chdir(tempdir)
-    >>> cmd = ['svn', 'co', '%s/singular/branch/mybranch' % repo_url, 'mysingularbranch']
-    >>> dont_care = execute_command(cmd)
-    >>> cmd = ['svn', 'co', '%s/singular/tag/mytag' % repo_url, 'mysingulartag']
-    >>> dont_care = execute_command(cmd)
-
-    >>> os.chdir(tempdir)
-    >>> os.chdir('mysingularbranch')
-    >>> singular_checkout = svn.Subversion()
-    >>> singular_checkout._base_from_svn()
-    'file://TESTREPO/singular/'
-
-    >>> os.chdir(tempdir)
-    >>> os.chdir('mysingulartag')
-    >>> singular_checkout = svn.Subversion()
-    >>> singular_checkout._base_from_svn()
-    'file://TESTREPO/singular/'
-
-
-Pushing changes
----------------
-
-For svn, committing is enough, no need to push stuff to the server as that
-happens automatically:
-
-    >>> checkout.push_commands()
-    []
-
-
-Tags within a trunk
---------------------
-
-Some legacy projects, often converted from CVS, have multiple subprojects
-under a single trunk.  This creates URLs that contain a regular svn layout
-within an top level trunk. For example::
-
-    trunk/
-        projectA/
-            trunk/
-            tags/
-            branches/
-
-        projectB
-            trunk/
-            tags/
-            branches/
-
-Create trunk/ for both projects:
-
-    >>> repo_url
-    'file://TESTREPO'
-    >>> cmd = ['svn', 'mkdir', '--parents', '%s/trunk/projectA/trunk' % repo_url, '-m', 'mk projectA']
-    >>> dont_care = execute_command(cmd)
-    >>> cmd = ['svn', 'mkdir', '%s/trunk/projectB/trunk' % repo_url, '-m', 'mkdir projectB']
-    >>> dont_care = execute_command(cmd)
-
-Make a checkout:
-
-    >>> os.chdir(tempdir)
-    >>> cmd = ['svn', 'co', '%s/trunk' % repo_url]
-    >>> dont_care = execute_command(cmd)
-
-Now check that we try to create the proper trunk/projectA/tags directory
-
-    >>> os.chdir('trunk/projectA/trunk')
-    >>> from zest.releaser import utils
-    >>> utils.TESTMODE = True
-    >>> sample_checkout = svn.Subversion()
-    >>> utils.test_answer_book.set_answers(['y'])
-    >>> sample_checkout.available_tags()
-    tags dir does not exist at file://TESTREPO/trunk/projectA/tags
-    Question: Shall I create it (Y/n)?
-    Our reply: y
-    <BLANKLINE>
-    Committed revision 18.
-    []
-    >>> cmd = ['svn', 'list', '-R', '%s/trunk' % repo_url]
-    >>> print(execute_command(cmd))
-    projectA/
-    projectA/tags/
-    projectA/trunk/
-    <BLANKLINE>
diff --git a/zest/releaser/tests/test_setup.py b/zest/releaser/tests/test_setup.py
index 74142167..b3b0c49f 100644
--- a/zest/releaser/tests/test_setup.py
+++ b/zest/releaser/tests/test_setup.py
@@ -1,10 +1,7 @@
-from __future__ import unicode_literals
-
 from colorama import Fore
 from zope.testing import renormalizing
 
 import re
-import six
 import tempfile
 import twine.cli
 import z3c.testsetup
@@ -21,74 +18,28 @@ def mock_dispatch(*args):
 checker = renormalizing.RENormalizing([
     # Date formatting
     (re.compile(r'\d{4}-\d{2}-\d{2}'), '1972-12-25'),
-    # Hg tag list hash formatting
-    (re.compile(r'\d:[0-9a-f]{12}'), '1:234567890abc'),
-    # Hg bare hash formatting
-    (re.compile(r'[0-9a-f]{12}'), '234567890abc'),
-    # Hg has an updated comment
-    (re.compile('updating working directory'),
-     'updating to branch default'),
-    # Newer Hg no longer prints 'requesting all changes'
-    (re.compile('requesting all changes'), ''),
     # Git diff hash formatting
     (re.compile(r'[0-9a-f]{7}\.\.[0-9a-f]{7} [0-9a-f]{6}'),
      '1234567..890abcd ef0123'),
     # .pypirc seems to be case insensitive
-    (re.compile('[Pp][Yy][Pp][Ii]'), 'pypi'),
+    (re.compile(r'[Pp][Yy][Pp][Ii]'), 'pypi'),
     # Normalize tempdirs.  For this to work reliably, we need to use a prefix
     # in all tempfile.mkdtemp() calls.
     (re.compile(
-        '%s/testtemp[^/]+/svnrepo' % re.escape(tempfile.gettempdir())),
-     'TESTREPO'),
-    (re.compile(
-        '/private%s/testtemp[^/]+' % re.escape(tempfile.gettempdir())),
+        r'/private%s/testtemp[^/]+' % re.escape(tempfile.gettempdir())),
      'TESTTEMP'),  # OSX madness
     (re.compile(
-        '%s/testtemp[^/]+' % re.escape(tempfile.gettempdir())),
+        r'%s/testtemp[^/]+' % re.escape(tempfile.gettempdir())),
      'TESTTEMP'),
     (re.compile(re.escape(tempfile.gettempdir())),
      'TMPDIR'),
-    # Python 2.7 prints 'Creating tar archive' instead of
-    # 'tar -cf dist/tha.example-0.1.tar tha.example-0.1
-    #  ...':
-    (re.compile('tar -cf dist/tha.example-0.1.tar tha.example-0.1'),
-     'Creating tar archive'),
-    # Harmless line when using subversion 1.7:
-    (re.compile('unrecognized .svn/entries format in'), ''),
-    # git before 1.7.9.2 reported 0 deletions when committing:
-    (re.compile(r', 0 deletions\(-\)'), ''),
-    # Change in git 1.7.9.2: '1 files changed':
-    (re.compile(' 1 files changed'), ' 1 file changed'),
-    # Change in git 1.8.0:
-    (re.compile(r'nothing to commit \(working directory clean\)'),
-     'nothing to commit, working directory clean'),
     # Change in git 2.9.1:
-    (re.compile('nothing to commit, working directory clean'),
+    (re.compile(r'nothing to commit, working directory clean'),
      'nothing to commit, working tree clean'),
-    # Change in git 1.8.5, the hash is removed:
-    (re.compile('# On branch'),
-     'On branch'),
-    # Hg 3.3 prints 'committing files' or 'committing <filename>'
-    (re.compile(r'^committing.*'), ''),
     # We should ignore coloring by colorama.  Or actually print it
     # clearly.  This catches Fore.RED and Fore.MAGENTA.
-    # Note the extra backslash in front of the left bracket, otherwise
-    # you get: "error: unexpected end of regular expression"
     (re.compile(re.escape(Fore.RED)), 'RED '),
     (re.compile(re.escape(Fore.MAGENTA)), 'MAGENTA '),
-    # svn 1.9 prints 'Committing transaction...'
-    (re.compile('Committing transaction...'), ''),
-    (re.compile('FileNotFoundError'), 'IOError'),
-    # mercurial 4.4.1 includes an extra line in checkout_from_tag, e.g.:
-    # new changesets 234567890abc:234567890abc
-    (re.compile('new changesets [0-9a-f]{12}:[0-9a-f]{12}'), ''),
-] + ([
-
-] if six.PY3 else [
-    (re.compile('u\''), '\''),
-    (re.compile('u"'), '"'),
-    (re.compile('zest.releaser.utils.CommandException'), 'CommandException'),
-]))
-
+])
 
 test_suite = z3c.testsetup.register_all_tests('zest.releaser', checker=checker)
diff --git a/zest/releaser/tests/utils.txt b/zest/releaser/tests/utils.txt
index 5d8482de..625978ab 100644
--- a/zest/releaser/tests/utils.txt
+++ b/zest/releaser/tests/utils.txt
@@ -708,7 +708,7 @@ internally).  Because of the entry points, good documentation is necessary.
 So we can check whether all keys have attached documentation:
 
     >>> data = {'commit_msg': 'get me some booze'}
-    >>> documentation = {'commit_msg': 'Commit message for svn',
+    >>> documentation = {'commit_msg': 'Commit message for git',
     ...                  'target': 'Some other thingy'}
     >>> utils.is_data_documented(data, documentation)
     Checking data dict
diff --git a/zest/releaser/tests/vcs.txt b/zest/releaser/tests/vcs.txt
index 90bda89e..1acad47c 100644
--- a/zest/releaser/tests/vcs.txt
+++ b/zest/releaser/tests/vcs.txt
@@ -18,12 +18,12 @@ handles some base cases and has a bunch of NotImplementedError methods.
 
 When started, it stores the current working directory:
 
-    >>> svnsourcedir
-    'TESTTEMP/tha.example-svn'
-    >>> os.chdir(svnsourcedir)
+    >>> gitsourcedir
+    'TESTTEMP/tha.example-git'
+    >>> os.chdir(gitsourcedir)
     >>> checkout = vcs.BaseVersionControl()
     >>> checkout.workingdir
-    'TESTTEMP/tha.example-svn'
+    'TESTTEMP/tha.example-git'
 
 
 Methods that must be implemented in subclasses