Allow to specify multiple patch names on push command-line
[stgit] / stgit / commands / imprt.py
CommitLineData
0d2cd1e4
CM
1__copyright__ = """
2Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
3
4This program is free software; you can redistribute it and/or modify
5it under the terms of the GNU General Public License version 2 as
6published by the Free Software Foundation.
7
8This program is distributed in the hope that it will be useful,
9but WITHOUT ANY WARRANTY; without even the implied warranty of
10MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11GNU General Public License for more details.
12
13You should have received a copy of the GNU General Public License
14along with this program; if not, write to the Free Software
15Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16"""
17
9417ece4 18import sys, os, re
0d2cd1e4
CM
19from optparse import OptionParser, make_option
20
21from stgit.commands.common import *
22from stgit.utils import *
23from stgit import stack, git
24
25
26help = 'import a GNU diff file as a new patch'
b8a0986f 27usage = """%prog [options] [<file>]
0d2cd1e4 28
b8a0986f
CM
29Create a new patch and apply the given GNU diff file (or the standard
30input). By default, the file name is used as the patch name but this
388f63b6 31can be overridden with the '--name' option. The patch can either be a
b8a0986f
CM
32normal file with the description at the top or it can have standard
33mail format, the Subject, From and Date headers being used for
34generating the patch information.
0d2cd1e4 35
b8a0986f 36The patch description has to be separated from the data with a '---'
99e73103 37line."""
0d2cd1e4
CM
38
39options = [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'),
b0cdad5e
CM
44 make_option('-t', '--strip',
45 help = 'strip numbering and extension from patch name',
46 action = 'store_true'),
9417ece4
CM
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'),
b21bc8d1 53 make_option('-b', '--base',
35344f86 54 help = 'use BASE instead of HEAD for file importing'),
33e580e0
CM
55 make_option('-e', '--edit',
56 help = 'invoke an editor for the patch description',
57 action = 'store_true'),
9417ece4 58 make_option('-p', '--showpatch',
6ad48e48
PBG
59 help = 'show the patch content in the editor buffer',
60 action = 'store_true'),
0d2cd1e4
CM
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
d4c43e19
PBG
75def __end_descr(line):
76 return re.match('---\s*$', line) or re.match('diff -', line) or \
77 re.match('Index: ', line)
99e73103 78
b0cdad5e 79def __strip_patch_name(name):
bcb6d890
CM
80 stripped = re.sub('^[0-9]+-(.*)$', '\g<1>', name)
81 stripped = re.sub('^(.*)\.(diff|patch)$', '\g<1>', stripped)
82
83 return stripped
b0cdad5e 84
99e73103
CM
85def __parse_description(descr):
86 """Parse the patch description and return the new description and
87 author information (if any).
88 """
89 subject = body = ''
0543bc5f 90 authname = authemail = authdate = None
99e73103 91
0543bc5f 92 descr_lines = [line.rstrip() for line in descr.split('\n')]
99e73103
CM
93 if not descr_lines:
94 raise CmdException, "Empty patch description"
95
0543bc5f 96 lasthdr = 0
99e73103
CM
97 end = len(descr_lines)
98
0543bc5f 99 # Parse the patch header
61dabd0e 100 for pos in range(0, end):
0543bc5f
TM
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
99e73103
CM
119
120 # get the body
0543bc5f
TM
121 if lasthdr < end:
122 body = reduce(lambda x, y: x + '\n' + y, descr_lines[lasthdr:], '')
99e73103 123
0543bc5f 124 return (subject + body, authname, authemail, authdate)
99e73103 125
0d2cd1e4
CM
126def __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
6fe6b1bd
CM
138 while True:
139 line = f.readline()
140 if not line:
141 break
0d2cd1e4
CM
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
186e6b6b 154 # remove the '[*PATCH*]' expression in the subject
0d2cd1e4 155 if descr:
7c02f338
CM
156 descr = re.findall('^(\[[^\s]*[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$',
157 descr)[0][1]
0d2cd1e4
CM
158 descr += '\n\n'
159 else:
160 raise CmdException, 'Subject: line not found'
161
162 # the rest of the patch description
6fe6b1bd
CM
163 while True:
164 line = f.readline()
165 if not line:
166 break
d4c43e19 167 if __end_descr(line):
0d2cd1e4
CM
168 break
169 else:
170 descr += line
171 descr.rstrip()
172
173 if filename:
174 f.close()
175
99e73103 176 # parse the description for author information
0543bc5f 177 descr, descr_authname, descr_authemail, descr_authdate = __parse_description(descr)
99e73103
CM
178 if descr_authname:
179 authname = descr_authname
180 if descr_authemail:
181 authemail = descr_authemail
0543bc5f
TM
182 if descr_authdate:
183 authdate = descr_authdate
99e73103 184
0d2cd1e4
CM
185 return (descr, authname, authemail, authdate)
186
187def __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
0d2cd1e4 196 descr = ''
6fe6b1bd
CM
197 while True:
198 line = f.readline()
199 if not line:
200 break
201
d4c43e19 202 if __end_descr(line):
0d2cd1e4
CM
203 break
204 else:
205 descr += line
206 descr.rstrip()
207
0d2cd1e4
CM
208 if filename:
209 f.close()
210
0543bc5f 211 descr, authname, authemail, authdate = __parse_description(descr)
99e73103
CM
212
213 # we don't yet have an agreed place for the creation date.
214 # Just return None
0543bc5f 215 return (descr, authname, authemail, authdate)
0d2cd1e4 216
9417ece4
CM
217def __import_patch(patch, filename, options):
218 """Import a patch from a file or standard input
0d2cd1e4 219 """
0d2cd1e4
CM
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
95742cfc
PBG
234 # refresh_patch() will invoke the editor in this case, with correct
235 # patch content
9d15ccd8 236 if not message:
95742cfc 237 can_edit = False
9d15ccd8 238
0d2cd1e4
CM
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
95742cfc 251 crt_series.new_patch(patch, message = message, can_edit = False,
0d2cd1e4
CM
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
9417ece4 258 print 'Importing patch "%s"...' % patch,
0d2cd1e4
CM
259 sys.stdout.flush()
260
35344f86 261 if options.base:
be3e6bd9 262 git.apply_patch(filename, git_id(options.base))
35344f86
CM
263 else:
264 git.apply_patch(filename)
265
6ad48e48
PBG
266 crt_series.refresh_patch(edit = options.edit,
267 show_patch = options.showpatch)
0d2cd1e4
CM
268
269 print 'done'
9417ece4
CM
270
271def __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
bcb6d890
CM
287 patchfile = os.path.join(patchdir, patch)
288
b0cdad5e
CM
289 if options.strip:
290 patch = __strip_patch_name(patch)
9417ece4
CM
291 if options.ignore and patch in applied:
292 print 'Ignoring already applied patch "%s"' % patch
293 continue
294
9417ece4
CM
295 __import_patch(patch, patchfile, options)
296
297def 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'
b0cdad5e
CM
321 if options.strip:
322 patch = __strip_patch_name(patch)
9417ece4
CM
323
324 __import_patch(patch, filename, options)
325
0d2cd1e4 326 print_crt_patch()