control.py 9.6 KB

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