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 | ||
21 | from optparse import OptionParser, make_option | |
22 | from email.Utils import formatdate | |
23 | ||
24 | from stgit.commands.common import * | |
25 | from stgit.utils import * | |
26 | from stgit.out import * | |
27 | from stgit import stack, git | |
28 | ||
29 | ||
30 | help = 'edit a patch description or diff' | |
31 | usage = """%prog [options] [<patch>] | |
32 | ||
b52f3780 KH |
33 | Edit the description and author information of the given patch (or the |
34 | current patch if no patch name was given). With --diff, also edit the | |
35 | diff. | |
ed60fdae | 36 | |
b52f3780 | 37 | The editor is invoked with the following contents: |
ed60fdae | 38 | |
b52f3780 | 39 | From: A U Thor <author@example.com> |
ed60fdae CM |
40 | Date: creation date |
41 | ||
7e301a73 | 42 | Patch description |
b52f3780 KH |
43 | |
44 | If --diff was specified, the diff appears at the bottom, after a | |
45 | separator: | |
46 | ||
47 | --- | |
ed60fdae | 48 | |
b52f3780 | 49 | Diff text |
ed60fdae CM |
50 | |
51 | Command-line options can be used to modify specific information | |
52 | without invoking the editor. | |
53 | ||
54 | If the patch diff is edited but the patch application fails, the | |
55 | rejected patch is stored in the .stgit-failed.patch file (and also in | |
56 | .stgit-edit.{diff,txt}). The edited patch can be replaced with one of | |
57 | these files using the '--file' and '--diff' options. | |
58 | """ | |
59 | ||
60 | options = [make_option('-d', '--diff', | |
b52f3780 | 61 | help = 'edit the patch diff', |
ed60fdae CM |
62 | action = 'store_true'), |
63 | make_option('-f', '--file', | |
64 | help = 'use FILE instead of invoking the editor'), | |
65 | make_option('-O', '--diff-opts', | |
66 | help = 'options to pass to git-diff'), | |
67 | make_option('--undo', | |
68 | help = 'revert the commit generated by the last edit', | |
69 | action = 'store_true'), | |
70 | make_option('-a', '--annotate', metavar = 'NOTE', | |
71 | help = 'annotate the patch log entry'), | |
72 | make_option('-m', '--message', | |
73 | help = 'replace the patch description with MESSAGE'), | |
74 | make_option('--author', metavar = '"NAME <EMAIL>"', | |
75 | help = 'replae the author details with "NAME <EMAIL>"'), | |
76 | make_option('--authname', | |
77 | help = 'replace the author name with AUTHNAME'), | |
78 | make_option('--authemail', | |
79 | help = 'replace the author e-mail with AUTHEMAIL'), | |
80 | make_option('--authdate', | |
81 | help = 'replace the author date with AUTHDATE'), | |
82 | make_option('--commname', | |
83 | help = 'replace the committer name with COMMNAME'), | |
84 | make_option('--commemail', | |
85 | help = 'replace the committer e-mail with COMMEMAIL') | |
86 | ] + make_sign_options() | |
87 | ||
88 | def __update_patch(pname, fname, options): | |
89 | """Update the current patch from the given file. | |
90 | """ | |
91 | patch = crt_series.get_patch(pname) | |
92 | ||
93 | bottom = patch.get_bottom() | |
94 | top = patch.get_top() | |
95 | ||
96 | f = open(fname) | |
97 | message, author_name, author_email, author_date, diff = parse_patch(f) | |
98 | f.close() | |
99 | ||
ead8e892 CM |
100 | out.start('Updating patch "%s"' % pname) |
101 | ||
ed60fdae CM |
102 | if options.diff: |
103 | git.switch(bottom) | |
104 | try: | |
105 | git.apply_patch(fname) | |
106 | except: | |
107 | # avoid inconsistent repository state | |
108 | git.switch(top) | |
109 | raise | |
110 | ||
ed60fdae CM |
111 | crt_series.refresh_patch(message = message, |
112 | author_name = author_name, | |
113 | author_email = author_email, | |
114 | author_date = author_date, | |
115 | backup = True, log = 'edit') | |
ead8e892 | 116 | |
ed60fdae CM |
117 | if crt_series.empty_patch(pname): |
118 | out.done('empty patch') | |
119 | else: | |
120 | out.done() | |
121 | ||
122 | def __edit_update_patch(pname, options): | |
123 | """Edit the given patch interactively. | |
124 | """ | |
125 | patch = crt_series.get_patch(pname) | |
126 | ||
127 | if options.diff_opts: | |
128 | if not options.diff: | |
129 | raise CmdException, '--diff-opts only available with --diff' | |
130 | diff_flags = options.diff_opts.split() | |
131 | else: | |
132 | diff_flags = [] | |
133 | ||
134 | # generate the file to be edited | |
135 | descr = patch.get_description().strip() | |
ed60fdae CM |
136 | authdate = patch.get_authdate() |
137 | ||
7e301a73 | 138 | tmpl = 'From: %(authname)s <%(authemail)s>\n' |
ed60fdae CM |
139 | if authdate: |
140 | tmpl += 'Date: %(authdate)s\n' | |
7e301a73 | 141 | tmpl += '\n%(descr)s\n' |
ed60fdae CM |
142 | |
143 | tmpl_dict = { | |
7e301a73 | 144 | 'descr': descr, |
ed60fdae CM |
145 | 'authname': patch.get_authname(), |
146 | 'authemail': patch.get_authemail(), | |
147 | 'authdate': patch.get_authdate() | |
148 | } | |
149 | ||
150 | if options.diff: | |
151 | # add the patch diff to the edited file | |
152 | bottom = patch.get_bottom() | |
153 | top = patch.get_top() | |
154 | ||
155 | tmpl += '---\n\n' \ | |
156 | '%(diffstat)s\n' \ | |
157 | '%(diff)s' | |
158 | ||
159 | tmpl_dict['diffstat'] = git.diffstat(rev1 = bottom, rev2 = top) | |
160 | tmpl_dict['diff'] = git.diff(rev1 = bottom, rev2 = top, | |
161 | diff_flags = diff_flags) | |
162 | ||
163 | for key in tmpl_dict: | |
164 | # make empty strings if key is not available | |
165 | if tmpl_dict[key] is None: | |
166 | tmpl_dict[key] = '' | |
167 | ||
168 | text = tmpl % tmpl_dict | |
169 | ||
170 | if options.diff: | |
171 | fname = '.stgit-edit.diff' | |
172 | else: | |
173 | fname = '.stgit-edit.txt' | |
174 | ||
175 | # write the file to be edited | |
176 | f = open(fname, 'w+') | |
177 | f.write(text) | |
178 | f.close() | |
179 | ||
180 | # invoke the editor | |
181 | call_editor(fname) | |
182 | ||
183 | __update_patch(pname, fname, options) | |
184 | ||
185 | def func(parser, options, args): | |
186 | """Edit the given patch or the current one. | |
187 | """ | |
188 | crt_pname = crt_series.get_current() | |
189 | ||
190 | if not args: | |
191 | pname = crt_pname | |
192 | if not pname: | |
193 | raise CmdException, 'No patches applied' | |
194 | elif len(args) == 1: | |
195 | pname = args[0] | |
196 | if crt_series.patch_unapplied(pname) or crt_series.patch_hidden(pname): | |
197 | raise CmdException, 'Cannot edit unapplied or hidden patches' | |
198 | elif not crt_series.patch_applied(pname): | |
199 | raise CmdException, 'Unknown patch "%s"' % pname | |
200 | else: | |
201 | parser.error('incorrect number of arguments') | |
202 | ||
203 | check_local_changes() | |
204 | check_conflicts() | |
205 | check_head_top_equal() | |
206 | ||
207 | if pname != crt_pname: | |
208 | # Go to the patch to be edited | |
209 | applied = crt_series.get_applied() | |
210 | between = applied[:applied.index(pname):-1] | |
211 | pop_patches(between) | |
212 | ||
213 | if options.author: | |
214 | options.authname, options.authemail = name_email(options.author) | |
215 | ||
216 | if options.undo: | |
217 | out.start('Undoing the editing of "%s"' % pname) | |
218 | crt_series.undo_refresh() | |
219 | out.done() | |
220 | elif options.message or options.authname or options.authemail \ | |
221 | or options.authdate or options.commname or options.commemail \ | |
222 | or options.sign_str: | |
223 | # just refresh the patch with the given information | |
224 | out.start('Updating patch "%s"' % pname) | |
225 | crt_series.refresh_patch(message = options.message, | |
226 | author_name = options.authname, | |
227 | author_email = options.authemail, | |
228 | author_date = options.authdate, | |
229 | committer_name = options.commname, | |
230 | committer_email = options.commemail, | |
231 | backup = True, sign_str = options.sign_str, | |
232 | log = 'edit', | |
233 | notes = options.annotate) | |
234 | out.done() | |
235 | elif options.file: | |
236 | __update_patch(pname, options.file, options) | |
237 | else: | |
238 | __edit_update_patch(pname, options) | |
239 | ||
240 | if pname != crt_pname: | |
241 | # Push the patches back | |
242 | between.reverse() | |
243 | push_patches(between) |