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