release_robot.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # release_robot.py
  2. #
  3. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  4. # General Public License as public by the Free Software Foundation; version 2.0
  5. # or (at your option) any later version. You can redistribute it and/or
  6. # modify it under the terms of either of these two licenses.
  7. #
  8. # Unless required by applicable law or agreed to in writing, software
  9. # distributed under the License is distributed on an "AS IS" BASIS,
  10. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. # See the License for the specific language governing permissions and
  12. # limitations under the License.
  13. #
  14. # You should have received a copy of the licenses; if not, see
  15. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  16. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  17. # License, Version 2.0.
  18. #
  19. """Determine last version string from tags.
  20. Alternate to `Versioneer <https://pypi.python.org/pypi/versioneer/>`_ using
  21. `Dulwich <https://pypi.python.org/pypi/dulwich>`_ to sort tags by time from
  22. newest to oldest.
  23. Import this module into the package ``__init__.py`` and then set ``__version__``
  24. as follows::
  25. from dulwich.contrib.release_robot import get_current_version
  26. __version__ = get_current_version()
  27. # other dunder classes like __author__, etc.
  28. This example assumes the tags have a leading "v" like "v0.3", and that the
  29. ``.git`` folder is in the project folder that containts the package folder.
  30. """
  31. from dulwich.repo import Repo
  32. import time
  33. import datetime
  34. import os
  35. import re
  36. import sys
  37. # CONSTANTS
  38. DIRNAME = os.path.abspath(os.path.dirname(__file__))
  39. PROJDIR = os.path.dirname(DIRNAME)
  40. PATTERN = '[ a-zA-Z_\-]*([\d\.]+[\-\w\.]*)'
  41. def get_recent_tags(projdir=PROJDIR):
  42. """Get list of tags in order from newest to oldest and their datetimes.
  43. :param projdir: path to ``.git``
  44. :returns: list of (tag, [datetime, commit, author]) sorted from new to old
  45. """
  46. project = Repo(projdir) # dulwich repository object
  47. refs = project.get_refs() # dictionary of refs and their SHA-1 values
  48. tags = {} # empty dictionary to hold tags, commits and datetimes
  49. # iterate over refs in repository
  50. for key, value in refs.items():
  51. obj = project.get_object(value) # dulwich object from SHA-1
  52. # check if object is tag
  53. if obj.type_name != 'tag':
  54. # skip ref if not a tag
  55. continue
  56. # strip the leading text from "refs/tag/<tag name>" to get "tag name"
  57. _, tag = key.rsplit('/', 1)
  58. # check if tag object is commit, altho it should always be true
  59. if obj.object[0].type_name == 'commit':
  60. commit = project.get_object(obj.object[1]) # commit object
  61. # get tag commit datetime, but dulwich returns seconds since
  62. # beginning of epoch, so use Python time module to convert it to
  63. # timetuple then convert to datetime
  64. tags[tag] = [
  65. datetime.datetime(*time.gmtime(commit.commit_time)[:6]),
  66. commit.id,
  67. commit.author
  68. ]
  69. # return list of tags sorted by their datetimes from newest to oldest
  70. return sorted(tags.items(), key=lambda tag: tag[1][0], reverse=True)
  71. def get_current_version(pattern=PATTERN, projdir=PROJDIR, logger=None):
  72. """Return the most recent tag, using an options regular expression pattern.
  73. The default pattern will strip any characters preceding the first semantic
  74. version. *EG*: "Release-0.2.1-rc.1" will be come "0.2.1-rc.1". If no match
  75. is found, then the most recent tag is return without modification.
  76. :param pattern: regular expression pattern with group that matches version
  77. :param projdir: path to ``.git``
  78. :param logger: a Python logging instance to capture exception
  79. :returns: tag matching first group in regular expression pattern
  80. """
  81. tags = get_recent_tags(projdir)
  82. try:
  83. tag = tags[0][0]
  84. except IndexError:
  85. return
  86. m = re.match(pattern, tag)
  87. try:
  88. current_version = m.group(1)
  89. except (IndexError, AttributeError) as err:
  90. if logger:
  91. logger.exception(err)
  92. return tag
  93. return current_version
  94. def test_tag_pattern():
  95. test_cases = {
  96. '0.3': '0.3', 'v0.3': '0.3', 'release0.3': '0.3', 'Release-0.3': '0.3',
  97. 'v0.3rc1': '0.3rc1', 'v0.3-rc1': '0.3-rc1', 'v0.3-rc.1': '0.3-rc.1',
  98. 'version 0.3': '0.3', 'version_0.3_rc_1': '0.3_rc_1', 'v1': '1',
  99. '0.3rc1': '0.3rc1'
  100. }
  101. for tc, version in test_cases.iteritems():
  102. m = re.match(PATTERN, tc)
  103. assert m.group(1) == version
  104. if __name__ == '__main__':
  105. if len(sys.argv) > 1:
  106. projdir = sys.argv[1]
  107. else:
  108. projdir = PROJDIR
  109. print(get_current_version(projdir=projdir))