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()