release_robot.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. Copy the following into the package ``__init__.py`` module::
  24. from dulwich.contrib.release_robot import get_current_version
  25. __version__ = get_current_version()
  26. This example assumes the tags have a leading "v" like "v0.3", and that the
  27. ``.git`` folder is in a project folder that containts the package folder.
  28. EG::
  29. * project
  30. |
  31. * .git
  32. |
  33. +-* package
  34. |
  35. * __init__.py <-- put __version__ here
  36. """
  37. import datetime
  38. import re
  39. import sys
  40. import time
  41. from dulwich.repo import Repo
  42. # CONSTANTS
  43. PROJDIR = '.'
  44. PATTERN = r'[ a-zA-Z_\-]*([\d\.]+[\-\w\.]*)'
  45. def get_recent_tags(projdir=PROJDIR):
  46. """Get list of tags in order from newest to oldest and their datetimes.
  47. :param projdir: path to ``.git``
  48. :returns: list of tags sorted by commit time from newest to oldest
  49. Each tag in the list contains the tag name, commit time, commit id, author
  50. and any tag meta. If a tag isn't annotated, then its tag meta is ``None``.
  51. Otherwise the tag meta is a tuple containing the tag time, tag id and tag
  52. name. Time is in UTC.
  53. """
  54. with Repo(projdir) as project: # dulwich repository object
  55. refs = project.get_refs() # dictionary of refs and their SHA-1 values
  56. tags = {} # empty dictionary to hold tags, commits and datetimes
  57. # iterate over refs in repository
  58. for key, value in refs.items():
  59. key = key.decode('utf-8') # compatible with Python-3
  60. obj = project.get_object(value) # dulwich object from SHA-1
  61. # don't just check if object is "tag" b/c it could be a "commit"
  62. # instead check if "tags" is in the ref-name
  63. if u'tags' not in key:
  64. # skip ref if not a tag
  65. continue
  66. # strip the leading text from refs to get "tag name"
  67. _, tag = key.rsplit(u'/', 1)
  68. # check if tag object is "commit" or "tag" pointing to a "commit"
  69. try:
  70. commit = obj.object # a tuple (commit class, commit id)
  71. except AttributeError:
  72. commit = obj
  73. tag_meta = None
  74. else:
  75. tag_meta = (
  76. datetime.datetime(*time.gmtime(obj.tag_time)[:6]),
  77. obj.id.decode('utf-8'),
  78. obj.name.decode('utf-8')
  79. ) # compatible with Python-3
  80. commit = project.get_object(commit[1]) # commit object
  81. # get tag commit datetime, but dulwich returns seconds since
  82. # beginning of epoch, so use Python time module to convert it to
  83. # timetuple then convert to datetime
  84. tags[tag] = [
  85. datetime.datetime(*time.gmtime(commit.commit_time)[:6]),
  86. commit.id.decode('utf-8'),
  87. commit.author.decode('utf-8'),
  88. tag_meta
  89. ] # compatible with Python-3
  90. # return list of tags sorted by their datetimes from newest to oldest
  91. return sorted(tags.items(), key=lambda tag: tag[1][0], reverse=True)
  92. def get_current_version(projdir=PROJDIR, pattern=PATTERN, logger=None):
  93. """Return the most recent tag, using an options regular expression pattern.
  94. The default pattern will strip any characters preceding the first semantic
  95. version. *EG*: "Release-0.2.1-rc.1" will be come "0.2.1-rc.1". If no match
  96. is found, then the most recent tag is return without modification.
  97. :param projdir: path to ``.git``
  98. :param pattern: regular expression pattern with group that matches version
  99. :param logger: a Python logging instance to capture exception
  100. :returns: tag matching first group in regular expression pattern
  101. """
  102. tags = get_recent_tags(projdir)
  103. try:
  104. tag = tags[0][0]
  105. except IndexError:
  106. return
  107. matches = re.match(pattern, tag)
  108. try:
  109. current_version = matches.group(1)
  110. except (IndexError, AttributeError) as err:
  111. if logger:
  112. logger.exception(err)
  113. return tag
  114. return current_version
  115. if __name__ == '__main__':
  116. if len(sys.argv) > 1:
  117. _PROJDIR = sys.argv[1]
  118. else:
  119. _PROJDIR = PROJDIR
  120. print(get_current_version(projdir=_PROJDIR))