blob: 1a5253668c143057519bbf4a6c1b86d4cc8bbc29 [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
32import common
33import git_interface
34import synchronize
35import util
36
37
38# extract_numerics matches numeric parts of a Linux version as separate elements
39# For example, "v5.4" matches "5" and "4", and "v5.4.12" matches "5", "4", and "12"
40extract_numerics = re.compile(r'(?:v)?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?\s*')
41
42def branch_order(branch):
43 """Calculate numeric order of tag or branch.
44
45 A branch with higher version number will return a larger number.
46 Ignores release candidates. For example, v5.7-rc1 will return the same
47 number as v5.7-rc2.
48
49 Returns 0 if the kernel version can not be extracted.
50 """
51
52 m = extract_numerics.match(branch)
53 if m:
54 major = int(m.group(1))
55 minor1 = int(m.group(2))
56 minor2 = int(m.group(3)) if m.group(3) else 0
57 return major * 10000 + minor1 * 100 + minor2
58 return 0
59
60
61# version_baseline matches the numeric part of the major Linux version as a string
62# For example, "v5.4.15" and "v5.4-rc3" both match "5.4"
63version_baseline = re.compile(r'(?:v)?([0-9]+\.[0-9]+(?:\.[0-9]+)?)\s*')
64
65def version_list(branches, start, end):
66 """Return list of stable release branches between 'start' and 'end'.
67
68 'branches' is the sorted list of branches to match.
69 'start' and 'end' can be any valid kernel version or tag.
70 If 'start' is empty or invalid, start list with first supported
71 stable branch.
72 If 'end' is empty or invalid, end list with last supported stable
73 branch.
74 """
75
76 offset = 0
77
78 if start:
79 # Extract numeric part of 'start'
80 start_match = version_baseline.match(start)
81 start = start_match.group(1) if start_match else None
82 if not start:
83 start = branches[0]
84 if end:
85 end_match = version_baseline.match(end)
86 end = end_match.group(1) if end_match else None
87 if not end:
88 end = branches[-1]
89 # If we have no 'end', also match the last branch
90 offset = 1
91
92 min_order = branch_order(start)
93 max_order = branch_order(end) + offset
94 return list(filter(lambda x: branch_order(x) >= min_order and branch_order(x) < max_order,
95 branches))
96
97
98def mysort(elem):
99 """Function to sort branches based on version number"""
100 _, branch = elem
101 return branch_order(branch)
102
103
104def report_integration_status_sha(repository, merge_base, branch_name, sha):
105 """Report integration status for given repository, branch, and sha"""
106
107 if repository is common.STABLE_PATH:
108 # It is possible that the patch has not yet been applied but is queued
109 # in a stable release candidate. Try to find it there as well. We use
110 # this information to override the final status if appropriate.
111 rc_metadata = common.get_kernel_metadata(common.Kernel.linux_stable_rc)
112 rc_status = git_interface.get_cherrypick_status(rc_metadata.path, merge_base,
113 branch_name, sha)
114 else:
115 rc_status = common.Status.OPEN
116
117 status = git_interface.get_cherrypick_status(repository, merge_base, branch_name, sha)
118 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'
133 branch_name_pattern = 'linux-%s.y'
134 else:
135 table = 'linux_chrome'
136 branch_name_pattern = 'chromeos-%s'
137
138 c = db.cursor()
139
140 status = 'CONFLICT' if conflicts else 'OPEN'
141
142 # Walk through all fixes table entries with given status
143 q = """SELECT ct.fixedby_upstream_sha, lu.description, t.upstream_sha, t.description
144 FROM {chosen_table} AS ct
145 JOIN linux_upstream AS lu
146 ON ct.fixedby_upstream_sha = lu.sha
147 JOIN {table} as t
148 ON ct.kernel_sha = t.sha
149 WHERE ct.status = %s
150 AND ct.branch = %s""".format(chosen_table=metadata.kernel_fixes_table, table=table)
151 c.execute(q, [status, branch])
152 for fixedby_sha, fixedby_description, fixes_sha, fixes_description in c.fetchall():
153 # If we already handled a SHA while running this command while examining
154 # another branch, we don't need to handle it again.
155 if fixedby_sha in handled_shas:
156 continue
157 handled_shas += [fixedby_sha]
158
159 print('Upstream commit %s ("%s")' % (fixedby_sha, fixedby_description))
160 integrated = git_interface.get_integrated_tag(fixedby_sha)
161 end = integrated
162 if not integrated:
163 integrated = 'ToT'
164 print(' upstream: %s' % integrated)
165 print(' Fixes: %s ("%s")' % (fixes_sha, fixes_description))
166
167 q = """SELECT sha, branch
168 FROM {table}
169 WHERE upstream_sha = %s""".format(table=table)
170 c.execute(q, [fixes_sha])
171 fix_rows = sorted(c.fetchall(), key=mysort)
172 affected_branches = []
173 for fix_sha, fix_branch in fix_rows:
174 if fix_branch in metadata.branches:
175 affected_branches += [fix_branch]
176 branch_name = branch_name_pattern % fix_branch
177 print(' in %s: %s' % (branch_name, fix_sha))
178
179 start = git_interface.get_integrated_tag(fixes_sha)
180 if start:
181 print(' upstream: %s' % git_interface.get_integrated_tag(fixes_sha))
182
183 affected_branches += version_list(metadata.branches, start, end)
184 affected_branches = sorted(list(set(affected_branches)), key=branch_order)
185 if affected_branches:
186 print(' Affected branches:')
187 for affected_branch in affected_branches:
188 merge_base = 'v%s' % affected_branch
189 branch_name = branch_name_pattern % affected_branch
190 report_integration_status_sha(metadata.path, merge_base, branch_name, fixedby_sha)
191
192
193@util.cloud_sql_proxy_decorator
194@util.preliminary_check_decorator(False)
195def report_integration_status(branch, conflicts, is_chromium):
196 """Report list of open patches"""
197
198 handled_shas = []
199
200 if is_chromium:
201 metadata = common.get_kernel_metadata(common.Kernel.linux_chrome)
202 else:
203 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
204
205 synchronize.synchronize_repositories(True)
206
207 db = MySQLdb.Connect(user='linux_patches_robot', host='127.0.0.1', db='linuxdb')
208
209 if branch:
210 report_integration_status_branch(db, metadata, handled_shas, branch, conflicts)
211 else:
212 for b in metadata.branches:
213 print('\nBranch: linux-%s.y\n' % b)
214 report_integration_status_branch(db, metadata, handled_shas, b, conflicts)
215
216 db.close()
217
218
219def report_integration_status_parse():
220 """Parses command line args and calls the actual function with parsed parameters
221
222 To execute:
223 ./getopen [-b branch] [-c] [-C]
224 """
225
226 metadata = common.get_kernel_metadata(common.Kernel.linux_stable)
227 parser = argparse.ArgumentParser(description='Local functions to retrieve data from database')
228 parser.add_argument('-b', '--branch', type=str, choices=metadata.branches,
229 help='Branch to check')
230 parser.add_argument('-C', '--chromium', action='store_true',
231 help='Look for pending chromium patches')
232 parser.add_argument('-c', '--conflicts', action='store_true',
233 help='Check for conflicting patches')
234 args = parser.parse_args()
235
236 report_integration_status(args.branch, args.conflicts, args.chromium)
237
238if __name__ == '__main__':
239 report_integration_status_parse()