chiark / gitweb /
Don't have a global crt_series in stgit.commans.common
[stgit] / stgit / commands / edit.py
CommitLineData
ed60fdae
CM
1"""Patch editing command
2"""
3
4__copyright__ = """
5Copyright (C) 2007, Catalin Marinas <catalin.marinas@gmail.com>
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License version 2 as
9published by the Free Software Foundation.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program; if not, write to the Free Software
18Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19"""
20
21from optparse import OptionParser, make_option
22from email.Utils import formatdate
23
24from stgit.commands.common import *
25from stgit.utils import *
26from stgit.out import *
27from stgit import stack, git
28
29
30help = 'edit a patch description or diff'
31usage = """%prog [options] [<patch>]
32
b52f3780
KH
33Edit the description and author information of the given patch (or the
34current patch if no patch name was given). With --diff, also edit the
35diff.
ed60fdae 36
b52f3780 37The 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
44If --diff was specified, the diff appears at the bottom, after a
45separator:
46
47 ---
ed60fdae 48
b52f3780 49 Diff text
ed60fdae
CM
50
51Command-line options can be used to modify specific information
52without invoking the editor.
53
54If the patch diff is edited but the patch application fails, the
55rejected 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
57these files using the '--file' and '--diff' options.
58"""
59
6dd8fafa 60directory = DirectoryHasRepository()
ed60fdae 61options = [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
89def __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
123def __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
186def 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)