blob: ce0c3258050a62d89a93966f9277124bdabf9f09 [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):
20>> ./scripts/local/local_database_setup.py
21
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 Roeck32b23dc2021-01-17 13:29:06 -0800134 kernel = common.Kernel.linux_stable
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700135 else:
136 table = 'linux_chrome'
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800137 kernel = common.Kernel.linux_chrome
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700138
139 c = db.cursor()
140
141 status = 'CONFLICT' if conflicts else 'OPEN'
142
143 # Walk through all fixes table entries with given status
144 q = """SELECT ct.fixedby_upstream_sha, lu.description, t.upstream_sha, t.description
145 FROM {chosen_table} AS ct
146 JOIN linux_upstream AS lu
147 ON ct.fixedby_upstream_sha = lu.sha
148 JOIN {table} as t
149 ON ct.kernel_sha = t.sha
150 WHERE ct.status = %s
151 AND ct.branch = %s""".format(chosen_table=metadata.kernel_fixes_table, table=table)
152 c.execute(q, [status, branch])
153 for fixedby_sha, fixedby_description, fixes_sha, fixes_description in c.fetchall():
154 # If we already handled a SHA while running this command while examining
155 # another branch, we don't need to handle it again.
156 if fixedby_sha in handled_shas:
157 continue
158 handled_shas += [fixedby_sha]
159
160 print('Upstream commit %s ("%s")' % (fixedby_sha, fixedby_description))
Guenter Roeck0520f922020-12-29 09:52:50 -0800161
Guenter Roeckd8bf2022021-01-19 07:31:54 -0800162 changeid, _ = missing.get_change_id(db, branch, fixedby_sha)
Guenter Roeck0520f922020-12-29 09:52:50 -0800163 if changeid:
164 print(' CL:%s' % changeid)
165
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700166 integrated = git_interface.get_integrated_tag(fixedby_sha)
167 end = integrated
168 if not integrated:
169 integrated = 'ToT'
170 print(' upstream: %s' % integrated)
171 print(' Fixes: %s ("%s")' % (fixes_sha, fixes_description))
172
173 q = """SELECT sha, branch
174 FROM {table}
175 WHERE upstream_sha = %s""".format(table=table)
176 c.execute(q, [fixes_sha])
177 fix_rows = sorted(c.fetchall(), key=mysort)
178 affected_branches = []
179 for fix_sha, fix_branch in fix_rows:
180 if fix_branch in metadata.branches:
181 affected_branches += [fix_branch]
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800182 branch_name = metadata.get_kernel_branch(fix_branch)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700183 print(' in %s: %s' % (branch_name, fix_sha))
184
185 start = git_interface.get_integrated_tag(fixes_sha)
186 if start:
187 print(' upstream: %s' % git_interface.get_integrated_tag(fixes_sha))
188
189 affected_branches += version_list(metadata.branches, start, end)
190 affected_branches = sorted(list(set(affected_branches)), key=branch_order)
191 if affected_branches:
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800192 handler = git_interface.commitHandler(kernel)
193 if kernel == common.Kernel.linux_stable:
194 rc_handler = git_interface.commitHandler(common.Kernel.linux_stable_rc)
195 else:
196 rc_handler = None
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700197 print(' Affected branches:')
198 for affected_branch in affected_branches:
Guenter Roeck32b23dc2021-01-17 13:29:06 -0800199 branch_name = metadata.get_kernel_branch(affected_branch)
200 report_integration_status_sha(handler, rc_handler, affected_branch, branch_name,
201 fixedby_sha)
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700202
Curtis Malainey584006e2020-05-27 16:01:35 -0700203 # Check if this commit has been fixed as well and, if so, report it
204 subsequent_fixes = missing.get_subsequent_fixes(db, fixedby_sha)
205 # remove initial fix
206 subsequent_fixes.pop(0)
207 if subsequent_fixes:
208 subsequent_fixes = ["\'" + sha + "\'" for sha in subsequent_fixes]
209 parsed_fixes = ', '.join(subsequent_fixes)
210 # format query here since we are inserting n values
211 q = """SELECT sha, description
212 FROM linux_upstream
Guenter Roeck0520f922020-12-29 09:52:50 -0800213 WHERE sha in ({sha})
214 ORDER BY FIELD(sha, {sha})""".format(sha=parsed_fixes)
Curtis Malainey584006e2020-05-27 16:01:35 -0700215 c.execute(q)
216 fixes = c.fetchall()
217 print(' Fixed by:')
218 for fix in fixes:
219 print(' %s ("%s")' % fix)
220
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700221
222@util.cloud_sql_proxy_decorator
223@util.preliminary_check_decorator(False)
224def report_integration_status(branch, conflicts, is_chromium):
225 """Report list of open patches"""
226
227 handled_shas = []
228
229 if is_chromium:
230 metadata = common.get_kernel_metadata(common.Kernel.linux_chrome)
231 else:
232 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
233
234 synchronize.synchronize_repositories(True)
235
Guenter Roeckbcb73e22020-09-27 18:04:11 -0700236 db = MySQLdb.Connect(user='linux_patches_robot', host='127.0.0.1', db='linuxdb',
237 charset='utf8mb4')
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700238
239 if branch:
240 report_integration_status_branch(db, metadata, handled_shas, branch, conflicts)
241 else:
242 for b in metadata.branches:
243 print('\nBranch: linux-%s.y\n' % b)
244 report_integration_status_branch(db, metadata, handled_shas, b, conflicts)
245
246 db.close()
247
248
249def report_integration_status_parse():
250 """Parses command line args and calls the actual function with parsed parameters
251
252 To execute:
253 ./getopen [-b branch] [-c] [-C]
254 """
255
256 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
257 parser = argparse.ArgumentParser(description='Local functions to retrieve data from database')
258 parser.add_argument('-b', '--branch', type=str, choices=metadata.branches,
259 help='Branch to check')
260 parser.add_argument('-C', '--chromium', action='store_true',
261 help='Look for pending chromium patches')
262 parser.add_argument('-c', '--conflicts', action='store_true',
263 help='Check for conflicting patches')
264 args = parser.parse_args()
265
266 report_integration_status(args.branch, args.conflicts, args.chromium)
267
268if __name__ == '__main__':
269 report_integration_status_parse()