patman: Support updating a branch with review tags
[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 status.add_argument('-d', '--dest-branch', type=str,
96                     help='Name of branch to create with collected responses')
97 status.add_argument('-f', '--force', action='store_true',
98                     help='Force overwriting an existing branch')
99 AddCommonArgs(status)
100
101 # Parse options twice: first to get the project and second to handle
102 # defaults properly (which depends on project).
103 argv = sys.argv[1:]
104 if len(argv) < 1 or argv[0].startswith('-'):
105     argv = ['send'] + argv
106 args = parser.parse_args(argv)
107 if hasattr(args, 'project'):
108     settings.Setup(gitutil, send, args.project, '')
109     args = parser.parse_args(argv)
110
111 if __name__ != "__main__":
112     pass
113
114 if not args.debug:
115     sys.tracebacklimit = 0
116
117 # Run our meagre tests
118 if args.cmd == 'test':
119     import doctest
120     from patman import func_test
121
122     sys.argv = [sys.argv[0]]
123     result = unittest.TestResult()
124     suite = unittest.TestSuite()
125     loader = unittest.TestLoader()
126     for module in (test_checkpatch.TestPatch, func_test.TestFunctional):
127         if args.testname:
128             try:
129                 suite.addTests(loader.loadTestsFromName(args.testname, module))
130             except AttributeError:
131                 continue
132         else:
133             suite.addTests(loader.loadTestsFromTestCase(module))
134     suite.run(result)
135
136     for module in ['gitutil', 'settings', 'terminal']:
137         suite = doctest.DocTestSuite(module)
138         suite.run(result)
139
140     sys.exit(test_util.ReportResult('patman', args.testname, result))
141
142 # Process commits, produce patches files, check them, email them
143 elif args.cmd == 'send':
144     # Called from git with a patch filename as argument
145     # Printout a list of additional CC recipients for this patch
146     if args.cc_cmd:
147         fd = open(args.cc_cmd, 'r')
148         re_line = re.compile('(\S*) (.*)')
149         for line in fd.readlines():
150             match = re_line.match(line)
151             if match and match.group(1) == args.patchfiles[0]:
152                 for cc in match.group(2).split('\0'):
153                     cc = cc.strip()
154                     if cc:
155                         print(cc)
156         fd.close()
157
158     elif args.full_help:
159         pager = os.getenv('PAGER')
160         if not pager:
161             pager = 'more'
162         fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
163                              'README')
164         command.Run(pager, fname)
165
166     else:
167         control.send(args)
168
169 # Check status of patches in patchwork
170 elif args.cmd == 'status':
171     ret_code = 0
172     try:
173         control.patchwork_status(args.branch, args.count, args.start, args.end,
174                                  args.dest_branch, args.force)
175     except Exception as e:
176         terminal.Print('patman: %s: %s' % (type(e).__name__, e),
177                        colour=terminal.Color.RED)
178         if args.debug:
179             print()
180             traceback.print_exc()
181         ret_code = 1
182     sys.exit(ret_code)