Skip to content

Commit 0bb6f94

Browse files
committed
check_kernel_commits.py: Fix corner case around Fixes: commits (cve-bf)
Original issue: ctrliq/kernel-src-tree#1212 (comment) In the above link, we had a weird case where commit X was reverted by commit Y, but then our cve tracker picked the X commit again (why is irrelevant to this story). check_kernel_commits.py did not detect any issues locally, but it did flag it during the validate step when the PR was created. The discrepancy is due to the fact that, locally, the whole pr/local branch is fetched, but during Pr reviews, only the new commits are fetches, hence the history is way limited, which is more than enough considering that the 'fixes:' (cve-bf) commits should be applied after the commit of interest. While the different results locally vs during Pr review make sense, we need to catch issues like this locally, during development as well. Because locally we cannot fetch just the new commits (we need the whole history to make sure we cherry pick commits properly), check_kernel_commits now checks the interval of commits it has to check. And since we ran into this weird case described at the beginning, flag a warnining in case a commit X has a cve-bf that was applied beforehand so that the developer sees it and acts accordingly and not be confused during Pr review. Signed-off-by: Roxana Nicolescu <rnicolescu@ciq.com>
1 parent d8589c6 commit 0bb6f94

3 files changed

Lines changed: 130 additions & 16 deletions

File tree

check_kernel_commits.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import textwrap
88

99
from kt.ktlib.ciq_helpers import (
10+
CIQ_filter_unapplied_commits,
1011
CIQ_find_fixes_in_mainline,
1112
CIQ_find_matching_cve,
1213
CIQ_get_commit_body,
@@ -126,6 +127,9 @@ def main():
126127
any_findings = False
127128
out_lines = []
128129

130+
# len(pr_commits) >= 1
131+
oldest_pr_commit = pr_commits[-1]
132+
newest_pr_commit = pr_commits[0]
129133
for sha in reversed(pr_commits): # oldest first
130134
short_hash, subject = get_short_hash_and_subject(args.repo, sha)
131135
pr_commit_desc = f"{short_hash} ({subject})"
@@ -155,7 +159,68 @@ def main():
155159
)
156160
out_lines.append("") # blank line
157161
continue
158-
fixes = CIQ_find_fixes_in_mainline(args.repo, args.pr_branch, upstream_ref, uhash)
162+
163+
# Find commits that contains Fixes: <uhash> in upstream_ref
164+
fixes = CIQ_find_fixes_in_mainline(repo=args.repo, upstream_ref=upstream_ref, hash_=uhash)
165+
166+
# Filter the above commits if they were unapplied in the pr_branch (whole fetched history is used)
167+
fixes_unapplied_without_limit = CIQ_filter_unapplied_commits(
168+
repo=args.repo, pr_branch=args.pr_branch, commits=fixes
169+
)
170+
171+
# Filter the above commits if they were unapplied in the pr_branch (oldest_pr_commit..newest_pr_commit interval is used)
172+
fixes_unapplied_with_limit = CIQ_filter_unapplied_commits(
173+
repo=args.repo, pr_branch=args.pr_branch, commits=fixes, interval=(oldest_pr_commit, newest_pr_commit)
174+
)
175+
176+
# Issue a warning if we found that a commit that contains Fixes: <uhash> has been applied before <uhash>
177+
# Very specific case, but check this PR where it did happen https://github.com/ctrliq/kernel-src-tree/pull/1212#issuecomment-4421837799
178+
# It is up to the developer to assess the situation if this happens. In the example case, a commit X was pushed, then it was reverted by commit Y,
179+
# then commit X was pushed again but automation locally did not flag that it was reverted before because its dependency Y appeared to have been applied
180+
# in the local tree.
181+
fixes_applied_before_commit = set(fixes_unapplied_with_limit) - set(fixes_unapplied_without_limit)
182+
if fixes_applied_before_commit:
183+
# Build the fixes display text
184+
fixes_lines = []
185+
for fix_hash, display_str in fixes_applied_before_commit:
186+
fixes_lines.append(display_str)
187+
188+
fixes_text = "\n".join(fixes_lines)
189+
any_findings = True
190+
if args.markdown:
191+
fixes_block = " " + fixes_text.replace("\n", "\n ")
192+
out_lines.append(
193+
f"- ❗ PR commit `{pr_commit_desc}` references upstream commit \n"
194+
f" `{short_uhash}` which has been referenced by a `Fixes:` tag in the upstream \n"
195+
f" Linux kernel:\n\n"
196+
f"```text\n{fixes_block}\n```\n"
197+
" was applied after its deps, not before\n"
198+
)
199+
else:
200+
prefix = "[FIXES-WRONG-ORDER] "
201+
header = (
202+
f"{prefix}PR commit {pr_commit_desc} references upstream commit "
203+
f"{short_uhash}, which has Fixes tags:"
204+
)
205+
out_lines.append(
206+
wrap_paragraph(
207+
header, width=80, initial_indent="", subsequent_indent=" " * len(prefix)
208+
) # spaces for '[FIXES-WRONG-ORDER] '
209+
)
210+
out_lines.append("") # blank line after 'Fixes tags:'
211+
for line in fixes_text.splitlines():
212+
out_lines.append(" " + line)
213+
214+
out_lines.append(
215+
wrap_paragraph(
216+
text="was applied after its deps, not before\n",
217+
initial_indent=" " * len(prefix),
218+
)
219+
)
220+
out_lines.append("") # blank line
221+
222+
# We continue the usual checks for fixes that were unapplied after the commit of interest
223+
fixes = fixes_unapplied_with_limit
159224
if fixes:
160225
any_findings = True
161226

ciq-cherry-pick.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from kt.ktlib.ciq_helpers import (
1313
CIQ_cherry_pick_commit_standardization,
1414
CIQ_commit_exists_in_current_branch,
15-
CIQ_find_fixes_in_mainline_current_branch,
15+
CIQ_find_fixes_in_mainline_current_branch_unapplied,
1616
CIQ_find_matching_cve,
1717
CIQ_fixes_references,
1818
CIQ_get_full_hash,
@@ -251,7 +251,9 @@ def cherry_pick_fixes(
251251
Fixes: <sha>. If any, these will also be cherry picked with the ciq
252252
tag = cve-bf. If the tag was cve-pre, it stays the same.
253253
"""
254-
fixes_in_mainline = CIQ_find_fixes_in_mainline_current_branch(os.getcwd(), upstream_ref, sha)
254+
fixes_in_mainline = CIQ_find_fixes_in_mainline_current_branch_unapplied(
255+
repo=os.getcwd(), upstream_ref=upstream_ref, hash_=sha
256+
)
255257

256258
# Replace cve with cve-bf
257259
# Leave cve-pre and cve-bf as they are

kt/ktlib/ciq_helpers.py

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -229,35 +229,48 @@ def CIQ_hash_exists_in_ref(repo, pr_ref, hash_):
229229
return False
230230

231231

232-
def CIQ_commit_exists_in_branch(repo, pr_branch, upstream_hash_):
232+
def CIQ_commit_exists_in_branch(repo, pr_branch, upstream_hash_, interval=None):
233233
"""
234-
Return True if upstream_hash_ has been backported and it exists in the pr branch
234+
Return True if upstream_hash_ has been backported and it exists in the pr branch.
235+
If interval that consists of a pair of commit hashes is not None, we only
236+
check the interval[0]..interval[1], otherwise the whole history of the pr_branch will be used.
235237
"""
236238

237239
# First check if the commit has been backported by CIQ
238-
output = CIQ_run_git(repo, ["log", pr_branch, "--grep", "^commit " + upstream_hash_])
240+
git_command = ["log", pr_branch, "--grep", "^commit " + upstream_hash_]
241+
if interval:
242+
oldest_commit, newest_commit = interval
243+
git_command.append(f"{oldest_commit}..{newest_commit}")
244+
245+
output = CIQ_run_git(repo_path=repo, args=git_command)
239246
if output:
240247
return True
241248

249+
# If we are searching in the interval <oldest_commit>..<newest_commit>,
250+
# we do not need to check way back if the commit came from upstream as it is
251+
if interval:
252+
return False
253+
242254
# If it was not backported by CIQ, maybe it came from upstream as it is
243-
return CIQ_hash_exists_in_ref(repo, pr_branch, upstream_hash_)
255+
return CIQ_hash_exists_in_ref(repo=repo, pr_ref=pr_branch, hash_=upstream_hash_)
244256

245257

246-
def CIQ_commit_exists_in_current_branch(repo, upstream_hash_):
258+
def CIQ_commit_exists_in_current_branch(repo, upstream_hash_, interval=None):
247259
"""
248260
Return True if upstream_hash_ has been backported and it exists in the current branch
249261
"""
250262

251263
current_branch = CIQ_get_current_branch(repo)
252264
full_upstream_hash = CIQ_get_full_hash(repo, upstream_hash_)
253265

254-
return CIQ_commit_exists_in_branch(repo, current_branch, full_upstream_hash)
266+
return CIQ_commit_exists_in_branch(
267+
repo=repo, pr_branch=current_branch, upstream_hash_=full_upstream_hash, interval=interval
268+
)
255269

256270

257-
def CIQ_find_fixes_in_mainline(repo, pr_branch, upstream_ref, hash_):
271+
def CIQ_find_fixes_in_mainline(repo, upstream_ref, hash_):
258272
"""
259-
Return unique commits in upstream_ref that have Fixes: <N chars of hash_> in their message, case-insensitive,
260-
if they have not been committed in the pr_branch.
273+
Return unique commits in upstream_ref that have Fixes: <N chars of hash_> in their message, case-insensitive.
261274
Start from 12 chars and work down to 6, but do not include duplicates if already found at a longer length.
262275
Returns a list of tuples: (full_hash, display_string)
263276
"""
@@ -295,17 +308,51 @@ def CIQ_find_fixes_in_mainline(repo, pr_branch, upstream_ref, hash_):
295308
for fix in fixes:
296309
for prefix in hash_prefixes:
297310
if fix.lower().startswith(prefix.lower()):
298-
if not CIQ_commit_exists_in_branch(repo, pr_branch, full_hash):
299-
results.append((full_hash, display_string))
311+
results.append((full_hash, display_string))
300312
break
301313

302314
return results
303315

304316

305-
def CIQ_find_fixes_in_mainline_current_branch(repo, upstream_ref, hash_):
317+
def CIQ_filter_unapplied_commits(repo, pr_branch, commits, interval=None):
318+
"""
319+
Receives a list of tuples: (full_hash, display_string)
320+
Returns filtered list with commits that were not applied in the pr_branch.
321+
If interval that consists of a pair of commit hashes is not None, we only
322+
check the interval[0]..interval[1], otherwise the whole history of the pr_branch will be used.
323+
324+
Returns a list of tuples: (full_hash, display_string)
325+
"""
326+
327+
results = []
328+
for commit in commits:
329+
full_hash = commit[0]
330+
if not CIQ_commit_exists_in_branch(repo=repo, pr_branch=pr_branch, upstream_hash_=full_hash, interval=interval):
331+
results.append(commit)
332+
333+
return results
334+
335+
336+
def CIQ_find_fixes_in_mainline_unapplied(repo, pr_branch, upstream_ref, hash_, interval=None):
337+
"""
338+
Return unique commits in upstream_ref that have Fixes: <N chars of hash_> in their message, case-insensitive,
339+
if they have not been committed in the pr_branch.
340+
If interval that consists of a pair of commit hashes is not None, we only
341+
check the interval[0]..interval[1], otherwise the whole history of the pr_branch will be used.
342+
Start from 12 chars and work down to 6, but do not include duplicates if already found at a longer length.
343+
Returns a list of tuples: (full_hash, display_string)
344+
"""
345+
346+
fixes = CIQ_find_fixes_in_mainline(repo=repo, upstream_ref=upstream_ref, hash_=hash_)
347+
return CIQ_filter_unapplied_commits(repo=repo, pr_branch=pr_branch, commits=fixes, interval=interval)
348+
349+
350+
def CIQ_find_fixes_in_mainline_current_branch_unapplied(repo, upstream_ref, hash_, interval=None):
306351
current_branch = CIQ_get_current_branch(repo)
307352

308-
return CIQ_find_fixes_in_mainline(repo, current_branch, upstream_ref, hash_)
353+
return CIQ_find_fixes_in_mainline_unapplied(
354+
repo=repo, pr_branch=current_branch, upstream_ref=upstream_ref, hash_=hash_, interval=interval
355+
)
309356

310357

311358
def CIQ_reset_HEAD(repo):

0 commit comments

Comments
 (0)