Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-import sys, os
+import sys, os, re
from optparse import OptionParser, make_option
from stgit.commands.common import *
Create a new patch and apply the given GNU diff file (or the standard
input). By default, the file name is used as the patch name but this
-can be overriden with the '--name' option. The patch can either be a
+can be overridden with the '--name' option. The patch can either be a
normal file with the description at the top or it can have standard
mail format, the Subject, From and Date headers being used for
generating the patch information.
The patch description has to be separated from the data with a '---'
-line. For a normal file, if no author information is given, the first
-'Signed-off-by:' line is used."""
+line."""
options = [make_option('-m', '--mail',
help = 'import the patch from a standard e-mail file',
action = 'store_true'),
make_option('-n', '--name',
help = 'use NAME as the patch name'),
+ make_option('-s', '--series',
+ help = 'import a series of patches',
+ action = 'store_true'),
+ make_option('-i', '--ignore',
+ help = 'ignore the applied patches in the series',
+ action = 'store_true'),
+ make_option('-b', '--base',
+ help = 'use BASE instead of HEAD for file importing'),
make_option('-e', '--edit',
help = 'invoke an editor for the patch description',
action = 'store_true'),
- make_option('-s', '--showpatch',
+ make_option('-p', '--showpatch',
help = 'show the patch content in the editor buffer',
action = 'store_true'),
make_option('-a', '--author', metavar = '"NAME <EMAIL>"',
help = 'use COMMEMAIL as the committer e-mail')]
+def __end_descr(line):
+ return re.match('---\s*$', line) or re.match('diff -', line) or \
+ re.match('Index: ', line)
+
+def __parse_description(descr):
+ """Parse the patch description and return the new description and
+ author information (if any).
+ """
+ subject = body = ''
+ authname = authemail = authdate = None
+
+ descr_lines = [line.rstrip() for line in descr.split('\n')]
+ if not descr_lines:
+ raise CmdException, "Empty patch description"
+
+ lasthdr = 0
+ end = len(descr_lines)
+
+ # Parse the patch header
+ for pos in range(0, end):
+ if not descr_lines[pos]:
+ continue
+ # check for a "From|Author:" line
+ if re.match('\s*(?:from|author):\s+', descr_lines[pos], re.I):
+ auth = re.findall('^.*?:\s+(.*)$', descr_lines[pos])[0]
+ authname, authemail = name_email(auth)
+ lasthdr = pos + 1
+ continue
+ # check for a "Date:" line
+ if re.match('\s*date:\s+', descr_lines[pos], re.I):
+ authdate = re.findall('^.*?:\s+(.*)$', descr_lines[pos])[0]
+ lasthdr = pos + 1
+ continue
+ if subject:
+ break
+ # get the subject
+ subject = descr_lines[pos]
+ lasthdr = pos + 1
+
+ # get the body
+ if lasthdr < end:
+ body = reduce(lambda x, y: x + '\n' + y, descr_lines[lasthdr:], '')
+
+ return (subject + body, authname, authemail, authdate)
+
def __parse_mail(filename = None):
"""Parse the input file in a mail format and return (description,
authname, authemail, authdate)
descr = authname = authemail = authdate = None
# parse the headers
- for line in f:
+ while True:
+ line = f.readline()
+ if not line:
+ break
line = line.strip()
if re.match('from:\s+', line, re.I):
auth = re.findall('^.*?:\s+(.*)$', line)[0]
# remove the '[*PATCH*]' expression in the subject
if descr:
- descr = re.findall('^(\[[^\s]*PATCH.*?\])?\s*(.*)$', descr)[0][1]
+ descr = re.findall('^(\[[^\s]*[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$',
+ descr)[0][1]
descr += '\n\n'
else:
raise CmdException, 'Subject: line not found'
# the rest of the patch description
- for line in f:
- if re.match('---\s*$', line) or re.match('diff -', line):
+ while True:
+ line = f.readline()
+ if not line:
+ break
+ if __end_descr(line):
break
else:
descr += line
if filename:
f.close()
+ # parse the description for author information
+ descr, descr_authname, descr_authemail, descr_authdate = __parse_description(descr)
+ if descr_authname:
+ authname = descr_authname
+ if descr_authemail:
+ authemail = descr_authemail
+ if descr_authdate:
+ authdate = descr_authdate
+
return (descr, authname, authemail, authdate)
def __parse_patch(filename = None):
else:
f = sys.stdin
- authname = authemail = authdate = None
-
descr = ''
- for line in f:
- # the first 'Signed-of-by:' is the author
- if not authname and re.match('signed-off-by:\s+', line, re.I):
- auth = re.findall('^.*?:\s+(.*)$', line)[0]
- authname, authemail = name_email(auth)
+ while True:
+ line = f.readline()
+ if not line:
+ break
- if re.match('---\s*$', line) or re.match('diff -', line):
+ if __end_descr(line):
break
else:
descr += line
descr.rstrip()
- if descr == '':
- descr = None
-
if filename:
f.close()
+ descr, authname, authemail, authdate = __parse_description(descr)
+
+ # we don't yet have an agreed place for the creation date.
+ # Just return None
return (descr, authname, authemail, authdate)
-def func(parser, options, args):
- """Import a GNU diff file as a new patch
+def __import_patch(patch, filename, options):
+ """Import a patch from a file or standard input
"""
- if len(args) > 1:
- parser.error('incorrect number of arguments')
-
- check_local_changes()
- check_conflicts()
- check_head_top_equal()
-
- if len(args) == 1:
- filename = args[0]
- else:
- filename = None
-
- if options.name:
- patch = options.name
- elif filename:
- patch = os.path.basename(filename)
- else:
- raise CmdException, 'Unkown patch name'
-
# the defaults
message = author_name = author_email = author_date = committer_name = \
committer_email = None
committer_name = committer_name,
committer_email = committer_email)
- print 'Importing patch %s...' % patch,
+ print 'Importing patch "%s"...' % patch,
sys.stdout.flush()
- git.apply_patch(filename)
+ if options.base:
+ git.apply_patch(filename, git_id(options.base))
+ else:
+ git.apply_patch(filename)
+
crt_series.refresh_patch(edit = options.edit,
show_patch = options.showpatch)
print 'done'
+
+def __import_series(filename, options):
+ """Import a series of patches
+ """
+ applied = crt_series.get_applied()
+
+ if filename:
+ f = file(filename)
+ patchdir = os.path.dirname(filename)
+ else:
+ f = sys.stdin
+ patchdir = ''
+
+ for line in f:
+ patch = re.sub('#.*$', '', line).strip()
+ if not patch:
+ continue
+ if options.ignore and patch in applied:
+ print 'Ignoring already applied patch "%s"' % patch
+ continue
+
+ patchfile = os.path.join(patchdir, patch)
+ __import_patch(patch, patchfile, options)
+
+def func(parser, options, args):
+ """Import a GNU diff file as a new patch
+ """
+ if len(args) > 1:
+ parser.error('incorrect number of arguments')
+
+ check_local_changes()
+ check_conflicts()
+ check_head_top_equal()
+
+ if len(args) == 1:
+ filename = args[0]
+ else:
+ filename = None
+
+ if options.series:
+ __import_series(filename, options)
+ else:
+ if options.name:
+ patch = options.name
+ elif filename:
+ patch = os.path.basename(filename)
+ else:
+ raise CmdException, 'Unknown patch name'
+
+ __import_patch(patch, filename, options)
+
print_crt_patch()