Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-from optparse import OptionParser, make_option
-from email.Utils import formatdate
-
-from stgit.commands.common import *
-from stgit.utils import *
+from stgit.argparse import opt
+from stgit import argparse, git, utils
+from stgit.commands import common
+from stgit.lib import git as gitlib, transaction, edit
from stgit.out import *
-from stgit import stack, git
-
help = 'edit a patch description or diff'
-usage = """%prog [options] [<patch>]
-
+kind = 'patch'
+usage = ['[options] [<patch>]']
+description = """
Edit the description and author information of the given patch (or the
current patch if no patch name was given). With --diff, also edit the
diff.
Diff text
Command-line options can be used to modify specific information
-without invoking the editor.
-
-If the patch diff is edited but the patch application fails, the
-rejected patch is stored in the .stgit-failed.patch file (and also in
-.stgit-edit.{diff,txt}). The edited patch can be replaced with one of
-these files using the '--file' and '--diff' options.
-"""
-
-directory = DirectoryGotoToplevel()
-options = [make_option('-d', '--diff',
- help = 'edit the patch diff',
- action = 'store_true'),
- make_option('-O', '--diff-opts',
- help = 'options to pass to git-diff'),
- make_option('--undo',
- help = 'revert the commit generated by the last edit',
- action = 'store_true'),
- make_option('-a', '--annotate', metavar = 'NOTE',
- help = 'annotate the patch log entry'),
- make_option('--author', metavar = '"NAME <EMAIL>"',
- help = 'replae the author details with "NAME <EMAIL>"'),
- make_option('--authname',
- help = 'replace the author name with AUTHNAME'),
- make_option('--authemail',
- help = 'replace the author e-mail with AUTHEMAIL'),
- make_option('--authdate',
- help = 'replace the author date with AUTHDATE'),
- make_option('--commname',
- help = 'replace the committer name with COMMNAME'),
- make_option('--commemail',
- help = 'replace the committer e-mail with COMMEMAIL')
- ] + make_sign_options() + make_message_options()
-
-def __update_patch(pname, text, options):
- """Update the current patch from the given text.
- """
- patch = crt_series.get_patch(pname)
-
- bottom = patch.get_bottom()
- top = patch.get_top()
-
- message, author_name, author_email, author_date, diff = parse_patch(text)
-
- out.start('Updating patch "%s"' % pname)
-
- if options.diff:
- git.switch(bottom)
- try:
- git.apply_patch(diff = diff)
- except:
- # avoid inconsistent repository state
- git.switch(top)
- raise
-
- def c(a, b):
- if a != None:
- return a
- return b
- crt_series.refresh_patch(message = message,
- author_name = c(options.authname, author_name),
- author_email = c(options.authemail, author_email),
- author_date = c(options.authdate, author_date),
- committer_name = options.commname,
- committer_email = options.commemail,
- backup = True, sign_str = options.sign_str,
- log = 'edit', notes = options.annotate)
-
- if crt_series.empty_patch(pname):
- out.done('empty patch')
- else:
- out.done()
-
-def __generate_file(pname, write_fn, options):
- """Generate a file containing the description to edit
- """
- patch = crt_series.get_patch(pname)
-
- if options.diff_opts:
- if not options.diff:
- raise CmdException, '--diff-opts only available with --diff'
- diff_flags = options.diff_opts.split()
- else:
- diff_flags = []
-
- # generate the file to be edited
- descr = patch.get_description().strip()
- authdate = patch.get_authdate()
-
- tmpl = 'From: %(authname)s <%(authemail)s>\n'
- if authdate:
- tmpl += 'Date: %(authdate)s\n'
- tmpl += '\n%(descr)s\n'
-
- tmpl_dict = {
- 'descr': descr,
- 'authname': patch.get_authname(),
- 'authemail': patch.get_authemail(),
- 'authdate': patch.get_authdate()
- }
-
- if options.diff:
- # add the patch diff to the edited file
- bottom = patch.get_bottom()
- top = patch.get_top()
-
- tmpl += '---\n\n' \
- '%(diffstat)s\n' \
- '%(diff)s'
-
- tmpl_dict['diffstat'] = git.diffstat(rev1 = bottom, rev2 = top)
- tmpl_dict['diff'] = git.diff(rev1 = bottom, rev2 = top,
- diff_flags = diff_flags)
-
- for key in tmpl_dict:
- # make empty strings if key is not available
- if tmpl_dict[key] is None:
- tmpl_dict[key] = ''
-
- text = tmpl % tmpl_dict
-
- # write the file to be edited
- write_fn(text)
-
-def __edit_update_patch(pname, options):
- """Edit the given patch interactively.
- """
- if options.diff:
- fname = '.stgit-edit.diff'
- else:
- fname = '.stgit-edit.txt'
- def write_fn(text):
- f = file(fname, 'w')
- f.write(text)
- f.close()
-
- __generate_file(pname, write_fn, options)
-
- # invoke the editor
- call_editor(fname)
-
- __update_patch(pname, file(fname).read(), options)
+without invoking the editor. (With the --edit option, the editor is
+invoked even if such command-line options are given.)
+
+If the patch diff is edited but does not apply, no changes are made to
+the patch at all. The edited patch is saved to a file which you can
+feed to "stg edit --file", once you have made sure it does apply."""
+
+args = [argparse.applied_patches, argparse.unapplied_patches,
+ argparse.hidden_patches]
+options = [
+ opt('-d', '--diff', action = 'store_true',
+ short = 'Edit the patch diff'),
+ opt('-e', '--edit', action = 'store_true',
+ short = 'Invoke interactive editor'),
+ ] + (argparse.sign_options() +
+ argparse.message_options(save_template = True) +
+ argparse.author_options() + argparse.diff_opts_option())
+
+directory = common.DirectoryHasRepositoryLib()
def func(parser, options, args):
"""Edit the given patch or the current one.
"""
- crt_pname = crt_series.get_current()
+ stack = directory.repository.current_stack
- if not args:
- pname = crt_pname
- if not pname:
- raise CmdException, 'No patches applied'
+ if len(args) == 0:
+ if not stack.patchorder.applied:
+ raise common.CmdException(
+ 'Cannot edit top patch, because no patches are applied')
+ patchname = stack.patchorder.applied[-1]
elif len(args) == 1:
- pname = args[0]
- if crt_series.patch_unapplied(pname) or crt_series.patch_hidden(pname):
- raise CmdException, 'Cannot edit unapplied or hidden patches'
- elif not crt_series.patch_applied(pname):
- raise CmdException, 'Unknown patch "%s"' % pname
+ [patchname] = args
+ if not stack.patches.exists(patchname):
+ raise common.CmdException('%s: no such patch' % patchname)
else:
- parser.error('incorrect number of arguments')
-
- check_local_changes()
- check_conflicts()
- check_head_top_equal(crt_series)
-
- if pname != crt_pname:
- # Go to the patch to be edited
- applied = crt_series.get_applied()
- between = applied[:applied.index(pname):-1]
- pop_patches(crt_series, between)
-
- if options.author:
- options.authname, options.authemail = name_email(options.author)
-
- if options.undo:
- out.start('Undoing the editing of "%s"' % pname)
- crt_series.undo_refresh()
- out.done()
- elif options.save_template:
- __generate_file(pname, options.save_template, options)
- elif any([options.message, options.authname, options.authemail,
- options.authdate, options.commname, options.commemail,
- options.sign_str]):
- out.start('Updating patch "%s"' % pname)
- __update_patch(pname, options.message, options)
- out.done()
+ parser.error('Cannot edit more than one patch')
+
+ cd = orig_cd = stack.patches.get(patchname).commit.data
+
+ cd, failed_diff = edit.auto_edit_patch(
+ stack.repository, cd, msg = options.message, contains_diff = True,
+ author = options.author, committer = lambda p: p,
+ sign_str = options.sign_str)
+
+ if options.save_template:
+ options.save_template(
+ edit.patch_desc(stack.repository, cd,
+ options.diff, options.diff_flags, failed_diff))
+ return utils.STGIT_SUCCESS
+
+ if cd == orig_cd or options.edit:
+ cd, failed_diff = edit.interactive_edit_patch(
+ stack.repository, cd, options.diff, options.diff_flags, failed_diff)
+
+ def failed():
+ fn = '.stgit-failed.patch'
+ f = file(fn, 'w')
+ f.write(edit.patch_desc(stack.repository, cd,
+ options.diff, options.diff_flags, failed_diff))
+ f.close()
+ out.error('Edited patch did not apply.',
+ 'It has been saved to "%s".' % fn)
+ return utils.STGIT_COMMAND_ERROR
+
+ # If we couldn't apply the patch, fail without even trying to
+ # effect any of the changes.
+ if failed_diff:
+ return failed()
+
+ # The patch applied, so now we have to rewrite the StGit patch
+ # (and any patches on top of it).
+ iw = stack.repository.default_iw
+ trans = transaction.StackTransaction(stack, 'edit', allow_conflicts = True)
+ if patchname in trans.applied:
+ popped = trans.applied[trans.applied.index(patchname)+1:]
+ assert not trans.pop_patches(lambda pn: pn in popped)
else:
- __edit_update_patch(pname, options)
-
- if pname != crt_pname:
- # Push the patches back
- between.reverse()
- push_patches(crt_series, between)
+ popped = []
+ trans.patches[patchname] = stack.repository.commit(cd)
+ try:
+ for pn in popped:
+ trans.push_patch(pn, iw, allow_interactive = True)
+ except transaction.TransactionHalted:
+ pass
+ try:
+ # Either a complete success, or a conflict during push. But in
+ # either case, we've successfully effected the edits the user
+ # asked us for.
+ return trans.run(iw)
+ except transaction.TransactionException:
+ # Transaction aborted -- we couldn't check out files due to
+ # dirty index/worktree. The edits were not carried out.
+ return failed()