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