chiark / gitweb /
New test: "stg pop --keep"
[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
33Edit the given patch (defaulting to the current one) description,
34author information or its diff (if the '--diff' option is
35passed). Without any other option, the command invokes the editor with
36the patch description and diff in the form below:
37
38 Subject line
39
40 From: author information
41 Date: creation date
42
43 Patch description
44
45 Signed-off-by: author
46
47Command-line options can be used to modify specific information
48without invoking the editor.
49
50If the patch diff is edited but the patch application fails, the
51rejected patch is stored in the .stgit-failed.patch file (and also in
52.stgit-edit.{diff,txt}). The edited patch can be replaced with one of
53these files using the '--file' and '--diff' options.
54"""
55
56options = [make_option('-d', '--diff',
57 help = 'allow the editing of the patch diff',
58 action = 'store_true'),
59 make_option('-f', '--file',
60 help = 'use FILE instead of invoking the editor'),
61 make_option('-O', '--diff-opts',
62 help = 'options to pass to git-diff'),
63 make_option('--undo',
64 help = 'revert the commit generated by the last edit',
65 action = 'store_true'),
66 make_option('-a', '--annotate', metavar = 'NOTE',
67 help = 'annotate the patch log entry'),
68 make_option('-m', '--message',
69 help = 'replace the patch description with MESSAGE'),
70 make_option('--author', metavar = '"NAME <EMAIL>"',
71 help = 'replae the author details with "NAME <EMAIL>"'),
72 make_option('--authname',
73 help = 'replace the author name with AUTHNAME'),
74 make_option('--authemail',
75 help = 'replace the author e-mail with AUTHEMAIL'),
76 make_option('--authdate',
77 help = 'replace the author date with AUTHDATE'),
78 make_option('--commname',
79 help = 'replace the committer name with COMMNAME'),
80 make_option('--commemail',
81 help = 'replace the committer e-mail with COMMEMAIL')
82 ] + make_sign_options()
83
84def __update_patch(pname, fname, options):
85 """Update the current patch from the given file.
86 """
87 patch = crt_series.get_patch(pname)
88
89 bottom = patch.get_bottom()
90 top = patch.get_top()
91
92 f = open(fname)
93 message, author_name, author_email, author_date, diff = parse_patch(f)
94 f.close()
95
ead8e892
CM
96 out.start('Updating patch "%s"' % pname)
97
ed60fdae
CM
98 if options.diff:
99 git.switch(bottom)
100 try:
101 git.apply_patch(fname)
102 except:
103 # avoid inconsistent repository state
104 git.switch(top)
105 raise
106
ed60fdae
CM
107 crt_series.refresh_patch(message = message,
108 author_name = author_name,
109 author_email = author_email,
110 author_date = author_date,
111 backup = True, log = 'edit')
ead8e892 112
ed60fdae
CM
113 if crt_series.empty_patch(pname):
114 out.done('empty patch')
115 else:
116 out.done()
117
118def __edit_update_patch(pname, options):
119 """Edit the given patch interactively.
120 """
121 patch = crt_series.get_patch(pname)
122
123 if options.diff_opts:
124 if not options.diff:
125 raise CmdException, '--diff-opts only available with --diff'
126 diff_flags = options.diff_opts.split()
127 else:
128 diff_flags = []
129
130 # generate the file to be edited
131 descr = patch.get_description().strip()
132 descr_lines = descr.split('\n')
133 authdate = patch.get_authdate()
134
135 short_descr = descr_lines[0].rstrip()
136 long_descr = reduce(lambda x, y: x + '\n' + y,
137 descr_lines[1:], '').strip()
138
139 tmpl = '%(shortdescr)s\n\n' \
140 'From: %(authname)s <%(authemail)s>\n'
141 if authdate:
142 tmpl += 'Date: %(authdate)s\n'
143 tmpl += '\n%(longdescr)s\n'
144
145 tmpl_dict = {
146 'shortdescr': short_descr,
147 'longdescr': long_descr,
148 'authname': patch.get_authname(),
149 'authemail': patch.get_authemail(),
150 'authdate': patch.get_authdate()
151 }
152
153 if options.diff:
154 # add the patch diff to the edited file
155 bottom = patch.get_bottom()
156 top = patch.get_top()
157
158 tmpl += '---\n\n' \
159 '%(diffstat)s\n' \
160 '%(diff)s'
161
162 tmpl_dict['diffstat'] = git.diffstat(rev1 = bottom, rev2 = top)
163 tmpl_dict['diff'] = git.diff(rev1 = bottom, rev2 = top,
164 diff_flags = diff_flags)
165
166 for key in tmpl_dict:
167 # make empty strings if key is not available
168 if tmpl_dict[key] is None:
169 tmpl_dict[key] = ''
170
171 text = tmpl % tmpl_dict
172
173 if options.diff:
174 fname = '.stgit-edit.diff'
175 else:
176 fname = '.stgit-edit.txt'
177
178 # write the file to be edited
179 f = open(fname, 'w+')
180 f.write(text)
181 f.close()
182
183 # invoke the editor
184 call_editor(fname)
185
186 __update_patch(pname, fname, options)
187
188def func(parser, options, args):
189 """Edit the given patch or the current one.
190 """
191 crt_pname = crt_series.get_current()
192
193 if not args:
194 pname = crt_pname
195 if not pname:
196 raise CmdException, 'No patches applied'
197 elif len(args) == 1:
198 pname = args[0]
199 if crt_series.patch_unapplied(pname) or crt_series.patch_hidden(pname):
200 raise CmdException, 'Cannot edit unapplied or hidden patches'
201 elif not crt_series.patch_applied(pname):
202 raise CmdException, 'Unknown patch "%s"' % pname
203 else:
204 parser.error('incorrect number of arguments')
205
206 check_local_changes()
207 check_conflicts()
208 check_head_top_equal()
209
210 if pname != crt_pname:
211 # Go to the patch to be edited
212 applied = crt_series.get_applied()
213 between = applied[:applied.index(pname):-1]
214 pop_patches(between)
215
216 if options.author:
217 options.authname, options.authemail = name_email(options.author)
218
219 if options.undo:
220 out.start('Undoing the editing of "%s"' % pname)
221 crt_series.undo_refresh()
222 out.done()
223 elif options.message or options.authname or options.authemail \
224 or options.authdate or options.commname or options.commemail \
225 or options.sign_str:
226 # just refresh the patch with the given information
227 out.start('Updating patch "%s"' % pname)
228 crt_series.refresh_patch(message = options.message,
229 author_name = options.authname,
230 author_email = options.authemail,
231 author_date = options.authdate,
232 committer_name = options.commname,
233 committer_email = options.commemail,
234 backup = True, sign_str = options.sign_str,
235 log = 'edit',
236 notes = options.annotate)
237 out.done()
238 elif options.file:
239 __update_patch(pname, options.file, options)
240 else:
241 __edit_update_patch(pname, options)
242
243 if pname != crt_pname:
244 # Push the patches back
245 between.reverse()
246 push_patches(between)