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