Fix the patch name stripping in import
[stgit] / stgit / commands / imprt.py
1 __copyright__ = """
2 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
3
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.
7
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.
12
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
16 """
17
18 import sys, os, re
19 from optparse import OptionParser, make_option
20
21 from stgit.commands.common import *
22 from stgit.utils import *
23 from stgit import stack, git
24
25
26 help = 'import a GNU diff file as a new patch'
27 usage = """%prog [options] [<file>]
28
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.
35
36 The patch description has to be separated from the data with a '---'
37 line."""
38
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')]
73
74
75 def __end_descr(line):
76 return re.match('---\s*$', line) or re.match('diff -', line) or \
77 re.match('Index: ', line)
78
79 def __strip_patch_name(name):
80 stripped = re.sub('^[0-9]+-(.*)$', '\g<1>', name)
81 stripped = re.sub('^(.*)\.(diff|patch)$', '\g<1>', stripped)
82
83 return stripped
84
85 def __parse_description(descr):
86 """Parse the patch description and return the new description and
87 author information (if any).
88 """
89 subject = body = ''
90 authname = authemail = authdate = None
91
92 descr_lines = [line.rstrip() for line in descr.split('\n')]
93 if not descr_lines:
94 raise CmdException, "Empty patch description"
95
96 lasthdr = 0
97 end = len(descr_lines)
98
99 # Parse the patch header
100 for pos in range(0, end):
101 if not descr_lines[pos]:
102 continue
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)
107 lasthdr = pos + 1
108 continue
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]
112 lasthdr = pos + 1
113 continue
114 if subject:
115 break
116 # get the subject
117 subject = descr_lines[pos]
118 lasthdr = pos + 1
119
120 # get the body
121 if lasthdr < end:
122 body = reduce(lambda x, y: x + '\n' + y, descr_lines[lasthdr:], '')
123
124 return (subject + body, authname, authemail, authdate)
125
126 def __parse_mail(filename = None):
127 """Parse the input file in a mail format and return (description,
128 authname, authemail, authdate)
129 """
130 if filename:
131 f = file(filename)
132 else:
133 f = sys.stdin
134
135 descr = authname = authemail = authdate = None
136
137 # parse the headers
138 while True:
139 line = f.readline()
140 if not line:
141 break
142 line = line.strip()
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]
150 elif line == '':
151 # end of headers
152 break
153
154 # remove the '[*PATCH*]' expression in the subject
155 if descr:
156 descr = re.findall('^(\[[^\s]*[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$',
157 descr)[0][1]
158 descr += '\n\n'
159 else:
160 raise CmdException, 'Subject: line not found'
161
162 # the rest of the patch description
163 while True:
164 line = f.readline()
165 if not line:
166 break
167 if __end_descr(line):
168 break
169 else:
170 descr += line
171 descr.rstrip()
172
173 if filename:
174 f.close()
175
176 # parse the description for author information
177 descr, descr_authname, descr_authemail, descr_authdate = __parse_description(descr)
178 if descr_authname:
179 authname = descr_authname
180 if descr_authemail:
181 authemail = descr_authemail
182 if descr_authdate:
183 authdate = descr_authdate
184
185 return (descr, authname, authemail, authdate)
186
187 def __parse_patch(filename = None):
188 """Parse the input file and return (description, authname,
189 authemail, authdate)
190 """
191 if filename:
192 f = file(filename)
193 else:
194 f = sys.stdin
195
196 descr = ''
197 while True:
198 line = f.readline()
199 if not line:
200 break
201
202 if __end_descr(line):
203 break
204 else:
205 descr += line
206 descr.rstrip()
207
208 if filename:
209 f.close()
210
211 descr, authname, authemail, authdate = __parse_description(descr)
212
213 # we don't yet have an agreed place for the creation date.
214 # Just return None
215 return (descr, authname, authemail, authdate)
216
217 def __import_patch(patch, filename, options):
218 """Import a patch from a file or standard input
219 """
220 # the defaults
221 message = author_name = author_email = author_date = committer_name = \
222 committer_email = None
223
224 if options.author:
225 options.authname, options.authemail = name_email(options.author)
226
227 if options.mail:
228 message, author_name, author_email, author_date = \
229 __parse_mail(filename)
230 else:
231 message, author_name, author_email, author_date = \
232 __parse_patch(filename)
233
234 # refresh_patch() will invoke the editor in this case, with correct
235 # patch content
236 if not message:
237 can_edit = False
238
239 # override the automatically parsed settings
240 if options.authname:
241 author_name = options.authname
242 if options.authemail:
243 author_email = options.authemail
244 if options.authdate:
245 author_date = options.authdate
246 if options.commname:
247 committer_name = options.commname
248 if options.commemail:
249 committer_email = options.commemail
250
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)
257
258 print 'Importing patch "%s"...' % patch,
259 sys.stdout.flush()
260
261 if options.base:
262 git.apply_patch(filename, git_id(options.base))
263 else:
264 git.apply_patch(filename)
265
266 crt_series.refresh_patch(edit = options.edit,
267 show_patch = options.showpatch)
268
269 print 'done'
270
271 def __import_series(filename, options):
272 """Import a series of patches
273 """
274 applied = crt_series.get_applied()
275
276 if filename:
277 f = file(filename)
278 patchdir = os.path.dirname(filename)
279 else:
280 f = sys.stdin
281 patchdir = ''
282
283 for line in f:
284 patch = re.sub('#.*$', '', line).strip()
285 if not patch:
286 continue
287 patchfile = os.path.join(patchdir, patch)
288
289 if options.strip:
290 patch = __strip_patch_name(patch)
291 if options.ignore and patch in applied:
292 print 'Ignoring already applied patch "%s"' % patch
293 continue
294
295 __import_patch(patch, patchfile, options)
296
297 def func(parser, options, args):
298 """Import a GNU diff file as a new patch
299 """
300 if len(args) > 1:
301 parser.error('incorrect number of arguments')
302
303 check_local_changes()
304 check_conflicts()
305 check_head_top_equal()
306
307 if len(args) == 1:
308 filename = args[0]
309 else:
310 filename = None
311
312 if options.series:
313 __import_series(filename, options)
314 else:
315 if options.name:
316 patch = options.name
317 elif filename:
318 patch = os.path.basename(filename)
319 else:
320 raise CmdException, 'Unknown patch name'
321 if options.strip:
322 patch = __strip_patch_name(patch)
323
324 __import_patch(patch, filename, options)
325
326 print_crt_patch()