Commit | Line | Data |
---|---|---|
ed60fdae CM |
1 | """Patch editing command |
2 | """ | |
3 | ||
4 | __copyright__ = """ | |
5 | Copyright (C) 2007, Catalin Marinas <catalin.marinas@gmail.com> | |
6 | ||
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. | |
10 | ||
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. | |
15 | ||
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 | |
19 | """ | |
20 | ||
4491d19b | 21 | from optparse import make_option |
ed60fdae | 22 | |
20a52e06 | 23 | from stgit import argparse, git, utils |
4491d19b KH |
24 | from stgit.commands import common |
25 | from stgit.lib import git as gitlib, transaction | |
ed60fdae | 26 | from stgit.out import * |
ed60fdae CM |
27 | |
28 | help = 'edit a patch description or diff' | |
29 | usage = """%prog [options] [<patch>] | |
30 | ||
b52f3780 KH |
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 | |
33 | diff. | |
ed60fdae | 34 | |
b52f3780 | 35 | The 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 | |
42 | If --diff was specified, the diff appears at the bottom, after a | |
43 | separator: | |
44 | ||
45 | --- | |
ed60fdae | 46 | |
b52f3780 | 47 | Diff text |
ed60fdae CM |
48 | |
49 | Command-line options can be used to modify specific information | |
4491d19b KH |
50 | without invoking the editor. (With the --edit option, the editor is |
51 | invoked even if such command-line options are given.) | |
ed60fdae | 52 | |
4491d19b KH |
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.""" | |
ed60fdae | 56 | |
4491d19b | 57 | directory = common.DirectoryHasRepositoryLib() |
ed60fdae | 58 | options = [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 |
67 | def 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 | ||
74 | def 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 | ||
87 | def 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 | ||
91 | def 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 | |
106 | def 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() |