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