Skip to content

Commit 940787a

Browse files
Jackenmenezio-melottiMariatta
authored
Allow passing a base branch that doesn't have version info (#70)
Co-authored-by: Ezio Melotti <[email protected]> Co-authored-by: jack1142 <[email protected]> Co-authored-by: Mariatta Wijaya <[email protected]>
1 parent a1552fb commit 940787a

File tree

3 files changed

+100
-41
lines changed

3 files changed

+100
-41
lines changed

README.md

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -137,33 +137,38 @@ repo = "aiohttp"
137137
check_sha = "f382b5ffc445e45a110734f5396728da7914aeb6"
138138
fix_commit_msg = false
139139
default_branch = "devel"
140+
require_version_in_branch_name = false
140141
```
141142

142143
Available config options:
143144

144145
```
145-
team github organization or individual nick,
146-
e.g "aio-libs" for https://github.com/aio-libs/aiohttp
147-
("python" by default)
148-
149-
repo github project name,
150-
e.g "aiohttp" for https://github.com/aio-libs/aiohttp
151-
("cpython" by default)
152-
153-
check_sha A long hash for any commit from the repo,
154-
e.g. a sha1 hash from the very first initial commit
155-
("7f777ed95a19224294949e1b4ce56bbffcb1fe9f" by default)
156-
157-
fix_commit_msg Replace # with GH- in cherry-picked commit message.
158-
It is the default behavior for CPython because of external
159-
Roundup bug tracker (https://bugs.python.org) behavior:
160-
#xxxx should point on issue xxxx but GH-xxxx points
161-
on pull-request xxxx.
162-
For projects using GitHub Issues, this option can be disabled.
163-
164-
default_branch Project's default branch name,
165-
e.g "devel" for https://github.com/ansible/ansible
166-
("main" by default)
146+
team github organization or individual nick,
147+
e.g "aio-libs" for https://github.com/aio-libs/aiohttp
148+
("python" by default)
149+
150+
repo github project name,
151+
e.g "aiohttp" for https://github.com/aio-libs/aiohttp
152+
("cpython" by default)
153+
154+
check_sha A long hash for any commit from the repo,
155+
e.g. a sha1 hash from the very first initial commit
156+
("7f777ed95a19224294949e1b4ce56bbffcb1fe9f" by default)
157+
158+
fix_commit_msg Replace # with GH- in cherry-picked commit message.
159+
It is the default behavior for CPython because of external
160+
Roundup bug tracker (https://bugs.python.org) behavior:
161+
#xxxx should point on issue xxxx but GH-xxxx points
162+
on pull-request xxxx.
163+
For projects using GitHub Issues, this option can be disabled.
164+
165+
default_branch Project's default branch name,
166+
e.g "devel" for https://github.com/ansible/ansible
167+
("main" by default)
168+
169+
require_version_in_branch_name Allow backporting to branches whose names don't contain
170+
something that resembles a version number
171+
(i.e. at least two dot-separated numbers).
167172
```
168173

169174
To customize the tool for used by other project:

cherry_picker/cherry_picker.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import collections
66
import enum
7+
import functools
78
import os
89
import re
910
import subprocess
@@ -31,6 +32,7 @@
3132
"check_sha": "7f777ed95a19224294949e1b4ce56bbffcb1fe9f",
3233
"fix_commit_msg": True,
3334
"default_branch": "main",
35+
"require_version_in_branch_name": True,
3436
}
3537
)
3638

@@ -191,7 +193,9 @@ def upstream(self):
191193
@property
192194
def sorted_branches(self):
193195
"""Return the branches to cherry-pick to, sorted by version."""
194-
return sorted(self.branches, reverse=True, key=version_from_branch)
196+
return sorted(
197+
self.branches, key=functools.partial(compute_version_sort_key, self.config)
198+
)
195199

196200
@property
197201
def username(self):
@@ -333,7 +337,7 @@ def get_updated_commit_message(self, cherry_pick_branch):
333337
updated_commit_message = self.get_commit_message(self.commit_sha1)
334338
if self.prefix_commit:
335339
updated_commit_message = remove_commit_prefix(updated_commit_message)
336-
base_branch = get_base_branch(cherry_pick_branch)
340+
base_branch = get_base_branch(cherry_pick_branch, config=self.config)
337341
updated_commit_message = f"[{base_branch}] {updated_commit_message}"
338342

339343
# Add '(cherry picked from commit ...)' to the message
@@ -600,7 +604,7 @@ def continue_cherry_pick(self):
600604
if cherry_pick_branch.startswith("backport-"):
601605
set_state(WORKFLOW_STATES.CONTINUATION_STARTED)
602606
# amend the commit message, prefix with [X.Y]
603-
base = get_base_branch(cherry_pick_branch)
607+
base = get_base_branch(cherry_pick_branch, config=self.config)
604608
short_sha = cherry_pick_branch[
605609
cherry_pick_branch.index("-") + 1 : cherry_pick_branch.index(base) - 1
606610
]
@@ -831,7 +835,7 @@ def cherry_pick_cli(
831835
sys.exit(-1)
832836

833837

834-
def get_base_branch(cherry_pick_branch):
838+
def get_base_branch(cherry_pick_branch, *, config):
835839
"""
836840
return '2.7' from 'backport-sha-2.7'
837841
@@ -855,7 +859,7 @@ def get_base_branch(cherry_pick_branch):
855859

856860
# Subject the parsed base_branch to the same tests as when we generated it
857861
# This throws a ValueError if the base_branch doesn't meet our requirements
858-
version_from_branch(base_branch)
862+
compute_version_sort_key(config, base_branch)
859863

860864
return base_branch
861865

@@ -876,14 +880,31 @@ def validate_sha(sha):
876880
)
877881

878882

879-
def version_from_branch(branch):
883+
def compute_version_sort_key(config, branch):
880884
"""
881-
return version information from a git branch name
885+
Get sort key based on version information from the given git branch name.
886+
887+
This function can be used as a sort key in list.sort()/sorted() provided that
888+
you additionally pass config as a first argument by e.g. wrapping it with
889+
functools.partial().
890+
891+
Branches with version information come first and are sorted from latest
892+
to oldest version.
893+
Branches without version information come second and are sorted alphabetically.
882894
"""
883895
m = re.search(r"\d+(?:\.\d+)+", branch)
884-
if not m:
896+
if m:
897+
raw_version = m[0].split(".")
898+
# Use 0 to sort version numbers *before* regular branch names
899+
return (0, *(-int(x) for x in raw_version))
900+
901+
if not branch:
902+
raise ValueError("Branch name is an empty string.")
903+
if config["require_version_in_branch_name"]:
885904
raise ValueError(f"Branch {branch} seems to not have a version in its name.")
886-
return tuple(map(int, m[0].split(".")))
905+
906+
# Use 1 to sort regular branch names *after* version numbers
907+
return (1, branch)
887908

888909

889910
def get_current_branch():

cherry_picker/test_cherry_picker.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,20 +149,20 @@ def tmp_git_repo_dir(tmpdir, cd, git_init, git_commit, git_config):
149149

150150

151151
@mock.patch("subprocess.check_output")
152-
def test_get_base_branch(subprocess_check_output):
152+
def test_get_base_branch(subprocess_check_output, config):
153153
# The format of cherry-pick branches we create are::
154154
# backport-{SHA}-{base_branch}
155155
subprocess_check_output.return_value = b"22a594a0047d7706537ff2ac676cdc0f1dcb329c"
156156
cherry_pick_branch = "backport-22a594a-2.7"
157-
result = get_base_branch(cherry_pick_branch)
157+
result = get_base_branch(cherry_pick_branch, config=config)
158158
assert result == "2.7"
159159

160160

161161
@mock.patch("subprocess.check_output")
162-
def test_get_base_branch_which_has_dashes(subprocess_check_output):
162+
def test_get_base_branch_which_has_dashes(subprocess_check_output, config):
163163
subprocess_check_output.return_value = b"22a594a0047d7706537ff2ac676cdc0f1dcb329c"
164164
cherry_pick_branch = "backport-22a594a-baseprefix-2.7-basesuffix"
165-
result = get_base_branch(cherry_pick_branch)
165+
result = get_base_branch(cherry_pick_branch, config=config)
166166
assert result == "baseprefix-2.7-basesuffix"
167167

168168

@@ -171,14 +171,14 @@ def test_get_base_branch_which_has_dashes(subprocess_check_output):
171171
[
172172
"backport-22a594a", # Not enough fields
173173
"prefix-22a594a-2.7", # Not the prefix we were expecting
174-
"backport-22a594a-base", # No version info in the base branch
174+
"backport-22a594a-", # No base branch
175175
],
176176
)
177177
@mock.patch("subprocess.check_output")
178-
def test_get_base_branch_invalid(subprocess_check_output, cherry_pick_branch):
178+
def test_get_base_branch_invalid(subprocess_check_output, cherry_pick_branch, config):
179179
subprocess_check_output.return_value = b"22a594a0047d7706537ff2ac676cdc0f1dcb329c"
180180
with pytest.raises(ValueError):
181-
get_base_branch(cherry_pick_branch)
181+
get_base_branch(cherry_pick_branch, config=config)
182182

183183

184184
@mock.patch("subprocess.check_output")
@@ -206,18 +206,33 @@ def test_get_author_info_from_short_sha(subprocess_check_output):
206206

207207

208208
@pytest.mark.parametrize(
209-
"input_branches,sorted_branches",
209+
"input_branches,sorted_branches,require_version",
210210
[
211-
(["3.1", "2.7", "3.10", "3.6"], ["3.10", "3.6", "3.1", "2.7"]),
211+
(["3.1", "2.7", "3.10", "3.6"], ["3.10", "3.6", "3.1", "2.7"], True),
212212
(
213213
["stable-3.1", "lts-2.7", "3.10-other", "smth3.6else"],
214214
["3.10-other", "smth3.6else", "stable-3.1", "lts-2.7"],
215+
True,
216+
),
217+
(["3.1", "2.7", "3.10", "3.6"], ["3.10", "3.6", "3.1", "2.7"], False),
218+
(
219+
["stable-3.1", "lts-2.7", "3.10-other", "smth3.6else"],
220+
["3.10-other", "smth3.6else", "stable-3.1", "lts-2.7"],
221+
False,
222+
),
223+
(
224+
["3.7", "3.10", "2.7", "foo", "stable", "branch"],
225+
["3.10", "3.7", "2.7", "branch", "foo", "stable"],
226+
False,
215227
),
216228
],
217229
)
218230
@mock.patch("os.path.exists")
219-
def test_sorted_branch(os_path_exists, config, input_branches, sorted_branches):
231+
def test_sorted_branch(
232+
os_path_exists, config, input_branches, sorted_branches, require_version
233+
):
220234
os_path_exists.return_value = True
235+
config["require_version_in_branch_name"] = require_version
221236
cp = CherryPicker(
222237
"origin",
223238
"22a594a0047d7706537ff2ac676cdc0f1dcb329c",
@@ -227,6 +242,21 @@ def test_sorted_branch(os_path_exists, config, input_branches, sorted_branches):
227242
assert cp.sorted_branches == sorted_branches
228243

229244

245+
@mock.patch("os.path.exists")
246+
def test_invalid_branch_empty_string(os_path_exists, config):
247+
os_path_exists.return_value = True
248+
# already tested for require_version_in_branch_name=True below
249+
config["require_version_in_branch_name"] = False
250+
cp = CherryPicker(
251+
"origin",
252+
"22a594a0047d7706537ff2ac676cdc0f1dcb329c",
253+
["3.1", "2.7", "3.10", "3.6", ""],
254+
config=config,
255+
)
256+
with pytest.raises(ValueError, match=r"^Branch name is an empty string\.$"):
257+
cp.sorted_branches
258+
259+
230260
@pytest.mark.parametrize(
231261
"input_branches",
232262
[
@@ -460,6 +490,7 @@ def test_load_full_config(tmp_git_repo_dir, git_add, git_commit):
460490
"team": "python",
461491
"fix_commit_msg": True,
462492
"default_branch": "devel",
493+
"require_version_in_branch_name": True,
463494
},
464495
)
465496

@@ -483,6 +514,7 @@ def test_load_partial_config(tmp_git_repo_dir, git_add, git_commit):
483514
"team": "python",
484515
"fix_commit_msg": True,
485516
"default_branch": "main",
517+
"require_version_in_branch_name": True,
486518
},
487519
)
488520

@@ -511,6 +543,7 @@ def test_load_config_no_head_sha(tmp_git_repo_dir, git_add, git_commit):
511543
"team": "python",
512544
"fix_commit_msg": True,
513545
"default_branch": "devel",
546+
"require_version_in_branch_name": True,
514547
},
515548
)
516549

0 commit comments

Comments
 (0)