Detect description in quilt patches
[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('-e', '--edit',
57 help = 'invoke an editor for the patch description',
58 action = 'store_true'),
59 make_option('-s', '--showpatch',
60 help = 'show the patch content in the editor buffer',
61 action = 'store_true'),
62 make_option('-a', '--author', metavar = '"NAME <EMAIL>"',
63 help = 'use "NAME <EMAIL>" as the author details'),
64 make_option('--authname',
65 help = 'use AUTHNAME as the author name'),
66 make_option('--authemail',
67 help = 'use AUTHEMAIL as the author e-mail'),
68 make_option('--authdate',
69 help = 'use AUTHDATE as the author date'),
70 make_option('--commname',
71 help = 'use COMMNAME as the committer name'),
72 make_option('--commemail',
73 help = 'use COMMEMAIL as the committer e-mail')]
74
75
76 def __parse_mail(filename = None):
77 """Parse the input file in a mail format and return (description,
78 authname, authemail, authdate)
79 """
80 if filename:
81 f = file(filename)
82 else:
83 f = sys.stdin
84
85 descr = authname = authemail = authdate = None
86
87 # parse the headers
88 for line in f:
89 line = line.strip()
90 if re.match('from:\s+', line, re.I):
91 auth = re.findall('^.*?:\s+(.*)$', line)[0]
92 authname, authemail = name_email(auth)
93 elif re.match('date:\s+', line, re.I):
94 authdate = re.findall('^.*?:\s+(.*)$', line)[0]
95 elif re.match('subject:\s+', line, re.I):
96 descr = re.findall('^.*?:\s+(.*)$', line)[0]
97 elif line == '':
98 # end of headers
99 break
100
101 # remove the '[*PATCH*]' expression in the subject
102 if descr:
103 descr = re.findall('^(\[[^\s]*[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$',
104 descr)[0][1]
105 descr += '\n\n'
106 else:
107 raise CmdException, 'Subject: line not found'
108
109 # the rest of the patch description
110 for line in f:
111 if re.match('---\s*$', line) or re.match('diff -', line) or \
112 re.match('^Index: ', line):
113 break
114 else:
115 descr += line
116 descr.rstrip()
117
118 if filename:
119 f.close()
120
121 return (descr, authname, authemail, authdate)
122
123 def __parse_patch(filename = None):
124 """Parse the input file and return (description, authname,
125 authemail, authdate)
126 """
127 if filename:
128 f = file(filename)
129 else:
130 f = sys.stdin
131
132 authname = authemail = authdate = None
133
134 descr = ''
135 for line in f:
136 # the first 'Signed-of-by:' is the author
137 if not authname and re.match('signed-off-by:\s+', line, re.I):
138 auth = re.findall('^.*?:\s+(.*)$', line)[0]
139 authname, authemail = name_email(auth)
140
141 if re.match('---\s*$', line) or re.match('diff -', line):
142 break
143 else:
144 descr += line
145 descr.rstrip()
146
147 if descr == '':
148 descr = None
149
150 if filename:
151 f.close()
152
153 return (descr, authname, authemail, authdate)
154
155 def import_file(parser, options, args):
156 """Import a GNU diff file as a new patch
157 """
158 if len(args) > 1:
159 parser.error('incorrect number of arguments')
160 elif len(args) == 1:
161 filename = args[0]
162 else:
163 filename = None
164
165 if options.name:
166 patch = options.name
167 elif filename:
168 patch = os.path.basename(filename)
169 else:
170 raise CmdException, 'Unkown patch name'
171
172 # the defaults
173 message = author_name = author_email = author_date = committer_name = \
174 committer_email = None
175
176 if options.author:
177 options.authname, options.authemail = name_email(options.author)
178
179 if options.mail:
180 message, author_name, author_email, author_date = \
181 __parse_mail(filename)
182 else:
183 message, author_name, author_email, author_date = \
184 __parse_patch(filename)
185
186 # refresh_patch() will invoke the editor in this case, with correct
187 # patch content
188 if not message:
189 can_edit = False
190
191 # override the automatically parsed settings
192 if options.authname:
193 author_name = options.authname
194 if options.authemail:
195 author_email = options.authemail
196 if options.authdate:
197 author_date = options.authdate
198 if options.commname:
199 committer_name = options.commname
200 if options.commemail:
201 committer_email = options.commemail
202
203 crt_series.new_patch(patch, message = message, can_edit = False,
204 author_name = author_name,
205 author_email = author_email,
206 author_date = author_date,
207 committer_name = committer_name,
208 committer_email = committer_email)
209
210 print 'Importing patch %s...' % patch,
211 sys.stdout.flush()
212
213 git.apply_patch(filename)
214 crt_series.refresh_patch(edit = options.edit,
215 show_patch = options.showpatch)
216
217 print 'done'
218 print_crt_patch()
219
220 def import_commit(parser, options, args):
221 """Import a commit object as a new patch
222 """
223 if len(args) != 1:
224 parser.error('incorrect number of arguments')
225
226 commit_id = args[0]
227
228 if options.name:
229 patch = options.name
230 else:
231 raise CmdException, 'Unkown patch name'
232
233 commit = git.Commit(commit_id)
234
235 if not options.reverse:
236 bottom = commit.get_parent()
237 top = commit_id
238 else:
239 bottom = commit_id
240 top = commit.get_parent()
241
242 message = commit.get_log()
243 author_name, author_email, author_date = \
244 name_email_date(commit.get_author())
245
246 print 'Importing commit %s...' % commit_id,
247 sys.stdout.flush()
248
249 crt_series.new_patch(patch, message = message, can_edit = False,
250 unapplied = True, bottom = bottom, top = top,
251 author_name = author_name,
252 author_email = author_email,
253 author_date = author_date)
254 crt_series.push_patch(patch)
255
256 print 'done'
257 print_crt_patch()
258
259 def func(parser, options, args):
260 """Import a GNU diff file or a commit object as a new patch
261 """
262 check_local_changes()
263 check_conflicts()
264 check_head_top_equal()
265
266 if options.commit:
267 import_commit(parser, options, args)
268 else:
269 import_file(parser, options, args)