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 | ||
575bbdae | 21 | from stgit.argparse import opt |
20a52e06 | 22 | from stgit import argparse, git, utils |
4491d19b KH |
23 | from stgit.commands import common |
24 | from stgit.lib import git as gitlib, transaction | |
ed60fdae | 25 | from stgit.out import * |
ed60fdae CM |
26 | |
27 | help = 'edit a patch description or diff' | |
575bbdae KH |
28 | usage = ['[options] [<patch>]'] |
29 | description = """ | |
b52f3780 KH |
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 | |
32 | diff. | |
ed60fdae | 33 | |
b52f3780 | 34 | The 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 | |
41 | If --diff was specified, the diff appears at the bottom, after a | |
42 | separator: | |
43 | ||
44 | --- | |
ed60fdae | 45 | |
b52f3780 | 46 | Diff text |
ed60fdae CM |
47 | |
48 | Command-line options can be used to modify specific information | |
4491d19b KH |
49 | without invoking the editor. (With the --edit option, the editor is |
50 | invoked even if such command-line options are given.) | |
ed60fdae | 51 | |
4491d19b KH |
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.""" | |
ed60fdae | 55 | |
575bbdae KH |
56 | options = [ |
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 | 64 | directory = common.DirectoryHasRepositoryLib() |
ed60fdae | 65 | |
4491d19b KH |
66 | def 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 | ||
73 | def 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 | ||
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)) | |
89 | ||
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) | |
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 | |
105 | def 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() |