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 given patch (defaulting to the current one) description,
34 author information or its diff (if the '--diff' option is
35 passed). Without any other option, the command invokes the editor with
36 the patch description and diff in the form below:
40 From: author information
47 Command-line options can be used to modify specific information
48 without invoking the editor.
50 If the patch diff is edited but the patch application fails, the
51 rejected 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
53 these files using the '--file' and '--diff' options.
56 options = [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'),
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()
84 def __update_patch(pname, fname, options):
85 """Update the current patch from the given file.
87 patch = crt_series.get_patch(pname)
89 bottom = patch.get_bottom()
93 message, author_name, author_email, author_date, diff = parse_patch(f)
96 out.start('Updating patch "%s"' % pname)
101 git.apply_patch(fname)
103 # avoid inconsistent repository state
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')
113 if crt_series.empty_patch(pname):
114 out.done('empty patch')
118 def __edit_update_patch(pname, options):
119 """Edit the given patch interactively.
121 patch = crt_series.get_patch(pname)
123 if options.diff_opts:
125 raise CmdException, '--diff-opts only available with --diff'
126 diff_flags = options.diff_opts.split()
130 # generate the file to be edited
131 descr = patch.get_description().strip()
132 descr_lines = descr.split('\n')
133 authdate = patch.get_authdate()
135 short_descr = descr_lines[0].rstrip()
136 long_descr = reduce(lambda x, y: x + '\n' + y,
137 descr_lines[1:], '').strip()
139 tmpl = '%(shortdescr)s\n\n' \
140 'From: %(authname)s <%(authemail)s>\n'
142 tmpl += 'Date: %(authdate)s\n'
143 tmpl += '\n%(longdescr)s\n'
146 'shortdescr': short_descr,
147 'longdescr': long_descr,
148 'authname': patch.get_authname(),
149 'authemail': patch.get_authemail(),
150 'authdate': patch.get_authdate()
154 # add the patch diff to the edited file
155 bottom = patch.get_bottom()
156 top = patch.get_top()
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)
166 for key in tmpl_dict:
167 # make empty strings if key is not available
168 if tmpl_dict[key] is None:
171 text = tmpl % tmpl_dict
174 fname = '.stgit-edit.diff'
176 fname = '.stgit-edit.txt'
178 # write the file to be edited
179 f = open(fname, 'w+')
186 __update_patch(pname, fname, options)
188 def func(parser, options, args):
189 """Edit the given patch or the current one.
191 crt_pname = crt_series.get_current()
196 raise CmdException, 'No patches applied'
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
204 parser.error('incorrect number of arguments')
206 check_local_changes()
208 check_head_top_equal()
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]
217 options.authname, options.authemail = name_email(options.author)
220 out.start('Undoing the editing of "%s"' % pname)
221 crt_series.undo_refresh()
223 elif options.message or options.authname or options.authemail \
224 or options.authdate or options.commname or options.commemail \
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,
236 notes = options.annotate)
239 __update_patch(pname, options.file, options)
241 __edit_update_patch(pname, options)
243 if pname != crt_pname:
244 # Push the patches back
246 push_patches(between)