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