Preparing patches for submission to the FreeIPA mailing list

Here’s a little ditty I wrote to get patches in the format we use for the FreeIPA mailing list:

#!/bin/bash -x
. $HOME/bin/ipa-dev

NUMPATCHES=$(( $1 ))


PREPDIR=$HOME/Documents/IPA/patchprep
SHIPDIR=$HOME/Documents/IPA/patches
NUMFILE=$HOME/Documents/IPA/patchprep/nextnumber

#initialize
[ -f $NUMFILE ] || echo 0 > $NUMFILE

#evaluate as a number
NEXTNUM=$(( `cat $NUMFILE` ))

mkdir -p $SHIPDIR
mkdir -p $PREPDIR

ipa-pushd
git format-patch --start-number $NEXTNUM  -o $PREPDIR  -$NUMPATCHES
pushd $PREPDIR
for PATCH in *patch
do
        mv $PATCH admiyo-freeipa-$PATCH
done

popd
mv $PREPDIR/*patch $SHIPDIR
NEXTNUM=$(( $NUMPATCHES + $NEXTNUM ))

echo $NEXTNUM >  $PREPDIR/nextnumber

popd

Here is John Dennis’ version:

#!/usr/bin/python

import getopt
import os
import errno
import sys
import subprocess
import re
import smtplib
import email
from cStringIO import StringIO
from email.generator import Generator
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase

#-------------------------------------------------------------------------------

prog_name = sys.argv[0]

config = {
    'patch_dir'             : '/home/jdennis/freeipa-patches',
    'smtp_server'           : 'smtp.corp.redhat.com',
    'email_from_addr'       : 'John Dennis ',
    #'email_to_addrs'        : ['jdennis@redhat.com'],
    'email_to_addrs'        : ['freeipa-devel@redhat.com'],
    'start_number_basename' : 'StartNumber',
    'default_number'        : 2,
    'number'                : None,
    'send'                  : False,
    'run_format'            : True,
    'dry_run'               : False,
    'verbose'               : False,
}

signature = '''
-- 
John Dennis 

Looking to carve out IT costs?
www.redhat.com/carveoutcosts/
'''


#-------------------------------------------------------------------------------

class CommandError(Exception):
    def __init__(self, cmd, msg):
        self.cmd = cmd
        self.msg = msg

    def __str__(self):
        return "COMMAND ERROR: cmd='%s'\n%s" % (self.cmd, self.msg)

def run_cmd(cmd):
    if config['dry_run']:
        print >>sys.stdout, cmd
        return
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    status = os.waitpid(p.pid, 0)[1]
    msg = p.stdout.read().strip()
    if (status != 0):
        err_msg = p.stderr.read().strip()
        raise CommandError(cmd, err_msg)
    print >>sys.stdout, msg

def next_number():
    try:
        f = open(config['start_number_filepath'], 'r')
        number = int(f.read())
        f.close()
        if config['verbose']:
            print "number %d read from '%s'" % (number, config['start_number_filepath'])
    except Exception, e:
        if e.errno == errno.ENOENT:
            number = config['default_number']
            if config['verbose']:
                print "'%s' does not exist yet, using default %d" % (config['start_number_filepath'], number)
        else:
            raise

    if not config['dry_run']:
        f = open(config['start_number_filepath'], 'w')
        f.write('%d\n' % (number + 1))
        f.close()

    return number

def find_patch(number):
    filename_re = re.compile('^0*%d-(.*).patch$' % number)
    for filename in os.listdir(config['patch_dir']):
        match = filename_re.search(filename)
        if match:
            patch_filename = filename
            return os.path.join(config['patch_dir'], patch_filename)
    return None

def send_patch(filename):
    f = open(filename)
    patch = email.email.message_from_file(f)
    f.close()

    patch_name = os.path.basename(filename)

    # Get the entire raw message, including headers
    if False:
        f = StringIO()
        g = Generator(f, mangle_from_=False, maxheaderlen=0)
        g.flatten(patch)
        raw_msg = f.getvalue()
    else:
        f = open(filename)
        raw_msg = f.read()
        f.close()

    payload = patch.get_payload()
    i = payload.find('\n---\n')
    if i == -1:
        commit_msg = ''
    else:
        commit_msg = payload[:i]

    msg = MIMEMultipart()

    mime_part = MIMEText(commit_msg + '\n' + signature)
    msg.attach(mime_part)

    mime_part = MIMEBase('text', 'x-patch', name=patch_name)
    mime_part.set_charset('utf-8')
    mime_part.add_header('Content-Disposition', 'attachment', filename=patch_name)
    mime_part.set_payload(raw_msg)
    email.encoders.encode_base64(mime_part)
    msg.attach(mime_part)

    msg['Subject'] = patch['subject']
    msg['From']    = config['email_from_addr']
    msg['To']      = ', '.join(config['email_to_addrs'])

    if config['dry_run']:
        print msg
    else:
        s = smtplib.SMTP(config['smtp_server'])
        s.sendmail(config['email_from_addr'], config['email_to_addrs'], msg.as_string())
        s.quit()
    

def usage():
    '''
    Print command help.
    '''

    print '''\
-h --help               print help
-n --number nn          use this number for patch instead of next sequence number
-s --send               send patch using git send-email
-N --dry-run            don't execute, just report what would have been done
-F --no-format          don't run git format-patch

Examples:

Run command at minutes after the hour
%(prog_name)s -n 10
''' % {'prog_name' : prog_name,
      }

#-------------------------------------------------------------------------------

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'hn:sNF',
                                   ['help', 'number=', 'send', 'dry-run', 'no-format'])
    except getopt.GetoptError, err:
        print >>sys.stderr, str(err)
        usage()
        sys.exit(2)

    for o, a in opts:
        if o in ('-h', '--help'):
            usage()
            sys.exit()
        elif o in ('-n', '--number'):
            config['number'] = int(a)
        elif o in ('-s', '--send'):
            config['send'] = True
        elif o in ('-N', '--dry-run'):
            config['dry_run'] = True
        elif o in ('-F', '--no-format'):
            config['run_format'] = False
        else:
            assert False, 'unhandled option'

    config['start_number_filepath'] = os.path.join(config['patch_dir'],
                                                   config['start_number_basename'])

    try:
        if config['number'] is None:
            number = next_number()
        else:
            number = config['number']

        if config['run_format']:
            if len(args) > 0:
                extra_args = ' '.join(args)
            else:
                extra_args = '-1'
            cmd = 'git format-patch --start-number %d -n -o %s' % (number, config['patch_dir'])
            cmd += ' ' + extra_args
            run_cmd(cmd)

        if config['send']:
            patch_file = find_patch(number)
            if patch_file is None:
                print >>sys.stderr, "cannot find patch file for patch %d" % (number)
            else:
                send_patch(patch_file)

    except Exception, e:
        print >>sys.stderr, e
#-------------------------------------------------------------------------------

if __name__ == '__main__':
    main()

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.