patman: Support checking for review tags in patchwork
[platform/kernel/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 argparse import ArgumentParser
10 import os
11 import re
12 import sys
13 import traceback
14 import unittest
15
16 if __name__ == "__main__":
17     # Allow 'from patman import xxx to work'
18     our_path = os.path.dirname(os.path.realpath(__file__))
19     sys.path.append(os.path.join(our_path, '..'))
20
21 # Our modules
22 from patman import command
23 from patman import control
24 from patman import gitutil
25 from patman import project
26 from patman import settings
27 from patman import terminal
28 from patman import test_util
29 from patman import test_checkpatch
30
31 def AddCommonArgs(parser):
32     parser.add_argument('-b', '--branch', type=str,
33         help="Branch to process (by default, the current branch)")
34     parser.add_argument('-c', '--count', dest='count', type=int,
35         default=-1, help='Automatically create patches from top n commits')
36     parser.add_argument('-e', '--end', type=int, default=0,
37         help='Commits to skip at end of patch list')
38     parser.add_argument('-D', '--debug', action='store_true',
39         help='Enabling debugging (provides a full traceback on error)')
40     parser.add_argument('-s', '--start', dest='start', type=int,
41         default=0, help='Commit to start creating patches from (0 = HEAD)')
42
43 epilog = '''Create patches from commits in a branch, check them and email them
44 as specified by tags you place in the commits. Use -n to do a dry run first.'''
45
46 parser = ArgumentParser(epilog=epilog)
47 subparsers = parser.add_subparsers(dest='cmd')
48 send = subparsers.add_parser('send')
49 send.add_argument('-H', '--full-help', action='store_true', dest='full_help',
50        default=False, help='Display the README file')
51 send.add_argument('-i', '--ignore-errors', action='store_true',
52        dest='ignore_errors', default=False,
53        help='Send patches email even if patch errors are found')
54 send.add_argument('-l', '--limit-cc', dest='limit', type=int, default=None,
55        help='Limit the cc list to LIMIT entries [default: %(default)s]')
56 send.add_argument('-m', '--no-maintainers', action='store_false',
57        dest='add_maintainers', default=True,
58        help="Don't cc the file maintainers automatically")
59 send.add_argument('-n', '--dry-run', action='store_true', dest='dry_run',
60        default=False, help="Do a dry run (create but don't email patches)")
61 send.add_argument('-p', '--project', default=project.DetectProject(),
62                   help="Project name; affects default option values and "
63                   "aliases [default: %(default)s]")
64 send.add_argument('-r', '--in-reply-to', type=str, action='store',
65                   help="Message ID that this series is in reply to")
66 send.add_argument('-t', '--ignore-bad-tags', action='store_true',
67                   default=False, help='Ignore bad tags / aliases')
68 send.add_argument('-v', '--verbose', action='store_true', dest='verbose',
69        default=False, help='Verbose output of errors and warnings')
70 send.add_argument('-T', '--thread', action='store_true', dest='thread',
71                   default=False, help='Create patches as a single thread')
72 send.add_argument('--cc-cmd', dest='cc_cmd', type=str, action='store',
73        default=None, help='Output cc list for patch file (used by git)')
74 send.add_argument('--no-binary', action='store_true', dest='ignore_binary',
75                   default=False,
76                   help="Do not output contents of changes in binary files")
77 send.add_argument('--no-check', action='store_false', dest='check_patch',
78                   default=True,
79                   help="Don't check for patch compliance")
80 send.add_argument('--no-tags', action='store_false', dest='process_tags',
81                   default=True, help="Don't process subject tags as aliases")
82 send.add_argument('--smtp-server', type=str,
83                   help="Specify the SMTP server to 'git send-email'")
84 AddCommonArgs(send)
85
86 send.add_argument('patchfiles', nargs='*')
87
88 test_parser = subparsers.add_parser('test', help='Run tests')
89 test_parser.add_argument('testname', type=str, default=None, nargs='?',
90                          help="Specify the test to run")
91 AddCommonArgs(test_parser)
92
93 status = subparsers.add_parser('status',
94                                help='Check status of patches in patchwork')
95 AddCommonArgs(status)
96
97 # Parse options twice: first to get the project and second to handle
98 # defaults properly (which depends on project).
99 argv = sys.argv[1:]
100 if len(argv) < 1 or argv[0].startswith('-'):
101     argv = ['send'] + argv
102 args = parser.parse_args(argv)
103 if hasattr(args, 'project'):
104     settings.Setup(gitutil, send, args.project, '')
105     args = parser.parse_args(argv)
106
107 if __name__ != "__main__":
108     pass
109
110 if not args.debug:
111     sys.tracebacklimit = 0
112
113 # Run our meagre tests
114 if args.cmd == 'test':
115     import doctest
116     from patman import func_test
117
118     sys.argv = [sys.argv[0]]
119     result = unittest.TestResult()
120     suite = unittest.TestSuite()
121     loader = unittest.TestLoader()
122     for module in (test_checkpatch.TestPatch, func_test.TestFunctional):
123         if args.testname:
124             try:
125                 suite.addTests(loader.loadTestsFromName(args.testname, module))
126             except AttributeError:
127                 continue
128         else:
129             suite.addTests(loader.loadTestsFromTestCase(module))
130     suite.run(result)
131
132     for module in ['gitutil', 'settings', 'terminal']:
133         suite = doctest.DocTestSuite(module)
134         suite.run(result)
135
136     sys.exit(test_util.ReportResult('patman', args.testname, result))
137
138 # Process commits, produce patches files, check them, email them
139 elif args.cmd == 'send':
140     # Called from git with a patch filename as argument
141     # Printout a list of additional CC recipients for this patch
142     if args.cc_cmd:
143         fd = open(args.cc_cmd, 'r')
144         re_line = re.compile('(\S*) (.*)')
145         for line in fd.readlines():
146             match = re_line.match(line)
147             if match and match.group(1) == args.patchfiles[0]:
148                 for cc in match.group(2).split('\0'):
149                     cc = cc.strip()
150                     if cc:
151                         print(cc)
152         fd.close()
153
154     elif args.full_help:
155         pager = os.getenv('PAGER')
156         if not pager:
157             pager = 'more'
158         fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
159                              'README')
160         command.Run(pager, fname)
161
162     else:
163         control.send(args)
164
165 # Check status of patches in patchwork
166 elif args.cmd == 'status':
167     ret_code = 0
168     try:
169         control.patchwork_status(args.branch, args.count, args.start, args.end)
170     except Exception as e:
171         terminal.Print('patman: %s: %s' % (type(e).__name__, e),
172                        colour=terminal.Color.RED)
173         if args.debug:
174             print()
175             traceback.print_exc()
176         ret_code = 1
177     sys.exit(ret_code)