Add support for initializing a branch for stgit from Emacs.
[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
4491d19b 21from optparse import make_option
ed60fdae 22
20a52e06 23from stgit import argparse, git, utils
4491d19b
KH
24from stgit.commands import common
25from stgit.lib import git as gitlib, transaction
ed60fdae 26from stgit.out import *
ed60fdae
CM
27
28help = 'edit a patch description or diff'
29usage = """%prog [options] [<patch>]
30
b52f3780
KH
31Edit the description and author information of the given patch (or the
32current patch if no patch name was given). With --diff, also edit the
33diff.
ed60fdae 34
b52f3780 35The editor is invoked with the following contents:
ed60fdae 36
b52f3780 37 From: A U Thor <author@example.com>
ed60fdae
CM
38 Date: creation date
39
7e301a73 40 Patch description
b52f3780
KH
41
42If --diff was specified, the diff appears at the bottom, after a
43separator:
44
45 ---
ed60fdae 46
b52f3780 47 Diff text
ed60fdae
CM
48
49Command-line options can be used to modify specific information
4491d19b
KH
50without invoking the editor. (With the --edit option, the editor is
51invoked even if such command-line options are given.)
ed60fdae 52
4491d19b
KH
53If the patch diff is edited but does not apply, no changes are made to
54the patch at all. The edited patch is saved to a file which you can
55feed to "stg edit --file", once you have made sure it does apply."""
ed60fdae 56
4491d19b 57directory = common.DirectoryHasRepositoryLib()
ed60fdae 58options = [make_option('-d', '--diff',
b52f3780 59 help = 'edit the patch diff',
ed60fdae 60 action = 'store_true'),
4491d19b
KH
61 make_option('-e', '--edit', action = 'store_true',
62 help = 'invoke interactive editor'),
20a52e06
KH
63 ] + (argparse.sign_options() + argparse.message_options()
64 + argparse.author_committer_options()
65 + argparse.diff_opts_option())
ed60fdae 66
4491d19b
KH
67def patch_diff(repository, cd, diff, diff_flags):
68 if diff:
69 diff = repository.diff_tree(cd.parent.data.tree, cd.tree, diff_flags)
70 return '\n'.join([git.diffstat(diff), diff])
ed60fdae 71 else:
4491d19b
KH
72 return None
73
74def patch_description(cd, diff):
75 """Generate a string containing the description to edit."""
76
77 desc = ['From: %s <%s>' % (cd.author.name, cd.author.email),
78 'Date: %s' % cd.author.date.isoformat(),
79 '',
80 cd.message]
81 if diff:
82 desc += ['---',
83 '',
84 diff]
85 return '\n'.join(desc)
86
87def patch_desc(repository, cd, failed_diff, diff, diff_flags):
88 return patch_description(cd, failed_diff or patch_diff(
89 repository, cd, diff, diff_flags))
90
91def update_patch_description(repository, cd, text):
92 message, authname, authemail, authdate, diff = common.parse_patch(text)
93 cd = (cd.set_message(message)
94 .set_author(cd.author.set_name(authname)
95 .set_email(authemail)
96 .set_date(gitlib.Date.maybe(authdate))))
97 failed_diff = None
98 if diff:
29d9a264 99 tree = repository.apply(cd.parent.data.tree, diff, quiet = False)
4491d19b
KH
100 if tree == None:
101 failed_diff = diff
102 else:
103 cd = cd.set_tree(tree)
104 return cd, failed_diff
ed60fdae
CM
105
106def func(parser, options, args):
107 """Edit the given patch or the current one.
108 """
4491d19b 109 stack = directory.repository.current_stack
ed60fdae 110
4491d19b
KH
111 if len(args) == 0:
112 if not stack.patchorder.applied:
113 raise common.CmdException(
114 'Cannot edit top patch, because no patches are applied')
115 patchname = stack.patchorder.applied[-1]
ed60fdae 116 elif len(args) == 1:
4491d19b
KH
117 [patchname] = args
118 if not stack.patches.exists(patchname):
119 raise common.CmdException('%s: no such patch' % patchname)
ed60fdae 120 else:
4491d19b
KH
121 parser.error('Cannot edit more than one patch')
122
123 cd = orig_cd = stack.patches.get(patchname).commit.data
ed60fdae 124
4491d19b
KH
125 # Read patch from user-provided description.
126 if options.message == None:
127 failed_diff = None
128 else:
129 cd, failed_diff = update_patch_description(stack.repository, cd,
130 options.message)
131
132 # Modify author and committer data.
bea18554
KH
133 a, c = options.author(cd.author), options.committer(cd.committer)
134 if (a, c) != (cd.author, cd.committer):
135 cd = cd.set_author(a).set_committer(c)
4491d19b
KH
136
137 # Add Signed-off-by: or similar.
138 if options.sign_str != None:
139 cd = cd.set_message(utils.add_sign_line(
140 cd.message, options.sign_str, gitlib.Person.committer().name,
141 gitlib.Person.committer().email))
142
143 if options.save_template:
144 options.save_template(
145 patch_desc(stack.repository, cd, failed_diff,
146 options.diff, options.diff_flags))
147 return utils.STGIT_SUCCESS
148
149 # Let user edit the patch manually.
150 if cd == orig_cd or options.edit:
151 fn = '.stgit-edit.' + ['txt', 'patch'][bool(options.diff)]
152 cd, failed_diff = update_patch_description(
153 stack.repository, cd, utils.edit_string(
154 patch_desc(stack.repository, cd, failed_diff,
155 options.diff, options.diff_flags),
156 fn))
157
158 def failed():
159 fn = '.stgit-failed.patch'
160 f = file(fn, 'w')
161 f.write(patch_desc(stack.repository, cd, failed_diff,
162 options.diff, options.diff_flags))
163 f.close()
164 out.error('Edited patch did not apply.',
165 'It has been saved to "%s".' % fn)
166 return utils.STGIT_COMMAND_ERROR
167
168 # If we couldn't apply the patch, fail without even trying to
169 # effect any of the changes.
170 if failed_diff:
171 return failed()
172
173 # The patch applied, so now we have to rewrite the StGit patch
174 # (and any patches on top of it).
175 iw = stack.repository.default_iw
781e549a 176 trans = transaction.StackTransaction(stack, 'edit', allow_conflicts = True)
4491d19b
KH
177 if patchname in trans.applied:
178 popped = trans.applied[trans.applied.index(patchname)+1:]
179 assert not trans.pop_patches(lambda pn: pn in popped)
180 else:
181 popped = []
182 trans.patches[patchname] = stack.repository.commit(cd)
183 try:
184 for pn in popped:
185 trans.push_patch(pn, iw)
186 except transaction.TransactionHalted:
187 pass
188 try:
189 # Either a complete success, or a conflict during push. But in
190 # either case, we've successfully effected the edits the user
191 # asked us for.
192 return trans.run(iw)
193 except transaction.TransactionException:
194 # Transaction aborted -- we couldn't check out files due to
195 # dirty index/worktree. The edits were not carried out.
196 return failed()