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 make_option
23 from stgit import git, utils
24 from stgit.commands import common
25 from stgit.lib import git as gitlib, transaction
26 from stgit.out import *
28 help = 'edit a patch description or diff'
29 usage = """%prog [options] [<patch>]
31 Edit the description and author information of the given patch (or the
32 current patch if no patch name was given). With --diff, also edit the
35 The editor is invoked with the following contents:
37 From: A U Thor <author@example.com>
42 If --diff was specified, the diff appears at the bottom, after a
49 Command-line options can be used to modify specific information
50 without invoking the editor. (With the --edit option, the editor is
51 invoked even if such command-line options are given.)
53 If the patch diff is edited but does not apply, no changes are made to
54 the patch at all. The edited patch is saved to a file which you can
55 feed to "stg edit --file", once you have made sure it does apply."""
57 directory = common.DirectoryHasRepositoryLib()
58 options = [make_option('-d', '--diff',
59 help = 'edit the patch diff',
60 action = 'store_true'),
61 make_option('-e', '--edit', action = 'store_true',
62 help = 'invoke interactive editor'),
63 make_option('--author', metavar = '"NAME <EMAIL>"',
64 help = 'replae the author details with "NAME <EMAIL>"'),
65 make_option('--authname',
66 help = 'replace the author name with AUTHNAME'),
67 make_option('--authemail',
68 help = 'replace the author e-mail with AUTHEMAIL'),
69 make_option('--authdate',
70 help = 'replace the author date with AUTHDATE'),
71 make_option('--commname',
72 help = 'replace the committer name with COMMNAME'),
73 make_option('--commemail',
74 help = 'replace the committer e-mail with COMMEMAIL')
75 ] + (utils.make_sign_options() + utils.make_message_options()
76 + utils.make_diff_opts_option())
78 def patch_diff(repository, cd, diff, diff_flags):
80 diff = repository.diff_tree(cd.parent.data.tree, cd.tree, diff_flags)
81 return '\n'.join([git.diffstat(diff), diff])
85 def patch_description(cd, diff):
86 """Generate a string containing the description to edit."""
88 desc = ['From: %s <%s>' % (cd.author.name, cd.author.email),
89 'Date: %s' % cd.author.date.isoformat(),
96 return '\n'.join(desc)
98 def patch_desc(repository, cd, failed_diff, diff, diff_flags):
99 return patch_description(cd, failed_diff or patch_diff(
100 repository, cd, diff, diff_flags))
102 def update_patch_description(repository, cd, text):
103 message, authname, authemail, authdate, diff = common.parse_patch(text)
104 cd = (cd.set_message(message)
105 .set_author(cd.author.set_name(authname)
106 .set_email(authemail)
107 .set_date(gitlib.Date.maybe(authdate))))
110 tree = repository.apply(cd.parent.data.tree, diff)
114 cd = cd.set_tree(tree)
115 return cd, failed_diff
117 def func(parser, options, args):
118 """Edit the given patch or the current one.
120 stack = directory.repository.current_stack
123 if not stack.patchorder.applied:
124 raise common.CmdException(
125 'Cannot edit top patch, because no patches are applied')
126 patchname = stack.patchorder.applied[-1]
129 if not stack.patches.exists(patchname):
130 raise common.CmdException('%s: no such patch' % patchname)
132 parser.error('Cannot edit more than one patch')
134 cd = orig_cd = stack.patches.get(patchname).commit.data
136 # Read patch from user-provided description.
137 if options.message == None:
140 cd, failed_diff = update_patch_description(stack.repository, cd,
143 # Modify author and committer data.
144 if options.author != None:
145 options.authname, options.authemail = common.name_email(options.author)
146 for p, f, val in [('author', 'name', options.authname),
147 ('author', 'email', options.authemail),
148 ('author', 'date', gitlib.Date.maybe(options.authdate)),
149 ('committer', 'name', options.commname),
150 ('committer', 'email', options.commemail)]:
152 cd = getattr(cd, 'set_' + p)(
153 getattr(getattr(cd, p), 'set_' + f)(val))
155 # Add Signed-off-by: or similar.
156 if options.sign_str != None:
157 cd = cd.set_message(utils.add_sign_line(
158 cd.message, options.sign_str, gitlib.Person.committer().name,
159 gitlib.Person.committer().email))
161 if options.save_template:
162 options.save_template(
163 patch_desc(stack.repository, cd, failed_diff,
164 options.diff, options.diff_flags))
165 return utils.STGIT_SUCCESS
167 # Let user edit the patch manually.
168 if cd == orig_cd or options.edit:
169 fn = '.stgit-edit.' + ['txt', 'patch'][bool(options.diff)]
170 cd, failed_diff = update_patch_description(
171 stack.repository, cd, utils.edit_string(
172 patch_desc(stack.repository, cd, failed_diff,
173 options.diff, options.diff_flags),
177 fn = '.stgit-failed.patch'
179 f.write(patch_desc(stack.repository, cd, failed_diff,
180 options.diff, options.diff_flags))
182 out.error('Edited patch did not apply.',
183 'It has been saved to "%s".' % fn)
184 return utils.STGIT_COMMAND_ERROR
186 # If we couldn't apply the patch, fail without even trying to
187 # effect any of the changes.
191 # The patch applied, so now we have to rewrite the StGit patch
192 # (and any patches on top of it).
193 iw = stack.repository.default_iw
194 trans = transaction.StackTransaction(stack, 'stg edit')
195 if patchname in trans.applied:
196 popped = trans.applied[trans.applied.index(patchname)+1:]
197 assert not trans.pop_patches(lambda pn: pn in popped)
200 trans.patches[patchname] = stack.repository.commit(cd)
203 trans.push_patch(pn, iw)
204 except transaction.TransactionHalted:
207 # Either a complete success, or a conflict during push. But in
208 # either case, we've successfully effected the edits the user
211 except transaction.TransactionException:
212 # Transaction aborted -- we couldn't check out files due to
213 # dirty index/worktree. The edits were not carried out.