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