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