__main__.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/usr/bin/env python3
  2. # SPDX-License-Identifier: GPL-2.0+
  3. #
  4. # Copyright (c) 2011 The Chromium OS Authors.
  5. #
  6. """See README for more information"""
  7. from argparse import ArgumentParser
  8. try:
  9. import importlib.resources
  10. except ImportError:
  11. # for Python 3.6
  12. import importlib_resources
  13. import os
  14. import re
  15. import sys
  16. import traceback
  17. if __name__ == "__main__":
  18. # Allow 'from patman import xxx to work'
  19. our_path = os.path.dirname(os.path.realpath(__file__))
  20. sys.path.append(os.path.join(our_path, '..'))
  21. # Our modules
  22. from patman import control
  23. from patman import func_test
  24. from patman import gitutil
  25. from patman import project
  26. from patman import settings
  27. from u_boot_pylib import terminal
  28. from u_boot_pylib import test_util
  29. from u_boot_pylib import tools
  30. epilog = '''Create patches from commits in a branch, check them and email them
  31. as specified by tags you place in the commits. Use -n to do a dry run first.'''
  32. parser = ArgumentParser(epilog=epilog)
  33. parser.add_argument('-b', '--branch', type=str,
  34. help="Branch to process (by default, the current branch)")
  35. parser.add_argument('-c', '--count', dest='count', type=int,
  36. default=-1, help='Automatically create patches from top n commits')
  37. parser.add_argument('-e', '--end', type=int, default=0,
  38. help='Commits to skip at end of patch list')
  39. parser.add_argument('-D', '--debug', action='store_true',
  40. help='Enabling debugging (provides a full traceback on error)')
  41. parser.add_argument('-p', '--project', default=project.detect_project(),
  42. help="Project name; affects default option values and "
  43. "aliases [default: %(default)s]")
  44. parser.add_argument('-P', '--patchwork-url',
  45. default='https://patchwork.ozlabs.org',
  46. help='URL of patchwork server [default: %(default)s]')
  47. parser.add_argument('-s', '--start', dest='start', type=int,
  48. default=0, help='Commit to start creating patches from (0 = HEAD)')
  49. parser.add_argument('-v', '--verbose', action='store_true', dest='verbose',
  50. default=False, help='Verbose output of errors and warnings')
  51. parser.add_argument('-H', '--full-help', action='store_true', dest='full_help',
  52. default=False, help='Display the README file')
  53. subparsers = parser.add_subparsers(dest='cmd')
  54. send = subparsers.add_parser(
  55. 'send', help='Format, check and email patches (default command)')
  56. send.add_argument('-i', '--ignore-errors', action='store_true',
  57. dest='ignore_errors', default=False,
  58. help='Send patches email even if patch errors are found')
  59. send.add_argument('-l', '--limit-cc', dest='limit', type=int, default=None,
  60. help='Limit the cc list to LIMIT entries [default: %(default)s]')
  61. send.add_argument('-m', '--no-maintainers', action='store_false',
  62. dest='add_maintainers', default=True,
  63. help="Don't cc the file maintainers automatically")
  64. send.add_argument(
  65. '--get-maintainer-script', dest='get_maintainer_script', type=str,
  66. action='store',
  67. default=os.path.join(gitutil.get_top_level(), 'scripts',
  68. 'get_maintainer.pl') + ' --norolestats',
  69. help='File name of the get_maintainer.pl (or compatible) script.')
  70. send.add_argument('-n', '--dry-run', action='store_true', dest='dry_run',
  71. default=False, help="Do a dry run (create but don't email patches)")
  72. send.add_argument('-r', '--in-reply-to', type=str, action='store',
  73. help="Message ID that this series is in reply to")
  74. send.add_argument('-t', '--ignore-bad-tags', action='store_true',
  75. default=False,
  76. help='Ignore bad tags / aliases (default=warn)')
  77. send.add_argument('-T', '--thread', action='store_true', dest='thread',
  78. default=False, help='Create patches as a single thread')
  79. send.add_argument('--cc-cmd', dest='cc_cmd', type=str, action='store',
  80. default=None, help='Output cc list for patch file (used by git)')
  81. send.add_argument('--no-binary', action='store_true', dest='ignore_binary',
  82. default=False,
  83. help="Do not output contents of changes in binary files")
  84. send.add_argument('--no-check', action='store_false', dest='check_patch',
  85. default=True,
  86. help="Don't check for patch compliance")
  87. send.add_argument('--tree', dest='check_patch_use_tree', default=False,
  88. action='store_true',
  89. help=("Set `tree` to True. If `tree` is False then we'll "
  90. "pass '--no-tree' to checkpatch (default: tree=%(default)s)"))
  91. send.add_argument('--no-tree', dest='check_patch_use_tree',
  92. action='store_false', help="Set `tree` to False")
  93. send.add_argument('--no-tags', action='store_false', dest='process_tags',
  94. default=True, help="Don't process subject tags as aliases")
  95. send.add_argument('--no-signoff', action='store_false', dest='add_signoff',
  96. default=True, help="Don't add Signed-off-by to patches")
  97. send.add_argument('--smtp-server', type=str,
  98. help="Specify the SMTP server to 'git send-email'")
  99. send.add_argument('patchfiles', nargs='*')
  100. # Only add the 'test' action if the test data files are available.
  101. if os.path.exists(func_test.TEST_DATA_DIR):
  102. test_parser = subparsers.add_parser('test', help='Run tests')
  103. test_parser.add_argument('testname', type=str, default=None, nargs='?',
  104. help="Specify the test to run")
  105. status = subparsers.add_parser('status',
  106. help='Check status of patches in patchwork')
  107. status.add_argument('-C', '--show-comments', action='store_true',
  108. help='Show comments from each patch')
  109. status.add_argument('-d', '--dest-branch', type=str,
  110. help='Name of branch to create with collected responses')
  111. status.add_argument('-f', '--force', action='store_true',
  112. help='Force overwriting an existing branch')
  113. # Parse options twice: first to get the project and second to handle
  114. # defaults properly (which depends on project)
  115. # Use parse_known_args() in case 'cmd' is omitted
  116. argv = sys.argv[1:]
  117. args, rest = parser.parse_known_args(argv)
  118. if hasattr(args, 'project'):
  119. settings.Setup(parser, args.project)
  120. args, rest = parser.parse_known_args(argv)
  121. # If we have a command, it is safe to parse all arguments
  122. if args.cmd:
  123. args = parser.parse_args(argv)
  124. else:
  125. # No command, so insert it after the known arguments and before the ones
  126. # that presumably relate to the 'send' subcommand
  127. nargs = len(rest)
  128. argv = argv[:-nargs] + ['send'] + rest
  129. args = parser.parse_args(argv)
  130. if __name__ != "__main__":
  131. pass
  132. if not args.debug:
  133. sys.tracebacklimit = 0
  134. # Run our meagre tests
  135. if args.cmd == 'test':
  136. from patman import func_test
  137. from patman import test_checkpatch
  138. result = test_util.run_test_suites(
  139. 'patman', False, False, False, None, None, None,
  140. [test_checkpatch.TestPatch, func_test.TestFunctional,
  141. 'gitutil', 'settings'])
  142. sys.exit(0 if result.wasSuccessful() else 1)
  143. # Process commits, produce patches files, check them, email them
  144. elif args.cmd == 'send':
  145. # Called from git with a patch filename as argument
  146. # Printout a list of additional CC recipients for this patch
  147. if args.cc_cmd:
  148. fd = open(args.cc_cmd, 'r')
  149. re_line = re.compile('(\S*) (.*)')
  150. for line in fd.readlines():
  151. match = re_line.match(line)
  152. if match and match.group(1) == args.patchfiles[0]:
  153. for cc in match.group(2).split('\0'):
  154. cc = cc.strip()
  155. if cc:
  156. print(cc)
  157. fd.close()
  158. elif args.full_help:
  159. with importlib.resources.path('patman', 'README.rst') as readme:
  160. tools.print_full_help(str(readme))
  161. else:
  162. # If we are not processing tags, no need to warning about bad ones
  163. if not args.process_tags:
  164. args.ignore_bad_tags = True
  165. control.send(args)
  166. # Check status of patches in patchwork
  167. elif args.cmd == 'status':
  168. ret_code = 0
  169. try:
  170. control.patchwork_status(args.branch, args.count, args.start, args.end,
  171. args.dest_branch, args.force,
  172. args.show_comments, args.patchwork_url)
  173. except Exception as e:
  174. terminal.tprint('patman: %s: %s' % (type(e).__name__, e),
  175. colour=terminal.Color.RED)
  176. if args.debug:
  177. print()
  178. traceback.print_exc()
  179. ret_code = 1
  180. sys.exit(ret_code)