blob: 30068c66063790eaad8fdc58c1555ffa4169abf8 [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
31import MySQLdb
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))
162 integrated = git_interface.get_integrated_tag(fixedby_sha)
163 end = integrated
164 if not integrated:
165 integrated = 'ToT'
166 print(' upstream: %s' % integrated)
167 print(' Fixes: %s ("%s")' % (fixes_sha, fixes_description))
168
169 q = """SELECT sha, branch
170 FROM {table}
171 WHERE upstream_sha = %s""".format(table=table)
172 c.execute(q, [fixes_sha])
173 fix_rows = sorted(c.fetchall(), key=mysort)
174 affected_branches = []
175 for fix_sha, fix_branch in fix_rows:
176 if fix_branch in metadata.branches:
177 affected_branches += [fix_branch]
178 branch_name = branch_name_pattern % fix_branch
179 print(' in %s: %s' % (branch_name, fix_sha))
180
181 start = git_interface.get_integrated_tag(fixes_sha)
182 if start:
183 print(' upstream: %s' % git_interface.get_integrated_tag(fixes_sha))
184
185 affected_branches += version_list(metadata.branches, start, end)
186 affected_branches = sorted(list(set(affected_branches)), key=branch_order)
187 if affected_branches:
188 print(' Affected branches:')
189 for affected_branch in affected_branches:
190 merge_base = 'v%s' % affected_branch
191 branch_name = branch_name_pattern % affected_branch
192 report_integration_status_sha(metadata.path, merge_base, branch_name, fixedby_sha)
193
Curtis Malainey584006e2020-05-27 16:01:35 -0700194 # Check if this commit has been fixed as well and, if so, report it
195 subsequent_fixes = missing.get_subsequent_fixes(db, fixedby_sha)
196 # remove initial fix
197 subsequent_fixes.pop(0)
198 if subsequent_fixes:
199 subsequent_fixes = ["\'" + sha + "\'" for sha in subsequent_fixes]
200 parsed_fixes = ', '.join(subsequent_fixes)
201 # format query here since we are inserting n values
202 q = """SELECT sha, description
203 FROM linux_upstream
204 WHERE sha in ({})
205 ORDER BY FIELD(sha, {})""".format(parsed_fixes, parsed_fixes)
206 c.execute(q)
207 fixes = c.fetchall()
208 print(' Fixed by:')
209 for fix in fixes:
210 print(' %s ("%s")' % fix)
211
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700212
213@util.cloud_sql_proxy_decorator
214@util.preliminary_check_decorator(False)
215def report_integration_status(branch, conflicts, is_chromium):
216 """Report list of open patches"""
217
218 handled_shas = []
219
220 if is_chromium:
221 metadata = common.get_kernel_metadata(common.Kernel.linux_chrome)
222 else:
223 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
224
225 synchronize.synchronize_repositories(True)
226
Guenter Roeckbcb73e22020-09-27 18:04:11 -0700227 db = MySQLdb.Connect(user='linux_patches_robot', host='127.0.0.1', db='linuxdb',
228 charset='utf8mb4')
Guenter Roeck0e9e8d12020-04-14 14:02:54 -0700229
230 if branch:
231 report_integration_status_branch(db, metadata, handled_shas, branch, conflicts)
232 else:
233 for b in metadata.branches:
234 print('\nBranch: linux-%s.y\n' % b)
235 report_integration_status_branch(db, metadata, handled_shas, b, conflicts)
236
237 db.close()
238
239
240def report_integration_status_parse():
241 """Parses command line args and calls the actual function with parsed parameters
242
243 To execute:
244 ./getopen [-b branch] [-c] [-C]
245 """
246
247 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
248 parser = argparse.ArgumentParser(description='Local functions to retrieve data from database')
249 parser.add_argument('-b', '--branch', type=str, choices=metadata.branches,
250 help='Branch to check')
251 parser.add_argument('-C', '--chromium', action='store_true',
252 help='Look for pending chromium patches')
253 parser.add_argument('-c', '--conflicts', action='store_true',
254 help='Check for conflicting patches')
255 args = parser.parse_args()
256
257 report_integration_status(args.branch, args.conflicts, args.chromium)
258
259if __name__ == '__main__':
260 report_integration_status_parse()