[Freeipa-devel] Experimental patchwork server
John Dennis
jdennis at redhat.com
Tue Oct 23 13:53:08 UTC 2012
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 <jdennis at redhat.com>
Looking to carve out IT costs?
www.redhat.com/carveoutcosts/
-------------- next part --------------
#!/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 at USER_DOMAIN>',
'email_to_addrs' : ['freeipa-devel at 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 at 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()
More information about the Freeipa-devel
mailing list