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 stgit.argparse import opt
22 from stgit import argparse, git, utils
23 from stgit.commands import common
24 from stgit.lib import git as gitlib, transaction
25 from stgit.out import *
27 help = 'edit a patch description or diff'
28 usage = ['[options] [<patch>]']
30 Edit the description and author information of the given patch (or the
31 current patch if no patch name was given). With --diff, also edit the
34 The editor is invoked with the following contents:
36 From: A U Thor <author@example.com>
41 If --diff was specified, the diff appears at the bottom, after a
48 Command-line options can be used to modify specific information
49 without invoking the editor. (With the --edit option, the editor is
50 invoked even if such command-line options are given.)
52 If the patch diff is edited but does not apply, no changes are made to
53 the patch at all. The edited patch is saved to a file which you can
54 feed to "stg edit --file", once you have made sure it does apply."""
57 opt('-d', '--diff', action = 'store_true',
58 short = 'Edit the patch diff'),
59 opt('-e', '--edit', action = 'store_true',
60 short = 'Invoke interactive editor'),
61 ] + (argparse.sign_options() + argparse.message_options() +
62 argparse.author_committer_options() + argparse.diff_opts_option())
64 directory = common.DirectoryHasRepositoryLib()
66 def patch_diff(repository, cd, diff, diff_flags):
68 diff = repository.diff_tree(cd.parent.data.tree, cd.tree, diff_flags)
69 return '\n'.join([git.diffstat(diff), diff])
73 def patch_description(cd, diff):
74 """Generate a string containing the description to edit."""
76 desc = ['From: %s <%s>' % (cd.author.name, cd.author.email),
77 'Date: %s' % cd.author.date.isoformat(),
84 return '\n'.join(desc)
86 def patch_desc(repository, cd, failed_diff, diff, diff_flags):
87 return patch_description(cd, failed_diff or patch_diff(
88 repository, cd, diff, diff_flags))
90 def update_patch_description(repository, cd, text):
91 message, authname, authemail, authdate, diff = common.parse_patch(text)
92 cd = (cd.set_message(message)
93 .set_author(cd.author.set_name(authname)
95 .set_date(gitlib.Date.maybe(authdate))))
98 tree = repository.apply(cd.parent.data.tree, diff, quiet = False)
102 cd = cd.set_tree(tree)
103 return cd, failed_diff
105 def func(parser, options, args):
106 """Edit the given patch or the current one.
108 stack = directory.repository.current_stack
111 if not stack.patchorder.applied:
112 raise common.CmdException(
113 'Cannot edit top patch, because no patches are applied')
114 patchname = stack.patchorder.applied[-1]
117 if not stack.patches.exists(patchname):
118 raise common.CmdException('%s: no such patch' % patchname)
120 parser.error('Cannot edit more than one patch')
122 cd = orig_cd = stack.patches.get(patchname).commit.data
124 # Read patch from user-provided description.
125 if options.message == None:
128 cd, failed_diff = update_patch_description(stack.repository, cd,
131 # Modify author and committer data.
132 a, c = options.author(cd.author), options.committer(cd.committer)
133 if (a, c) != (cd.author, cd.committer):
134 cd = cd.set_author(a).set_committer(c)
136 # Add Signed-off-by: or similar.
137 if options.sign_str != None:
138 cd = cd.set_message(utils.add_sign_line(
139 cd.message, options.sign_str, gitlib.Person.committer().name,
140 gitlib.Person.committer().email))
142 if options.save_template:
143 options.save_template(
144 patch_desc(stack.repository, cd, failed_diff,
145 options.diff, options.diff_flags))
146 return utils.STGIT_SUCCESS
148 # Let user edit the patch manually.
149 if cd == orig_cd or options.edit:
150 fn = '.stgit-edit.' + ['txt', 'patch'][bool(options.diff)]
151 cd, failed_diff = update_patch_description(
152 stack.repository, cd, utils.edit_string(
153 patch_desc(stack.repository, cd, failed_diff,
154 options.diff, options.diff_flags),
158 fn = '.stgit-failed.patch'
160 f.write(patch_desc(stack.repository, cd, failed_diff,
161 options.diff, options.diff_flags))
163 out.error('Edited patch did not apply.',
164 'It has been saved to "%s".' % fn)
165 return utils.STGIT_COMMAND_ERROR
167 # If we couldn't apply the patch, fail without even trying to
168 # effect any of the changes.
172 # The patch applied, so now we have to rewrite the StGit patch
173 # (and any patches on top of it).
174 iw = stack.repository.default_iw
175 trans = transaction.StackTransaction(stack, 'edit', allow_conflicts = True)
176 if patchname in trans.applied:
177 popped = trans.applied[trans.applied.index(patchname)+1:]
178 assert not trans.pop_patches(lambda pn: pn in popped)
181 trans.patches[patchname] = stack.repository.commit(cd)
184 trans.push_patch(pn, iw)
185 except transaction.TransactionHalted:
188 # Either a complete success, or a conflict during push. But in
189 # either case, we've successfully effected the edits the user
192 except transaction.TransactionException:
193 # Transaction aborted -- we couldn't check out files due to
194 # dirty index/worktree. The edits were not carried out.