f3d9c0c4348657540a1fdfb35621816b55080289
[oweals/u-boot.git] / tools / patman / main.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0+
3 #
4 # Copyright (c) 2011 The Chromium OS Authors.
5 #
6
7 """See README for more information"""
8
9 from optparse import OptionParser
10 import os
11 import re
12 import sys
13 import unittest
14
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, '..'))
19
20 # Our modules
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
29
30
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',
62                   default=True,
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')
70
71 parser.usage += """
72
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."""
75
76
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()
82
83 if __name__ != "__main__":
84     pass
85
86 # Run our meagre tests
87 elif options.test:
88     import doctest
89     from patman import func_test
90
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)
95         suite.run(result)
96
97     for module in ['gitutil', 'settings', 'terminal']:
98         suite = doctest.DocTestSuite(module)
99         suite.run(result)
100
101     # TODO: Surely we can just 'print' result?
102     print(result)
103     for test, err in result.errors:
104         print(err)
105     for test, err in result.failures:
106         print(err)
107
108 # Called from git with a patch filename as argument
109 # Printout a list of additional CC recipients for this patch
110 elif options.cc_cmd:
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'):
117                 cc = cc.strip()
118                 if cc:
119                     print(cc)
120     fd.close()
121
122 elif options.full_help:
123     pager = os.getenv('PAGER')
124     if not pager:
125         pager = 'more'
126     fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
127                          'README')
128     command.Run(pager, fname)
129
130 # Process commits, produce patches files, check them, email them
131 else:
132     gitutil.Setup()
133
134     if options.count == -1:
135         # Work out how many patches to send if we can
136         options.count = gitutil.CountCommitsToBranch() - options.start
137
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))
142
143     # Read the metadata from the commits
144     if options.count:
145         series = patchstream.GetMetaData(options.start, options.count)
146         cover_fname, args = gitutil.CreatePatches(options.start, options.count,
147                 series)
148
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)
153
154     # Do a few checks on the series
155     series.DoChecks()
156
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)
160     else:
161         ok = True
162
163     cc_file = series.MakeCcFile(options.process_tags, cover_fname,
164                                 not options.ignore_bad_tags,
165                                 options.add_maintainers, options.limit)
166
167     # Email the patches out (giving the user time to check / cancel)
168     cmd = ''
169     its_a_go = ok or options.ignore_errors
170     if its_a_go:
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)
175     else:
176         print(col.Color(col.RED, "Not sending emails due to errors/warnings"))
177
178     # For a dry run, just show our actions as a sanity check
179     if options.dry_run:
180         series.ShowActions(args, cmd, options.process_tags)
181         if not its_a_go:
182             print(col.Color(col.RED, "Email would not be sent"))
183
184     os.remove(cc_file)