1# SPDX-License-Identifier: GPL-2.0+ 2# 3# Copyright 2020 Google LLC 4# 5"""Handles the main control logic of patman 6 7This module provides various functions called by the main program to implement 8the features of patman. 9""" 10 11import re 12import traceback 13 14try: 15 from importlib import resources 16except ImportError: 17 # for Python 3.6 18 import importlib_resources as resources 19 20from u_boot_pylib import gitutil 21from u_boot_pylib import terminal 22from u_boot_pylib import tools 23from u_boot_pylib import tout 24from patman import cseries 25from patman import cser_helper 26from patman import patchstream 27from patman.patchwork import Patchwork 28from patman import send 29from patman import settings 30 31 32def setup(): 33 """Do required setup before doing anything""" 34 gitutil.setup() 35 alias_fname = gitutil.get_alias_file() 36 if alias_fname: 37 settings.ReadGitAliases(alias_fname) 38 39 40def do_send(args): 41 """Create, check and send patches by email 42 43 Args: 44 args (argparse.Namespace): Arguments to patman 45 """ 46 setup() 47 send.send(args) 48 49 50def patchwork_status(branch, count, start, end, dest_branch, force, 51 show_comments, url, single_thread=False): 52 """Check the status of patches in patchwork 53 54 This finds the series in patchwork using the Series-link tag, checks for new 55 comments and review tags, displays then and creates a new branch with the 56 review tags. 57 58 Args: 59 branch (str): Branch to create patches from (None = current) 60 count (int): Number of patches to produce, or -1 to produce patches for 61 the current branch back to the upstream commit 62 start (int): Start partch to use (0=first / top of branch) 63 end (int): End patch to use (0=last one in series, 1=one before that, 64 etc.) 65 dest_branch (str): Name of new branch to create with the updated tags 66 (None to not create a branch) 67 force (bool): With dest_branch, force overwriting an existing branch 68 show_comments (bool): True to display snippets from the comments 69 provided by reviewers 70 url (str): URL of patchwork server, e.g. 'https://patchwork.ozlabs.org'. 71 This is ignored if the series provides a Series-patchwork-url tag. 72 73 Raises: 74 ValueError: if the branch has no Series-link value 75 """ 76 if not branch: 77 branch = gitutil.get_branch() 78 if count == -1: 79 # Work out how many patches to send if we can 80 count = gitutil.count_commits_to_branch(branch) - start 81 82 series = patchstream.get_metadata(branch, start, count - end) 83 warnings = 0 84 for cmt in series.commits: 85 if cmt.warn: 86 print('%d warnings for %s:' % (len(cmt.warn), cmt.hash)) 87 for warn in cmt.warn: 88 print('\t', warn) 89 warnings += 1 90 print 91 if warnings: 92 raise ValueError('Please fix warnings before running status') 93 links = series.get('links') 94 if not links: 95 raise ValueError("Branch has no Series-links value") 96 97 _, version = cser_helper.split_name_version(branch) 98 link = series.get_link_for_version(version, links) 99 if not link: 100 raise ValueError('Series-links has no link for v{version}') 101 tout.debug(f"Link '{link}") 102 103 # Allow the series to override the URL 104 if 'patchwork_url' in series: 105 url = series.patchwork_url 106 pwork = Patchwork(url, single_thread=single_thread) 107 108 # Import this here to avoid failing on other commands if the dependencies 109 # are not present 110 from patman import status 111 pwork = Patchwork(url) 112 status.check_and_show_status(series, link, branch, dest_branch, force, 113 show_comments, False, pwork) 114 115 116def do_series(args, test_db=None, pwork=None, cser=None): 117 """Process a series subcommand 118 119 Args: 120 args (Namespace): Arguments to process 121 test_db (str or None): Directory containing the test database, None to 122 use the normal one 123 pwork (Patchwork): Patchwork object to use, None to create one if 124 needed 125 cser (Cseries): Cseries object to use, None to create one 126 """ 127 if not cser: 128 cser = cseries.Cseries(test_db) 129 needs_patchwork = [ 130 'autolink', 'autolink-all', 'open', 'send', 'status', 'gather', 131 'gather-all' 132 ] 133 try: 134 cser.open_database() 135 if args.subcmd in needs_patchwork: 136 if not pwork: 137 pwork = Patchwork(args.patchwork_url) 138 proj = cser.project_get() 139 if not proj: 140 raise ValueError( 141 "Please set project ID with 'patman patchwork set-project'") 142 _, proj_id, link_name = cser.project_get() 143 pwork.project_set(proj_id, link_name) 144 elif pwork and pwork is not True: 145 raise ValueError( 146 f"Internal error: command '{args.subcmd}' should not have patchwork") 147 if args.subcmd == 'add': 148 cser.add(args.series, args.desc, mark=args.mark, 149 allow_unmarked=args.allow_unmarked, end=args.upstream, 150 dry_run=args.dry_run) 151 elif args.subcmd == 'archive': 152 cser.archive(args.series) 153 elif args.subcmd == 'autolink': 154 cser.link_auto(pwork, args.series, args.version, args.update, 155 args.autolink_wait) 156 elif args.subcmd == 'autolink-all': 157 cser.link_auto_all(pwork, update_commit=args.update, 158 link_all_versions=args.link_all_versions, 159 replace_existing=args.replace_existing, 160 dry_run=args.dry_run, show_summary=True) 161 elif args.subcmd == 'dec': 162 cser.decrement(args.series, args.dry_run) 163 elif args.subcmd == 'gather': 164 cser.gather(pwork, args.series, args.version, args.show_comments, 165 args.show_cover_comments, args.gather_tags, 166 dry_run=args.dry_run) 167 elif args.subcmd == 'gather-all': 168 cser.gather_all( 169 pwork, args.show_comments, args.show_cover_comments, 170 args.gather_all_versions, args.gather_tags, args.dry_run) 171 elif args.subcmd == 'get-link': 172 link = cser.link_get(args.series, args.version) 173 print(link) 174 elif args.subcmd == 'inc': 175 cser.increment(args.series, args.dry_run) 176 elif args.subcmd == 'ls': 177 cser.series_list() 178 elif args.subcmd == 'open': 179 cser.open(pwork, args.series, args.version) 180 elif args.subcmd == 'mark': 181 cser.mark(args.series, args.allow_marked, dry_run=args.dry_run) 182 elif args.subcmd == 'patches': 183 cser.list_patches(args.series, args.version, args.commit, 184 args.patch) 185 elif args.subcmd == 'progress': 186 cser.progress(args.series, args.show_all_versions, 187 args.list_patches) 188 elif args.subcmd == 'rm': 189 cser.remove(args.series, dry_run=args.dry_run) 190 elif args.subcmd == 'rm-version': 191 cser.version_remove(args.series, args.version, dry_run=args.dry_run) 192 elif args.subcmd == 'rename': 193 cser.rename(args.series, args.new_name, dry_run=args.dry_run) 194 elif args.subcmd == 'scan': 195 cser.scan(args.series, mark=args.mark, 196 allow_unmarked=args.allow_unmarked, end=args.upstream, 197 dry_run=args.dry_run) 198 elif args.subcmd == 'send': 199 cser.send(pwork, args.series, args.autolink, args.autolink_wait, 200 args) 201 elif args.subcmd == 'set-link': 202 cser.link_set(args.series, args.version, args.link, args.update) 203 elif args.subcmd == 'status': 204 cser.status(pwork, args.series, args.version, args.show_comments, 205 args.show_cover_comments) 206 elif args.subcmd == 'summary': 207 cser.summary(args.series) 208 elif args.subcmd == 'unarchive': 209 cser.unarchive(args.series) 210 elif args.subcmd == 'unmark': 211 cser.unmark(args.series, args.allow_unmarked, dry_run=args.dry_run) 212 elif args.subcmd == 'version-change': 213 cser.version_change(args.series, args.version, args.new_version, 214 dry_run=args.dry_run) 215 else: 216 raise ValueError(f"Unknown series subcommand '{args.subcmd}'") 217 finally: 218 cser.close_database() 219 220 221def upstream(args, test_db=None): 222 """Process an 'upstream' subcommand 223 224 Args: 225 args (Namespace): Arguments to process 226 test_db (str or None): Directory containing the test database, None to 227 use the normal one 228 """ 229 cser = cseries.Cseries(test_db) 230 try: 231 cser.open_database() 232 if args.subcmd == 'add': 233 cser.upstream_add(args.remote_name, args.url) 234 elif args.subcmd == 'default': 235 if args.unset: 236 cser.upstream_set_default(None) 237 elif args.remote_name: 238 cser.upstream_set_default(args.remote_name) 239 else: 240 result = cser.upstream_get_default() 241 print(result if result else 'unset') 242 elif args.subcmd == 'delete': 243 cser.upstream_delete(args.remote_name) 244 elif args.subcmd == 'list': 245 cser.upstream_list() 246 else: 247 raise ValueError(f"Unknown upstream subcommand '{args.subcmd}'") 248 finally: 249 cser.close_database() 250 251 252def patchwork(args, test_db=None, pwork=None): 253 """Process a 'patchwork' subcommand 254 Args: 255 args (Namespace): Arguments to process 256 test_db (str or None): Directory containing the test database, None to 257 use the normal one 258 pwork (Patchwork): Patchwork object to use 259 """ 260 cser = cseries.Cseries(test_db) 261 try: 262 cser.open_database() 263 if args.subcmd == 'set-project': 264 if not pwork: 265 pwork = Patchwork(args.patchwork_url) 266 cser.project_set(pwork, args.project_name) 267 elif args.subcmd == 'get-project': 268 info = cser.project_get() 269 if not info: 270 raise ValueError("Project has not been set; use 'patman patchwork set-project'") 271 name, pwid, link_name = info 272 print(f"Project '{name}' patchwork-ID {pwid} link-name {link_name}") 273 else: 274 raise ValueError(f"Unknown patchwork subcommand '{args.subcmd}'") 275 finally: 276 cser.close_database() 277 278def do_patman(args, test_db=None, pwork=None, cser=None): 279 """Process a patman command 280 281 Args: 282 args (Namespace): Arguments to process 283 test_db (str or None): Directory containing the test database, None to 284 use the normal one 285 pwork (Patchwork): Patchwork object to use, or None to create one 286 cser (Cseries): Cseries object to use when executing the command, 287 or None to create one 288 """ 289 if args.full_help: 290 with resources.path('patman', 'README.rst') as readme: 291 tools.print_full_help(str(readme)) 292 return 0 293 if args.cmd == 'send': 294 # Called from git with a patch filename as argument 295 # Printout a list of additional CC recipients for this patch 296 if args.cc_cmd: 297 re_line = re.compile(r'(\S*) (.*)') 298 with open(args.cc_cmd, 'r', encoding='utf-8') as inf: 299 for line in inf.readlines(): 300 match = re_line.match(line) 301 if match and match.group(1) == args.patchfiles[0]: 302 for cca in match.group(2).split('\0'): 303 cca = cca.strip() 304 if cca: 305 print(cca) 306 else: 307 # If we are not processing tags, no need to warning about bad ones 308 if not args.process_tags: 309 args.ignore_bad_tags = True 310 do_send(args) 311 return 0 312 313 ret_code = 0 314 try: 315 # Check status of patches in patchwork 316 if args.cmd == 'status': 317 patchwork_status(args.branch, args.count, args.start, args.end, 318 args.dest_branch, args.force, args.show_comments, 319 args.patchwork_url) 320 elif args.cmd == 'series': 321 do_series(args, test_db, pwork, cser) 322 elif args.cmd == 'upstream': 323 upstream(args, test_db) 324 elif args.cmd == 'patchwork': 325 patchwork(args, test_db, pwork) 326 except Exception as exc: 327 terminal.tprint(f'patman: {type(exc).__name__}: {exc}', 328 colour=terminal.Color.RED) 329 if args.debug: 330 print() 331 traceback.print_exc() 332 ret_code = 1 333 return ret_code 334