blob: 3e5989e829f1cad81ca868c10f7b77929f0c61dc [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
27from __future__ import print_function
28
29import argparse
30import re
Guenter Roeck0520f922020-12-29 09:52:50 -080031import MySQLdb # pylint: disable=import-error
Curtis Malainey584006e2020-05-27 16:01:35 -070032
Guenter Roeck0e9e8d12020-04-14 14:02:54 -070033import common
34import git_interface
Curtis Malainey584006e2020-05-27 16:01:35 -070035import missing
Guenter Roeck0e9e8d12020-04-14 14:02:54 -070036import synchronize
37import util
38
39
40# extract_numerics matches numeric parts of a Linux version as separate elements
41# For example, "v5.4" matches "5" and "4", and "v5.4.12" matches "5", "4", and "12"
42extract_numerics = re.compile(r'(?:v)?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?\s*')
43
44def branch_order(branch):
45 """Calculate numeric order of tag or branch.
46
47 A branch with higher version number will return a larger number.
48 Ignores release candidates. For example, v5.7-rc1 will return the same
49 number as v5.7-rc2.
50
51 Returns 0 if the kernel version can not be extracted.
52 """
53
54 m = extract_numerics.match(branch)
55 if m:
56 major = int(m.group(1))
57 minor1 = int(m.group(2))
58 minor2 = int(m.group(3)) if m.group(3) else 0
59 return major * 10000 + minor1 * 100 + minor2
60 return 0
61
62
63# version_baseline matches the numeric part of the major Linux version as a string
64# For example, "v5.4.15" and "v5.4-rc3" both match "5.4"
65version_baseline = re.compile(r'(?:v)?([0-9]+\.[0-9]+(?:\.[0-9]+)?)\s*')
66
67def version_list(branches, start, end):
68 """Return list of stable release branches between 'start' and 'end'.
69
70 'branches' is the sorted list of branches to match.
71 'start' and 'end' can be any valid kernel version or tag.
72 If 'start' is empty or invalid, start list with first supported
73 stable branch.
74 If 'end' is empty or invalid, end list with last supported stable
75 branch.
76 """
77
78 offset = 0
79
80 if start:
81 # Extract numeric part of 'start'
82 start_match = version_baseline.match(start)
83 start = start_match.group(1) if start_match else None
84 if not start:
85 start = branches[0]
86 if end:
87 end_match = version_baseline.match(end)
88 end = end_match.group(1) if end_match else None
89 if not end:
90 end = branches[-1]
91 # If we have no 'end', also match the last branch
92 offset = 1
93
94 min_order = branch_order(start)
95 max_order = branch_order(end) + offset
96 return list(filter(lambda x: branch_order(x) >= min_order and branch_order(x) < max_order,
97 branches))
98
99
100def mysort(elem):
101 """Function to sort branches based on version number"""
102 _, branch = elem
103 return branch_order(branch)
104
105
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800106def report_integration_status_sha(handler, rc_handler, branch, branch_name, sha):
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700107 """Report integration status for given repository, branch, and sha"""
108
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800109 if rc_handler:
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700110 # It is possible that the patch has not yet been applied but is queued
111 # in a stable release candidate. Try to find it there as well. We use
112 # this information to override the final status if appropriate.
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800113 # Try applying patch and get status
114 rc_status = rc_handler.cherrypick_status(sha, branch=branch)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700115 else:
116 rc_status = common.Status.OPEN
117
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800118 status = handler.cherrypick_status(sha, branch=branch)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700119 if status == common.Status.CONFLICT:
120 disposition = ' (queued)' if rc_status == common.Status.MERGED \
121 else ' (conflicts - backport needed)'
122 elif status == common.Status.MERGED:
123 disposition = ' (already applied)'
124 else:
125 disposition = ' (queued)' if rc_status == common.Status.MERGED else ''
126
127 print(' %s%s' % (branch_name, disposition))
128
129def report_integration_status_branch(db, metadata, handled_shas, branch, conflicts):
130 """Report integration status for list of open patches in given repository and branch"""
131
132 if metadata.kernel_fixes_table == 'stable_fixes':
133 table = 'linux_stable'
Guenter Roeckbc00c2f2021-01-20 16:28:22 -0800134 handler = git_interface.commitHandler(common.Kernel.linux_stable)
135 rc_handler = git_interface.commitHandler(common.Kernel.linux_stable_rc)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700136 else:
137 table = 'linux_chrome'
Guenter Roeckbc00c2f2021-01-20 16:28:22 -0800138 handler = git_interface.commitHandler(common.Kernel.linux_chrome)
139 rc_handler = None
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700140
141 c = db.cursor()
142
143 status = 'CONFLICT' if conflicts else 'OPEN'
144
145 # Walk through all fixes table entries with given status
146 q = """SELECT ct.fixedby_upstream_sha, lu.description, t.upstream_sha, t.description
147 FROM {chosen_table} AS ct
148 JOIN linux_upstream AS lu
149 ON ct.fixedby_upstream_sha = lu.sha
150 JOIN {table} as t
151 ON ct.kernel_sha = t.sha
152 WHERE ct.status = %s
153 AND ct.branch = %s""".format(chosen_table=metadata.kernel_fixes_table, table=table)
154 c.execute(q, [status, branch])
155 for fixedby_sha, fixedby_description, fixes_sha, fixes_description in c.fetchall():
156 # If we already handled a SHA while running this command while examining
157 # another branch, we don't need to handle it again.
158 if fixedby_sha in handled_shas:
159 continue
160 handled_shas += [fixedby_sha]
161
162 print('Upstream commit %s ("%s")' % (fixedby_sha, fixedby_description))
Guenter Roeck0520f922020-12-29 09:52:50 -0800163
Guenter Roeckd8bf2022021-01-19 07:31:54 -0800164 changeid, _ = missing.get_change_id(db, branch, fixedby_sha)
Guenter Roeck0520f922020-12-29 09:52:50 -0800165 if changeid:
166 print(' CL:%s' % changeid)
167
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700168 integrated = git_interface.get_integrated_tag(fixedby_sha)
169 end = integrated
170 if not integrated:
171 integrated = 'ToT'
172 print(' upstream: %s' % integrated)
173 print(' Fixes: %s ("%s")' % (fixes_sha, fixes_description))
174
175 q = """SELECT sha, branch
176 FROM {table}
177 WHERE upstream_sha = %s""".format(table=table)
178 c.execute(q, [fixes_sha])
179 fix_rows = sorted(c.fetchall(), key=mysort)
180 affected_branches = []
181 for fix_sha, fix_branch in fix_rows:
182 if fix_branch in metadata.branches:
183 affected_branches += [fix_branch]
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800184 branch_name = metadata.get_kernel_branch(fix_branch)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700185 print(' in %s: %s' % (branch_name, fix_sha))
186
187 start = git_interface.get_integrated_tag(fixes_sha)
188 if start:
189 print(' upstream: %s' % git_interface.get_integrated_tag(fixes_sha))
190
191 affected_branches += version_list(metadata.branches, start, end)
192 affected_branches = sorted(list(set(affected_branches)), key=branch_order)
193 if affected_branches:
194 print(' Affected branches:')
195 for affected_branch in affected_branches:
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800196 branch_name = metadata.get_kernel_branch(affected_branch)
197 report_integration_status_sha(handler, rc_handler, affected_branch, branch_name,
198 fixedby_sha)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700199
Curtis Malainey584006e2020-05-27 16:01:35 -0700200 # Check if this commit has been fixed as well and, if so, report it
201 subsequent_fixes = missing.get_subsequent_fixes(db, fixedby_sha)
202 # remove initial fix
203 subsequent_fixes.pop(0)
204 if subsequent_fixes:
205 subsequent_fixes = ["\'" + sha + "\'" for sha in subsequent_fixes]
206 parsed_fixes = ', '.join(subsequent_fixes)
207 # format query here since we are inserting n values
208 q = """SELECT sha, description
209 FROM linux_upstream
Guenter Roeck0520f922020-12-29 09:52:50 -0800210 WHERE sha in ({sha})
211 ORDER BY FIELD(sha, {sha})""".format(sha=parsed_fixes)
Curtis Malainey584006e2020-05-27 16:01:35 -0700212 c.execute(q)
213 fixes = c.fetchall()
214 print(' Fixed by:')
215 for fix in fixes:
216 print(' %s ("%s")' % fix)
217
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700218
219@util.cloud_sql_proxy_decorator
220@util.preliminary_check_decorator(False)
221def report_integration_status(branch, conflicts, is_chromium):
222 """Report list of open patches"""
223
224 handled_shas = []
225
226 if is_chromium:
227 metadata = common.get_kernel_metadata(common.Kernel.linux_chrome)
228 else:
229 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
230
231 synchronize.synchronize_repositories(True)
232
Guenter Roeckbcb73e22020-09-27 18:04:11 -0700233 db = MySQLdb.Connect(user='linux_patches_robot', host='127.0.0.1', db='linuxdb',
234 charset='utf8mb4')
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700235
236 if branch:
237 report_integration_status_branch(db, metadata, handled_shas, branch, conflicts)
238 else:
239 for b in metadata.branches:
240 print('\nBranch: linux-%s.y\n' % b)
241 report_integration_status_branch(db, metadata, handled_shas, b, conflicts)
242
243 db.close()
244
245
246def report_integration_status_parse():
247 """Parses command line args and calls the actual function with parsed parameters
248
249 To execute:
250 ./getopen [-b branch] [-c] [-C]
251 """
252
253 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
254 parser = argparse.ArgumentParser(description='Local functions to retrieve data from database')
255 parser.add_argument('-b', '--branch', type=str, choices=metadata.branches,
256 help='Branch to check')
257 parser.add_argument('-C', '--chromium', action='store_true',
258 help='Look for pending chromium patches')
259 parser.add_argument('-c', '--conflicts', action='store_true',
260 help='Check for conflicting patches')
261 args = parser.parse_args()
262
263 report_integration_status(args.branch, args.conflicts, args.chromium)
264
265if __name__ == '__main__':
266 report_integration_status_parse()