On 10/23/2012 09:00 AM, Simo Sorce wrote:
I strongly suggest you use git-send-email instead of thunderbird, it
makes everything a lot faster, see the instructions I sent in my
followup email.


I wrote a python script to manage my patch submissions a while ago which might be useful to folks, it's attached.

The basic idea is you keep a directory of your patch submissions. Inside the directory is also a file that has then next number to use for your submission. By default it runs git format-patch selecting the last commit. It creates a patch file using the patch submission format defined for IPA. If you use the -s option it also sends it to the list. It doesn't use git-send-email, rather it builds an email with a mime attachment according to our IPA recommendations. I don't recall why I didn't use git-send-email, but there was some reason (probably because I couldn't get it follow the IPA conventions, not sure though).

If you have to rework a patch use the -n option to specify which patch you're modifying. The script automatically searches the patch directory and finds the next revision number for the patch.

The config dict at the top will have to be modified to match your username, smtp server, etc. look for anything in UPPERCASE and replace with your specifics.

I like to use it because I don't have to remember my patch numbers and the result will always follow the IPA conventions without any fumbling around.

Petr3 will probably complain about using getopt and a config dict instead of optparse but it works and it wasn't worth it to me to port it to a different mechanism. Anybody which wants to is more than welcome.


--
John Dennis <jden...@redhat.com>

Looking to carve out IT costs?
www.redhat.com/carveoutcosts/
#!/usr/bin/python

import getopt
import os
import errno
import sys
import subprocess
import re
import smtplib
import email
import traceback
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 = os.path.basename(sys.argv[0])

config = {
    'project_name'          : 'freeipa',
    'user_name'             : 'USERNAME',
    'patch_dir'             : '/home/USERNAME/freeipa-patches',
    'smtp_server'           : 'SMTP_SERVER',
    'email_from_addr'       : 'FULLNAME <USERNAME@USER_DOMAIN>',
    'email_to_addrs'        : ['freeipa-devel@redhat.com'],
    'start_number_basename' : 'StartNumber',
    'default_number'        : 1,
    'number'                : None,
    'send'                  : False,
    'run_format'            : True,
    'dry_run'               : False,
    'verbose'               : False,
    'revision_range'        : '-1',
}

signature = '''
--
FULLNAME <USERNAME@USER_DOMAIN>
'''


#-------------------------------------------------------------------------------
git_filename_re = re.compile(r'^(\d+)-(.*).patch$')
ipa_filename_re = re.compile(r'^([^-]+)-([^-]+)-(\d+)(-(\d+))?-(.*)\.patch$')
patch_subject_re = re.compile(r'Subject:\s+\[PATCH\s+(\d+)(-(\d+))?\]')
#-------------------------------------------------------------------------------

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)

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

class PatchInfo:
    def __init__(self, project, user, number, revision, description, path=None):
        self.project = project
        self.user = user
        self.number = int(number)
        self.revision = int(revision)
        self.description = description
        self.path = path
        self.extension = 'patch'

    def __str__(self):
        extension = 'patch'

        if self.revision:
            filename = '%s-%s-%04d-%d-%s.%s' % \
                (self.project, self.user, self.number, self.revision, 
self.description, self.extension)
        else:
            filename = '%s-%s-%04d-%s.%s' % \
                (self.project, self.user, self.number, self.description, 
self.extension)

        return filename

    def __cmp__(self, other):
        result = cmp(self.project, other.project)
        if result != 0: return result

        result = cmp(self.user, other.user)
        if result != 0: return result

        result = cmp(self.number, other.number)
        if result != 0: return result

        result = cmp(self.revision, other.revision)
        if result != 0: return result

        result = cmp(self.description, other.description)
        if result != 0: return result

        return 0

#-------------------------------------------------------------------------------
def run_cmd(cmd):
    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)
    return 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 get_patch_filename(number, revision, description):
    project = config['project_name']
    user = config['user_name']

    info = PatchInfo(project, user, number, revision, description)
    return str(info)

def find_patch(number, patch_dir):
    for filename in os.listdir(patch_dir):
        match = git_filename_re.search(filename)
        if match:
            number = int(match.group(1))
            description = match.group(2)
            if patch_number == number:
                patch_filename = filename
                return os.path.join(patch_dir, patch_filename)
    return None

def rename_git_patchfile(git_patchfile, number, revision):
    directory = os.path.dirname(git_patchfile)
    old_basename = os.path.basename(git_patchfile)
    match = git_filename_re.search(old_basename)
    if match:
        git_number = int(match.group(1))
        description = match.group(2)
        new_basename = get_patch_filename(number, revision, description)
        old_path = git_patchfile
        new_path = os.path.join(directory, new_basename)
        os.rename(old_path, new_path)
    else:
        raise ValueError("git_patchfile cannot be parsed (%s)" % 
(git_patchfile))
    return new_path

def parse_patch_filename(patch_filename):
    match = ipa_filename_re.search(patch_filename)
    if match is None:
        return None
    project = match.group(1)
    user = match.group(2)
    number = int(match.group(3))
    if match.group(4) is not None:
        revision = int(match.group(5))
    else:
        revision = 0
    description = match.group(6)
    return PatchInfo(project, user, number, revision, description, 
patch_filename)


def get_patch_revisions(number, patch_dir):
    patches = []
    for filename in os.listdir(patch_dir):
        info = parse_patch_filename(filename)
        if info is not None:
            if number == info.number:
                patches.append(info)

    if len(patches) == 0:
        return None

    patches.sort()
    revisions = [x.revision for x in patches]
    return revisions

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
-r --revision-range     use this revision range instead of default -1
-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:

%(prog_name)s
''' % {'prog_name' : prog_name,
      }

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

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'hn:r:sNF',
                                   ['help', 'number=', 'revision-range=', 
'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 ('-r', '--revision-range'):
            config['revision_range'] = 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['dry_run']:
            patch_dir = '.'
        else:
            patch_dir = config['patch_dir']

        revision = 0
        if config['number'] is None:
            number = next_number()
        else:
            number = config['number']
            revisions = get_patch_revisions(number, patch_dir)
            if revisions is not None:
                revision = revisions[-1] + 1

        if config['run_format']:
            if len(args) > 0:
                extra_args = ' '.join(args)
            else:
                extra_args = config['revision_range']
            if revision:
                subject_number = '%d-%d' % (number, revision)
            else:
                subject_number = number
            cmd = 'git format-patch --start-number %d --subject-prefix "PATCH 
%s" -N -o %s' % \
                  (number, subject_number, patch_dir)
            cmd += ' ' + extra_args
            git_patchfile = run_cmd(cmd)
            patch_file = rename_git_patchfile(git_patchfile, number, revision)
            print patch_file

        if config['send']:
            send_patch(patch_file)

        return 0

    except Exception, e:
        print >>sys.stderr, e
        traceback.print_exc()
        return 1
#-------------------------------------------------------------------------------

if __name__ == '__main__':
    main()
_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to