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)
99 git.apply_patch(fname)
101 # avoid inconsistent repository state
105 out.start('Updating patch "%s"' % pname)
106 crt_series.refresh_patch(message = message,
107 author_name = author_name,
108 author_email = author_email,
109 author_date = author_date,
110 backup = True, log = 'edit')
111 if crt_series.empty_patch(pname):
112 out.done('empty patch')
116 def __edit_update_patch(pname, options):
117 """Edit the given patch interactively.
119 patch = crt_series.get_patch(pname)
121 if options.diff_opts:
123 raise CmdException, '--diff-opts only available with --diff'
124 diff_flags = options.diff_opts.split()
128 # generate the file to be edited
129 descr = patch.get_description().strip()
130 descr_lines = descr.split('\n')
131 authdate = patch.get_authdate()
133 short_descr = descr_lines[0].rstrip()
134 long_descr = reduce(lambda x, y: x + '\n' + y,
135 descr_lines[1:], '').strip()
137 tmpl = '%(shortdescr)s\n\n' \
138 'From: %(authname)s <%(authemail)s>\n'
140 tmpl += 'Date: %(authdate)s\n'
141 tmpl += '\n%(longdescr)s\n'
144 'shortdescr': short_descr,
145 'longdescr': long_descr,
146 'authname': patch.get_authname(),
147 'authemail': patch.get_authemail(),
148 'authdate': patch.get_authdate()
152 # add the patch diff to the edited file
153 bottom = patch.get_bottom()
154 top = patch.get_top()
160 tmpl_dict['diffstat'] = git.diffstat(rev1 = bottom, rev2 = top)
161 tmpl_dict['diff'] = git.diff(rev1 = bottom, rev2 = top,
162 diff_flags = diff_flags)
164 for key in tmpl_dict:
165 # make empty strings if key is not available
166 if tmpl_dict[key] is None:
169 text = tmpl % tmpl_dict
172 fname = '.stgit-edit.diff'
174 fname = '.stgit-edit.txt'
176 # write the file to be edited
177 f = open(fname, 'w+')
184 __update_patch(pname, fname, options)
186 def func(parser, options, args):
187 """Edit the given patch or the current one.
189 crt_pname = crt_series.get_current()
194 raise CmdException, 'No patches applied'
197 if crt_series.patch_unapplied(pname) or crt_series.patch_hidden(pname):
198 raise CmdException, 'Cannot edit unapplied or hidden patches'
199 elif not crt_series.patch_applied(pname):
200 raise CmdException, 'Unknown patch "%s"' % pname
202 parser.error('incorrect number of arguments')
204 check_local_changes()
206 check_head_top_equal()
208 if pname != crt_pname:
209 # Go to the patch to be edited
210 applied = crt_series.get_applied()
211 between = applied[:applied.index(pname):-1]
215 options.authname, options.authemail = name_email(options.author)
218 out.start('Undoing the editing of "%s"' % pname)
219 crt_series.undo_refresh()
221 elif options.message or options.authname or options.authemail \
222 or options.authdate or options.commname or options.commemail \
224 # just refresh the patch with the given information
225 out.start('Updating patch "%s"' % pname)
226 crt_series.refresh_patch(message = options.message,
227 author_name = options.authname,
228 author_email = options.authemail,
229 author_date = options.authdate,
230 committer_name = options.commname,
231 committer_email = options.commemail,
232 backup = True, sign_str = options.sign_str,
234 notes = options.annotate)
237 __update_patch(pname, options.file, options)
239 __edit_update_patch(pname, options)
241 if pname != crt_pname:
242 # Push the patches back
244 push_patches(between)