blob: 1a95686979f345d078bb13b938d0f09a2011ed50 [file] [log] [blame]
Guenter Roeck0e9e8d12020-04-14 14:02:54 -07001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-"
3#
4# Copyright 2020 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7#
8# pylint: pylint: disable=filter-builtin-not-iterating
9
10"""Utility command for engineers to get list of unresolved fixes
11
12This command can be used by engineers to get a list of unresolved
13chromium or stable fixes. Users can ask for unresolved fixes with open CLs
14or for unresolved fixes with conflicts.
15
16Before running this script, make sure you have been added to the
17chromeos-missing-patches GCP project.
18
19Prerequisites to execute this script locally (RUN ONCE):
Tzung-Bi Shih0283c972022-02-15 11:29:06 +080020>> ./scripts/local/local_database_setup.sh
Guenter Roeck0e9e8d12020-04-14 14:02:54 -070021
22All locally executed commands must be run in this directory's
23virtual env (source env/bin/activate) before running any commands.
24The associated shell script enables this environment.
25"""
26
Guenter Roeck0e9e8d12020-04-14 14:02:54 -070027
28import argparse
29import re
Guenter Roeck0520f922020-12-29 09:52:50 -080030import MySQLdb # pylint: disable=import-error
Curtis Malainey584006e2020-05-27 16:01:35 -070031
Guenter Roeck0e9e8d12020-04-14 14:02:54 -070032import common
33import git_interface
Curtis Malainey584006e2020-05-27 16:01:35 -070034import missing
Guenter Roeck0e9e8d12020-04-14 14:02:54 -070035import synchronize
36import util
37
38
39# extract_numerics matches numeric parts of a Linux version as separate elements
40# For example, "v5.4" matches "5" and "4", and "v5.4.12" matches "5", "4", and "12"
41extract_numerics = re.compile(r'(?:v)?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?\s*')
42
43def branch_order(branch):
44 """Calculate numeric order of tag or branch.
45
46 A branch with higher version number will return a larger number.
47 Ignores release candidates. For example, v5.7-rc1 will return the same
48 number as v5.7-rc2.
49
50 Returns 0 if the kernel version can not be extracted.
51 """
52
53 m = extract_numerics.match(branch)
54 if m:
55 major = int(m.group(1))
56 minor1 = int(m.group(2))
57 minor2 = int(m.group(3)) if m.group(3) else 0
58 return major * 10000 + minor1 * 100 + minor2
59 return 0
60
61
62# version_baseline matches the numeric part of the major Linux version as a string
63# For example, "v5.4.15" and "v5.4-rc3" both match "5.4"
64version_baseline = re.compile(r'(?:v)?([0-9]+\.[0-9]+(?:\.[0-9]+)?)\s*')
65
66def version_list(branches, start, end):
67 """Return list of stable release branches between 'start' and 'end'.
68
69 'branches' is the sorted list of branches to match.
70 'start' and 'end' can be any valid kernel version or tag.
71 If 'start' is empty or invalid, start list with first supported
72 stable branch.
73 If 'end' is empty or invalid, end list with last supported stable
74 branch.
75 """
76
77 offset = 0
78
79 if start:
80 # Extract numeric part of 'start'
81 start_match = version_baseline.match(start)
82 start = start_match.group(1) if start_match else None
83 if not start:
84 start = branches[0]
85 if end:
86 end_match = version_baseline.match(end)
87 end = end_match.group(1) if end_match else None
88 if not end:
89 end = branches[-1]
90 # If we have no 'end', also match the last branch
91 offset = 1
92
93 min_order = branch_order(start)
94 max_order = branch_order(end) + offset
95 return list(filter(lambda x: branch_order(x) >= min_order and branch_order(x) < max_order,
96 branches))
97
98
99def mysort(elem):
100 """Function to sort branches based on version number"""
101 _, branch = elem
102 return branch_order(branch)
103
104
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800105def report_integration_status_sha(handler, rc_handler, branch, branch_name, sha):
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700106 """Report integration status for given repository, branch, and sha"""
107
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800108 if rc_handler:
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700109 # It is possible that the patch has not yet been applied but is queued
110 # in a stable release candidate. Try to find it there as well. We use
111 # this information to override the final status if appropriate.
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800112 # Try applying patch and get status
113 rc_status = rc_handler.cherrypick_status(sha, branch=branch)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700114 else:
115 rc_status = common.Status.OPEN
116
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800117 status = handler.cherrypick_status(sha, branch=branch)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700118 if status == common.Status.CONFLICT:
119 disposition = ' (queued)' if rc_status == common.Status.MERGED \
120 else ' (conflicts - backport needed)'
121 elif status == common.Status.MERGED:
122 disposition = ' (already applied)'
123 else:
124 disposition = ' (queued)' if rc_status == common.Status.MERGED else ''
125
126 print(' %s%s' % (branch_name, disposition))
127
128def report_integration_status_branch(db, metadata, handled_shas, branch, conflicts):
129 """Report integration status for list of open patches in given repository and branch"""
130
131 if metadata.kernel_fixes_table == 'stable_fixes':
132 table = 'linux_stable'
Guenter Roeckbc00c2f2021-01-20 16:28:22 -0800133 handler = git_interface.commitHandler(common.Kernel.linux_stable)
134 rc_handler = git_interface.commitHandler(common.Kernel.linux_stable_rc)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700135 else:
136 table = 'linux_chrome'
Guenter Roeckbc00c2f2021-01-20 16:28:22 -0800137 handler = git_interface.commitHandler(common.Kernel.linux_chrome)
138 rc_handler = None
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700139
140 c = db.cursor()
141
142 status = 'CONFLICT' if conflicts else 'OPEN'
143
144 # Walk through all fixes table entries with given status
145 q = """SELECT ct.fixedby_upstream_sha, lu.description, t.upstream_sha, t.description
146 FROM {chosen_table} AS ct
147 JOIN linux_upstream AS lu
148 ON ct.fixedby_upstream_sha = lu.sha
149 JOIN {table} as t
150 ON ct.kernel_sha = t.sha
151 WHERE ct.status = %s
152 AND ct.branch = %s""".format(chosen_table=metadata.kernel_fixes_table, table=table)
153 c.execute(q, [status, branch])
154 for fixedby_sha, fixedby_description, fixes_sha, fixes_description in c.fetchall():
155 # If we already handled a SHA while running this command while examining
156 # another branch, we don't need to handle it again.
157 if fixedby_sha in handled_shas:
158 continue
159 handled_shas += [fixedby_sha]
160
161 print('Upstream commit %s ("%s")' % (fixedby_sha, fixedby_description))
Guenter Roeck0520f922020-12-29 09:52:50 -0800162
Guenter Roeckd8bf2022021-01-19 07:31:54 -0800163 changeid, _ = missing.get_change_id(db, branch, fixedby_sha)
Guenter Roeck0520f922020-12-29 09:52:50 -0800164 if changeid:
165 print(' CL:%s' % changeid)
166
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700167 integrated = git_interface.get_integrated_tag(fixedby_sha)
168 end = integrated
169 if not integrated:
170 integrated = 'ToT'
171 print(' upstream: %s' % integrated)
172 print(' Fixes: %s ("%s")' % (fixes_sha, fixes_description))
173
174 q = """SELECT sha, branch
175 FROM {table}
176 WHERE upstream_sha = %s""".format(table=table)
177 c.execute(q, [fixes_sha])
178 fix_rows = sorted(c.fetchall(), key=mysort)
179 affected_branches = []
180 for fix_sha, fix_branch in fix_rows:
181 if fix_branch in metadata.branches:
182 affected_branches += [fix_branch]
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800183 branch_name = metadata.get_kernel_branch(fix_branch)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700184 print(' in %s: %s' % (branch_name, fix_sha))
185
186 start = git_interface.get_integrated_tag(fixes_sha)
187 if start:
188 print(' upstream: %s' % git_interface.get_integrated_tag(fixes_sha))
189
190 affected_branches += version_list(metadata.branches, start, end)
191 affected_branches = sorted(list(set(affected_branches)), key=branch_order)
192 if affected_branches:
193 print(' Affected branches:')
194 for affected_branch in affected_branches:
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800195 branch_name = metadata.get_kernel_branch(affected_branch)
196 report_integration_status_sha(handler, rc_handler, affected_branch, branch_name,
197 fixedby_sha)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700198
Curtis Malainey584006e2020-05-27 16:01:35 -0700199 # Check if this commit has been fixed as well and, if so, report it
200 subsequent_fixes = missing.get_subsequent_fixes(db, fixedby_sha)
201 # remove initial fix
202 subsequent_fixes.pop(0)
203 if subsequent_fixes:
204 subsequent_fixes = ["\'" + sha + "\'" for sha in subsequent_fixes]
205 parsed_fixes = ', '.join(subsequent_fixes)
206 # format query here since we are inserting n values
207 q = """SELECT sha, description
208 FROM linux_upstream
Guenter Roeck0520f922020-12-29 09:52:50 -0800209 WHERE sha in ({sha})
210 ORDER BY FIELD(sha, {sha})""".format(sha=parsed_fixes)
Curtis Malainey584006e2020-05-27 16:01:35 -0700211 c.execute(q)
212 fixes = c.fetchall()
213 print(' Fixed by:')
214 for fix in fixes:
215 print(' %s ("%s")' % fix)
216
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700217
218@util.cloud_sql_proxy_decorator
219@util.preliminary_check_decorator(False)
220def report_integration_status(branch, conflicts, is_chromium):
221 """Report list of open patches"""
222
223 handled_shas = []
224
225 if is_chromium:
226 metadata = common.get_kernel_metadata(common.Kernel.linux_chrome)
227 else:
228 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
229
230 synchronize.synchronize_repositories(True)
231
Guenter Roeckbcb73e22020-09-27 18:04:11 -0700232 db = MySQLdb.Connect(user='linux_patches_robot', host='127.0.0.1', db='linuxdb',
233 charset='utf8mb4')
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700234
235 if branch:
236 report_integration_status_branch(db, metadata, handled_shas, branch, conflicts)
237 else:
238 for b in metadata.branches:
239 print('\nBranch: linux-%s.y\n' % b)
240 report_integration_status_branch(db, metadata, handled_shas, b, conflicts)
241
242 db.close()
243
244
245def report_integration_status_parse():
246 """Parses command line args and calls the actual function with parsed parameters
247
248 To execute:
249 ./getopen [-b branch] [-c] [-C]
250 """
251
252 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
253 parser = argparse.ArgumentParser(description='Local functions to retrieve data from database')
254 parser.add_argument('-b', '--branch', type=str, choices=metadata.branches,
255 help='Branch to check')
256 parser.add_argument('-C', '--chromium', action='store_true',
257 help='Look for pending chromium patches')
258 parser.add_argument('-c', '--conflicts', action='store_true',
259 help='Check for conflicting patches')
260 args = parser.parse_args()
261
262 report_integration_status(args.branch, args.conflicts, args.chromium)
263
264if __name__ == '__main__':
265 report_integration_status_parse()