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_checkpatch
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('-l', '--limit-cc', dest='limit', type='int',
40 default=None, help='Limit the cc list to LIMIT entries [default: %default]')
41 parser.add_option('-m', '--no-maintainers', action='store_false',
42 dest='add_maintainers', default=True,
43 help="Don't cc the file maintainers automatically")
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('-v', '--verbose', action='store_true', dest='verbose',
56 default=False, help='Verbose output of errors and warnings')
57 parser.add_option('-T', '--thread', action='store_true', dest='thread',
58 default=False, help='Create patches as a single thread')
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-binary', action='store_true', dest='ignore_binary',
63 help="Do not output contents of changes in binary files")
64 parser.add_option('--no-check', action='store_false', dest='check_patch',
66 help="Don't check for patch compliance")
67 parser.add_option('--no-tags', action='store_false', dest='process_tags',
68 default=True, help="Don't process subject tags as aliases")
69 parser.add_option('--smtp-server', type='str',
70 help="Specify the SMTP server to 'git send-email'")
71 parser.add_option('--test', action='store_true', dest='test',
72 default=False, help='run tests')
76 Create patches from commits in a branch, check them and email them as
77 specified by tags you place in the commits. Use -n to do a dry run first."""
80 # Parse options twice: first to get the project and second to handle
81 # defaults properly (which depends on project).
82 (options, args) = parser.parse_args()
83 settings.Setup(gitutil, parser, options.project, '')
84 (options, args) = parser.parse_args()
86 if __name__ != "__main__":
89 # Run our meagre tests
92 from patman import func_test
94 sys.argv = [sys.argv[0]]
95 result = unittest.TestResult()
96 for module in (test_checkpatch.TestPatch, func_test.TestFunctional):
97 suite = unittest.TestLoader().loadTestsFromTestCase(module)
100 for module in ['gitutil', 'settings', 'terminal']:
101 suite = doctest.DocTestSuite(module)
104 # TODO: Surely we can just 'print' result?
106 for test, err in result.errors:
108 for test, err in result.failures:
111 # Called from git with a patch filename as argument
112 # Printout a list of additional CC recipients for this patch
114 fd = open(options.cc_cmd, 'r')
115 re_line = re.compile('(\S*) (.*)')
116 for line in fd.readlines():
117 match = re_line.match(line)
118 if match and match.group(1) == args[0]:
119 for cc in match.group(2).split('\0'):
125 elif options.full_help:
126 pager = os.getenv('PAGER')
129 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
131 command.Run(pager, fname)
133 # Process commits, produce patches files, check them, email them
137 if options.count == -1:
138 # Work out how many patches to send if we can
139 options.count = gitutil.CountCommitsToBranch() - options.start
141 col = terminal.Color()
142 if not options.count:
143 str = 'No commits found to process - please use -c flag'
144 sys.exit(col.Color(col.RED, str))
146 # Read the metadata from the commits
148 series = patchstream.GetMetaData(options.start, options.count)
149 cover_fname, args = gitutil.CreatePatches(options.start, options.count,
150 options.ignore_binary, series)
152 # Fix up the patch files to our liking, and insert the cover letter
153 patchstream.FixPatches(series, args)
154 if cover_fname and series.get('cover'):
155 patchstream.InsertCoverLetter(cover_fname, series, options.count)
157 # Do a few checks on the series
160 # Check the patches, and run them through 'git am' just to be sure
161 if options.check_patch:
162 ok = checkpatch.CheckPatches(options.verbose, args)
166 cc_file = series.MakeCcFile(options.process_tags, cover_fname,
167 not options.ignore_bad_tags,
168 options.add_maintainers, options.limit)
170 # Email the patches out (giving the user time to check / cancel)
172 its_a_go = ok or options.ignore_errors
174 cmd = gitutil.EmailPatches(series, cover_fname, args,
175 options.dry_run, not options.ignore_bad_tags, cc_file,
176 in_reply_to=options.in_reply_to, thread=options.thread,
177 smtp_server=options.smtp_server)
179 print(col.Color(col.RED, "Not sending emails due to errors/warnings"))
181 # For a dry run, just show our actions as a sanity check
183 series.ShowActions(args, cmd, options.process_tags)
185 print(col.Color(col.RED, "Email would not be sent"))