Let parse_patch take a string instead of a file parameter
[stgit] / stgit / commands / edit.py
CommitLineData
ed60fdae
CM
1"""Patch editing command
2"""
3
4__copyright__ = """
5Copyright (C) 2007, Catalin Marinas <catalin.marinas@gmail.com>
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License version 2 as
9published by the Free Software Foundation.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program; if not, write to the Free Software
18Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19"""
20
21from optparse import OptionParser, make_option
22from email.Utils import formatdate
23
24from stgit.commands.common import *
25from stgit.utils import *
26from stgit.out import *
27from stgit import stack, git
28
29
30help = 'edit a patch description or diff'
31usage = """%prog [options] [<patch>]
32
b52f3780
KH
33Edit the description and author information of the given patch (or the
34current patch if no patch name was given). With --diff, also edit the
35diff.
ed60fdae 36
b52f3780 37The editor is invoked with the following contents:
ed60fdae 38
b52f3780 39 From: A U Thor <author@example.com>
ed60fdae
CM
40 Date: creation date
41
7e301a73 42 Patch description
b52f3780
KH
43
44If --diff was specified, the diff appears at the bottom, after a
45separator:
46
47 ---
ed60fdae 48
b52f3780 49 Diff text
ed60fdae
CM
50
51Command-line options can be used to modify specific information
52without invoking the editor.
53
54If the patch diff is edited but the patch application fails, the
55rejected patch is stored in the .stgit-failed.patch file (and also in
56.stgit-edit.{diff,txt}). The edited patch can be replaced with one of
57these files using the '--file' and '--diff' options.
58"""
59
7b601c9e 60directory = DirectoryGotoToplevel()
ed60fdae 61options = [make_option('-d', '--diff',
b52f3780 62 help = 'edit the patch diff',
ed60fdae
CM
63 action = 'store_true'),
64 make_option('-f', '--file',
65 help = 'use FILE instead of invoking the editor'),
66 make_option('-O', '--diff-opts',
67 help = 'options to pass to git-diff'),
68 make_option('--undo',
69 help = 'revert the commit generated by the last edit',
70 action = 'store_true'),
71 make_option('-a', '--annotate', metavar = 'NOTE',
72 help = 'annotate the patch log entry'),
73 make_option('-m', '--message',
74 help = 'replace the patch description with MESSAGE'),
3cbb569f
DK
75 make_option('--save-template', metavar = 'FILE',
76 help = 'save the patch to FILE in the format used by -f'),
ed60fdae
CM
77 make_option('--author', metavar = '"NAME <EMAIL>"',
78 help = 'replae the author details with "NAME <EMAIL>"'),
79 make_option('--authname',
80 help = 'replace the author name with AUTHNAME'),
81 make_option('--authemail',
82 help = 'replace the author e-mail with AUTHEMAIL'),
83 make_option('--authdate',
84 help = 'replace the author date with AUTHDATE'),
85 make_option('--commname',
86 help = 'replace the committer name with COMMNAME'),
87 make_option('--commemail',
88 help = 'replace the committer e-mail with COMMEMAIL')
89 ] + make_sign_options()
90
91def __update_patch(pname, fname, options):
92 """Update the current patch from the given file.
93 """
94 patch = crt_series.get_patch(pname)
95
96 bottom = patch.get_bottom()
97 top = patch.get_top()
98
8a88f7cb
DK
99 if fname == '-':
100 f = sys.stdin
101 else:
102 f = open(fname)
c37f9747
KH
103 (message, author_name, author_email, author_date, diff
104 ) = parse_patch(f.read())
ed60fdae
CM
105 f.close()
106
ead8e892
CM
107 out.start('Updating patch "%s"' % pname)
108
ed60fdae
CM
109 if options.diff:
110 git.switch(bottom)
111 try:
112 git.apply_patch(fname)
113 except:
114 # avoid inconsistent repository state
115 git.switch(top)
116 raise
117
ed60fdae
CM
118 crt_series.refresh_patch(message = message,
119 author_name = author_name,
120 author_email = author_email,
121 author_date = author_date,
122 backup = True, log = 'edit')
ead8e892 123
ed60fdae
CM
124 if crt_series.empty_patch(pname):
125 out.done('empty patch')
126 else:
127 out.done()
128
3cbb569f
DK
129def __generate_file(pname, fname, options):
130 """Generate a file containing the description to edit
ed60fdae
CM
131 """
132 patch = crt_series.get_patch(pname)
133
134 if options.diff_opts:
135 if not options.diff:
136 raise CmdException, '--diff-opts only available with --diff'
137 diff_flags = options.diff_opts.split()
138 else:
139 diff_flags = []
140
141 # generate the file to be edited
142 descr = patch.get_description().strip()
ed60fdae
CM
143 authdate = patch.get_authdate()
144
7e301a73 145 tmpl = 'From: %(authname)s <%(authemail)s>\n'
ed60fdae
CM
146 if authdate:
147 tmpl += 'Date: %(authdate)s\n'
7e301a73 148 tmpl += '\n%(descr)s\n'
ed60fdae
CM
149
150 tmpl_dict = {
7e301a73 151 'descr': descr,
ed60fdae
CM
152 'authname': patch.get_authname(),
153 'authemail': patch.get_authemail(),
154 'authdate': patch.get_authdate()
155 }
156
157 if options.diff:
158 # add the patch diff to the edited file
159 bottom = patch.get_bottom()
160 top = patch.get_top()
161
162 tmpl += '---\n\n' \
163 '%(diffstat)s\n' \
164 '%(diff)s'
165
166 tmpl_dict['diffstat'] = git.diffstat(rev1 = bottom, rev2 = top)
167 tmpl_dict['diff'] = git.diff(rev1 = bottom, rev2 = top,
168 diff_flags = diff_flags)
169
170 for key in tmpl_dict:
171 # make empty strings if key is not available
172 if tmpl_dict[key] is None:
173 tmpl_dict[key] = ''
174
175 text = tmpl % tmpl_dict
176
3cbb569f 177 # write the file to be edited
8a88f7cb
DK
178 if fname == '-':
179 sys.stdout.write(text)
180 else:
181 f = open(fname, 'w+')
182 f.write(text)
183 f.close()
3cbb569f
DK
184
185def __edit_update_patch(pname, options):
186 """Edit the given patch interactively.
187 """
ed60fdae
CM
188 if options.diff:
189 fname = '.stgit-edit.diff'
190 else:
191 fname = '.stgit-edit.txt'
192
3cbb569f 193 __generate_file(pname, fname, options)
ed60fdae
CM
194
195 # invoke the editor
196 call_editor(fname)
197
198 __update_patch(pname, fname, options)
199
200def func(parser, options, args):
201 """Edit the given patch or the current one.
202 """
203 crt_pname = crt_series.get_current()
204
205 if not args:
206 pname = crt_pname
207 if not pname:
208 raise CmdException, 'No patches applied'
209 elif len(args) == 1:
210 pname = args[0]
211 if crt_series.patch_unapplied(pname) or crt_series.patch_hidden(pname):
212 raise CmdException, 'Cannot edit unapplied or hidden patches'
213 elif not crt_series.patch_applied(pname):
214 raise CmdException, 'Unknown patch "%s"' % pname
215 else:
216 parser.error('incorrect number of arguments')
217
218 check_local_changes()
219 check_conflicts()
6972fd6b 220 check_head_top_equal(crt_series)
ed60fdae
CM
221
222 if pname != crt_pname:
223 # Go to the patch to be edited
224 applied = crt_series.get_applied()
225 between = applied[:applied.index(pname):-1]
6972fd6b 226 pop_patches(crt_series, between)
ed60fdae
CM
227
228 if options.author:
229 options.authname, options.authemail = name_email(options.author)
230
231 if options.undo:
232 out.start('Undoing the editing of "%s"' % pname)
233 crt_series.undo_refresh()
234 out.done()
235 elif options.message or options.authname or options.authemail \
236 or options.authdate or options.commname or options.commemail \
237 or options.sign_str:
238 # just refresh the patch with the given information
239 out.start('Updating patch "%s"' % pname)
240 crt_series.refresh_patch(message = options.message,
241 author_name = options.authname,
242 author_email = options.authemail,
243 author_date = options.authdate,
244 committer_name = options.commname,
245 committer_email = options.commemail,
246 backup = True, sign_str = options.sign_str,
247 log = 'edit',
248 notes = options.annotate)
249 out.done()
3cbb569f
DK
250 elif options.save_template:
251 __generate_file(pname, options.save_template, options)
ed60fdae
CM
252 elif options.file:
253 __update_patch(pname, options.file, options)
254 else:
255 __edit_update_patch(pname, options)
256
257 if pname != crt_pname:
258 # Push the patches back
259 between.reverse()
6972fd6b 260 push_patches(crt_series, between)