diff --git a/.gitignore b/.gitignore index 2c22d75..eff40d7 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ ENV/ # CPython checkout for cherry_picker. cherry_picker/cpython + +# pytest +.pytest_cache/ diff --git a/cherry_picker/cherry_picker/cherry_picker.py b/cherry_picker/cherry_picker/cherry_picker.py index ff046a4..a107ee1 100755 --- a/cherry_picker/cherry_picker/cherry_picker.py +++ b/cherry_picker/cherry_picker/cherry_picker.py @@ -7,6 +7,7 @@ import pathlib import subprocess import webbrowser +import re import sys import requests import toml @@ -72,10 +73,16 @@ def upstream(self): @property def sorted_branches(self): + def version_from_branch(branch): + try: + return tuple(map(int, re.match(r'^.*(?P\d+(\.\d+)+).*$', branch).groupdict()['version'].split('.'))) + except AttributeError as attr_err: + raise ValueError(f'Branch {branch} seems to not have a version in its name.') from attr_err + return sorted( self.branches, reverse=True, - key=lambda v: tuple(map(int, v.split('.')))) + key=version_from_branch) @property def username(self): diff --git a/cherry_picker/cherry_picker/test.py b/cherry_picker/cherry_picker/test.py index 7ce83be..9be3498 100644 --- a/cherry_picker/cherry_picker/test.py +++ b/cherry_picker/cherry_picker/test.py @@ -64,13 +64,29 @@ def test_get_author_info_from_short_sha(subprocess_check_output): assert get_author_info_from_short_sha('22a594a') == 'Armin Rigo ' +@pytest.mark.parametrize('input_branches,sorted_branches', [ + (['3.1', '2.7', '3.10', '3.6'], ['3.10', '3.6', '3.1', '2.7']), + (['stable-3.1', 'lts-2.7', '3.10-other', 'smth3.6else'], ['3.10-other', 'smth3.6else', 'stable-3.1', 'lts-2.7']), +]) @mock.patch('os.path.exists') -def test_sorted_branch(os_path_exists, config): +def test_sorted_branch(os_path_exists, config, input_branches, sorted_branches): os_path_exists.return_value = True - branches = ["3.1", "2.7", "3.10", "3.6"] cp = CherryPicker('origin', '22a594a0047d7706537ff2ac676cdc0f1dcb329c', - branches, config=config) - assert cp.sorted_branches == ["3.10", "3.6", "3.1", "2.7"] + input_branches, config=config) + assert cp.sorted_branches == sorted_branches + + +@pytest.mark.parametrize('input_branches', [ + (['3.1', '2.7', '3.x10', '3.6', '']), + (['stable-3.1', 'lts-2.7', '3.10-other', 'smth3.6else', 'invalid']), +]) +@mock.patch('os.path.exists') +def test_invalid_branches(os_path_exists, config, input_branches): + os_path_exists.return_value = True + cp = CherryPicker('origin', '22a594a0047d7706537ff2ac676cdc0f1dcb329c', + input_branches, config=config) + with pytest.raises(ValueError): + cp.sorted_branches @mock.patch('os.path.exists') diff --git a/cherry_picker/readme.rst b/cherry_picker/readme.rst index e0d4a38..1e4f16f 100644 --- a/cherry_picker/readme.rst +++ b/cherry_picker/readme.rst @@ -11,8 +11,15 @@ Usage (from a cloned CPython directory) :: About ===== -Use this to backport CPython changes from ``master`` into one or more of the -maintenance branches (``3.6``, ``3.5``, ``2.7``). +This tool is used to backport CPython changes from ``master`` into one or more +of the maintenance branches (``3.6``, ``3.5``, ``2.7``). + +``cherry_picker`` can be configured to backport other projects with similar +workflow as CPython. See the configuration file options below for more details. + +The maintenance branch names should contain some sort of version number (X.Y). +For example: ``3.6``, ``3.5``, ``2.7``, ``stable-2.6``, ``2.5-lts``, are all +supported branch names. It will prefix the commit message with the branch, e.g. ``[3.6]``, and then opens up the pull request page. @@ -142,8 +149,12 @@ To customize the tool for used by other project: by ``pip install cherry_picker`` 5. Now everything is ready, use ``cherry_picker - `` for cherry-picking changes from ```` for cherry-picking changes from ```` into maintenance branches. + Branch name should contain at least major and minor version numbers + and may have some prefix or suffix. + Only the first version-like substring is matched when the version + is extracted from branch name. Demo ----