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