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