release_robot.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """Determine last version string from tags.
  2. Alternate to `Versioneer <https://pypi.python.org/pypi/versioneer/>`_ using
  3. `Dulwich <https://pypi.python.org/pypi/dulwich>`_ to sort tags by time from
  4. newest to oldest.
  5. Import this module into the package ``__init__.py`` and then set ``__version__``
  6. as follows::
  7. from dulwich.contrib.release_robot import get_current_version
  8. __version__ = get_current_version()
  9. # other dunder classes like __author__, etc.
  10. This example assumes the tags have a leading "v" like "v0.3", and that the
  11. ``.git`` folder is in the project folder that containts the package folder.
  12. """
  13. from dulwich.repo import Repo
  14. import time
  15. import datetime
  16. import os
  17. import re
  18. import sys
  19. # CONSTANTS
  20. DIRNAME = os.path.abspath(os.path.dirname(__file__))
  21. PROJDIR = os.path.dirname(DIRNAME)
  22. PATTERN = '[ a-zA-Z_\-]*([\d\.]+[\-\w\.]*)'
  23. def get_recent_tags(projdir=PROJDIR):
  24. """Get list of tags in order from newest to oldest and their datetimes.
  25. :param projdir: path to ``.git``
  26. :returns: list of (tag, [datetime, commit, author]) sorted from new to old
  27. """
  28. project = Repo(projdir) # dulwich repository object
  29. refs = project.get_refs() # dictionary of refs and their SHA-1 values
  30. tags = {} # empty dictionary to hold tags, commits and datetimes
  31. # iterate over refs in repository
  32. for key, value in refs.iteritems():
  33. obj = project.get_object(value) # dulwich object from SHA-1
  34. # check if object is tag
  35. if obj.type_name != 'tag':
  36. # skip ref if not a tag
  37. continue
  38. # strip the leading text from "refs/tag/<tag name>" to get "tag name"
  39. _, tag = key.rsplit('/', 1)
  40. # check if tag object is commit, altho it should always be true
  41. if obj.object[0].type_name == 'commit':
  42. commit = project.get_object(obj.object[1]) # commit object
  43. # get tag commit datetime, but dulwich returns seconds since
  44. # beginning of epoch, so use Python time module to convert it to
  45. # timetuple then convert to datetime
  46. tags[tag] = [
  47. datetime.datetime(*time.gmtime(commit.commit_time)[:6]),
  48. commit.id,
  49. commit.author
  50. ]
  51. # return list of tags sorted by their datetimes from newest to oldest
  52. return sorted(tags.iteritems(), key=lambda tag: tag[1][0], reverse=True)
  53. def get_current_version(pattern=PATTERN, projdir=PROJDIR, logger=None):
  54. """Return the most recent tag, using an options regular expression pattern.
  55. The default pattern will strip any characters preceding the first semantic
  56. version. *EG*: "Release-0.2.1-rc.1" will be come "0.2.1-rc.1". If no match
  57. is found, then the most recent tag is return without modification.
  58. :param pattern: regular expression pattern with group that matches version
  59. :param projdir: path to ``.git``
  60. :param logger: a Python logging instance to capture exception
  61. :returns: tag matching first group in regular expression pattern
  62. """
  63. tags = get_recent_tags(projdir)
  64. try:
  65. tag = tags[0][0]
  66. except IndexError:
  67. return
  68. m = re.match(pattern, tag)
  69. try:
  70. current_version = m.group(1)
  71. except (IndexError, AttributeError) as err:
  72. if logger:
  73. logger.exception(err)
  74. return tag
  75. return current_version
  76. def test_tag_pattern():
  77. test_cases = {
  78. '0.3': '0.3', 'v0.3': '0.3', 'release0.3': '0.3', 'Release-0.3': '0.3',
  79. 'v0.3rc1': '0.3rc1', 'v0.3-rc1': '0.3-rc1', 'v0.3-rc.1': '0.3-rc.1',
  80. 'version 0.3': '0.3', 'version_0.3_rc_1': '0.3_rc_1', 'v1': '1',
  81. '0.3rc1': '0.3rc1'
  82. }
  83. for tc, version in test_cases.iteritems():
  84. m = re.match(PATTERN, tc)
  85. assert m.group(1) == version
  86. if __name__ == '__main__':
  87. if len(sys.argv) > 1:
  88. projdir = sys.argv[1]
  89. else:
  90. projdir = PROJDIR
  91. print get_current_version(projdir=projdir)