2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (c) 2011 The Chromium OS Authors.
7 """See README for more information"""
9 from optparse import OptionParser
15 if __name__ == "__main__":
16 # Allow 'from patman import xxx to work'
17 our_path = os.path.dirname(os.path.realpath(__file__))
18 sys.path.append(os.path.join(our_path, '..'))
21 from patman import checkpatch
22 from patman import command
23 from patman import gitutil
24 from patman import patchstream
25 from patman import project
26 from patman import settings
27 from patman import terminal
28 from patman import test
31 parser = OptionParser()
32 parser.add_option('-H', '--full-help', action='store_true', dest='full_help',
33 default=False, help='Display the README file')
34 parser.add_option('-c', '--count', dest='count', type='int',
35 default=-1, help='Automatically create patches from top n commits')
36 parser.add_option('-i', '--ignore-errors', action='store_true',
37 dest='ignore_errors', default=False,
38 help='Send patches email even if patch errors are found')
39 parser.add_option('-m', '--no-maintainers', action='store_false',
40 dest='add_maintainers', default=True,
41 help="Don't cc the file maintainers automatically")
42 parser.add_option('-l', '--limit-cc', dest='limit', type='int',
43 default=None, help='Limit the cc list to LIMIT entries [default: %default]')
44 parser.add_option('-n', '--dry-run', action='store_true', dest='dry_run',
45 default=False, help="Do a dry run (create but don't email patches)")
46 parser.add_option('-p', '--project', default=project.DetectProject(),
47 help="Project name; affects default option values and "
48 "aliases [default: %default]")
49 parser.add_option('-r', '--in-reply-to', type='string', action='store',
50 help="Message ID that this series is in reply to")
51 parser.add_option('-s', '--start', dest='start', type='int',
52 default=0, help='Commit to start creating patches from (0 = HEAD)')
53 parser.add_option('-t', '--ignore-bad-tags', action='store_true',
54 default=False, help='Ignore bad tags / aliases')
55 parser.add_option('--test', action='store_true', dest='test',
56 default=False, help='run tests')
57 parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
58 default=False, help='Verbose output of errors and warnings')
59 parser.add_option('--cc-cmd', dest='cc_cmd', type='string', action='store',
60 default=None, help='Output cc list for patch file (used by git)')
61 parser.add_option('--no-check', action='store_false', dest='check_patch',
63 help="Don't check for patch compliance")
64 parser.add_option('--no-tags', action='store_false', dest='process_tags',
65 default=True, help="Don't process subject tags as aliaes")
66 parser.add_option('--smtp-server', type='str',
67 help="Specify the SMTP server to 'git send-email'")
68 parser.add_option('-T', '--thread', action='store_true', dest='thread',
69 default=False, help='Create patches as a single thread')
73 Create patches from commits in a branch, check them and email them as
74 specified by tags you place in the commits. Use -n to do a dry run first."""
77 # Parse options twice: first to get the project and second to handle
78 # defaults properly (which depends on project).
79 (options, args) = parser.parse_args()
80 settings.Setup(parser, options.project, '')
81 (options, args) = parser.parse_args()
83 if __name__ != "__main__":
86 # Run our meagre tests
89 from patman import func_test
91 sys.argv = [sys.argv[0]]
92 result = unittest.TestResult()
93 for module in (test.TestPatch, func_test.TestFunctional):
94 suite = unittest.TestLoader().loadTestsFromTestCase(module)
97 for module in ['gitutil', 'settings', 'terminal']:
98 suite = doctest.DocTestSuite(module)
101 # TODO: Surely we can just 'print' result?
103 for test, err in result.errors:
105 for test, err in result.failures:
108 # Called from git with a patch filename as argument
109 # Printout a list of additional CC recipients for this patch
111 fd = open(options.cc_cmd, 'r')
112 re_line = re.compile('(\S*) (.*)')
113 for line in fd.readlines():
114 match = re_line.match(line)
115 if match and match.group(1) == args[0]:
116 for cc in match.group(2).split('\0'):
122 elif options.full_help:
123 pager = os.getenv('PAGER')
126 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
128 command.Run(pager, fname)
130 # Process commits, produce patches files, check them, email them
134 if options.count == -1:
135 # Work out how many patches to send if we can
136 options.count = gitutil.CountCommitsToBranch() - options.start
138 col = terminal.Color()
139 if not options.count:
140 str = 'No commits found to process - please use -c flag'
141 sys.exit(col.Color(col.RED, str))
143 # Read the metadata from the commits
145 series = patchstream.GetMetaData(options.start, options.count)
146 cover_fname, args = gitutil.CreatePatches(options.start, options.count,
149 # Fix up the patch files to our liking, and insert the cover letter
150 patchstream.FixPatches(series, args)
151 if cover_fname and series.get('cover'):
152 patchstream.InsertCoverLetter(cover_fname, series, options.count)
154 # Do a few checks on the series
157 # Check the patches, and run them through 'git am' just to be sure
158 if options.check_patch:
159 ok = checkpatch.CheckPatches(options.verbose, args)
163 cc_file = series.MakeCcFile(options.process_tags, cover_fname,
164 not options.ignore_bad_tags,
165 options.add_maintainers, options.limit)
167 # Email the patches out (giving the user time to check / cancel)
169 its_a_go = ok or options.ignore_errors
171 cmd = gitutil.EmailPatches(series, cover_fname, args,
172 options.dry_run, not options.ignore_bad_tags, cc_file,
173 in_reply_to=options.in_reply_to, thread=options.thread,
174 smtp_server=options.smtp_server)
176 print(col.Color(col.RED, "Not sending emails due to errors/warnings"))
178 # For a dry run, just show our actions as a sanity check
180 series.ShowActions(args, cmd, options.process_tags)
182 print(col.Color(col.RED, "Email would not be sent"))