blob: 25a200237de202733a7f412d4b9bda55691aae30 [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
106def report_integration_status_sha(repository, merge_base, branch_name, sha):
107 """Report integration status for given repository, branch, and sha"""
108
109 if repository is common.STABLE_PATH:
110 # 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.
113 rc_metadata = common.get_kernel_metadata(common.Kernel.linux_stable_rc)
114 rc_status = git_interface.get_cherrypick_status(rc_metadata.path, merge_base,
115 branch_name, sha)
116 else:
117 rc_status = common.Status.OPEN
118
119 status = git_interface.get_cherrypick_status(repository, merge_base, branch_name, sha)
120 if status == common.Status.CONFLICT:
121 disposition = ' (queued)' if rc_status == common.Status.MERGED \
122 else ' (conflicts - backport needed)'
123 elif status == common.Status.MERGED:
124 disposition = ' (already applied)'
125 else:
126 disposition = ' (queued)' if rc_status == common.Status.MERGED else ''
127
128 print(' %s%s' % (branch_name, disposition))
129
130def report_integration_status_branch(db, metadata, handled_shas, branch, conflicts):
131 """Report integration status for list of open patches in given repository and branch"""
132
133 if metadata.kernel_fixes_table == 'stable_fixes':
134 table = 'linux_stable'
135 branch_name_pattern = 'linux-%s.y'
136 else:
137 table = 'linux_chrome'
138 branch_name_pattern = 'chromeos-%s'
139
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
163 changeid = missing.get_change_id(db, fixedby_sha)
164 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]
183 branch_name = branch_name_pattern % fix_branch
184 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:
195 merge_base = 'v%s' % affected_branch
196 branch_name = branch_name_pattern % affected_branch
197 report_integration_status_sha(metadata.path, merge_base, branch_name, fixedby_sha)
198
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()