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 Patch short description
41 From: A U Thor <author@example.com>
44 Patch long description
46 If --diff was specified, the diff appears at the bottom, after a
53 Command-line options can be used to modify specific information
54 without invoking the editor.
56 If the patch diff is edited but the patch application fails, the
57 rejected patch is stored in the .stgit-failed.patch file (and also in
58 .stgit-edit.{diff,txt}). The edited patch can be replaced with one of
59 these files using the '--file' and '--diff' options.
62 options = [make_option('-d', '--diff',
63 help = 'edit the patch diff',
64 action = 'store_true'),
65 make_option('-f', '--file',
66 help = 'use FILE instead of invoking the editor'),
67 make_option('-O', '--diff-opts',
68 help = 'options to pass to git-diff'),
70 help = 'revert the commit generated by the last edit',
71 action = 'store_true'),
72 make_option('-a', '--annotate', metavar = 'NOTE',
73 help = 'annotate the patch log entry'),
74 make_option('-m', '--message',
75 help = 'replace the patch description with MESSAGE'),
76 make_option('--author', metavar = '"NAME <EMAIL>"',
77 help = 'replae the author details with "NAME <EMAIL>"'),
78 make_option('--authname',
79 help = 'replace the author name with AUTHNAME'),
80 make_option('--authemail',
81 help = 'replace the author e-mail with AUTHEMAIL'),
82 make_option('--authdate',
83 help = 'replace the author date with AUTHDATE'),
84 make_option('--commname',
85 help = 'replace the committer name with COMMNAME'),
86 make_option('--commemail',
87 help = 'replace the committer e-mail with COMMEMAIL')
88 ] + make_sign_options()
90 def __update_patch(pname, fname, options):
91 """Update the current patch from the given file.
93 patch = crt_series.get_patch(pname)
95 bottom = patch.get_bottom()
99 message, author_name, author_email, author_date, diff = parse_patch(f)
102 out.start('Updating patch "%s"' % pname)
107 git.apply_patch(fname)
109 # avoid inconsistent repository state
113 crt_series.refresh_patch(message = message,
114 author_name = author_name,
115 author_email = author_email,
116 author_date = author_date,
117 backup = True, log = 'edit')
119 if crt_series.empty_patch(pname):
120 out.done('empty patch')
124 def __edit_update_patch(pname, options):
125 """Edit the given patch interactively.
127 patch = crt_series.get_patch(pname)
129 if options.diff_opts:
131 raise CmdException, '--diff-opts only available with --diff'
132 diff_flags = options.diff_opts.split()
136 # generate the file to be edited
137 descr = patch.get_description().strip()
138 descr_lines = descr.split('\n')
139 authdate = patch.get_authdate()
141 short_descr = descr_lines[0].rstrip()
142 long_descr = reduce(lambda x, y: x + '\n' + y,
143 descr_lines[1:], '').strip()
145 tmpl = '%(shortdescr)s\n\n' \
146 'From: %(authname)s <%(authemail)s>\n'
148 tmpl += 'Date: %(authdate)s\n'
149 tmpl += '\n%(longdescr)s\n'
152 'shortdescr': short_descr,
153 'longdescr': long_descr,
154 'authname': patch.get_authname(),
155 'authemail': patch.get_authemail(),
156 'authdate': patch.get_authdate()
160 # add the patch diff to the edited file
161 bottom = patch.get_bottom()
162 top = patch.get_top()
168 tmpl_dict['diffstat'] = git.diffstat(rev1 = bottom, rev2 = top)
169 tmpl_dict['diff'] = git.diff(rev1 = bottom, rev2 = top,
170 diff_flags = diff_flags)
172 for key in tmpl_dict:
173 # make empty strings if key is not available
174 if tmpl_dict[key] is None:
177 text = tmpl % tmpl_dict
180 fname = '.stgit-edit.diff'
182 fname = '.stgit-edit.txt'
184 # write the file to be edited
185 f = open(fname, 'w+')
192 __update_patch(pname, fname, options)
194 def func(parser, options, args):
195 """Edit the given patch or the current one.
197 crt_pname = crt_series.get_current()
202 raise CmdException, 'No patches applied'
205 if crt_series.patch_unapplied(pname) or crt_series.patch_hidden(pname):
206 raise CmdException, 'Cannot edit unapplied or hidden patches'
207 elif not crt_series.patch_applied(pname):
208 raise CmdException, 'Unknown patch "%s"' % pname
210 parser.error('incorrect number of arguments')
212 check_local_changes()
214 check_head_top_equal()
216 if pname != crt_pname:
217 # Go to the patch to be edited
218 applied = crt_series.get_applied()
219 between = applied[:applied.index(pname):-1]
223 options.authname, options.authemail = name_email(options.author)
226 out.start('Undoing the editing of "%s"' % pname)
227 crt_series.undo_refresh()
229 elif options.message or options.authname or options.authemail \
230 or options.authdate or options.commname or options.commemail \
232 # just refresh the patch with the given information
233 out.start('Updating patch "%s"' % pname)
234 crt_series.refresh_patch(message = options.message,
235 author_name = options.authname,
236 author_email = options.authemail,
237 author_date = options.authdate,
238 committer_name = options.commname,
239 committer_email = options.commemail,
240 backup = True, sign_str = options.sign_str,
242 notes = options.annotate)
245 __update_patch(pname, options.file, options)
247 __edit_update_patch(pname, options)
249 if pname != crt_pname:
250 # Push the patches back
252 push_patches(between)