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