chiark / gitweb /
0f7860bfec89a57308d5b07b42522f0f22645b90
[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 __end_descr(line):
79     return re.match('---\s*$', line) or re.match('diff -', line) or \
80             re.match('Index: ', line)
81     
82 def __parse_mail(filename = None):
83     """Parse the input file in a mail format and return (description,
84     authname, authemail, authdate)
85     """
86     if filename:
87         f = file(filename)
88     else:
89         f = sys.stdin
90
91     descr = authname = authemail = authdate = None
92
93     # parse the headers
94     while True:
95         line = f.readline()
96         if not line:
97             break
98         line = line.strip()
99         if re.match('from:\s+', line, re.I):
100             auth = re.findall('^.*?:\s+(.*)$', line)[0]
101             authname, authemail = name_email(auth)
102         elif re.match('date:\s+', line, re.I):
103             authdate = re.findall('^.*?:\s+(.*)$', line)[0]
104         elif re.match('subject:\s+', line, re.I):
105             descr = re.findall('^.*?:\s+(.*)$', line)[0]
106         elif line == '':
107             # end of headers
108             break
109
110     # remove the '[*PATCH*]' expression in the subject
111     if descr:
112         descr = re.findall('^(\[[^\s]*[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$',
113                            descr)[0][1]
114         descr += '\n\n'
115     else:
116         raise CmdException, 'Subject: line not found'
117
118     # the rest of the patch description
119     while True:
120         line = f.readline()
121         if not line:
122             break
123         if __end_descr(line):
124             break
125         else:
126             descr += line
127     descr.rstrip()
128
129     if filename:
130         f.close()
131
132     return (descr, authname, authemail, authdate)
133
134 def __parse_patch(filename = None):
135     """Parse the input file and return (description, authname,
136     authemail, authdate)
137     """
138     if filename:
139         f = file(filename)
140     else:
141         f = sys.stdin
142
143     authname = authemail = authdate = None
144
145     descr = ''
146     while True:
147         line = f.readline()
148         if not line:
149             break
150
151         # the first 'Signed-of-by:' is the author
152         if not authname and re.match('signed-off-by:\s+', line, re.I):
153             auth = re.findall('^.*?:\s+(.*)$', line)[0]
154             authname, authemail = name_email(auth)
155
156         if __end_descr(line):
157             break
158         else:
159             descr += line
160     descr.rstrip()
161
162     if descr == '':
163         descr = None
164
165     if filename:
166         f.close()
167
168     return (descr, authname, authemail, authdate)
169
170 def import_file(parser, options, args):
171     """Import a GNU diff file as a new patch
172     """
173     if len(args) > 1:
174         parser.error('incorrect number of arguments')
175     elif len(args) == 1:
176         filename = args[0]
177     else:
178         filename = None
179
180     if options.name:
181         patch = options.name
182     elif filename:
183         patch = os.path.basename(filename)
184     else:
185         raise CmdException, 'Unkown patch name'
186
187     # the defaults
188     message = author_name = author_email = author_date = committer_name = \
189               committer_email = None
190
191     if options.author:
192         options.authname, options.authemail = name_email(options.author)
193
194     if options.mail:
195         message, author_name, author_email, author_date = \
196                  __parse_mail(filename)
197     else:
198         message, author_name, author_email, author_date = \
199                  __parse_patch(filename)
200
201     # refresh_patch() will invoke the editor in this case, with correct
202     # patch content
203     if not message:
204         can_edit = False
205
206     # override the automatically parsed settings
207     if options.authname:
208         author_name = options.authname
209     if options.authemail:
210         author_email = options.authemail
211     if options.authdate:
212         author_date = options.authdate
213     if options.commname:
214         committer_name = options.commname
215     if options.commemail:
216         committer_email = options.commemail
217
218     crt_series.new_patch(patch, message = message, can_edit = False,
219                          author_name = author_name,
220                          author_email = author_email,
221                          author_date = author_date,
222                          committer_name = committer_name,
223                          committer_email = committer_email)
224
225     print 'Importing patch %s...' % patch,
226     sys.stdout.flush()
227
228     if options.base:
229         orig_head = git.get_head()
230         git.switch(options.base)
231
232         try:
233             git.apply_patch(filename)
234         except git.GitException, ex:
235             print >> sys.stderr, '"git apply" failed'
236             git.switch(orig_head)
237             raise
238
239         top = crt_series.refresh_patch(commit_only = True)
240         git.switch(orig_head)
241         git.merge(options.base, orig_head, top)
242     else:
243         git.apply_patch(filename)
244
245     crt_series.refresh_patch(edit = options.edit,
246                              show_patch = options.showpatch)
247
248     print 'done'
249     print_crt_patch()
250
251 def import_commit(parser, options, args):
252     """Import a commit object as a new patch
253     """
254     if len(args) != 1:
255         parser.error('incorrect number of arguments')
256
257     commit_id = args[0]
258
259     if options.name:
260         patch = options.name
261     else:
262         raise CmdException, 'Unkown patch name'
263
264     commit = git.Commit(commit_id)
265
266     if not options.reverse:
267         bottom = commit.get_parent()
268         top = commit_id
269     else:
270         bottom = commit_id
271         top = commit.get_parent()
272
273     message = commit.get_log()
274     author_name, author_email, author_date = \
275                  name_email_date(commit.get_author())
276
277     print 'Importing commit %s...' % commit_id,
278     sys.stdout.flush()
279
280     crt_series.new_patch(patch, message = message, can_edit = False,
281                          unapplied = True, bottom = bottom, top = top,
282                          author_name = author_name,
283                          author_email = author_email,
284                          author_date = author_date)
285     crt_series.push_patch(patch)
286
287     print 'done'
288     print_crt_patch()
289
290 def func(parser, options, args):
291     """Import a GNU diff file or a commit object as a new patch
292     """
293     check_local_changes()
294     check_conflicts()
295     check_head_top_equal()
296
297     if options.commit:
298         import_commit(parser, options, args)
299     else:
300         import_file(parser, options, args)