فهرست منبع

fix release robot issue for commit only tag

- if tag is not annotated, then it will be a commit object, instead of
creating a tag object
- don't check if ref object is tag, instead check if "tags" is in the
ref name
- change docstring to show how release_robot could be used as alternate
to versioneer, ie: copy and paste gist

https://gist.github.com/mikofski/e923750b415e4e4961b65a8eb42999e8#file-xyz__init__-py
- don't use contrib folder as default projdir, instead default to '.'
- return tag meta data if there is any from get_recent_tags() as tuple
(tag time, tag id, tag name) otherwise it's (None, None, None)
- remove test_tag_pattern from release_robot since it's in
test_release_robot()
- add tests

Signed-off-by: Mark Mikofski <mark.mikofski@sunpowercorp.com>
Mark Mikofski 8 سال پیش
والد
کامیت
e8c79d3096
2فایلهای تغییر یافته به همراه186 افزوده شده و 57 حذف شده
  1. 90 49
      dulwich/contrib/release_robot.py
  2. 96 8
      dulwich/contrib/test_release_robot.py

+ 90 - 49
dulwich/contrib/release_robot.py

@@ -23,74 +23,127 @@ 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.
 
-Import this module into the package ``__init__.py`` and then set ``__version__``
-as follows::
+Copy the following into the package ``__init__.py`` module::
 
     from dulwich.contrib.release_robot import get_current_version
+    from dulwich.repo import NotGitRepository
+    import os
+    import importlib
 
-    __version__ = get_current_version()
-    # other dunder classes like __author__, etc.
+    BASEDIR = os.path.dirname(__file__)  # this directory
+    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(os.path.dirname(BASEDIR))
+    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
+
+    __version__ = VERSION
+    # other dunder constants like __author__, __email__, __url__, 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.
+``.git`` folder is in a project folder that containts the package folder.
+
+EG::
+
+    * project
+    |
+    * .git
+    |
+    +-* package
+      |
+      * __init__.py  <-- put __version__ here
+
+
 """
 
-from dulwich.repo import Repo
-import time
 import datetime
-import os
 import re
 import sys
+import time
+
+from dulwich.repo import Repo
 
 # CONSTANTS
-DIRNAME = os.path.abspath(os.path.dirname(__file__))
-PROJDIR = os.path.dirname(DIRNAME)
-PATTERN = '[ a-zA-Z_\-]*([\d\.]+[\-\w\.]*)'
+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 (tag, [datetime, commit, author]) sorted from new to old
+    :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.
     """
-    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.items():
-        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/<tag name>" 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
+    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,
-                commit.author
-            ]
+                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(pattern=PATTERN, projdir=PROJDIR, logger=None):
+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 pattern: regular expression pattern with group that matches version
     :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
     """
@@ -99,9 +152,9 @@ def get_current_version(pattern=PATTERN, projdir=PROJDIR, logger=None):
         tag = tags[0][0]
     except IndexError:
         return
-    m = re.match(pattern, tag)
+    matches = re.match(pattern, tag)
     try:
-        current_version = m.group(1)
+        current_version = matches.group(1)
     except (IndexError, AttributeError) as err:
         if logger:
             logger.exception(err)
@@ -109,21 +162,9 @@ def get_current_version(pattern=PATTERN, projdir=PROJDIR, logger=None):
     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
-
-
 if __name__ == '__main__':
     if len(sys.argv) > 1:
-        projdir = sys.argv[1]
+        _PROJDIR = sys.argv[1]
     else:
-        projdir = PROJDIR
-    print(get_current_version(projdir=projdir))
+        _PROJDIR = PROJDIR
+    print(get_current_version(projdir=_PROJDIR))

+ 96 - 8
dulwich/contrib/test_release_robot.py

@@ -19,21 +19,109 @@
 
 """Tests for release_robot."""
 
+import datetime
+import os
 import re
+import shutil
+import tempfile
+import time
 import unittest
 
-from dulwich.contrib.release_robot import PATTERN
+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'
+            '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.items():
-            m = re.match(PATTERN, tc)
-            self.assertEqual(m.group(1), version)
+        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 <mark.mikofski@sunpowercorp.com>"
+    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)