asciidoc.conf: Steal updates from git
[stgit] / stgit / commands / edit.py
CommitLineData
ed60fdae
CM
1"""Patch editing command
2"""
3
4__copyright__ = """
5Copyright (C) 2007, Catalin Marinas <catalin.marinas@gmail.com>
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License version 2 as
9published by the Free Software Foundation.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program; if not, write to the Free Software
18Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19"""
20
575bbdae 21from stgit.argparse import opt
20a52e06 22from stgit import argparse, git, utils
4491d19b
KH
23from stgit.commands import common
24from stgit.lib import git as gitlib, transaction
ed60fdae 25from stgit.out import *
ed60fdae
CM
26
27help = 'edit a patch description or diff'
575bbdae
KH
28usage = ['[options] [<patch>]']
29description = """
b52f3780
KH
30Edit the description and author information of the given patch (or the
31current patch if no patch name was given). With --diff, also edit the
32diff.
ed60fdae 33
b52f3780 34The editor is invoked with the following contents:
ed60fdae 35
b52f3780 36 From: A U Thor <author@example.com>
ed60fdae
CM
37 Date: creation date
38
7e301a73 39 Patch description
b52f3780
KH
40
41If --diff was specified, the diff appears at the bottom, after a
42separator:
43
44 ---
ed60fdae 45
b52f3780 46 Diff text
ed60fdae
CM
47
48Command-line options can be used to modify specific information
4491d19b
KH
49without invoking the editor. (With the --edit option, the editor is
50invoked even if such command-line options are given.)
ed60fdae 51
4491d19b
KH
52If the patch diff is edited but does not apply, no changes are made to
53the patch at all. The edited patch is saved to a file which you can
54feed to "stg edit --file", once you have made sure it does apply."""
ed60fdae 55
575bbdae
KH
56options = [
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())
63
4491d19b 64directory = common.DirectoryHasRepositoryLib()
ed60fdae 65
4491d19b
KH
66def patch_diff(repository, cd, diff, diff_flags):
67 if diff:
68 diff = repository.diff_tree(cd.parent.data.tree, cd.tree, diff_flags)
69 return '\n'.join([git.diffstat(diff), diff])
ed60fdae 70 else:
4491d19b
KH
71 return None
72
73def patch_description(cd, diff):
74 """Generate a string containing the description to edit."""
75
76 desc = ['From: %s <%s>' % (cd.author.name, cd.author.email),
77 'Date: %s' % cd.author.date.isoformat(),
78 '',
79 cd.message]
80 if diff:
81 desc += ['---',
82 '',
83 diff]
84 return '\n'.join(desc)
85
86def 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))
89
90def 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)
94 .set_email(authemail)
95 .set_date(gitlib.Date.maybe(authdate))))
96 failed_diff = None
97 if diff:
29d9a264 98 tree = repository.apply(cd.parent.data.tree, diff, quiet = False)
4491d19b
KH
99 if tree == None:
100 failed_diff = diff
101 else:
102 cd = cd.set_tree(tree)
103 return cd, failed_diff
ed60fdae
CM
104
105def func(parser, options, args):
106 """Edit the given patch or the current one.
107 """
4491d19b 108 stack = directory.repository.current_stack
ed60fdae 109
4491d19b
KH
110 if len(args) == 0:
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]
ed60fdae 115 elif len(args) == 1:
4491d19b
KH
116 [patchname] = args
117 if not stack.patches.exists(patchname):
118 raise common.CmdException('%s: no such patch' % patchname)
ed60fdae 119 else:
4491d19b
KH
120 parser.error('Cannot edit more than one patch')
121
122 cd = orig_cd = stack.patches.get(patchname).commit.data
ed60fdae 123
4491d19b
KH
124 # Read patch from user-provided description.
125 if options.message == None:
126 failed_diff = None
127 else:
128 cd, failed_diff = update_patch_description(stack.repository, cd,
129 options.message)
130
131 # Modify author and committer data.
bea18554
KH
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)
4491d19b
KH
135
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))
141
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
147
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),
155 fn))
156
157 def failed():
158 fn = '.stgit-failed.patch'
159 f = file(fn, 'w')
160 f.write(patch_desc(stack.repository, cd, failed_diff,
161 options.diff, options.diff_flags))
162 f.close()
163 out.error('Edited patch did not apply.',
164 'It has been saved to "%s".' % fn)
165 return utils.STGIT_COMMAND_ERROR
166
167 # If we couldn't apply the patch, fail without even trying to
168 # effect any of the changes.
169 if failed_diff:
170 return failed()
171
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
781e549a 175 trans = transaction.StackTransaction(stack, 'edit', allow_conflicts = True)
4491d19b
KH
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)
179 else:
180 popped = []
181 trans.patches[patchname] = stack.repository.commit(cd)
182 try:
183 for pn in popped:
184 trans.push_patch(pn, iw)
185 except transaction.TransactionHalted:
186 pass
187 try:
188 # Either a complete success, or a conflict during push. But in
189 # either case, we've successfully effected the edits the user
190 # asked us for.
191 return trans.run(iw)
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.
195 return failed()