2 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License version 2 as
6 published by the Free Software Foundation.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 from optparse
import OptionParser
, make_option
21 from stgit
.commands
.common
import *
22 from stgit
.utils
import *
23 from stgit
import stack
, git
26 help = 'import a GNU diff file as a new patch'
27 usage
= """%prog [options] [<file>]
29 Create a new patch and apply the given GNU diff file (or the standard
30 input). By default, the file name is used as the patch name but this
31 can be overridden with the '--name' option. The patch can either be a
32 normal file with the description at the top or it can have standard
33 mail format, the Subject, From and Date headers being used for
34 generating the patch information.
36 The patch description has to be separated from the data with a '---'
39 options
= [make_option('-m', '--mail',
40 help = 'import the patch from a standard e-mail file',
41 action
= 'store_true'),
42 make_option('-n', '--name',
43 help = 'use NAME as the patch name'),
44 make_option('-t', '--strip',
45 help = 'strip numbering and extension from patch name',
46 action
= 'store_true'),
47 make_option('-s', '--series',
48 help = 'import a series of patches',
49 action
= 'store_true'),
50 make_option('-i', '--ignore',
51 help = 'ignore the applied patches in the series',
52 action
= 'store_true'),
53 make_option('-b', '--base',
54 help = 'use BASE instead of HEAD for file importing'),
55 make_option('-e', '--edit',
56 help = 'invoke an editor for the patch description',
57 action
= 'store_true'),
58 make_option('-p', '--showpatch',
59 help = 'show the patch content in the editor buffer',
60 action
= 'store_true'),
61 make_option('-a', '--author', metavar
= '"NAME <EMAIL>"',
62 help = 'use "NAME <EMAIL>" as the author details'),
63 make_option('--authname',
64 help = 'use AUTHNAME as the author name'),
65 make_option('--authemail',
66 help = 'use AUTHEMAIL as the author e-mail'),
67 make_option('--authdate',
68 help = 'use AUTHDATE as the author date'),
69 make_option('--commname',
70 help = 'use COMMNAME as the committer name'),
71 make_option('--commemail',
72 help = 'use COMMEMAIL as the committer e-mail')]
75 def __end_descr(line
):
76 return re
.match('---\s*$', line
) or re
.match('diff -', line
) or \
77 re
.match('Index: ', line
)
79 def __strip_patch_name(name
):
80 stripped
= re
.sub('^[0-9]+-(.*)$', '\g<1>', name
)
81 stripped
= re
.sub('^(.*)\.(diff|patch)$', '\g<1>', stripped
)
85 def __parse_description(descr
):
86 """Parse the patch description and return the new description and
87 author information (if any).
90 authname
= authemail
= authdate
= None
92 descr_lines
= [line
.rstrip() for line
in descr
.split('\n')]
94 raise CmdException
, "Empty patch description"
97 end
= len(descr_lines
)
99 # Parse the patch header
100 for pos
in range(0, end
):
101 if not descr_lines
[pos
]:
103 # check for a "From|Author:" line
104 if re
.match('\s*(?:from|author):\s+', descr_lines
[pos
], re
.I
):
105 auth
= re
.findall('^.*?:\s+(.*)$', descr_lines
[pos
])[0]
106 authname
, authemail
= name_email(auth
)
109 # check for a "Date:" line
110 if re
.match('\s*date:\s+', descr_lines
[pos
], re
.I
):
111 authdate
= re
.findall('^.*?:\s+(.*)$', descr_lines
[pos
])[0]
117 subject
= descr_lines
[pos
]
122 body
= reduce(lambda x
, y
: x
+ '\n' + y
, descr_lines
[lasthdr
:], '')
124 return (subject
+ body
, authname
, authemail
, authdate
)
126 def __parse_mail(filename
= None):
127 """Parse the input file in a mail format and return (description,
128 authname, authemail, authdate)
135 descr
= authname
= authemail
= authdate
= None
143 if re
.match('from:\s+', line
, re
.I
):
144 auth
= re
.findall('^.*?:\s+(.*)$', line
)[0]
145 authname
, authemail
= name_email(auth
)
146 elif re
.match('date:\s+', line
, re
.I
):
147 authdate
= re
.findall('^.*?:\s+(.*)$', line
)[0]
148 elif re
.match('subject:\s+', line
, re
.I
):
149 descr
= re
.findall('^.*?:\s+(.*)$', line
)[0]
154 # remove the '[*PATCH*]' expression in the subject
156 descr
= re
.findall('^(\[[^\s]*[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$',
160 raise CmdException
, 'Subject: line not found'
162 # the rest of the patch description
167 if __end_descr(line
):
176 # parse the description for author information
177 descr
, descr_authname
, descr_authemail
, descr_authdate
= __parse_description(descr
)
179 authname
= descr_authname
181 authemail
= descr_authemail
183 authdate
= descr_authdate
185 return (descr
, authname
, authemail
, authdate
)
187 def __parse_patch(filename
= None):
188 """Parse the input file and return (description, authname,
202 if __end_descr(line
):
211 descr
, authname
, authemail
, authdate
= __parse_description(descr
)
213 # we don't yet have an agreed place for the creation date.
215 return (descr
, authname
, authemail
, authdate
)
217 def __import_patch(patch
, filename
, options
):
218 """Import a patch from a file or standard input
221 message
= author_name
= author_email
= author_date
= committer_name
= \
222 committer_email
= None
225 options
.authname
, options
.authemail
= name_email(options
.author
)
228 message
, author_name
, author_email
, author_date
= \
229 __parse_mail(filename
)
231 message
, author_name
, author_email
, author_date
= \
232 __parse_patch(filename
)
234 # refresh_patch() will invoke the editor in this case, with correct
239 # override the automatically parsed settings
241 author_name
= options
.authname
242 if options
.authemail
:
243 author_email
= options
.authemail
245 author_date
= options
.authdate
247 committer_name
= options
.commname
248 if options
.commemail
:
249 committer_email
= options
.commemail
251 crt_series
.new_patch(patch
, message
= message
, can_edit
= False,
252 author_name
= author_name
,
253 author_email
= author_email
,
254 author_date
= author_date
,
255 committer_name
= committer_name
,
256 committer_email
= committer_email
)
258 print 'Importing patch "%s"...' % patch
,
262 git
.apply_patch(filename
, git_id(options
.base
))
264 git
.apply_patch(filename
)
266 crt_series
.refresh_patch(edit
= options
.edit
,
267 show_patch
= options
.showpatch
)
271 def __import_series(filename
, options
):
272 """Import a series of patches
274 applied
= crt_series
.get_applied()
278 patchdir
= os
.path
.dirname(filename
)
284 patch
= re
.sub('#.*$', '', line
).strip()
287 patchfile
= os
.path
.join(patchdir
, patch
)
290 patch
= __strip_patch_name(patch
)
291 if options
.ignore
and patch
in applied
:
292 print 'Ignoring already applied patch "%s"' % patch
295 __import_patch(patch
, patchfile
, options
)
297 def func(parser
, options
, args
):
298 """Import a GNU diff file as a new patch
301 parser
.error('incorrect number of arguments')
303 check_local_changes()
305 check_head_top_equal()
313 __import_series(filename
, options
)
318 patch
= os
.path
.basename(filename
)
320 raise CmdException
, 'Unknown patch name'
322 patch
= __strip_patch_name(patch
)
324 __import_patch(patch
, filename
, options
)