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 os 12import sys 13 14from patman import checkpatch 15from patman import gitutil 16from patman import patchstream 17from u_boot_pylib import terminal 18 19def setup(): 20 """Do required setup before doing anything""" 21 gitutil.setup() 22 23def prepare_patches(col, branch, count, start, end, ignore_binary, signoff): 24 """Figure out what patches to generate, then generate them 25 26 The patch files are written to the current directory, e.g. 0001_xxx.patch 27 0002_yyy.patch 28 29 Args: 30 col (terminal.Color): Colour output object 31 branch (str): Branch to create patches from (None = current) 32 count (int): Number of patches to produce, or -1 to produce patches for 33 the current branch back to the upstream commit 34 start (int): Start partch to use (0=first / top of branch) 35 end (int): End patch to use (0=last one in series, 1=one before that, 36 etc.) 37 ignore_binary (bool): Don't generate patches for binary files 38 39 Returns: 40 Tuple: 41 Series object for this series (set of patches) 42 Filename of the cover letter as a string (None if none) 43 patch_files: List of patch filenames, each a string, e.g. 44 ['0001_xxx.patch', '0002_yyy.patch'] 45 """ 46 if count == -1: 47 # Work out how many patches to send if we can 48 count = (gitutil.count_commits_to_branch(branch) - start) 49 50 if not count: 51 str = 'No commits found to process - please use -c flag, or run:\n' \ 52 ' git branch --set-upstream-to remote/branch' 53 sys.exit(col.build(col.RED, str)) 54 55 # Read the metadata from the commits 56 to_do = count - end 57 series = patchstream.get_metadata(branch, start, to_do) 58 cover_fname, patch_files = gitutil.create_patches( 59 branch, start, to_do, ignore_binary, series, signoff) 60 61 # Fix up the patch files to our liking, and insert the cover letter 62 patchstream.fix_patches(series, patch_files) 63 if cover_fname and series.get('cover'): 64 patchstream.insert_cover_letter(cover_fname, series, to_do) 65 return series, cover_fname, patch_files 66 67def check_patches(series, patch_files, run_checkpatch, verbose, use_tree): 68 """Run some checks on a set of patches 69 70 This santiy-checks the patman tags like Series-version and runs the patches 71 through checkpatch 72 73 Args: 74 series (Series): Series object for this series (set of patches) 75 patch_files (list): List of patch filenames, each a string, e.g. 76 ['0001_xxx.patch', '0002_yyy.patch'] 77 run_checkpatch (bool): True to run checkpatch.pl 78 verbose (bool): True to print out every line of the checkpatch output as 79 it is parsed 80 use_tree (bool): If False we'll pass '--no-tree' to checkpatch. 81 82 Returns: 83 bool: True if the patches had no errors, False if they did 84 """ 85 # Do a few checks on the series 86 series.DoChecks() 87 88 # Check the patches 89 if run_checkpatch: 90 ok = checkpatch.check_patches(verbose, patch_files, use_tree) 91 else: 92 ok = True 93 return ok 94 95 96def email_patches(col, series, cover_fname, patch_files, process_tags, its_a_go, 97 ignore_bad_tags, add_maintainers, get_maintainer_script, limit, 98 dry_run, in_reply_to, thread, smtp_server): 99 """Email patches to the recipients 100 101 This emails out the patches and cover letter using 'git send-email'. Each 102 patch is copied to recipients identified by the patch tag and output from 103 the get_maintainer.pl script. The cover letter is copied to all recipients 104 of any patch. 105 106 To make this work a CC file is created holding the recipients for each patch 107 and the cover letter. See the main program 'cc_cmd' for this logic. 108 109 Args: 110 col (terminal.Color): Colour output object 111 series (Series): Series object for this series (set of patches) 112 cover_fname (str): Filename of the cover letter as a string (None if 113 none) 114 patch_files (list): List of patch filenames, each a string, e.g. 115 ['0001_xxx.patch', '0002_yyy.patch'] 116 process_tags (bool): True to process subject tags in each patch, e.g. 117 for 'dm: spi: Add SPI support' this would be 'dm' and 'spi'. The 118 tags are looked up in the configured sendemail.aliasesfile and also 119 in ~/.patman (see README) 120 its_a_go (bool): True if we are going to actually send the patches, 121 False if the patches have errors and will not be sent unless 122 @ignore_errors 123 ignore_bad_tags (bool): True to just print a warning for unknown tags, 124 False to halt with an error 125 add_maintainers (bool): Run the get_maintainer.pl script for each patch 126 get_maintainer_script (str): The script used to retrieve which 127 maintainers to cc 128 limit (int): Limit on the number of people that can be cc'd on a single 129 patch or the cover letter (None if no limit) 130 dry_run (bool): Don't actually email the patches, just print out what 131 would be sent 132 in_reply_to (str): If not None we'll pass this to git as --in-reply-to. 133 Should be a message ID that this is in reply to. 134 thread (bool): True to add --thread to git send-email (make all patches 135 reply to cover-letter or first patch in series) 136 smtp_server (str): SMTP server to use to send patches (None for default) 137 """ 138 cc_file = series.MakeCcFile(process_tags, cover_fname, not ignore_bad_tags, 139 add_maintainers, limit, get_maintainer_script) 140 141 # Email the patches out (giving the user time to check / cancel) 142 cmd = '' 143 if its_a_go: 144 cmd = gitutil.email_patches( 145 series, cover_fname, patch_files, dry_run, not ignore_bad_tags, 146 cc_file, in_reply_to=in_reply_to, thread=thread, 147 smtp_server=smtp_server) 148 else: 149 print(col.build(col.RED, "Not sending emails due to errors/warnings")) 150 151 # For a dry run, just show our actions as a sanity check 152 if dry_run: 153 series.ShowActions(patch_files, cmd, process_tags) 154 if not its_a_go: 155 print(col.build(col.RED, "Email would not be sent")) 156 157 os.remove(cc_file) 158 159def send(args): 160 """Create, check and send patches by email 161 162 Args: 163 args (argparse.Namespace): Arguments to patman 164 """ 165 setup() 166 col = terminal.Color() 167 series, cover_fname, patch_files = prepare_patches( 168 col, args.branch, args.count, args.start, args.end, 169 args.ignore_binary, args.add_signoff) 170 ok = check_patches(series, patch_files, args.check_patch, 171 args.verbose, args.check_patch_use_tree) 172 173 ok = ok and gitutil.check_suppress_cc_config() 174 175 its_a_go = ok or args.ignore_errors 176 email_patches( 177 col, series, cover_fname, patch_files, args.process_tags, 178 its_a_go, args.ignore_bad_tags, args.add_maintainers, 179 args.get_maintainer_script, args.limit, args.dry_run, 180 args.in_reply_to, args.thread, args.smtp_server) 181 182def patchwork_status(branch, count, start, end, dest_branch, force, 183 show_comments, url): 184 """Check the status of patches in patchwork 185 186 This finds the series in patchwork using the Series-link tag, checks for new 187 comments and review tags, displays then and creates a new branch with the 188 review tags. 189 190 Args: 191 branch (str): Branch to create patches from (None = current) 192 count (int): Number of patches to produce, or -1 to produce patches for 193 the current branch back to the upstream commit 194 start (int): Start partch to use (0=first / top of branch) 195 end (int): End patch to use (0=last one in series, 1=one before that, 196 etc.) 197 dest_branch (str): Name of new branch to create with the updated tags 198 (None to not create a branch) 199 force (bool): With dest_branch, force overwriting an existing branch 200 show_comments (bool): True to display snippets from the comments 201 provided by reviewers 202 url (str): URL of patchwork server, e.g. 'https://patchwork.ozlabs.org'. 203 This is ignored if the series provides a Series-patchwork-url tag. 204 205 Raises: 206 ValueError: if the branch has no Series-link value 207 """ 208 if count == -1: 209 # Work out how many patches to send if we can 210 count = (gitutil.count_commits_to_branch(branch) - start) 211 212 series = patchstream.get_metadata(branch, start, count - end) 213 warnings = 0 214 for cmt in series.commits: 215 if cmt.warn: 216 print('%d warnings for %s:' % (len(cmt.warn), cmt.hash)) 217 for warn in cmt.warn: 218 print('\t', warn) 219 warnings += 1 220 print 221 if warnings: 222 raise ValueError('Please fix warnings before running status') 223 links = series.get('links') 224 if not links: 225 raise ValueError("Branch has no Series-links value") 226 227 # Find the link without a version number (we don't support versions yet) 228 found = [link for link in links.split() if not ':' in link] 229 if not found: 230 raise ValueError('Series-links has no current version (without :)') 231 232 # Allow the series to override the URL 233 if 'patchwork_url' in series: 234 url = series.patchwork_url 235 236 # Import this here to avoid failing on other commands if the dependencies 237 # are not present 238 from patman import status 239 status.check_patchwork_status(series, found[0], branch, dest_branch, force, 240 show_comments, url) 241