1 """Patch editing command
5 Copyright (C) 2007, Catalin Marinas <catalin.marinas@gmail.com>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 from optparse import OptionParser, make_option
22 from email.Utils import formatdate
24 from stgit.commands.common import *
25 from stgit.utils import *
26 from stgit.out import *
27 from stgit import stack, git
30 help = 'edit a patch description or diff'
31 usage = """%prog [options] [<patch>]
33 Edit the description and author information of the given patch (or the
34 current patch if no patch name was given). With --diff, also edit the
37 The editor is invoked with the following contents:
39 From: A U Thor <author@example.com>
44 If --diff was specified, the diff appears at the bottom, after a
51 Command-line options can be used to modify specific information
52 without invoking the editor.
54 If the patch diff is edited but the patch application fails, the
55 rejected 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
57 these files using the '--file' and '--diff' options.
60 directory = DirectoryGotoToplevel()
61 options = [make_option('-d', '--diff',
62 help = 'edit the patch diff',
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'),
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'),
75 make_option('--save-template', metavar = 'FILE',
76 help = 'save the patch to FILE in the format used by -f'),
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()
91 def __update_patch(pname, fname, options):
92 """Update the current patch from the given file.
94 patch = crt_series.get_patch(pname)
96 bottom = patch.get_bottom()
103 message, author_name, author_email, author_date, diff = parse_patch(f)
106 out.start('Updating patch "%s"' % pname)
111 git.apply_patch(fname)
113 # avoid inconsistent repository state
117 crt_series.refresh_patch(message = message,
118 author_name = author_name,
119 author_email = author_email,
120 author_date = author_date,
121 backup = True, log = 'edit')
123 if crt_series.empty_patch(pname):
124 out.done('empty patch')
128 def __generate_file(pname, fname, options):
129 """Generate a file containing the description to edit
131 patch = crt_series.get_patch(pname)
133 if options.diff_opts:
135 raise CmdException, '--diff-opts only available with --diff'
136 diff_flags = options.diff_opts.split()
140 # generate the file to be edited
141 descr = patch.get_description().strip()
142 authdate = patch.get_authdate()
144 tmpl = 'From: %(authname)s <%(authemail)s>\n'
146 tmpl += 'Date: %(authdate)s\n'
147 tmpl += '\n%(descr)s\n'
151 'authname': patch.get_authname(),
152 'authemail': patch.get_authemail(),
153 'authdate': patch.get_authdate()
157 # add the patch diff to the edited file
158 bottom = patch.get_bottom()
159 top = patch.get_top()
165 tmpl_dict['diffstat'] = git.diffstat(rev1 = bottom, rev2 = top)
166 tmpl_dict['diff'] = git.diff(rev1 = bottom, rev2 = top,
167 diff_flags = diff_flags)
169 for key in tmpl_dict:
170 # make empty strings if key is not available
171 if tmpl_dict[key] is None:
174 text = tmpl % tmpl_dict
176 # write the file to be edited
178 sys.stdout.write(text)
180 f = open(fname, 'w+')
184 def __edit_update_patch(pname, options):
185 """Edit the given patch interactively.
188 fname = '.stgit-edit.diff'
190 fname = '.stgit-edit.txt'
192 __generate_file(pname, fname, options)
197 __update_patch(pname, fname, options)
199 def func(parser, options, args):
200 """Edit the given patch or the current one.
202 crt_pname = crt_series.get_current()
207 raise CmdException, 'No patches applied'
210 if crt_series.patch_unapplied(pname) or crt_series.patch_hidden(pname):
211 raise CmdException, 'Cannot edit unapplied or hidden patches'
212 elif not crt_series.patch_applied(pname):
213 raise CmdException, 'Unknown patch "%s"' % pname
215 parser.error('incorrect number of arguments')
217 check_local_changes()
219 check_head_top_equal(crt_series)
221 if pname != crt_pname:
222 # Go to the patch to be edited
223 applied = crt_series.get_applied()
224 between = applied[:applied.index(pname):-1]
225 pop_patches(crt_series, between)
228 options.authname, options.authemail = name_email(options.author)
231 out.start('Undoing the editing of "%s"' % pname)
232 crt_series.undo_refresh()
234 elif options.message or options.authname or options.authemail \
235 or options.authdate or options.commname or options.commemail \
237 # just refresh the patch with the given information
238 out.start('Updating patch "%s"' % pname)
239 crt_series.refresh_patch(message = options.message,
240 author_name = options.authname,
241 author_email = options.authemail,
242 author_date = options.authdate,
243 committer_name = options.commname,
244 committer_email = options.commemail,
245 backup = True, sign_str = options.sign_str,
247 notes = options.annotate)
249 elif options.save_template:
250 __generate_file(pname, options.save_template, options)
252 __update_patch(pname, options.file, options)
254 __edit_update_patch(pname, options)
256 if pname != crt_pname:
257 # Push the patches back
259 push_patches(crt_series, between)