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