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