Last active
April 5, 2017 22:39
-
-
Save mikofski/e923750b415e4e4961b65a8eb42999e8 to your computer and use it in GitHub Desktop.
gets sorted list of latest git tags - part of dulwich.contrib see PR #462 and #489
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Determine last version string from tags. | |
Alternate to `Versioneer <https://pypi.python.org/pypi/versioneer/>`_ using | |
`Dulwich <https://pypi.python.org/pypi/dulwich>`_ to sort tags by time from | |
newest to oldest. | |
Copy the following into the package ``__init__.py`` module:: | |
from dulwich.contrib.release_robot import get_current_version | |
__version__ = get_current_version('..') | |
This example assumes the tags have a leading "v" like "v0.3", and that the | |
``.git`` folder is in a project folder that containts the package folder. | |
EG:: | |
* project | |
| | |
* .git | |
| | |
+-* package | |
| | |
* __init__.py <-- put __version__ here | |
""" | |
import datetime | |
import re | |
import sys | |
import time | |
from dulwich.repo import Repo | |
# CONSTANTS | |
PROJDIR = '.' | |
PATTERN = r'[ a-zA-Z_\-]*([\d\.]+[\-\w\.]*)' | |
def get_recent_tags(projdir=PROJDIR): | |
"""Get list of tags in order from newest to oldest and their datetimes. | |
:param projdir: path to ``.git`` | |
:returns: list of tags sorted by commit time from newest to oldest | |
Each tag in the list contains the tag name, commit time, commit id, author | |
and any tag meta. If a tag isn't annotated, then its tag meta is ``None``. | |
Otherwise the tag meta is a tuple containing the tag time, tag id and tag | |
name. Time is in UTC. | |
""" | |
with Repo(projdir) as project: # 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.items(): | |
key = key.decode('utf-8') # compatible with Python-3 | |
obj = project.get_object(value) # dulwich object from SHA-1 | |
# don't just check if object is "tag" b/c it could be a "commit" | |
# instead check if "tags" is in the ref-name | |
if u'tags' not in key: | |
# skip ref if not a tag | |
continue | |
# strip the leading text from refs to get "tag name" | |
_, tag = key.rsplit(u'/', 1) | |
# check if tag object is "commit" or "tag" pointing to a "commit" | |
try: | |
commit = obj.object # a tuple (commit class, commit id) | |
except AttributeError: | |
commit = obj | |
tag_meta = None | |
else: | |
tag_meta = ( | |
datetime.datetime(*time.gmtime(obj.tag_time)[:6]), | |
obj.id.decode('utf-8'), | |
obj.name.decode('utf-8') | |
) # compatible with Python-3 | |
commit = project.get_object(commit[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.decode('utf-8'), | |
commit.author.decode('utf-8'), | |
tag_meta | |
] # compatible with Python-3 | |
# return list of tags sorted by their datetimes from newest to oldest | |
return sorted(tags.items(), key=lambda tag: tag[1][0], reverse=True) | |
def get_current_version(projdir=PROJDIR, pattern=PATTERN, 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 projdir: path to ``.git`` | |
:param pattern: regular expression pattern with group that matches version | |
: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 | |
matches = re.match(pattern, tag) | |
try: | |
current_version = matches.group(1) | |
except (IndexError, AttributeError) as err: | |
if logger: | |
logger.exception(err) | |
return tag | |
return current_version | |
if __name__ == '__main__': | |
if len(sys.argv) > 1: | |
_PROJDIR = sys.argv[1] | |
else: | |
_PROJDIR = PROJDIR | |
print(get_current_version(projdir=_PROJDIR)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# release_robot.py | |
# | |
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU | |
# General Public License as public by the Free Software Foundation; version 2.0 | |
# or (at your option) any later version. You can redistribute it and/or | |
# modify it under the terms of either of these two licenses. | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
# | |
# You should have received a copy of the licenses; if not, see | |
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License | |
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache | |
# License, Version 2.0. | |
# | |
"""Tests for release_robot.""" | |
import datetime | |
import os | |
import re | |
import shutil | |
import tempfile | |
import time | |
import unittest | |
from dulwich.contrib import release_robot | |
from dulwich.repo import Repo | |
from dulwich.tests.utils import make_commit, make_tag | |
BASEDIR = os.path.abspath(os.path.dirname(__file__)) # this directory | |
def gmtime_to_datetime(gmt): | |
return datetime.datetime(*time.gmtime(gmt)[:6]) | |
class TagPatternTests(unittest.TestCase): | |
"""test tag patterns""" | |
def test_tag_pattern(self): | |
"""test tag patterns""" | |
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 testcase, version in test_cases.items(): | |
matches = re.match(release_robot.PATTERN, testcase) | |
self.assertEqual(matches.group(1), version) | |
class GetRecentTagsTest(unittest.TestCase): | |
"""test get recent tags""" | |
# Git repo for dulwich project | |
test_repo = os.path.join(BASEDIR, 'dulwich_test_repo.zip') | |
committer = b"Mark Mikofski <[email protected]>" | |
test_tags = [b'v0.1a', b'v0.1'] | |
tag_test_data = { | |
test_tags[0]: [1484788003, b'0' * 40, None], | |
test_tags[1]: [1484788314, b'1' * 40, (1484788401, b'2' * 40)] | |
} | |
@classmethod | |
def setUpClass(cls): | |
cls.projdir = tempfile.mkdtemp() # temporary project directory | |
cls.repo = Repo.init(cls.projdir) # test repo | |
obj_store = cls.repo.object_store # test repo object store | |
# commit 1 ('2017-01-19T01:06:43') | |
cls.c1 = make_commit( | |
id=cls.tag_test_data[cls.test_tags[0]][1], | |
commit_time=cls.tag_test_data[cls.test_tags[0]][0], | |
message=b'unannotated tag', | |
author=cls.committer | |
) | |
obj_store.add_object(cls.c1) | |
# tag 1: unannotated | |
cls.t1 = cls.test_tags[0] | |
cls.repo[b'refs/tags/' + cls.t1] = cls.c1.id # add unannotated tag | |
# commit 2 ('2017-01-19T01:11:54') | |
cls.c2 = make_commit( | |
id=cls.tag_test_data[cls.test_tags[1]][1], | |
commit_time=cls.tag_test_data[cls.test_tags[1]][0], | |
message=b'annotated tag', | |
parents=[cls.c1.id], | |
author=cls.committer | |
) | |
obj_store.add_object(cls.c2) | |
# tag 2: annotated ('2017-01-19T01:13:21') | |
cls.t2 = make_tag( | |
cls.c2, | |
id=cls.tag_test_data[cls.test_tags[1]][2][1], | |
name=cls.test_tags[1], | |
tag_time=cls.tag_test_data[cls.test_tags[1]][2][0] | |
) | |
obj_store.add_object(cls.t2) | |
cls.repo[b'refs/heads/master'] = cls.c2.id | |
cls.repo[b'refs/tags/' + cls.t2.name] = cls.t2.id # add annotated tag | |
@classmethod | |
def tearDownClass(cls): | |
cls.repo.close() | |
shutil.rmtree(cls.projdir) | |
def test_get_recent_tags(self): | |
"""test get recent tags""" | |
tags = release_robot.get_recent_tags(self.projdir) # get test tags | |
for tag, metadata in tags: | |
tag = tag.encode('utf-8') | |
test_data = self.tag_test_data[tag] # test data tag | |
# test commit date, id and author name | |
self.assertEqual(metadata[0], gmtime_to_datetime(test_data[0])) | |
self.assertEqual(metadata[1].encode('utf-8'), test_data[1]) | |
self.assertEqual(metadata[2].encode('utf-8'), self.committer) | |
# skip unannotated tags | |
tag_obj = test_data[2] | |
if not tag_obj: | |
continue | |
# tag date, id and name | |
self.assertEqual(metadata[3][0], gmtime_to_datetime(tag_obj[0])) | |
self.assertEqual(metadata[3][1].encode('utf-8'), tag_obj[1]) | |
self.assertEqual(metadata[3][2].encode('utf-8'), tag) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Example package dunder init module implementing | |
``dulwich.contrib.release_robot`` to get current version. | |
""" | |
import os | |
import importlib | |
# try to import Dulwich or create dummies | |
try: | |
from dulwich.contrib.release_robot import get_current_version | |
from dulwich.repo import NotGitRepository | |
except ImportError: | |
NotGitRepository = NotImplementedError | |
def get_current_version(): | |
raise NotGitRepository | |
BASEDIR = os.path.dirname(__file__) # this directory | |
PROJDIR = os.path.dirname(BASEDIR) | |
VER_FILE = 'version' # name of file to store version | |
# use release robot to try to get current Git tag | |
try: | |
GIT_TAG = get_current_version(PROJDIR) | |
except NotGitRepository: | |
GIT_TAG = None | |
# check version file | |
try: | |
version = importlib.import_module('%s.%s' % (__name__, VER_FILE)) | |
except ImportError: | |
VERSION = None | |
else: | |
VERSION = version.VERSION | |
# update version file if it differs from Git tag | |
if GIT_TAG is not None and VERSION != GIT_TAG: | |
with open(os.path.join(BASEDIR, VER_FILE + '.py'), 'w') as vf: | |
vf.write('VERSION = "%s"\n' % GIT_TAG) | |
else: | |
GIT_TAG = VERSION # if Git tag is none use version file | |
VERSION = GIT_TAG # version | |
__author__ = u'your name' | |
__email__ = u'[email protected]' | |
__url__ = u'https://github.com/your-org/your-project' | |
__version__ = VERSION | |
__release__ = u'your release name' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment