Specify a different base for the imported patch
[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
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>|<commit>]
28
29 Create a new patch and import the given GNU diff file (defaulting to
30 the standard input) or a given commit object into it. By default, the
31 file name is used as the patch name but this can be overriden with the
32 '--name' option.
33
34 The patch file can either be a normal file with the description at the
35 top or it can have standard mail format, the Subject, From and Date
36 headers being used for generating the patch information. The patch
37 description has to be separated from the data with a '---' line. For a
38 normal file, if no author information is given, the first
39 'Signed-off-by:' line is used.
40
41 When a commit object is imported, the log and author information are
42 those of the commit object. Passing the '--reverse' option will cancel
43 an existing commit object."""
44
45 options = [make_option('-m', '--mail',
46 help = 'import the patch from a standard e-mail file',
47 action = 'store_true'),
48 make_option('-c', '--commit',
49 help = 'import a commit object as a patch',
50 action = 'store_true'),
51 make_option('--reverse',
52 help = 'reverse the commit object before importing',
53 action = 'store_true'),
54 make_option('-n', '--name',
55 help = 'use NAME as the patch name'),
56 make_option('--base',
57 help = 'use BASE instead of HEAD for file importing'),
58 make_option('-e', '--edit',
59 help = 'invoke an editor for the patch description',
60 action = 'store_true'),
61 make_option('-s', '--showpatch',
62 help = 'show the patch content in the editor buffer',
63 action = 'store_true'),
64 make_option('-a', '--author', metavar = '"NAME <EMAIL>"',
65 help = 'use "NAME <EMAIL>" as the author details'),
66 make_option('--authname',
67 help = 'use AUTHNAME as the author name'),
68 make_option('--authemail',
69 help = 'use AUTHEMAIL as the author e-mail'),
70 make_option('--authdate',
71 help = 'use AUTHDATE as the author date'),
72 make_option('--commname',
73 help = 'use COMMNAME as the committer name'),
74 make_option('--commemail',
75 help = 'use COMMEMAIL as the committer e-mail')]
76
77
78 def __parse_mail(filename = None):
79 """Parse the input file in a mail format and return (description,
80 authname, authemail, authdate)
81 """
82 if filename:
83 f = file(filename)
84 else:
85 f = sys.stdin
86
87 descr = authname = authemail = authdate = None
88
89 # parse the headers
90 for line in f:
91 line = line.strip()
92 if re.match('from:\s+', line, re.I):
93 auth = re.findall('^.*?:\s+(.*)$', line)[0]
94 authname, authemail = name_email(auth)
95 elif re.match('date:\s+', line, re.I):
96 authdate = re.findall('^.*?:\s+(.*)$', line)[0]
97 elif re.match('subject:\s+', line, re.I):
98 descr = re.findall('^.*?:\s+(.*)$', line)[0]
99 elif line == '':
100 # end of headers
101 break
102
103 # remove the '[*PATCH*]' expression in the subject
104 if descr:
105 descr = re.findall('^(\[[^\s]*[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$',
106 descr)[0][1]
107 descr += '\n\n'
108 else:
109 raise CmdException, 'Subject: line not found'
110
111 # the rest of the patch description
112 for line in f:
113 if re.match('---\s*$', line) or re.match('diff -', line) or \
114 re.match('^Index: ', line):
115 break
116 else:
117 descr += line
118 descr.rstrip()
119
120 if filename:
121 f.close()
122
123 return (descr, authname, authemail, authdate)
124
125 def __parse_patch(filename = None):
126 """Parse the input file and return (description, authname,
127 authemail, authdate)
128 """
129 if filename:
130 f = file(filename)
131 else:
132 f = sys.stdin
133
134 authname = authemail = authdate = None
135
136 descr = ''
137 for line in f:
138 # the first 'Signed-of-by:' is the author
139 if not authname and re.match('signed-off-by:\s+', line, re.I):
140 auth = re.findall('^.*?:\s+(.*)$', line)[0]
141 authname, authemail = name_email(auth)
142
143 if re.match('---\s*$', line) or re.match('diff -', line):
144 break
145 else:
146 descr += line
147 descr.rstrip()
148
149 if descr == '':
150 descr = None
151
152 if filename:
153 f.close()
154
155 return (descr, authname, authemail, authdate)
156
157 def import_file(parser, options, args):
158 """Import a GNU diff file as a new patch
159 """
160 if len(args) > 1:
161 parser.error('incorrect number of arguments')
162 elif len(args) == 1:
163 filename = args[0]
164 else:
165 filename = None
166
167 if options.name:
168 patch = options.name
169 elif filename:
170 patch = os.path.basename(filename)
171 else:
172 raise CmdException, 'Unkown patch name'
173
174 # the defaults
175 message = author_name = author_email = author_date = committer_name = \
176 committer_email = None
177
178 if options.author:
179 options.authname, options.authemail = name_email(options.author)
180
181 if options.mail:
182 message, author_name, author_email, author_date = \
183 __parse_mail(filename)
184 else:
185 message, author_name, author_email, author_date = \
186 __parse_patch(filename)
187
188 # refresh_patch() will invoke the editor in this case, with correct
189 # patch content
190 if not message:
191 can_edit = False
192
193 # override the automatically parsed settings
194 if options.authname:
195 author_name = options.authname
196 if options.authemail:
197 author_email = options.authemail
198 if options.authdate:
199 author_date = options.authdate
200 if options.commname:
201 committer_name = options.commname
202 if options.commemail:
203 committer_email = options.commemail
204
205 crt_series.new_patch(patch, message = message, can_edit = False,
206 author_name = author_name,
207 author_email = author_email,
208 author_date = author_date,
209 committer_name = committer_name,
210 committer_email = committer_email)
211
212 print 'Importing patch %s...' % patch,
213 sys.stdout.flush()
214
215 if options.base:
216 orig_head = git.get_head()
217 git.switch(options.base)
218
219 try:
220 git.apply_patch(filename)
221 except git.GitException, ex:
222 print >> sys.stderr, '"git apply" failed'
223 git.switch(orig_head)
224 raise
225
226 top = crt_series.refresh_patch(commit_only = True)
227 git.switch(orig_head)
228 git.merge(options.base, orig_head, top)
229 else:
230 git.apply_patch(filename)
231
232 crt_series.refresh_patch(edit = options.edit,
233 show_patch = options.showpatch)
234
235 print 'done'
236 print_crt_patch()
237
238 def import_commit(parser, options, args):
239 """Import a commit object as a new patch
240 """
241 if len(args) != 1:
242 parser.error('incorrect number of arguments')
243
244 commit_id = args[0]
245
246 if options.name:
247 patch = options.name
248 else:
249 raise CmdException, 'Unkown patch name'
250
251 commit = git.Commit(commit_id)
252
253 if not options.reverse:
254 bottom = commit.get_parent()
255 top = commit_id
256 else:
257 bottom = commit_id
258 top = commit.get_parent()
259
260 message = commit.get_log()
261 author_name, author_email, author_date = \
262 name_email_date(commit.get_author())
263
264 print 'Importing commit %s...' % commit_id,
265 sys.stdout.flush()
266
267 crt_series.new_patch(patch, message = message, can_edit = False,
268 unapplied = True, bottom = bottom, top = top,
269 author_name = author_name,
270 author_email = author_email,
271 author_date = author_date)
272 crt_series.push_patch(patch)
273
274 print 'done'
275 print_crt_patch()
276
277 def func(parser, options, args):
278 """Import a GNU diff file or a commit object as a new patch
279 """
280 check_local_changes()
281 check_conflicts()
282 check_head_top_equal()
283
284 if options.commit:
285 import_commit(parser, options, args)
286 else:
287 import_file(parser, options, args)