1e8570757SMichal Simek#!/usr/bin/env python 20d24de9dSSimon Glass# 30d24de9dSSimon Glass# Copyright (c) 2011 The Chromium OS Authors. 40d24de9dSSimon Glass# 5*1a459660SWolfgang Denk# SPDX-License-Identifier: GPL-2.0+ 60d24de9dSSimon Glass# 70d24de9dSSimon Glass 80d24de9dSSimon Glass"""See README for more information""" 90d24de9dSSimon Glass 100d24de9dSSimon Glassfrom optparse import OptionParser 110d24de9dSSimon Glassimport os 120d24de9dSSimon Glassimport re 130d24de9dSSimon Glassimport sys 140d24de9dSSimon Glassimport unittest 150d24de9dSSimon Glass 160d24de9dSSimon Glass# Our modules 170d24de9dSSimon Glassimport checkpatch 180d24de9dSSimon Glassimport command 190d24de9dSSimon Glassimport gitutil 200d24de9dSSimon Glassimport patchstream 21a1dcee84SDoug Andersonimport project 228568baedSDoug Andersonimport settings 230d24de9dSSimon Glassimport terminal 240d24de9dSSimon Glassimport test 250d24de9dSSimon Glass 260d24de9dSSimon Glass 270d24de9dSSimon Glassparser = OptionParser() 28902a9715SSimon Glassparser.add_option('-a', '--no-apply', action='store_false', 29902a9715SSimon Glass dest='apply_patches', default=True, 30902a9715SSimon Glass help="Don't test-apply patches with git am") 310d24de9dSSimon Glassparser.add_option('-H', '--full-help', action='store_true', dest='full_help', 320d24de9dSSimon Glass default=False, help='Display the README file') 330d24de9dSSimon Glassparser.add_option('-c', '--count', dest='count', type='int', 340d24de9dSSimon Glass default=-1, help='Automatically create patches from top n commits') 350d24de9dSSimon Glassparser.add_option('-i', '--ignore-errors', action='store_true', 360d24de9dSSimon Glass dest='ignore_errors', default=False, 370d24de9dSSimon Glass help='Send patches email even if patch errors are found') 380d24de9dSSimon Glassparser.add_option('-n', '--dry-run', action='store_true', dest='dry_run', 39ca706e76SSimon Glass default=False, help="Do a dry run (create but don't email patches)") 4099adf6edSVadim Bendeburyparser.add_option('-p', '--project', default=project.DetectProject(), 4199adf6edSVadim Bendebury help="Project name; affects default option values and " 4299adf6edSVadim Bendebury "aliases [default: %default]") 436d819925SDoug Andersonparser.add_option('-r', '--in-reply-to', type='string', action='store', 446d819925SDoug Anderson help="Message ID that this series is in reply to") 450d24de9dSSimon Glassparser.add_option('-s', '--start', dest='start', type='int', 460d24de9dSSimon Glass default=0, help='Commit to start creating patches from (0 = HEAD)') 47a1318f7cSSimon Glassparser.add_option('-t', '--ignore-bad-tags', action='store_true', 48a1318f7cSSimon Glass default=False, help='Ignore bad tags / aliases') 49a1318f7cSSimon Glassparser.add_option('--test', action='store_true', dest='test', 500d24de9dSSimon Glass default=False, help='run tests') 510d24de9dSSimon Glassparser.add_option('-v', '--verbose', action='store_true', dest='verbose', 520d24de9dSSimon Glass default=False, help='Verbose output of errors and warnings') 530d24de9dSSimon Glassparser.add_option('--cc-cmd', dest='cc_cmd', type='string', action='store', 540d24de9dSSimon Glass default=None, help='Output cc list for patch file (used by git)') 5599adf6edSVadim Bendeburyparser.add_option('--no-check', action='store_false', dest='check_patch', 5699adf6edSVadim Bendebury default=True, 5799adf6edSVadim Bendebury help="Don't check for patch compliance") 580d24de9dSSimon Glassparser.add_option('--no-tags', action='store_false', dest='process_tags', 590d24de9dSSimon Glass default=True, help="Don't process subject tags as aliaes") 600d24de9dSSimon Glass 610d24de9dSSimon Glassparser.usage = """patman [options] 620d24de9dSSimon Glass 630d24de9dSSimon GlassCreate patches from commits in a branch, check them and email them as 64ca706e76SSimon Glassspecified by tags you place in the commits. Use -n to do a dry run first.""" 650d24de9dSSimon Glass 668568baedSDoug Anderson 67a1dcee84SDoug Anderson# Parse options twice: first to get the project and second to handle 68a1dcee84SDoug Anderson# defaults properly (which depends on project). 69a1dcee84SDoug Anderson(options, args) = parser.parse_args() 70a1dcee84SDoug Andersonsettings.Setup(parser, options.project, '') 710d24de9dSSimon Glass(options, args) = parser.parse_args() 720d24de9dSSimon Glass 730d24de9dSSimon Glass# Run our meagre tests 740d24de9dSSimon Glassif options.test: 750d24de9dSSimon Glass import doctest 760d24de9dSSimon Glass 770d24de9dSSimon Glass sys.argv = [sys.argv[0]] 780d24de9dSSimon Glass suite = unittest.TestLoader().loadTestsFromTestCase(test.TestPatch) 790d24de9dSSimon Glass result = unittest.TestResult() 800d24de9dSSimon Glass suite.run(result) 810d24de9dSSimon Glass 82656cffebSDoug Anderson for module in ['gitutil', 'settings']: 83656cffebSDoug Anderson suite = doctest.DocTestSuite(module) 840d24de9dSSimon Glass suite.run(result) 850d24de9dSSimon Glass 860d24de9dSSimon Glass # TODO: Surely we can just 'print' result? 870d24de9dSSimon Glass print result 880d24de9dSSimon Glass for test, err in result.errors: 890d24de9dSSimon Glass print err 900d24de9dSSimon Glass for test, err in result.failures: 910d24de9dSSimon Glass print err 920d24de9dSSimon Glass 930d24de9dSSimon Glass# Called from git with a patch filename as argument 940d24de9dSSimon Glass# Printout a list of additional CC recipients for this patch 950d24de9dSSimon Glasselif options.cc_cmd: 960d24de9dSSimon Glass fd = open(options.cc_cmd, 'r') 970d24de9dSSimon Glass re_line = re.compile('(\S*) (.*)') 980d24de9dSSimon Glass for line in fd.readlines(): 990d24de9dSSimon Glass match = re_line.match(line) 1000d24de9dSSimon Glass if match and match.group(1) == args[0]: 1010d24de9dSSimon Glass for cc in match.group(2).split(', '): 1020d24de9dSSimon Glass cc = cc.strip() 1030d24de9dSSimon Glass if cc: 1040d24de9dSSimon Glass print cc 1050d24de9dSSimon Glass fd.close() 1060d24de9dSSimon Glass 1070d24de9dSSimon Glasselif options.full_help: 1080d24de9dSSimon Glass pager = os.getenv('PAGER') 1090d24de9dSSimon Glass if not pager: 1100d24de9dSSimon Glass pager = 'more' 1110d24de9dSSimon Glass fname = os.path.join(os.path.dirname(sys.argv[0]), 'README') 1120d24de9dSSimon Glass command.Run(pager, fname) 1130d24de9dSSimon Glass 1140d24de9dSSimon Glass# Process commits, produce patches files, check them, email them 1150d24de9dSSimon Glasselse: 1160d24de9dSSimon Glass gitutil.Setup() 1170d24de9dSSimon Glass 1180d24de9dSSimon Glass if options.count == -1: 1190d24de9dSSimon Glass # Work out how many patches to send if we can 1200d24de9dSSimon Glass options.count = gitutil.CountCommitsToBranch() - options.start 1210d24de9dSSimon Glass 1220d24de9dSSimon Glass col = terminal.Color() 1230d24de9dSSimon Glass if not options.count: 1240d24de9dSSimon Glass str = 'No commits found to process - please use -c flag' 1250d24de9dSSimon Glass print col.Color(col.RED, str) 1260d24de9dSSimon Glass sys.exit(1) 1270d24de9dSSimon Glass 1280d24de9dSSimon Glass # Read the metadata from the commits 1290d24de9dSSimon Glass if options.count: 1300d24de9dSSimon Glass series = patchstream.GetMetaData(options.start, options.count) 1310d24de9dSSimon Glass cover_fname, args = gitutil.CreatePatches(options.start, options.count, 1320d24de9dSSimon Glass series) 1330d24de9dSSimon Glass 1340d24de9dSSimon Glass # Fix up the patch files to our liking, and insert the cover letter 1350d24de9dSSimon Glass series = patchstream.FixPatches(series, args) 1360d24de9dSSimon Glass if series and cover_fname and series.get('cover'): 1370d24de9dSSimon Glass patchstream.InsertCoverLetter(cover_fname, series, options.count) 1380d24de9dSSimon Glass 1390d24de9dSSimon Glass # Do a few checks on the series 1400d24de9dSSimon Glass series.DoChecks() 1410d24de9dSSimon Glass 1420d24de9dSSimon Glass # Check the patches, and run them through 'git am' just to be sure 14399adf6edSVadim Bendebury if options.check_patch: 1440d24de9dSSimon Glass ok = checkpatch.CheckPatches(options.verbose, args) 14599adf6edSVadim Bendebury else: 14699adf6edSVadim Bendebury ok = True 147902a9715SSimon Glass if options.apply_patches: 1480d24de9dSSimon Glass if not gitutil.ApplyPatches(options.verbose, args, 1490d24de9dSSimon Glass options.count + options.start): 1500d24de9dSSimon Glass ok = False 1510d24de9dSSimon Glass 152a1318f7cSSimon Glass cc_file = series.MakeCcFile(options.process_tags, cover_fname, 153a1318f7cSSimon Glass not options.ignore_bad_tags) 154d94566a1SDoug Anderson 1550d24de9dSSimon Glass # Email the patches out (giving the user time to check / cancel) 1560d24de9dSSimon Glass cmd = '' 1570d24de9dSSimon Glass if ok or options.ignore_errors: 1580d24de9dSSimon Glass cmd = gitutil.EmailPatches(series, cover_fname, args, 159a1318f7cSSimon Glass options.dry_run, not options.ignore_bad_tags, cc_file, 160a1318f7cSSimon Glass in_reply_to=options.in_reply_to) 1610d24de9dSSimon Glass 1620d24de9dSSimon Glass # For a dry run, just show our actions as a sanity check 1630d24de9dSSimon Glass if options.dry_run: 1640d24de9dSSimon Glass series.ShowActions(args, cmd, options.process_tags) 165d94566a1SDoug Anderson 166d94566a1SDoug Anderson os.remove(cc_file) 167