pax_global_header00006660000000000000000000000064127031517040014512gustar00rootroot0000000000000052 comment=72f8f0a7e0121cf5989a2cb00d5e1395e02a0445 vcversioner-2.16.0.0/000077500000000000000000000000001270315170400143035ustar00rootroot00000000000000vcversioner-2.16.0.0/.coveragerc000066400000000000000000000000331270315170400164200ustar00rootroot00000000000000[run] source = vcversioner vcversioner-2.16.0.0/.gitignore000066400000000000000000000001031270315170400162650ustar00rootroot00000000000000/MANIFEST /build /dist *.egg-info /version.txt /.coverage /htmlcov vcversioner-2.16.0.0/.travis.yml000066400000000000000000000003051270315170400164120ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.3" - "3.4" - "3.5" install: "pip install pytest coveralls --use-mirrors" script: "coverage run $(which py.test)" after_success: "coveralls" vcversioner-2.16.0.0/COPYING000066400000000000000000000013611270315170400153370ustar00rootroot00000000000000Copyright (c) 2013-2014, Aaron Gallagher <_@habnab.it> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. vcversioner-2.16.0.0/MANIFEST.in000066400000000000000000000000241270315170400160350ustar00rootroot00000000000000include version.txt vcversioner-2.16.0.0/README.rst000066400000000000000000000240161270315170400157750ustar00rootroot00000000000000.. image:: https://travis-ci.org/habnabit/vcversioner.png =========== vcversioner =========== `Elevator pitch`_: you can write a ``setup.py`` with no version information specified, and vcversioner will find a recent, properly-formatted VCS tag and extract a version from it. It's much more convenient to be able to use your version control system's tagging mechanism to derive a version number than to have to duplicate that information all over the place. I eventually ended up copy-pasting the same code into a couple different ``setup.py`` files just to avoid duplicating version information. But, copy-pasting is dumb and unit testing ``setup.py`` files is hard. This code got factored out into vcversioner. Basic usage ----------- vcversioner installs itself as a setuptools hook, which makes its use exceedingly simple:: from setuptools import setup setup( # [...] setup_requires=['vcversioner'], vcversioner={}, ) The presence of a ``vcversioner`` argument automagically activates vcversioner and updates the project's version. The parameter to the ``vcversioner`` argument can also be a dict of keyword arguments which |find_version| will be called with. To allow tarballs to be distributed without requiring a ``.git`` (or ``.hg``, etc.) directory, vcversioner will also write out a file named (by default) ``version.txt``. Then, if there is no VCS program or the program is unable to find any version information, vcversioner will read version information from the ``version.txt`` file. However, this file needs to be included in a distributed tarball, so the following line should be added to ``MANIFEST.in``:: include version.txt This isn't necessary if ``setup.py`` will always be run from a checkout, but otherwise is essential for vcversioner to know what version to use. The name ``version.txt`` also can be changed by specifying the ``version_file`` parameter. For example:: from setuptools import setup setup( # [...] setup_requires=['vcversioner'], vcversioner={ 'version_file': 'custom_version.txt', }, ) For compatibility with `semantic versioning`_, ``vcversioner`` will strip leading ``'v'``\ s from version tags. That is, the tag ``v1.0`` will be treated as if it was ``1.0``. Other prefixes can be specified to be stripped by using the ``strip_prefix`` argument to vcversioner. For compatibility with ``git-dch``, one could specify the ``strip_prefix`` as ``'debian/'``. Non-hook usage -------------- It's not necessary to depend on vcversioner; while `pip`_ will take care of dependencies automatically, sometimes having a self-contained project is simpler. vcversioner is a single file which is easy to add to a project. Simply copy the entire ``vcversioner.py`` file adjacent to the existing ``setup.py`` file and update the usage slightly:: from setuptools import setup import vcversioner setup( # [...] version=vcversioner.find_version().version, ) This is necessary because the ``vcversioner`` distutils hook won't be available. Version modules --------------- ``setup.py`` isn't the only place that version information gets duplicated. By generating a version module, the ``__init__.py`` file of a package can import version information. For example, with a package named ``spam``:: from setuptools import setup setup( # [...] setup_requires=['vcversioner'], vcversioner={ 'version_module_paths': ['spam/_version.py'], }, ) This will generate a ``spam/_version.py`` file that defines ``__version__`` and ``__revision__``. Then, in ``spam/__init__.py``:: from spam._version import __version__, __revision__ Since this acts like (and *is*) a regular python module, changing ``MANIFEST.in`` is not required. Customizing VCS commands ------------------------ vcversioner by default tries to detect which VCS is being used and picks a command to run based on that. For git, that is ``git --git-dir %(root)s/.git describe --tags --long``. For hg, that is ``hg log -R %(root)s -r . --template '{latesttag}-{latesttagdistance}-hg{node|short}'``. Any command should output a string that describes the current commit in the format ``1.0-0-gdeadbeef``. Specifically, that is ``--``. The revision should have a VCS-specific prefix, e.g. ``g`` for git and ``hg`` for hg. However, sometimes this isn't sufficient. If someone wanted to only use annotated tags, the git command could be amended like so:: from setuptools import setup setup( # [...] setup_requires=['vcversioner'], vcversioner={ 'vcs_args': ['git', 'describe', '--long'], }, ) The ``vcs_args`` parameter must always be a list of strings, which will not be interpreted by the shell. This is the same as what ``subprocess.Popen`` expects. This argument used to be spelled ``git_args`` until support for multiple VCS systems was added. Development versions -------------------- vcversioner can also automatically make a version that corresponds to a commit that isn't itself tagged. Following `PEP 386`_, this is done by adding a ``.post`` suffix to the version specified by a tag on an earlier commit. For example, if the current commit is three revisions past the ``1.0`` tag, the computed version will be ``1.0.post3``. This behavior can be disabled by setting the ``include_dev_version`` parameter to ``False``. In that case, the aforementioned untagged commit's version would be just ``1.0``. Since hg requires a commit to make a tag, there's a parameter ``decrement_dev_version`` to subtract one from the number of commits after the most recent tag. If the VCS used is detected to be hg (i.e. the revision starts with ``'hg'``) and ``decrement_dev_version`` is not specified as ``False``, ``decrement_dev_version`` will be automatically set to ``True``. Project roots ------------- In order to prevent contamination from other source repositories, vcversioner in the 1.x version series will only look in the project root directory for repositories. The project root defaults to the current working directory, which is often the case when running setup.py. This can be changed by specifying the ``root`` parameter. Someone concerned with being able to run setup.py from directories other than the directory containing setup.py should determine the project root from ``__file__`` in setup.py:: from setuptools import setup import os setup( # [...] setup_requires=['vcversioner'], vcversioner={ 'root': os.path.dirname(os.path.abspath(__file__)), }, ) To get the same behavior in the 0.x version series, ``vcs_args`` can be set to include the ``--git-dir`` flag:: from setuptools import setup setup( # [...] setup_requires=['vcversioner'], vcversioner={ vcs_args=['git', '--git-dir', '%(root)s/.git', 'describe', '--tags', '--long'], }, ) By default, ``version.txt`` is also read from the project root. Substitutions ~~~~~~~~~~~~~ As seen above, *root*, *version_file*, and *vcs_args* each support some substitutions: ``%(root)s`` The value provided for *root*. This is not available for the *root* parameter itself. ``%(pwd)s`` The current working directory. ``/`` will automatically be translated into the correct path separator for the current platform, such as ``:`` or ``\``. Sphinx documentation -------------------- `Sphinx`_ documentation is yet another place where version numbers get duplicated. Fortunately, since sphinx configuration is python code, vcversioner can be used there too. Assuming vcversioner is installed system-wide, this is quite easy. Since Sphinx is typically run with the current working directory as ``/docs``, it's necessary to tell vcversioner where the project root is. Simply change your ``conf.py`` to include:: import vcversioner version = release = vcversioner.find_version(root='..').version This assumes that your project root is the parent directory of the current working directory. A slightly longer version which is a little more robust would be:: import vcversioner, os version = release = vcversioner.find_version( root=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).version This version is more robust because it finds the project root not relative to the current working directory but instead relative to the ``conf.py`` file. If vcversioner is bundled with your project instead of relying on it being installed, you might have to add the following to your ``conf.py`` before ``import vcversioner``:: import sys, os sys.path.insert(0, os.path.abspath('..')) This line, or something with the same effect, is sometimes already present when using the sphinx ``autodoc`` extension. Read the Docs ~~~~~~~~~~~~~ Using vcversioner is even possible when building documentation on `Read the Docs`_. If vcversioner is bundled with your project, nothing further needs to be done. Otherwise, you need to tell Read the Docs to install vcversioner before it builds the documentation. This means using a ``requirements.txt`` file. If your project is already set up to install dependencies with a ``requirements.txt`` file, add ``vcversioner`` to it. Otherwise, create a ``requirements.txt`` file. Assuming your documentation is in a ``docs`` subdirectory of the main project directory, create ``docs/requirements.txt`` containing a ``vcversioner`` line. Then, make the following changes to your project's configuration: (Project configuration is edited at e.g. https://readthedocs.org/dashboard/vcversioner/edit/) - Check the checkbox under **Use virtualenv**. - If there was no ``requirements.txt`` previously, set the **Requirements file** to the newly-created one, e.g. ``docs/requirements.txt``. .. _Elevator pitch: http://en.wikipedia.org/wiki/Elevator_pitch .. _pip: https://pypi.python.org/pypi/pip .. _PEP 386: http://www.python.org/dev/peps/pep-0386/ .. _Sphinx: http://sphinx-doc.org .. _Read the Docs: https://readthedocs.org/ .. _semantic versioning: http://semver.org/ .. |find_version| replace:: ``find_version`` vcversioner-2.16.0.0/docs/000077500000000000000000000000001270315170400152335ustar00rootroot00000000000000vcversioner-2.16.0.0/docs/.gitignore000066400000000000000000000000101270315170400172120ustar00rootroot00000000000000/_build vcversioner-2.16.0.0/docs/Makefile000066400000000000000000000151761270315170400167050ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/vcversioner.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/vcversioner.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/vcversioner" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/vcversioner" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." vcversioner-2.16.0.0/docs/_static/000077500000000000000000000000001270315170400166615ustar00rootroot00000000000000vcversioner-2.16.0.0/docs/_static/.keep000066400000000000000000000000001270315170400175740ustar00rootroot00000000000000vcversioner-2.16.0.0/docs/conf.py000066400000000000000000000173251270315170400165420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # vcversioner documentation build configuration file, created by # sphinx-quickstart on Sat Jul 6 18:55:57 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. try: import vcversioner except ImportError: import sys, os sys.stderr.write("couldn't import vcversioner; groping around for it\n") sys.path.insert(0, os.path.abspath('..')) import vcversioner # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'vcversioner' copyright = u'2013, Aaron Gallagher' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # version = release = vcversioner.find_version(root='..').version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'vcversionerdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'vcversioner.tex', u'vcversioner Documentation', u'Aaron Gallagher', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'vcversioner', u'vcversioner Documentation', [u'Aaron Gallagher'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'vcversioner', u'vcversioner Documentation', u'Aaron Gallagher', 'vcversioner', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False vcversioner-2.16.0.0/docs/index.rst000066400000000000000000000005141270315170400170740ustar00rootroot00000000000000=========== vcversioner =========== The code is available on github: https://github.com/habnabit/vcversioner .. include:: ../README.rst :start-line: 6 ``vcversioner`` API reference ----------------------------- .. automodule:: vcversioner :members: find_version, setup .. |find_version| replace:: :func:`.find_version` vcversioner-2.16.0.0/setup.py000066400000000000000000000024111270315170400160130ustar00rootroot00000000000000# Copyright (c) Aaron Gallagher <_@habnab.it> # See COPYING for details. import vcversioner # not this again from setuptools import setup with open('README.rst', 'r') as infile: long_description = infile.read() setup( name='vcversioner', version=vcversioner.find_version().version, description='Use version control tags to discover version numbers', long_description=long_description, author='Aaron Gallagher', author_email='_@habnab.it', url='https://github.com/habnabit/vcversioner', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: ISC License (ISCL)', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Version Control', ], license='ISC', py_modules=['vcversioner'], entry_points={ 'distutils.setup_keywords': ['vcversioner = vcversioner:setup'], }, ) vcversioner-2.16.0.0/test_vcversioner.py000066400000000000000000000354111270315170400202650ustar00rootroot00000000000000# Copyright (c) Aaron Gallagher <_@habnab.it> # See COPYING for details. from __future__ import unicode_literals import os import pytest import vcversioner try: unicode except NameError: unicode = str class FakePopen(object): def __init__(self, stdout, stderr=b''): self.stdout = stdout self.stderr = stderr def communicate(self): return self.stdout, self.stderr def __call__(self, *args, **kwargs): return self class RaisingFakePopen(object): def __call__(self, *args, **kwargs): self.args = args self.kwargs = kwargs raise OSError('hi!') empty = FakePopen(b'') invalid = FakePopen(b'foob') basic_version = FakePopen(b'1.0-0-gbeef') dev_version = FakePopen(b'1.0-2-gfeeb') hg_version = FakePopen(b'1.0-1-hgbeef') git_failed = FakePopen(b'', b'fatal: whatever') class FakeOpen(object): def __call__(self, *args, **kwargs): self.args = args self.kwargs = kwargs raise OSError('hi!') @pytest.fixture def gitdir(tmpdir): tmpdir.chdir() tmpdir.join('.git').mkdir() return tmpdir @pytest.fixture def hgdir(tmpdir): tmpdir.chdir() tmpdir.join('.hg').mkdir() return tmpdir def test_astounding_success(gitdir): "Successful output from git is cached and returned." version = vcversioner.find_version(Popen=basic_version) assert version == ('1.0', '0', 'gbeef') with gitdir.join('version.txt').open() as infile: assert infile.read() == '1.0-0-gbeef' def test_no_git(gitdir): "If git fails and there's no version.txt, abort." with pytest.raises(SystemExit) as excinfo: vcversioner.find_version(Popen=empty) assert excinfo.value.args[0] == 2 assert not gitdir.join('version.txt').check() def test_when_Popen_raises(gitdir): "If *spawning* git fails and there's no version.txt, abort." with pytest.raises(SystemExit) as excinfo: vcversioner.find_version(Popen=RaisingFakePopen()) assert excinfo.value.args[0] == 2 assert not gitdir.join('version.txt').check() def test_no_git_but_version_file(gitdir): "If git fails but there's a version.txt, that's fine too." with gitdir.join('version.txt').open('w') as outfile: outfile.write('1.0-0-gbeef') version = vcversioner.find_version(Popen=empty) assert version == ('1.0', '0', 'gbeef') def test_Popen_raises_but_version_file(gitdir): "If spawning git fails but there's a version.txt, that's similarly fine." with gitdir.join('version.txt').open('w') as outfile: outfile.write('1.0-0-gbeef') version = vcversioner.find_version(Popen=RaisingFakePopen()) assert version == ('1.0', '0', 'gbeef') def test_version_file_with_root(gitdir): "version.txt gets read from the project root by default." with gitdir.join('version.txt').open('w') as outfile: outfile.write('1.0-0-gbeef') version = vcversioner.find_version( root=gitdir.strpath, Popen=RaisingFakePopen()) assert version == ('1.0', '0', 'gbeef') def test_invalid_git(gitdir): "Invalid output from git is a failure too." with pytest.raises(SystemExit) as excinfo: vcversioner.find_version(Popen=invalid) assert excinfo.value.args[0] == 2 assert not gitdir.join('version.txt').check() def test_invalid_version_file(gitdir): "Invalid output in version.txt is similarly a failure." with gitdir.join('version.txt').open('w') as outfile: outfile.write('foob') with pytest.raises(SystemExit) as excinfo: vcversioner.find_version(Popen=empty) assert excinfo.value.args[0] == 2 def test_dev_version(gitdir): ".post version numbers are automatically created." version = vcversioner.find_version(Popen=dev_version) assert version == ('1.0.post2', '2', 'gfeeb') with gitdir.join('version.txt').open() as infile: assert infile.read() == '1.0-2-gfeeb' def test_dev_version_disabled(gitdir): ".post version numbers can also be disabled." version = vcversioner.find_version(Popen=dev_version, include_dev_version=False) assert version == ('1.0', '2', 'gfeeb') with gitdir.join('version.txt').open() as infile: assert infile.read() == '1.0-2-gfeeb' def test_custom_vcs_args(gitdir): "The command to execute to get the version can be customized." popen = RaisingFakePopen() with pytest.raises(SystemExit): vcversioner.find_version(Popen=popen, vcs_args=('foo', 'bar')) assert popen.args[0] == ['foo', 'bar'] def test_custom_vcs_args_substitutions(gitdir): "The command arguments have some substitutions performed." popen = RaisingFakePopen() with pytest.raises(SystemExit): vcversioner.find_version(Popen=popen, vcs_args=('foo', 'bar', '%(pwd)s', '%(root)s')) assert popen.args[0] == ['foo', 'bar', gitdir.strpath, gitdir.strpath] def test_custom_vcs_args_substitutions_with_different_root(tmpdir): "Specifying a different root will cause that root to be substituted." tmpdir.chdir() popen = RaisingFakePopen() with pytest.raises(SystemExit): vcversioner.find_version(Popen=popen, root='/spam', vcs_args=('%(root)s',)) assert popen.args[0] == ['/spam'] def test_custom_version_file(gitdir): "The version.txt file can have a unique name." version = vcversioner.find_version(Popen=basic_version, version_file='custom.txt') assert version == ('1.0', '0', 'gbeef') with gitdir.join('custom.txt').open() as infile: assert infile.read() == '1.0-0-gbeef' def test_custom_version_file_reading(gitdir): "The custom version.txt can be read from as well." with gitdir.join('custom.txt').open('w') as outfile: outfile.write('1.0-0-gbeef') version = vcversioner.find_version(Popen=empty, version_file='custom.txt') assert version == ('1.0', '0', 'gbeef') def test_version_file_disabled(gitdir): "The version.txt file can be disabled too." version = vcversioner.find_version(Popen=basic_version, version_file=None) assert version == ('1.0', '0', 'gbeef') assert not gitdir.join('version.txt').check() def test_version_file_disabled_git_failed(gitdir): "If version.txt is disabled and git fails, nothing can be done." with pytest.raises(SystemExit) as excinfo: vcversioner.find_version(Popen=empty, version_file=None) assert excinfo.value.args[0] == 2 assert not gitdir.join('version.txt').check() def test_version_file_disabled_Popen_raises(gitdir): "If version.txt is disabled and git fails to spawn, abort as well." with pytest.raises(SystemExit) as excinfo: vcversioner.find_version(Popen=RaisingFakePopen(), version_file=None) assert excinfo.value.args[0] == 2 assert not gitdir.join('version.txt').check() def test_namedtuple(tmpdir): "The output namedtuple has attribute names too." tmpdir.chdir() version = vcversioner.find_version(Popen=basic_version, version_file=None, vcs_args=[]) assert version.version == '1.0' assert version.commits == '0' assert version.sha == 'gbeef' def test_namedtuple_nonzero_commits(tmpdir): "The output namedtuple can have a nonzero number of commits." tmpdir.chdir() version = vcversioner.find_version(Popen=dev_version, version_file=None, vcs_args=[]) assert version.version == '1.0.post2' assert version.commits == '2' assert version.sha == 'gfeeb' def test_version_module_paths(gitdir): "Version modules can be written out too." paths = ['foo.py', 'bar.py'] vcversioner.find_version( Popen=basic_version, version_module_paths=paths) for path in paths: with open(path) as infile: assert infile.read() == """ # This file is automatically generated by setup.py. __version__ = '1.0' __sha__ = 'gbeef' __revision__ = 'gbeef' """ def test_git_arg_path_translation(gitdir, monkeypatch): "/ is translated into the correct path separator in git arguments." monkeypatch.setattr(os, 'sep', ':') popen = RaisingFakePopen() with pytest.raises(SystemExit): vcversioner.find_version(Popen=popen, vcs_args=['spam/eggs'], version_file=None) assert popen.args[0] == ['spam:eggs'] def test_version_file_path_translation(gitdir, monkeypatch): "/ is translated into the correct path separator for version.txt." monkeypatch.setattr(os, 'sep', ':') open = FakeOpen() with pytest.raises(OSError): vcversioner.find_version(Popen=basic_version, open=open, version_file='spam/eggs', vcs_args=[]) assert open.args[0] == 'spam:eggs' def test_git_output_on_no_version_file(gitdir, capsys): "The output from git is shown if it failed and the version file is disabled." with pytest.raises(SystemExit): vcversioner.find_version(Popen=git_failed, version_file=None, vcs_args=[]) out, err = capsys.readouterr() assert not err assert out == ( 'vcversioner: [] failed.\n' 'vcversioner: -- VCS output follows --\n' 'vcversioner: fatal: whatever\n') def test_git_output_on_version_file_absent(gitdir, capsys): "The output from git is shown if it failed and the version file doesn't exist." with pytest.raises(SystemExit): vcversioner.find_version(Popen=git_failed, version_file='version.txt', vcs_args=[]) out, err = capsys.readouterr() assert not err assert out == ( "vcversioner: [] failed and %r isn't present.\n" 'vcversioner: are you installing from a github tarball?\n' 'vcversioner: -- VCS output follows --\n' 'vcversioner: fatal: whatever\n' % ('version.txt',)) def test_git_output_on_version_unparsable(gitdir, capsys): "The output from git is shown if it failed and the version couldn't be parsed." gitdir.join('version.txt').write('doof') with pytest.raises(SystemExit): vcversioner.find_version(Popen=git_failed, version_file='version.txt', vcs_args=[]) out, err = capsys.readouterr() assert not err assert out == ( "vcversioner: %r (from %r) couldn't be parsed into a version.\n" 'vcversioner: -- VCS output follows --\n' 'vcversioner: fatal: whatever\n' % ('doof', 'version.txt')) def test_no_git_output_on_version_unparsable(capsys): "The output from git is not shown if git succeeded but the version couldn't be parsed." with pytest.raises(SystemExit): vcversioner.find_version(Popen=invalid, version_file='version.txt', vcs_args=[]) out, err = capsys.readouterr() assert not err assert out == ( "vcversioner: %r (from VCS) couldn't be parsed into a version.\n" % ('foob',)) def test_no_output_on_success(gitdir, capsys): "There is no output if everything succeeded." vcversioner.find_version(Popen=basic_version) out, err = capsys.readouterr() assert not out assert not err def test_no_output_on_version_file_success(gitdir, capsys): "There is no output if everything succeeded, even if the version was read from a version file." gitdir.join('version.txt').write('1.0-0-gbeef') vcversioner.find_version(Popen=git_failed) out, err = capsys.readouterr() assert not out assert not err def test_strip_leading_v(gitdir): "Leading 'v's are stripped from tags." version = vcversioner.find_version(Popen=FakePopen(b'v1.0-0-gbeef')) assert version == ('1.0', '0', 'gbeef') def test_strip_leading_prefix(gitdir): "The leading prefix stripped from tags can be customized." version = vcversioner.find_version(Popen=FakePopen(b'debian/1.0-0-gbeef'), strip_prefix='debian/') assert version == ('1.0', '0', 'gbeef') def test_git_args_deprecation(gitdir): "git_args is deprecated." pytest.deprecated_call(vcversioner.find_version, git_args=['git', 'spam'], Popen=basic_version) def test_git_args_still_works(gitdir): "git_args still works like vcs_args." popen = RaisingFakePopen() with pytest.raises(SystemExit): vcversioner.find_version(git_args=['git', 'spam'], Popen=popen) assert popen.args[0] == ['git', 'spam'] def test_hg_detection(hgdir): ".hg directories get detected and the appropriate hg command gets run." popen = RaisingFakePopen() with pytest.raises(SystemExit): vcversioner.find_version(Popen=popen) assert popen.args[0] == [ 'hg', 'log', '-R', hgdir.strpath, '-r', '.', '--template', '{latesttag}-{latesttagdistance}-hg{node|short}'] def test_no_vcs_no_version_file(tmpdir, capsys): "If no VCS is detected with no version_file, vcversioner aborts." tmpdir.chdir() with pytest.raises(SystemExit): vcversioner.find_version(version_file=None, Popen=basic_version) out, err = capsys.readouterr() assert not err assert out == ( 'vcversioner: no VCS could be detected in %r.\n' % (unicode(tmpdir.strpath),)) def test_no_vcs_absent_version_file(tmpdir, capsys): "If no VCS is detected with an absent version_file, vcversioner aborts." tmpdir.chdir() with pytest.raises(SystemExit): vcversioner.find_version(version_file='version.txt', Popen=basic_version) out, err = capsys.readouterr() assert not err assert out == ( "vcversioner: no VCS could be detected in %r and %r isn't present.\n" "vcversioner: are you installing from a github tarball?\n" % ( unicode(tmpdir.strpath), 'version.txt')) def test_decrement_dev_version(gitdir): "decrement_dev_version will subtract one from the number of commits." version = vcversioner.find_version(decrement_dev_version=True, Popen=dev_version) assert version == ('1.0.post1', '1', 'gfeeb') def test_decrement_dev_version_to_zero(gitdir): "decrement_dev_version with one commit will produce a non-dev version number." version = vcversioner.find_version(decrement_dev_version=True, Popen=FakePopen(b'1.0-1-gbeef')) assert version == ('1.0', '0', 'gbeef') def test_automatic_decrement_dev_version_with_hg(hgdir): "decrement_dev_version gets turned on automatically with hg revisions." version = vcversioner.find_version(Popen=hg_version) assert version == ('1.0', '0', 'hgbeef') def test_automatic_decrement_dev_version_disabled(hgdir): "decrement_dev_version does not get turned on automatically if explicitly disabled." version = vcversioner.find_version(decrement_dev_version=False, Popen=hg_version) assert version == ('1.0.post1', '1', 'hgbeef') def test_version_file_substituted_with_no_vcs(tmpdir): "The version file is substituted even if no VCS is present." tmpdir.chdir() tmpdir.join('version.txt').write('1.0-0-gbeef') version = vcversioner.find_version(Popen=empty) assert version == ('1.0', '0', 'gbeef') class Struct(object): pass def test_setup_astounding_success(tmpdir): "``find_version`` can be called through distutils too." tmpdir.chdir() dist = Struct() dist.metadata = Struct() vcversioner.setup( dist, 'vcversioner', {str('Popen'): basic_version, str('version_file'): None, str('vcs_args'): []}) assert dist.metadata.version == '1.0' vcversioner-2.16.0.0/vcversioner.py000066400000000000000000000230631270315170400172260ustar00rootroot00000000000000# Copyright (c) 2013-2014, Aaron Gallagher <_@habnab.it> # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY # AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. """Simplify your python project versioning. In-depth docs online: https://vcversioner.readthedocs.org/en/latest/ Code online: https://github.com/habnabit/vcversioner """ from __future__ import print_function, unicode_literals import collections import os import subprocess import warnings Version = collections.namedtuple('Version', 'version commits sha') _print = print def print(*a, **kw): _print('vcversioner:', *a, **kw) def _fix_path(p): "Translate ``/``s into the right path separator." return p.replace('/', os.sep) _vcs_args_by_path = [ ('%(root)s/.git', ( 'git', '--git-dir', '%(root)s/.git', 'describe', '--tags', '--long')), ('%(root)s/.hg', ( 'hg', 'log', '-R', '%(root)s', '-r', '.', '--template', '{latesttag}-{latesttagdistance}-hg{node|short}')), ] def find_version(include_dev_version=True, root='%(pwd)s', version_file='%(root)s/version.txt', version_module_paths=(), git_args=None, vcs_args=None, decrement_dev_version=None, strip_prefix='v', Popen=subprocess.Popen, open=open): """Find an appropriate version number from version control. It's much more convenient to be able to use your version control system's tagging mechanism to derive a version number than to have to duplicate that information all over the place. The default behavior is to write out a ``version.txt`` file which contains the VCS output, for systems where the appropriate VCS is not installed or there is no VCS metadata directory present. ``version.txt`` can (and probably should!) be packaged in release tarballs by way of the ``MANIFEST.in`` file. :param include_dev_version: By default, if there are any commits after the most recent tag (as reported by the VCS), that number will be included in the version number as a ``.post`` suffix. For example, if the most recent tag is ``1.0`` and there have been three commits after that tag, the version number will be ``1.0.post3``. This behavior can be disabled by setting this parameter to ``False``. :param root: The directory of the repository root. The default value is the current working directory, since when running ``setup.py``, this is often (but not always) the same as the current working directory. Standard substitutions are performed on this value. :param version_file: The name of the file where version information will be saved. Reading and writing version files can be disabled altogether by setting this parameter to ``None``. Standard substitutions are performed on this value. :param version_module_paths: A list of python modules which will be automatically generated containing ``__version__`` and ``__sha__`` attributes. For example, with ``package/_version.py`` as a version module path, ``package/__init__.py`` could do ``from package._version import __version__, __sha__``. :param git_args: **Deprecated.** Please use *vcs_args* instead. :param vcs_args: The command to run to get a version. By default, this is automatically guessed from directories present in the repository root. Specify this as a list of string arguments including the program to run, e.g. ``['git', 'describe']``. Standard substitutions are performed on each value in the provided list. :param decrement_dev_version: If ``True``, subtract one from the number of commits after the most recent tag. This is primarily for hg, as hg requires a commit to make a tag. If the VCS used is hg (i.e. the revision starts with ``'hg'``) and *decrement_dev_version* is not specified as ``False``, *decrement_dev_version* will be set to ``True``. :param strip_prefix: A string which will be stripped from the start of version number tags. By default this is ``'v'``, but could be ``'debian/'`` for compatibility with ``git-dch``. :param Popen: Defaults to ``subprocess.Popen``. This is for testing. :param open: Defaults to ``open``. This is for testing. *root*, *version_file*, and *git_args* each support some substitutions: ``%(root)s`` The value provided for *root*. This is not available for the *root* parameter itself. ``%(pwd)s`` The current working directory. ``/`` will automatically be translated into the correct path separator for the current platform, such as ``:`` or ``\``. ``vcversioner`` will perform automatic VCS detection with the following directories, in order, and run the specified commands. ``%(root)s/.git`` ``git --git-dir %(root)s/.git describe --tags --long``. ``--git-dir`` is used to prevent contamination from git repositories which aren't the git repository of your project. ``%(root)s/.hg`` ``hg log -R %(root)s -r . --template '{latesttag}-{latesttagdistance}-hg{node|short}'``. ``-R`` is similarly used to prevent contamination. """ substitutions = {'pwd': os.getcwd()} substitutions['root'] = root % substitutions def substitute(val): return _fix_path(val % substitutions) if version_file is not None: version_file = substitute(version_file) if git_args is not None: warnings.warn( 'passing `git_args is deprecated; please use vcs_args', DeprecationWarning) vcs_args = git_args if vcs_args is None: for path, args in _vcs_args_by_path: if os.path.exists(substitute(path)): vcs_args = args break raw_version = None vcs_output = [] if vcs_args is not None: vcs_args = [substitute(arg) for arg in vcs_args] # try to pull the version from some VCS, or (perhaps) fall back on a # previously-saved version. try: proc = Popen(vcs_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError: pass else: stdout, stderr = proc.communicate() raw_version = stdout.strip().decode() vcs_output = stderr.decode().splitlines() version_source = 'VCS' failure = '%r failed' % (vcs_args,) else: failure = 'no VCS could be detected in %(root)r' % substitutions def show_vcs_output(): if not vcs_output: return print('-- VCS output follows --') for line in vcs_output: print(line) # VCS failed if the string is empty if not raw_version: if version_file is None: print('%s.' % (failure,)) show_vcs_output() raise SystemExit(2) elif not os.path.exists(version_file): print("%s and %r isn't present." % (failure, version_file)) print("are you installing from a github tarball?") show_vcs_output() raise SystemExit(2) with open(version_file, 'rb') as infile: raw_version = infile.read().decode() version_source = repr(version_file) # try to parse the version into something usable. try: tag_version, commits, sha = raw_version.rsplit('-', 2) except ValueError: print("%r (from %s) couldn't be parsed into a version." % ( raw_version, version_source)) show_vcs_output() raise SystemExit(2) # remove leading prefix if tag_version.startswith(strip_prefix): tag_version = tag_version[len(strip_prefix):] if version_file is not None: with open(version_file, 'w') as outfile: outfile.write(raw_version) if sha.startswith('hg') and decrement_dev_version is None: decrement_dev_version = True if decrement_dev_version: commits = str(int(commits) - 1) if commits == '0' or not include_dev_version: version = tag_version else: version = '%s.post%s' % (tag_version, commits) for path in version_module_paths: with open(path, 'w') as outfile: outfile.write(""" # This file is automatically generated by setup.py. __version__ = {0} __sha__ = {1} __revision__ = {1} """.format(repr(version).lstrip('u'), repr(sha).lstrip('u'))) return Version(version, commits, sha) def setup(dist, attr, value): """A hook for simplifying ``vcversioner`` use from distutils. This hook, when installed properly, allows vcversioner to automatically run when specifying a ``vcversioner`` argument to ``setup``. For example:: from setuptools import setup setup( setup_requires=['vcversioner'], vcversioner={}, ) The parameter to the ``vcversioner`` argument is a dict of keyword arguments which :func:`find_version` will be called with. """ dist.metadata.version = find_version(**value).version