1*4882a593Smuzhiyun#!/usr/bin/env python2 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Copyright (c) 2011 The Chromium OS Authors. 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0+ 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun 8*4882a593Smuzhiyun"""See README for more information""" 9*4882a593Smuzhiyun 10*4882a593Smuzhiyunfrom optparse import OptionParser 11*4882a593Smuzhiyunimport os 12*4882a593Smuzhiyunimport re 13*4882a593Smuzhiyunimport sys 14*4882a593Smuzhiyunimport unittest 15*4882a593Smuzhiyun 16*4882a593Smuzhiyun# Our modules 17*4882a593Smuzhiyuntry: 18*4882a593Smuzhiyun from patman import checkpatch, command, gitutil, patchstream, \ 19*4882a593Smuzhiyun project, settings, terminal, test 20*4882a593Smuzhiyunexcept ImportError: 21*4882a593Smuzhiyun import checkpatch 22*4882a593Smuzhiyun import command 23*4882a593Smuzhiyun import gitutil 24*4882a593Smuzhiyun import patchstream 25*4882a593Smuzhiyun import project 26*4882a593Smuzhiyun import settings 27*4882a593Smuzhiyun import terminal 28*4882a593Smuzhiyun import test 29*4882a593Smuzhiyun 30*4882a593Smuzhiyun 31*4882a593Smuzhiyunparser = OptionParser() 32*4882a593Smuzhiyunparser.add_option('-H', '--full-help', action='store_true', dest='full_help', 33*4882a593Smuzhiyun default=False, help='Display the README file') 34*4882a593Smuzhiyunparser.add_option('-c', '--count', dest='count', type='int', 35*4882a593Smuzhiyun default=-1, help='Automatically create patches from top n commits') 36*4882a593Smuzhiyunparser.add_option('-i', '--ignore-errors', action='store_true', 37*4882a593Smuzhiyun dest='ignore_errors', default=False, 38*4882a593Smuzhiyun help='Send patches email even if patch errors are found') 39*4882a593Smuzhiyunparser.add_option('-m', '--no-maintainers', action='store_false', 40*4882a593Smuzhiyun dest='add_maintainers', default=True, 41*4882a593Smuzhiyun help="Don't cc the file maintainers automatically") 42*4882a593Smuzhiyunparser.add_option('-n', '--dry-run', action='store_true', dest='dry_run', 43*4882a593Smuzhiyun default=False, help="Do a dry run (create but don't email patches)") 44*4882a593Smuzhiyunparser.add_option('-p', '--project', default=project.DetectProject(), 45*4882a593Smuzhiyun help="Project name; affects default option values and " 46*4882a593Smuzhiyun "aliases [default: %default]") 47*4882a593Smuzhiyunparser.add_option('-r', '--in-reply-to', type='string', action='store', 48*4882a593Smuzhiyun help="Message ID that this series is in reply to") 49*4882a593Smuzhiyunparser.add_option('-s', '--start', dest='start', type='int', 50*4882a593Smuzhiyun default=0, help='Commit to start creating patches from (0 = HEAD)') 51*4882a593Smuzhiyunparser.add_option('-t', '--ignore-bad-tags', action='store_true', 52*4882a593Smuzhiyun default=False, help='Ignore bad tags / aliases') 53*4882a593Smuzhiyunparser.add_option('--test', action='store_true', dest='test', 54*4882a593Smuzhiyun default=False, help='run tests') 55*4882a593Smuzhiyunparser.add_option('-v', '--verbose', action='store_true', dest='verbose', 56*4882a593Smuzhiyun default=False, help='Verbose output of errors and warnings') 57*4882a593Smuzhiyunparser.add_option('--cc-cmd', dest='cc_cmd', type='string', action='store', 58*4882a593Smuzhiyun default=None, help='Output cc list for patch file (used by git)') 59*4882a593Smuzhiyunparser.add_option('--no-check', action='store_false', dest='check_patch', 60*4882a593Smuzhiyun default=True, 61*4882a593Smuzhiyun help="Don't check for patch compliance") 62*4882a593Smuzhiyunparser.add_option('--no-tags', action='store_false', dest='process_tags', 63*4882a593Smuzhiyun default=True, help="Don't process subject tags as aliaes") 64*4882a593Smuzhiyunparser.add_option('-T', '--thread', action='store_true', dest='thread', 65*4882a593Smuzhiyun default=False, help='Create patches as a single thread') 66*4882a593Smuzhiyun 67*4882a593Smuzhiyunparser.usage += """ 68*4882a593Smuzhiyun 69*4882a593SmuzhiyunCreate patches from commits in a branch, check them and email them as 70*4882a593Smuzhiyunspecified by tags you place in the commits. Use -n to do a dry run first.""" 71*4882a593Smuzhiyun 72*4882a593Smuzhiyun 73*4882a593Smuzhiyun# Parse options twice: first to get the project and second to handle 74*4882a593Smuzhiyun# defaults properly (which depends on project). 75*4882a593Smuzhiyun(options, args) = parser.parse_args() 76*4882a593Smuzhiyunsettings.Setup(parser, options.project, '') 77*4882a593Smuzhiyun(options, args) = parser.parse_args() 78*4882a593Smuzhiyun 79*4882a593Smuzhiyunif __name__ != "__main__": 80*4882a593Smuzhiyun pass 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun# Run our meagre tests 83*4882a593Smuzhiyunelif options.test: 84*4882a593Smuzhiyun import doctest 85*4882a593Smuzhiyun import func_test 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun sys.argv = [sys.argv[0]] 88*4882a593Smuzhiyun result = unittest.TestResult() 89*4882a593Smuzhiyun for module in (test.TestPatch, func_test.TestFunctional): 90*4882a593Smuzhiyun suite = unittest.TestLoader().loadTestsFromTestCase(module) 91*4882a593Smuzhiyun suite.run(result) 92*4882a593Smuzhiyun 93*4882a593Smuzhiyun for module in ['gitutil', 'settings']: 94*4882a593Smuzhiyun suite = doctest.DocTestSuite(module) 95*4882a593Smuzhiyun suite.run(result) 96*4882a593Smuzhiyun 97*4882a593Smuzhiyun # TODO: Surely we can just 'print' result? 98*4882a593Smuzhiyun print(result) 99*4882a593Smuzhiyun for test, err in result.errors: 100*4882a593Smuzhiyun print(err) 101*4882a593Smuzhiyun for test, err in result.failures: 102*4882a593Smuzhiyun print(err) 103*4882a593Smuzhiyun 104*4882a593Smuzhiyun# Called from git with a patch filename as argument 105*4882a593Smuzhiyun# Printout a list of additional CC recipients for this patch 106*4882a593Smuzhiyunelif options.cc_cmd: 107*4882a593Smuzhiyun fd = open(options.cc_cmd, 'r') 108*4882a593Smuzhiyun re_line = re.compile('(\S*) (.*)') 109*4882a593Smuzhiyun for line in fd.readlines(): 110*4882a593Smuzhiyun match = re_line.match(line) 111*4882a593Smuzhiyun if match and match.group(1) == args[0]: 112*4882a593Smuzhiyun for cc in match.group(2).split(', '): 113*4882a593Smuzhiyun cc = cc.strip() 114*4882a593Smuzhiyun if cc: 115*4882a593Smuzhiyun print(cc) 116*4882a593Smuzhiyun fd.close() 117*4882a593Smuzhiyun 118*4882a593Smuzhiyunelif options.full_help: 119*4882a593Smuzhiyun pager = os.getenv('PAGER') 120*4882a593Smuzhiyun if not pager: 121*4882a593Smuzhiyun pager = 'more' 122*4882a593Smuzhiyun fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 123*4882a593Smuzhiyun 'README') 124*4882a593Smuzhiyun command.Run(pager, fname) 125*4882a593Smuzhiyun 126*4882a593Smuzhiyun# Process commits, produce patches files, check them, email them 127*4882a593Smuzhiyunelse: 128*4882a593Smuzhiyun gitutil.Setup() 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun if options.count == -1: 131*4882a593Smuzhiyun # Work out how many patches to send if we can 132*4882a593Smuzhiyun options.count = gitutil.CountCommitsToBranch() - options.start 133*4882a593Smuzhiyun 134*4882a593Smuzhiyun col = terminal.Color() 135*4882a593Smuzhiyun if not options.count: 136*4882a593Smuzhiyun str = 'No commits found to process - please use -c flag' 137*4882a593Smuzhiyun sys.exit(col.Color(col.RED, str)) 138*4882a593Smuzhiyun 139*4882a593Smuzhiyun # Read the metadata from the commits 140*4882a593Smuzhiyun if options.count: 141*4882a593Smuzhiyun series = patchstream.GetMetaData(options.start, options.count) 142*4882a593Smuzhiyun cover_fname, args = gitutil.CreatePatches(options.start, options.count, 143*4882a593Smuzhiyun series) 144*4882a593Smuzhiyun 145*4882a593Smuzhiyun # Fix up the patch files to our liking, and insert the cover letter 146*4882a593Smuzhiyun patchstream.FixPatches(series, args) 147*4882a593Smuzhiyun if cover_fname and series.get('cover'): 148*4882a593Smuzhiyun patchstream.InsertCoverLetter(cover_fname, series, options.count) 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun # Do a few checks on the series 151*4882a593Smuzhiyun series.DoChecks() 152*4882a593Smuzhiyun 153*4882a593Smuzhiyun # Check the patches, and run them through 'git am' just to be sure 154*4882a593Smuzhiyun if options.check_patch: 155*4882a593Smuzhiyun ok = checkpatch.CheckPatches(options.verbose, args) 156*4882a593Smuzhiyun else: 157*4882a593Smuzhiyun ok = True 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun cc_file = series.MakeCcFile(options.process_tags, cover_fname, 160*4882a593Smuzhiyun not options.ignore_bad_tags, 161*4882a593Smuzhiyun options.add_maintainers) 162*4882a593Smuzhiyun 163*4882a593Smuzhiyun # Email the patches out (giving the user time to check / cancel) 164*4882a593Smuzhiyun cmd = '' 165*4882a593Smuzhiyun its_a_go = ok or options.ignore_errors 166*4882a593Smuzhiyun if its_a_go: 167*4882a593Smuzhiyun cmd = gitutil.EmailPatches(series, cover_fname, args, 168*4882a593Smuzhiyun options.dry_run, not options.ignore_bad_tags, cc_file, 169*4882a593Smuzhiyun in_reply_to=options.in_reply_to, thread=options.thread) 170*4882a593Smuzhiyun else: 171*4882a593Smuzhiyun print(col.Color(col.RED, "Not sending emails due to errors/warnings")) 172*4882a593Smuzhiyun 173*4882a593Smuzhiyun # For a dry run, just show our actions as a sanity check 174*4882a593Smuzhiyun if options.dry_run: 175*4882a593Smuzhiyun series.ShowActions(args, cmd, options.process_tags) 176*4882a593Smuzhiyun if not its_a_go: 177*4882a593Smuzhiyun print(col.Color(col.RED, "Email would not be sent")) 178*4882a593Smuzhiyun 179*4882a593Smuzhiyun os.remove(cc_file) 180