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