Detailed tests of vcs.py
========================

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

Some initial imports:

    >>> from zest.releaser import vcs
    >>> import os

BaseVersionControl is the base class for subversion/mercurial/git support.  It
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)
    >>> checkout = vcs.BaseVersionControl()
    >>> checkout.workingdir
    'TESTTEMP/tha.example-svn'


Methods that must be implemented in subclasses
----------------------------------------------

    >>> checkout.name()
    Traceback (most recent call last):
    ...
    NotImplementedError
    >>> checkout.available_tags()
    Traceback (most recent call last):
    ...
    NotImplementedError
    >>> checkout.prepare_checkout_dir('prefix')
    Traceback (most recent call last):
    ...
    NotImplementedError
    >>> checkout.tag_url('arg')
    Traceback (most recent call last):
    ...
    NotImplementedError
    >>> checkout.cmd_diff()
    Traceback (most recent call last):
    ...
    NotImplementedError
    >>> checkout.cmd_commit('arg')
    Traceback (most recent call last):
    ...
    NotImplementedError
    >>> checkout.cmd_diff_last_commit_against_tag('arg')
    Traceback (most recent call last):
    ...
    NotImplementedError
    >>> checkout.cmd_create_tag('arg')
    Traceback (most recent call last):
    ...
    NotImplementedError


Tag handling
------------

Extraction of tags is handled by the subclasses.  The one thing that the
baseclass does is to check if a tag has already been made earlier:

    >>> def mock_available_tags():
    ...     return ['0.1', '0.2']
    >>> checkout.available_tags = mock_available_tags
    >>> checkout.tag_exists('1.0')
    False
    >>> checkout.tag_exists('0.2')
    True


Version handling
----------------

Create a project with a ``version.txt`` in it like often found in old zope
products:

    >>> versiontxtproject = os.path.join(tempdir, 'vp')
    >>> os.mkdir(versiontxtproject)
    >>> os.chdir(versiontxtproject)
    >>> dont_care = open('version.txt', 'w').write('1.0 dev')

We need some version control marker (like an ``.svn`` dir):

    >>> os.mkdir('.marker')
    >>> checkout = vcs.BaseVersionControl()
    >>> checkout.internal_filename = '.marker'

Open the project with the BaseVersionControl and it finds the version file and
returns the cleaned-up version:

    >>> checkout.get_version_txt_version()
    '1.0dev'

Setting the version works also with version.txt files (the setup.py scenario
is tested in the other tests).

    >>> checkout.version = '1.1'
    >>> print(open('version.txt').read())
    1.1

In old Plone products, the setup.py often reads the version.txt and uses that
as the version value.  In those cases, the version must be written to the
version.txt and the setup.py must be left unmolested.

    >>> lines = [
    ...     "from setuptools import setup",
    ...     "setup(name='urgh', version=open('version.txt').read().strip())"]
    >>> dont_care = open('setup.py', 'w').write('\n'.join(lines))
    >>> checkout.version = '1.2'
    >>> print(open('version.txt').read())
    1.2
    >>> print(open('setup.py').read())
    from setuptools import setup
    setup(name='urgh', version=open('version.txt').read().strip())

If the setup.py version is different, this takes precedence and the
version.txt is the one left as-is.

    >>> lines = [
    ...     "from setuptools import setup",
    ...     "setup(name='urgh',",
    ...     "      version='1.3',",
    ...     "      )"]
    >>> dont_care = open('setup.py', 'w').write('\n'.join(lines))
    >>> checkout.version = '1.4'
    >>> print(open('version.txt').read())  # Still at 1.2
    1.2
    >>> print(open('setup.py').read()) # Modified into 1.4
    from setuptools import setup
    setup(name='urgh',
          version='1.4',
          )

The version writing breaks down if there's more than just a "version=" on that
line.  The 99.9% case works, though.

Another option is a ``__version__`` marker in a Python file. We cannot
reliably figure out the right Python file to look in, so this file needs to be
specified explicitly. As `PEP 396 <http://www.python.org/dev/peps/pep-0396/>`_
specifies, the ``__version__`` attribute and the ``setup.py`` version need to
be derived one from the other, so zest.releaser only looks at one version
source. If a ``__version__`` marker file is specified, that's where we'll look
and otherwise not.

Create a Python file with ``__version__`` without configuring it. This won't
change a thing:

    >>> lines = [
    ...     "import something",
    ...     "__version__ = '2.0'",
    ...     "print('something.else')"]
    >>> dont_care = open('some_file.py', 'w').write('\n'.join(lines))
    >>> checkout.version
    '1.4'

Add a ``setup.cfg`` with a pointer at the Python file and its version gets
picked up:

    >>> lines = [
    ...     "[zest.releaser]",
    ...     "python-file-with-version = some_file.py"]
    >>> dont_care = open('setup.cfg', 'w').write('\n'.join(lines))
    >>> checkout.get_python_file_version()
    '2.0'
    >>> checkout.version
    '2.0'

Setting it sets it in the correct Python file:

    >>> checkout.version = '2.1'
    >>> print(open('some_file.py').read())
    import something
    __version__ = '2.1'
    print('something.else')


Version corner cases
--------------------

Version files can also be called ``VERSION`` instead of ``version.txt`` (or
``VERSION.TXT``):

    >>> versiontxtproject = os.path.join(tempdir, 'vp2')
    >>> os.mkdir(versiontxtproject)
    >>> os.chdir(versiontxtproject)
    >>> dont_care = open('VERSION', 'w').write('1.0 dev')

We need some version control marker (like an ``.svn`` dir):

    >>> os.mkdir('.marker')
    >>> checkout = vcs.BaseVersionControl()
    >>> checkout.internal_filename = '.marker'

Open the project with the BaseVersionControl and it finds the version file and
returns the cleaned-up version:

    >>> checkout.get_version_txt_version()
    '1.0dev'

Setting the version works also with ``VERSION`` files:

    >>> checkout.version = '1.1'
    >>> print(open('VERSION').read())
    1.1

Same with a ``VERSION.txt``:

    >>> os.remove('VERSION')
    >>> dont_care = open('VERSION.txt', 'w').write('22.3')
    >>> checkout.get_version_txt_version()
    '22.3'
    >>> checkout.version = '22.4'
    >>> print(open('VERSION.txt').read())
    22.4

Version files can also be called ``version.rst`` (or .md or so) instead of ``version.txt``:

    >>> os.remove('VERSION.txt')
    >>> dont_care = open('version.rst', 'w').write('25.0')
    >>> checkout.get_version_txt_version()
    '25.0'
