diff --git a/dulwich/contrib/release_robot.py b/dulwich/contrib/release_robot.py new file mode 100644 index 00000000..eb00aa40 --- /dev/null +++ b/dulwich/contrib/release_robot.py @@ -0,0 +1,100 @@ +""" +Alternate to `Versioneer `_ using +`Dulwich `_ to sort tags by time from +newest to oldest. + +Import this module into the package ``__init__.py`` and then set ``__version__`` +as follows:: + + from dulwich.contrib.release_robot import get_current_version + + __version__ = get_current_version() + # other dunder classes like __author__, etc. + +This example assumes the tags have a leading "v" like "v0.3", and that the +``.git`` folder is in the project folder that containts the package folder. +""" + +from dulwich.repo import Repo +import time +import datetime +import os +import re + +# CONSTANTS +DIRNAME = os.path.abspath(os.path.dirname(__file__)) +PROJDIR = os.path.dirname(DIRNAME) +PATTERN = '[ a-zA-Z_\-]*([\d\.]+[\-\w\.]*)' + +def get_recent_tags(projdir=PROJDIR): + """ + Get list of recent tags in order from newest to oldest and their datetimes. + + :param projdir: path to ``.git`` + :returns: list of (tag, [datetime, commit, author]) sorted from new to old + """ + project = Repo(projdir) # dulwich repository object + refs = project.get_refs() # dictionary of refs and their SHA-1 values + tags = {} # empty dictionary to hold tags, commits and datetimes + # iterate over refs in repository + for key, value in refs.iteritems(): + obj = project.get_object(value) # dulwich object from SHA-1 + # check if object is tag + if obj.type_name != 'tag': + # skip ref if not a tag + continue + # strip the leading text from "refs/tag/" to get "tag name" + _, tag = key.rsplit('/', 1) + # check if tag object is commit, altho it should always be true + if obj.object[0].type_name == 'commit': + commit = project.get_object(obj.object[1]) # commit object + # get tag commit datetime, but dulwich returns seconds since + # beginning of epoch, so use Python time module to convert it to + # timetuple then convert to datetime + tags[tag] = [ + datetime.datetime(*time.gmtime(commit.commit_time)[:6]), + commit.id, + commit.author + ] + + # return list of tags sorted by their datetimes from newest to oldest + return sorted(tags.iteritems(), key=lambda tag: tag[1][0], reverse=True) + + +def get_current_version(pattern=PATTERN, projdir=PROJDIR, logger=None): + """ + Return the most recent tag, using an options regular expression pattern. The + default pattern will strip any characters preceding the first semantic + version. *EG*: "Release-0.2.1-rc.1" will be come "0.2.1-rc.1". If no match + is found, then the most recent tag is return without modification. + + :param pattern: regular expression pattern with group that matches version + :param projdir: path to ``.git`` + :param logger: a Python logging instance to capture exception + :returns: tag matching first group in regular expression pattern + """ + tags = get_recent_tags(projdir) + try: + tag = tags[0][0] + except IndexError: + return + m = re.match(pattern, tag) + try: + current_version = m.group(1) + except (IndexError, AttributeError) as err: + if logger: + logger.exception(err) + return tag + return current_version + + +def test_tag_pattern(): + test_cases = { + '0.3': '0.3', 'v0.3': '0.3', 'release0.3': '0.3', 'Release-0.3': '0.3', + 'v0.3rc1': '0.3rc1', 'v0.3-rc1': '0.3-rc1', 'v0.3-rc.1': '0.3-rc.1', + 'version 0.3': '0.3', 'version_0.3_rc_1': '0.3_rc_1', 'v1': '1', + '0.3rc1': '0.3rc1' + } + for tc, version in test_cases.iteritems(): + m = re.match(PATTERN, tc) + assert m.group(1) == version