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