chiark / gitweb /
8de0eaa9eb112d1aa119b8f3887c071475afe97b
[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) or \
112                 re.match('^Index: ', line):
113             break
114         else:
115             descr += line
116     descr.rstrip()
117
118     if filename:
119         f.close()
120
121     return (descr, authname, authemail, authdate)
122
123 def __parse_patch(filename = None):
124     """Parse the input file and return (description, authname,
125     authemail, authdate)
126     """
127     if filename:
128         f = file(filename)
129     else:
130         f = sys.stdin
131
132     authname = authemail = authdate = None
133
134     descr = ''
135     for line in f:
136         # the first 'Signed-of-by:' is the author
137         if not authname and re.match('signed-off-by:\s+', line, re.I):
138             auth = re.findall('^.*?:\s+(.*)$', line)[0]
139             authname, authemail = name_email(auth)
140
141         if re.match('---\s*$', line) or re.match('diff -', line):
142             break
143         else:
144             descr += line
145     descr.rstrip()
146
147     if descr == '':
148         descr = None
149
150     if filename:
151         f.close()
152
153     return (descr, authname, authemail, authdate)
154
155 def import_file(parser, options, args):
156     """Import a GNU diff file as a new patch
157     """
158     if len(args) > 1:
159         parser.error('incorrect number of arguments')
160     elif len(args) == 1:
161         filename = args[0]
162     else:
163         filename = None
164
165     if options.name:
166         patch = options.name
167     elif filename:
168         patch = os.path.basename(filename)
169     else:
170         raise CmdException, 'Unkown patch name'
171
172     # the defaults
173     message = author_name = author_email = author_date = committer_name = \
174               committer_email = None
175
176     if options.author:
177         options.authname, options.authemail = name_email(options.author)
178
179     if options.mail:
180         message, author_name, author_email, author_date = \
181                  __parse_mail(filename)
182     else:
183         message, author_name, author_email, author_date = \
184                  __parse_patch(filename)
185
186     # refresh_patch() will invoke the editor in this case, with correct
187     # patch content
188     if not message:
189         can_edit = False
190
191     # override the automatically parsed settings
192     if options.authname:
193         author_name = options.authname
194     if options.authemail:
195         author_email = options.authemail
196     if options.authdate:
197         author_date = options.authdate
198     if options.commname:
199         committer_name = options.commname
200     if options.commemail:
201         committer_email = options.commemail
202
203     crt_series.new_patch(patch, message = message, can_edit = False,
204                          author_name = author_name,
205                          author_email = author_email,
206                          author_date = author_date,
207                          committer_name = committer_name,
208                          committer_email = committer_email)
209
210     print 'Importing patch %s...' % patch,
211     sys.stdout.flush()
212
213     git.apply_patch(filename)
214     crt_series.refresh_patch(edit = options.edit,
215                              show_patch = options.showpatch)
216
217     print 'done'
218     print_crt_patch()
219
220 def import_commit(parser, options, args):
221     """Import a commit object as a new patch
222     """
223     if len(args) != 1:
224         parser.error('incorrect number of arguments')
225
226     commit_id = args[0]
227
228     if options.name:
229         patch = options.name
230     else:
231         raise CmdException, 'Unkown patch name'
232
233     commit = git.Commit(commit_id)
234
235     if not options.reverse:
236         bottom = commit.get_parent()
237         top = commit_id
238     else:
239         bottom = commit_id
240         top = commit.get_parent()
241
242     message = commit.get_log()
243     author_name, author_email, author_date = \
244                  name_email_date(commit.get_author())
245
246     print 'Importing commit %s...' % commit_id,
247     sys.stdout.flush()
248
249     crt_series.new_patch(patch, message = message, can_edit = False,
250                          unapplied = True, bottom = bottom, top = top,
251                          author_name = author_name,
252                          author_email = author_email,
253                          author_date = author_date)
254     crt_series.push_patch(patch)
255
256     print 'done'
257     print_crt_patch()
258
259 def func(parser, options, args):
260     """Import a GNU diff file or a commit object as a new patch
261     """
262     check_local_changes()
263     check_conflicts()
264     check_head_top_equal()
265
266     if options.commit:
267         import_commit(parser, options, args)
268     else:
269         import_file(parser, options, args)