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 | ||
7b601c9e | 60 | directory = DirectoryGotoToplevel() |
ed60fdae | 61 | options = [make_option('-d', '--diff', |
b52f3780 | 62 | help = 'edit the patch diff', |
ed60fdae CM |
63 | action = 'store_true'), |
64 | make_option('-f', '--file', | |
65 | help = 'use FILE instead of invoking the editor'), | |
66 | make_option('-O', '--diff-opts', | |
67 | help = 'options to pass to git-diff'), | |
68 | make_option('--undo', | |
69 | help = 'revert the commit generated by the last edit', | |
70 | action = 'store_true'), | |
71 | make_option('-a', '--annotate', metavar = 'NOTE', | |
72 | help = 'annotate the patch log entry'), | |
73 | make_option('-m', '--message', | |
74 | help = 'replace the patch description with MESSAGE'), | |
3cbb569f DK |
75 | make_option('--save-template', metavar = 'FILE', |
76 | help = 'save the patch to FILE in the format used by -f'), | |
ed60fdae CM |
77 | make_option('--author', metavar = '"NAME <EMAIL>"', |
78 | help = 'replae the author details with "NAME <EMAIL>"'), | |
79 | make_option('--authname', | |
80 | help = 'replace the author name with AUTHNAME'), | |
81 | make_option('--authemail', | |
82 | help = 'replace the author e-mail with AUTHEMAIL'), | |
83 | make_option('--authdate', | |
84 | help = 'replace the author date with AUTHDATE'), | |
85 | make_option('--commname', | |
86 | help = 'replace the committer name with COMMNAME'), | |
87 | make_option('--commemail', | |
88 | help = 'replace the committer e-mail with COMMEMAIL') | |
89 | ] + make_sign_options() | |
90 | ||
91 | def __update_patch(pname, fname, options): | |
92 | """Update the current patch from the given file. | |
93 | """ | |
94 | patch = crt_series.get_patch(pname) | |
95 | ||
96 | bottom = patch.get_bottom() | |
97 | top = patch.get_top() | |
98 | ||
8a88f7cb DK |
99 | if fname == '-': |
100 | f = sys.stdin | |
101 | else: | |
102 | f = open(fname) | |
c37f9747 KH |
103 | (message, author_name, author_email, author_date, diff |
104 | ) = parse_patch(f.read()) | |
ed60fdae CM |
105 | f.close() |
106 | ||
ead8e892 CM |
107 | out.start('Updating patch "%s"' % pname) |
108 | ||
ed60fdae CM |
109 | if options.diff: |
110 | git.switch(bottom) | |
111 | try: | |
112 | git.apply_patch(fname) | |
113 | except: | |
114 | # avoid inconsistent repository state | |
115 | git.switch(top) | |
116 | raise | |
117 | ||
ed60fdae CM |
118 | crt_series.refresh_patch(message = message, |
119 | author_name = author_name, | |
120 | author_email = author_email, | |
121 | author_date = author_date, | |
122 | backup = True, log = 'edit') | |
ead8e892 | 123 | |
ed60fdae CM |
124 | if crt_series.empty_patch(pname): |
125 | out.done('empty patch') | |
126 | else: | |
127 | out.done() | |
128 | ||
3cbb569f DK |
129 | def __generate_file(pname, fname, options): |
130 | """Generate a file containing the description to edit | |
ed60fdae CM |
131 | """ |
132 | patch = crt_series.get_patch(pname) | |
133 | ||
134 | if options.diff_opts: | |
135 | if not options.diff: | |
136 | raise CmdException, '--diff-opts only available with --diff' | |
137 | diff_flags = options.diff_opts.split() | |
138 | else: | |
139 | diff_flags = [] | |
140 | ||
141 | # generate the file to be edited | |
142 | descr = patch.get_description().strip() | |
ed60fdae CM |
143 | authdate = patch.get_authdate() |
144 | ||
7e301a73 | 145 | tmpl = 'From: %(authname)s <%(authemail)s>\n' |
ed60fdae CM |
146 | if authdate: |
147 | tmpl += 'Date: %(authdate)s\n' | |
7e301a73 | 148 | tmpl += '\n%(descr)s\n' |
ed60fdae CM |
149 | |
150 | tmpl_dict = { | |
7e301a73 | 151 | 'descr': descr, |
ed60fdae CM |
152 | 'authname': patch.get_authname(), |
153 | 'authemail': patch.get_authemail(), | |
154 | 'authdate': patch.get_authdate() | |
155 | } | |
156 | ||
157 | if options.diff: | |
158 | # add the patch diff to the edited file | |
159 | bottom = patch.get_bottom() | |
160 | top = patch.get_top() | |
161 | ||
162 | tmpl += '---\n\n' \ | |
163 | '%(diffstat)s\n' \ | |
164 | '%(diff)s' | |
165 | ||
166 | tmpl_dict['diffstat'] = git.diffstat(rev1 = bottom, rev2 = top) | |
167 | tmpl_dict['diff'] = git.diff(rev1 = bottom, rev2 = top, | |
168 | diff_flags = diff_flags) | |
169 | ||
170 | for key in tmpl_dict: | |
171 | # make empty strings if key is not available | |
172 | if tmpl_dict[key] is None: | |
173 | tmpl_dict[key] = '' | |
174 | ||
175 | text = tmpl % tmpl_dict | |
176 | ||
3cbb569f | 177 | # write the file to be edited |
8a88f7cb DK |
178 | if fname == '-': |
179 | sys.stdout.write(text) | |
180 | else: | |
181 | f = open(fname, 'w+') | |
182 | f.write(text) | |
183 | f.close() | |
3cbb569f DK |
184 | |
185 | def __edit_update_patch(pname, options): | |
186 | """Edit the given patch interactively. | |
187 | """ | |
ed60fdae CM |
188 | if options.diff: |
189 | fname = '.stgit-edit.diff' | |
190 | else: | |
191 | fname = '.stgit-edit.txt' | |
192 | ||
3cbb569f | 193 | __generate_file(pname, fname, options) |
ed60fdae CM |
194 | |
195 | # invoke the editor | |
196 | call_editor(fname) | |
197 | ||
198 | __update_patch(pname, fname, options) | |
199 | ||
200 | def func(parser, options, args): | |
201 | """Edit the given patch or the current one. | |
202 | """ | |
203 | crt_pname = crt_series.get_current() | |
204 | ||
205 | if not args: | |
206 | pname = crt_pname | |
207 | if not pname: | |
208 | raise CmdException, 'No patches applied' | |
209 | elif len(args) == 1: | |
210 | pname = args[0] | |
211 | if crt_series.patch_unapplied(pname) or crt_series.patch_hidden(pname): | |
212 | raise CmdException, 'Cannot edit unapplied or hidden patches' | |
213 | elif not crt_series.patch_applied(pname): | |
214 | raise CmdException, 'Unknown patch "%s"' % pname | |
215 | else: | |
216 | parser.error('incorrect number of arguments') | |
217 | ||
218 | check_local_changes() | |
219 | check_conflicts() | |
6972fd6b | 220 | check_head_top_equal(crt_series) |
ed60fdae CM |
221 | |
222 | if pname != crt_pname: | |
223 | # Go to the patch to be edited | |
224 | applied = crt_series.get_applied() | |
225 | between = applied[:applied.index(pname):-1] | |
6972fd6b | 226 | pop_patches(crt_series, between) |
ed60fdae CM |
227 | |
228 | if options.author: | |
229 | options.authname, options.authemail = name_email(options.author) | |
230 | ||
231 | if options.undo: | |
232 | out.start('Undoing the editing of "%s"' % pname) | |
233 | crt_series.undo_refresh() | |
234 | out.done() | |
235 | elif options.message or options.authname or options.authemail \ | |
236 | or options.authdate or options.commname or options.commemail \ | |
237 | or options.sign_str: | |
238 | # just refresh the patch with the given information | |
239 | out.start('Updating patch "%s"' % pname) | |
240 | crt_series.refresh_patch(message = options.message, | |
241 | author_name = options.authname, | |
242 | author_email = options.authemail, | |
243 | author_date = options.authdate, | |
244 | committer_name = options.commname, | |
245 | committer_email = options.commemail, | |
246 | backup = True, sign_str = options.sign_str, | |
247 | log = 'edit', | |
248 | notes = options.annotate) | |
249 | out.done() | |
3cbb569f DK |
250 | elif options.save_template: |
251 | __generate_file(pname, options.save_template, options) | |
ed60fdae CM |
252 | elif options.file: |
253 | __update_patch(pname, options.file, options) | |
254 | else: | |
255 | __edit_update_patch(pname, options) | |
256 | ||
257 | if pname != crt_pname: | |
258 | # Push the patches back | |
259 | between.reverse() | |
6972fd6b | 260 | push_patches(crt_series, between) |