Skip to content

Commit bca8c65

Browse files
committed
ciq-cherry-pick.py: Automatically cherry pick cve-bf commits
It now automatically cherry picks the Fixes: dependencies. To accomodate this, CIQ_find_mainline_fixes was moved to ciq_helpers. And an extra argument for upstream-ref was introduced, the default being origin/kernel-mainline, as the dependencies are looked up there. To simplify things and keep main cleaner, separate functions were used. If one of the commits (the original and its dependencies) cannot be applied, the return code will be 1. This is useful when ciq-cherry-pick.py is called from other script. This also removed redundant prints. Signed-off-by: Roxana Nicolescu <rnicolescu@ciq.com>
1 parent 4ad0f47 commit bca8c65

File tree

3 files changed

+162
-95
lines changed

3 files changed

+162
-95
lines changed

check_kernel_commits.py

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
from typing import Optional
1010

1111
from ciq_helpers import (
12-
CIQ_commit_exists_in_branch,
13-
CIQ_extract_fixes_references_from_commit_body_lines,
12+
CIQ_find_fixes_in_mainline,
1413
CIQ_get_commit_body,
1514
CIQ_hash_exists_in_ref,
1615
CIQ_run_git,
@@ -47,54 +46,6 @@ def hash_exists_in_mainline(repo, upstream_ref, hash_):
4746
return CIQ_hash_exists_in_ref(repo, upstream_ref, hash_)
4847

4948

50-
def find_fixes_in_mainline(repo, pr_branch, upstream_ref, hash_):
51-
"""
52-
Return unique commits in upstream_ref that have Fixes: <N chars of hash_> in their message, case-insensitive,
53-
if they have not been committed in the pr_branch.
54-
Start from 12 chars and work down to 6, but do not include duplicates if already found at a longer length.
55-
Returns a list of tuples: (full_hash, display_string)
56-
"""
57-
results = []
58-
59-
# Prepare hash prefixes from 12 down to 6
60-
hash_prefixes = [hash_[:index] for index in range(12, 5, -1)]
61-
62-
# Get all commits with 'Fixes:' in the message
63-
output = CIQ_run_git(
64-
repo,
65-
[
66-
"log",
67-
upstream_ref,
68-
"--grep",
69-
"Fixes:",
70-
"-i",
71-
"--format=%H %h %s (%an)%x0a%B%x00",
72-
],
73-
).strip()
74-
if not output:
75-
return []
76-
77-
# Each commit is separated by a NUL character and a newline
78-
commits = output.split("\x00\x0a")
79-
for commit in commits:
80-
if not commit.strip():
81-
continue
82-
83-
lines = commit.splitlines()
84-
# The first line is the summary, the rest is the body
85-
header = lines[0]
86-
full_hash, display_string = (lambda h: (h[0], " ".join(h[1:])))(header.split())
87-
fixes = CIQ_extract_fixes_references_from_commit_body_lines(lines=lines[1:])
88-
for fix in fixes:
89-
for prefix in hash_prefixes:
90-
if fix.lower().startswith(prefix.lower()):
91-
if not CIQ_commit_exists_in_branch(repo, pr_branch, full_hash):
92-
results.append((full_hash, display_string))
93-
break
94-
95-
return results
96-
97-
9849
def wrap_paragraph(text, width=80, initial_indent="", subsequent_indent=""):
9950
"""Wrap a paragraph of text to the specified width and indentation."""
10051
wrapper = textwrap.TextWrapper(
@@ -238,7 +189,7 @@ def main():
238189
)
239190
out_lines.append("") # blank line
240191
continue
241-
fixes = find_fixes_in_mainline(args.repo, args.pr_branch, upstream_ref, uhash)
192+
fixes = CIQ_find_fixes_in_mainline(args.repo, args.pr_branch, upstream_ref, uhash)
242193
if fixes:
243194
any_findings = True
244195

ciq-cherry-pick.py

Lines changed: 106 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import argparse
22
import logging
33
import os
4+
import re
45
import subprocess
56

67
import git
78

89
from ciq_helpers import (
910
CIQ_cherry_pick_commit_standardization,
1011
CIQ_commit_exists_in_current_branch,
12+
CIQ_find_fixes_in_mainline_current_branch,
1113
CIQ_fixes_references,
14+
CIQ_get_full_hash,
1215
CIQ_original_commit_author_to_tag_string,
16+
CIQ_run_git,
1317
)
1418

1519
MERGE_MSG = git.Repo(os.getcwd()).git_dir + "/MERGE_MSG"
20+
MERGE_MSG_BAK = f"{MERGE_MSG}.bak"
1621

1722

1823
def check_fixes(sha):
@@ -31,6 +36,100 @@ def check_fixes(sha):
3136
raise RuntimeError(f"The commit you want to cherry pick references a Fixes: {fix} but this is not here")
3237

3338

39+
def manage_commit_message(full_sha, ciq_tags, jira_ticket):
40+
"""
41+
It standardize the commit message by including the ciq_tags, original
42+
author and the original commit full sha.
43+
44+
Original message location: MERGE_MSG
45+
Makes a copy of the original message in MERGE_MSG_BAK
46+
47+
The new standardized commit message is written to MERGE_MSG
48+
"""
49+
50+
subprocess.run(["cp", MERGE_MSG, MERGE_MSG_BAK], check=True)
51+
52+
# Make sure it's a deep copy because ciq_tags may be used for other cherry-picks
53+
new_tags = [tag for tag in ciq_tags]
54+
55+
author = CIQ_original_commit_author_to_tag_string(repo_path=os.getcwd(), sha=full_sha)
56+
if author is None:
57+
raise RuntimeError(f"Could not find author of commit {full_sha}")
58+
59+
new_tags.append(author)
60+
with open(MERGE_MSG, "r") as file:
61+
original_msg = file.readlines()
62+
63+
new_msg = CIQ_cherry_pick_commit_standardization(original_msg, full_sha, jira=jira_ticket, tags=ciq_tags)
64+
65+
print(f"Cherry Pick New Message for {full_sha}")
66+
print(f"\n Original Message located here: {MERGE_MSG_BAK}")
67+
68+
with open(MERGE_MSG, "w") as file:
69+
file.writelines(new_msg)
70+
71+
72+
def cherry_pick(sha, ciq_tags, jira_ticket):
73+
"""
74+
Cherry picks a commit and it adds the ciq standardized format
75+
In case of error (cherry pick conflict):
76+
- MERGE_MSG.bak contains the original commit message
77+
- MERGE_MSG contains the standardized commit message
78+
- Conflict has to be solved manualy
79+
80+
In case of success:
81+
- the commit is cherry picked
82+
- MERGE_MSG.bak is deleted
83+
- You can still see MERGE_MSG for the original message
84+
"""
85+
86+
# Expand the provided SHA1 to the full SHA1 in case it's either abbreviated or an expression
87+
full_sha = CIQ_get_full_hash(repo=os.getcwd(), short_hash=sha)
88+
89+
check_fixes(sha=full_sha)
90+
91+
# Commit message is in MERGE_MSG
92+
git_res = subprocess.run(["git", "cherry-pick", "-nsx", full_sha])
93+
manage_commit_message(full_sha=full_sha, ciq_tags=ciq_tags, jira_ticket=jira_ticket)
94+
95+
if git_res.returncode != 0:
96+
error_str = (
97+
f"[FAILED] git cherry-pick -nsx {full_sha}\n"
98+
"Manually resolve conflict and include `upstream-diff` tag in commit message\n"
99+
f"Subprocess Call: {git_res}"
100+
)
101+
raise RuntimeError(error_str)
102+
103+
CIQ_run_git(repo_path=os.getcwd(), args=["commit", "-F", MERGE_MSG])
104+
105+
106+
def cherry_pick_fixes(sha, ciq_tags, jira_ticket, upstream_ref):
107+
"""
108+
It checks upstream_ref for commits that have this reference:
109+
Fixes: <sha>. If any, these will also be cherry picked with the ciq
110+
tag = cve-bf. If the tag was cve-pre, it stays the same.
111+
"""
112+
fixes_in_mainline = CIQ_find_fixes_in_mainline_current_branch(os.getcwd(), upstream_ref, sha)
113+
114+
# Replace cve with cve-bf
115+
# Leave cve-pre and cve-bf as they are
116+
bf_ciq_tags = [re.sub(r"^cve ", "cve-bf ", s) for s in ciq_tags]
117+
for full_hash, display_str in fixes_in_mainline:
118+
print(f"Extra cherry picking {display_str}")
119+
full_cherry_pick(sha=full_hash, ciq_tags=bf_ciq_tags, jira_ticket=jira_ticket, upstream_ref=upstream_ref)
120+
121+
122+
def full_cherry_pick(sha, ciq_tags, jira_ticket, upstream_ref):
123+
"""
124+
It cherry picks a commit from upstream-ref along with its Fixes: references.
125+
"""
126+
# Cherry pick the commit
127+
cherry_pick(sha=sha, ciq_tags=ciq_tags, jira_ticket=jira_ticket)
128+
129+
# Cherry pick the fixed-by dependencies
130+
cherry_pick_fixes(sha=sha, ciq_tags=ciq_tags, jira_ticket=jira_ticket, upstream_ref=upstream_ref)
131+
132+
34133
if __name__ == "__main__":
35134
print("CIQ custom cherry picker")
36135
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
@@ -44,59 +143,22 @@ def check_fixes(sha):
44143
" cve-pre CVE-1974-0001 - A pre-condition or dependency needed for the CVE\n"
45144
"Multiple tags are separated with a comma. ex: cve CVE-1974-0001, cve CVE-1974-0002\n",
46145
)
146+
parser.add_argument(
147+
"--upstream-ref",
148+
default="origin/kernel-mainline",
149+
help="Reference to upstream mainline branch (default: origin/kernel-mainline)",
150+
)
151+
47152
args = parser.parse_args()
48153

49154
logging.basicConfig(level=logging.INFO)
50155

51-
# Expand the provided SHA1 to the full SHA1 in case it's either abbreviated or an expression
52-
git_sha_res = subprocess.run(["git", "show", "--pretty=%H", "-s", args.sha], stdout=subprocess.PIPE)
53-
if git_sha_res.returncode != 0:
54-
print(f"[FAILED] git show --pretty=%H -s {args.sha}")
55-
print("Subprocess Call:")
56-
print(git_sha_res)
57-
print("")
58-
else:
59-
args.sha = git_sha_res.stdout.decode("utf-8").strip()
60-
61156
tags = []
62157
if args.ciq_tag is not None:
63158
tags = args.ciq_tag.split(",")
64159

65160
try:
66-
check_fixes(args.sha)
161+
full_cherry_pick(sha=args.sha, ciq_tags=tags, jira_ticket=args.ticket, upstream_ref=args.upstream_ref)
67162
except Exception as e:
68163
print(e)
69164
exit(1)
70-
71-
author = CIQ_original_commit_author_to_tag_string(repo_path=os.getcwd(), sha=args.sha)
72-
if author is None:
73-
exit(1)
74-
75-
git_res = subprocess.run(["git", "cherry-pick", "-nsx", args.sha])
76-
if git_res.returncode != 0:
77-
print(f"[FAILED] git cherry-pick -nsx {args.sha}")
78-
print(" Manually resolve conflict and include `upstream-diff` tag in commit message")
79-
print("Subprocess Call:")
80-
print(git_res)
81-
print("")
82-
83-
print(os.getcwd())
84-
subprocess.run(["cp", MERGE_MSG, f"{MERGE_MSG}.bak"])
85-
86-
tags.append(author)
87-
88-
with open(MERGE_MSG, "r") as file:
89-
original_msg = file.readlines()
90-
91-
new_msg = CIQ_cherry_pick_commit_standardization(original_msg, args.sha, jira=args.ticket, tags=tags)
92-
93-
print(f"Cherry Pick New Message for {args.sha}")
94-
for line in new_msg:
95-
print(line.strip("\n"))
96-
print(f"\n Original Message located here: {MERGE_MSG}.bak")
97-
98-
with open(MERGE_MSG, "w") as file:
99-
file.writelines(new_msg)
100-
101-
if git_res.returncode == 0:
102-
subprocess.run(["git", "commit", "-F", MERGE_MSG])

ciq_helpers.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,60 @@ def CIQ_commit_exists_in_current_branch(repo, upstream_hash_):
251251
return CIQ_commit_exists_in_branch(repo, current_branch, full_upstream_hash)
252252

253253

254+
def CIQ_find_fixes_in_mainline(repo, pr_branch, upstream_ref, hash_):
255+
"""
256+
Return unique commits in upstream_ref that have Fixes: <N chars of hash_> in their message, case-insensitive,
257+
if they have not been committed in the pr_branch.
258+
Start from 12 chars and work down to 6, but do not include duplicates if already found at a longer length.
259+
Returns a list of tuples: (full_hash, display_string)
260+
"""
261+
results = []
262+
263+
# Prepare hash prefixes from 12 down to 6
264+
hash_prefixes = [hash_[:index] for index in range(12, 5, -1)]
265+
266+
# Get all commits with 'Fixes:' in the message
267+
output = CIQ_run_git(
268+
repo,
269+
[
270+
"log",
271+
upstream_ref,
272+
"--grep",
273+
"Fixes:",
274+
"-i",
275+
"--format=%H %h %s (%an)%x0a%B%x00",
276+
],
277+
).strip()
278+
if not output:
279+
return []
280+
281+
# Each commit is separated by a NUL character and a newline
282+
commits = output.split("\x00\x0a")
283+
for commit in commits:
284+
if not commit.strip():
285+
continue
286+
287+
lines = commit.splitlines()
288+
# The first line is the summary, the rest is the body
289+
header = lines[0]
290+
full_hash, display_string = (lambda h: (h[0], " ".join(h[1:])))(header.split())
291+
fixes = CIQ_extract_fixes_references_from_commit_body_lines(lines=lines[1:])
292+
for fix in fixes:
293+
for prefix in hash_prefixes:
294+
if fix.lower().startswith(prefix.lower()):
295+
if not CIQ_commit_exists_in_branch(repo, pr_branch, full_hash):
296+
results.append((full_hash, display_string))
297+
break
298+
299+
return results
300+
301+
302+
def CIQ_find_fixes_in_mainline_current_branch(repo, upstream_ref, hash_):
303+
current_branch = CIQ_get_current_branch(repo)
304+
305+
return CIQ_find_fixes_in_mainline(repo, current_branch, upstream_ref, hash_)
306+
307+
254308
def repo_init(repo):
255309
"""Initialize a git repo object.
256310

0 commit comments

Comments
 (0)