chiark / gitweb /
Add patch editing command
authorCatalin Marinas <catalin.marinas@gmail.com>
Fri, 14 Sep 2007 17:51:57 +0000 (18:51 +0100)
committerCatalin Marinas <catalin.marinas@gmail.com>
Fri, 14 Sep 2007 17:51:57 +0000 (18:51 +0100)
This patch adds the 'edit' command which takes most of the similar
functionality out of 'refresh'. In addition, it allows the direct
patch diff editing with the '--diff' option. The patch/e-mail parsing
functions were moved from stgit.commands.imprt to
stgit.commands.common as they are now used by both 'import' and
'edit'.

Signed-off-by: Catalin Marinas <catalin.marinas@gmail.com>
contrib/stgit-completion.bash
stgit/commands/common.py
stgit/commands/edit.py [new file with mode: 0644]
stgit/commands/imprt.py
stgit/commands/refresh.py
stgit/git.py
stgit/main.py
stgit/stack.py
t/t1400-patch-history.sh

index 7ae9e6a8bb03bbcc3766ea3f3e5369881d4f3b23..b1d2730b65cea767cd53f886f96d342108026d06 100644 (file)
@@ -21,6 +21,7 @@ _stg_commands="
     clone
     commit
     cp
+    edit
     export
     files
     float
@@ -247,6 +248,7 @@ _stg ()
         unhide) _stg_patches $command _hidden_patches ;;
         # patch commands
         delete) _stg_patches $command _all_patches ;;
+        edit)   _stg_patches $command _applied_patches ;;
         export) _stg_patches $command _applied_patches ;;
         files)  _stg_patches $command _all_patches ;;
         log)    _stg_patches $command _all_patches ;;
index f3fa89d6630f2497f6054f9402b5e6786edabe01..d81de263eac0522d4b18c0daaebd6848012a7725 100644 (file)
@@ -348,3 +348,133 @@ def post_rebase(applied, nopush, merged):
     # push the patches back
     if not nopush:
         push_patches(applied, merged)
+
+#
+# Patch description/e-mail/diff parsing
+#
+def __end_descr(line):
+    return re.match('---\s*$', line) or re.match('diff -', line) or \
+            re.match('Index: ', line)
+
+def __split_descr_diff(string):
+    """Return the description and the diff from the given string
+    """
+    descr = diff = ''
+    top = True
+
+    for line in string.split('\n'):
+        if top:
+            if not __end_descr(line):
+                descr += line + '\n'
+                continue
+            else:
+                top = False
+        diff += line + '\n'
+
+    return (descr.rstrip(), diff)
+
+def __parse_description(descr):
+    """Parse the patch description and return the new description and
+    author information (if any).
+    """
+    subject = body = ''
+    authname = authemail = authdate = None
+
+    descr_lines = [line.rstrip() for line in  descr.split('\n')]
+    if not descr_lines:
+        raise CmdException, "Empty patch description"
+
+    lasthdr = 0
+    end = len(descr_lines)
+
+    # Parse the patch header
+    for pos in range(0, end):
+        if not descr_lines[pos]:
+           continue
+        # check for a "From|Author:" line
+        if re.match('\s*(?:from|author):\s+', descr_lines[pos], re.I):
+            auth = re.findall('^.*?:\s+(.*)$', descr_lines[pos])[0]
+            authname, authemail = name_email(auth)
+            lasthdr = pos + 1
+            continue
+        # check for a "Date:" line
+        if re.match('\s*date:\s+', descr_lines[pos], re.I):
+            authdate = re.findall('^.*?:\s+(.*)$', descr_lines[pos])[0]
+            lasthdr = pos + 1
+            continue
+        if subject:
+            break
+        # get the subject
+        subject = descr_lines[pos]
+        lasthdr = pos + 1
+
+    # get the body
+    if lasthdr < end:
+        body = reduce(lambda x, y: x + '\n' + y, descr_lines[lasthdr:], '')
+
+    return (subject + body, authname, authemail, authdate)
+
+def parse_mail(msg):
+    """Parse the message object and return (description, authname,
+    authemail, authdate, diff)
+    """
+    from email.Header import decode_header, make_header
+
+    def __decode_header(header):
+        """Decode a qp-encoded e-mail header as per rfc2047"""
+        try:
+            words_enc = decode_header(header)
+            hobj = make_header(words_enc)
+        except Exception, ex:
+            raise CmdException, 'header decoding error: %s' % str(ex)
+        return unicode(hobj).encode('utf-8')
+
+    # parse the headers
+    if msg.has_key('from'):
+        authname, authemail = name_email(__decode_header(msg['from']))
+    else:
+        authname = authemail = None
+
+    # '\n\t' can be found on multi-line headers
+    descr = __decode_header(msg['subject']).replace('\n\t', ' ')
+    authdate = msg['date']
+
+    # remove the '[*PATCH*]' expression in the subject
+    if descr:
+        descr = re.findall('^(\[.*?[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$',
+                           descr)[0][1]
+    else:
+        raise CmdException, 'Subject: line not found'
+
+    # the rest of the message
+    msg_text = ''
+    for part in msg.walk():
+        if part.get_content_type() == 'text/plain':
+            msg_text += part.get_payload(decode = True)
+
+    rem_descr, diff = __split_descr_diff(msg_text)
+    if rem_descr:
+        descr += '\n\n' + rem_descr
+
+    # parse the description for author information
+    descr, descr_authname, descr_authemail, descr_authdate = \
+           __parse_description(descr)
+    if descr_authname:
+        authname = descr_authname
+    if descr_authemail:
+        authemail = descr_authemail
+    if descr_authdate:
+       authdate = descr_authdate
+
+    return (descr, authname, authemail, authdate, diff)
+
+def parse_patch(fobj):
+    """Parse the input file and return (description, authname,
+    authemail, authdate, diff)
+    """
+    descr, diff = __split_descr_diff(fobj.read())
+    descr, authname, authemail, authdate = __parse_description(descr)
+
+    # we don't yet have an agreed place for the creation date.
+    # Just return None
+    return (descr, authname, authemail, authdate, diff)
diff --git a/stgit/commands/edit.py b/stgit/commands/edit.py
new file mode 100644 (file)
index 0000000..1a7b1b3
--- /dev/null
@@ -0,0 +1,244 @@
+"""Patch editing command
+"""
+
+__copyright__ = """
+Copyright (C) 2007, Catalin Marinas <catalin.marinas@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+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.out import *
+from stgit import stack, git
+
+
+help = 'edit a patch description or diff'
+usage = """%prog [options] [<patch>]
+
+Edit the given patch (defaulting to the current one) description,
+author information or its diff (if the '--diff' option is
+passed). Without any other option, the command invokes the editor with
+the patch description and diff in the form below:
+
+  Subject line
+
+  From: author information
+  Date: creation date
+
+  Patch description
+
+  Signed-off-by: author
+
+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.
+"""
+
+options = [make_option('-d', '--diff',
+                       help = 'allow the editing of the patch diff',
+                       action = 'store_true'),
+           make_option('-f', '--file',
+                       help = 'use FILE instead of invoking the editor'),
+           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('-m', '--message',
+                       help = 'replace the patch description with MESSAGE'),
+           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()
+
+def __update_patch(pname, fname, options):
+    """Update the current patch from the given file.
+    """
+    patch = crt_series.get_patch(pname)
+
+    bottom = patch.get_bottom()
+    top = patch.get_top()
+
+    f = open(fname)
+    message, author_name, author_email, author_date, diff = parse_patch(f)
+    f.close()
+
+    if options.diff:
+        git.switch(bottom)
+        try:
+            git.apply_patch(fname)
+        except:
+            # avoid inconsistent repository state
+            git.switch(top)
+            raise
+
+    out.start('Updating patch "%s"' % pname)
+    crt_series.refresh_patch(message = message,
+                             author_name = author_name,
+                             author_email = author_email,
+                             author_date = author_date,
+                             backup = True, log = 'edit')
+    if crt_series.empty_patch(pname):
+        out.done('empty patch')
+    else:
+        out.done()
+
+def __edit_update_patch(pname, options):
+    """Edit the given patch interactively.
+    """
+    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()
+    descr_lines = descr.split('\n')
+    authdate = patch.get_authdate()
+
+    short_descr = descr_lines[0].rstrip()
+    long_descr = reduce(lambda x, y: x + '\n' + y,
+                        descr_lines[1:], '').strip()
+
+    tmpl = '%(shortdescr)s\n\n' \
+           'From: %(authname)s <%(authemail)s>\n'
+    if authdate:
+        tmpl += 'Date: %(authdate)s\n'
+    tmpl += '\n%(longdescr)s\n'
+
+    tmpl_dict = {
+        'shortdescr': short_descr,
+        'longdescr': long_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
+
+    if options.diff:
+        fname = '.stgit-edit.diff'
+    else:
+        fname = '.stgit-edit.txt'
+
+    # write the file to be edited
+    f = open(fname, 'w+')
+    f.write(text)
+    f.close()
+
+    # invoke the editor
+    call_editor(fname)
+
+    __update_patch(pname, fname, options)
+
+def func(parser, options, args):
+    """Edit the given patch or the current one.
+    """
+    crt_pname = crt_series.get_current()
+
+    if not args:
+        pname = crt_pname
+        if not pname:
+            raise CmdException, 'No patches applied'
+    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
+    else:
+        parser.error('incorrect number of arguments')
+
+    check_local_changes()
+    check_conflicts()
+    check_head_top_equal()
+
+    if pname != crt_pname:
+        # Go to the patch to be edited
+        applied = crt_series.get_applied()
+        between = applied[:applied.index(pname):-1]
+        pop_patches(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.message or options.authname or options.authemail \
+             or options.authdate or options.commname or options.commemail \
+             or options.sign_str:
+        # just refresh the patch with the given information
+        out.start('Updating patch "%s"' % pname)
+        crt_series.refresh_patch(message = options.message,
+                                 author_name = options.authname,
+                                 author_email = options.authemail,
+                                 author_date = options.authdate,
+                                 committer_name = options.commname,
+                                 committer_email = options.commemail,
+                                 backup = True, sign_str = options.sign_str,
+                                 log = 'edit',
+                                 notes = options.annotate)
+        out.done()
+    elif options.file:
+        __update_patch(pname, options.file, options)
+    else:
+        __edit_update_patch(pname, options)
+
+    if pname != crt_pname:
+        # Push the patches back
+        between.reverse()
+        push_patches(between)
index fad51363f373918032959379e66437201c45cf87..717f3739c0da1aaa276de153e97eab0e39ecf896 100644 (file)
@@ -16,7 +16,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
 import sys, os, re, email
-from email.Header import decode_header, make_header
 from mailbox import UnixMailbox
 from StringIO import StringIO
 from optparse import OptionParser, make_option
@@ -91,10 +90,6 @@ options = [make_option('-m', '--mail',
            ] + make_sign_options()
 
 
-def __end_descr(line):
-    return re.match('---\s*$', line) or re.match('diff -', line) or \
-            re.match('Index: ', line)
-
 def __strip_patch_name(name):
     stripped = re.sub('^[0-9]+-(.*)$', '\g<1>', name)
     stripped = re.sub('^(.*)\.(diff|patch)$', '\g<1>', stripped)
@@ -106,127 +101,6 @@ def __replace_slashes_with_dashes(name):
 
     return stripped
 
-def __split_descr_diff(string):
-    """Return the description and the diff from the given string
-    """
-    descr = diff = ''
-    top = True
-
-    for line in string.split('\n'):
-        if top:
-            if not __end_descr(line):
-                descr += line + '\n'
-                continue
-            else:
-                top = False
-        diff += line + '\n'
-
-    return (descr.rstrip(), diff)
-
-def __parse_description(descr):
-    """Parse the patch description and return the new description and
-    author information (if any).
-    """
-    subject = body = ''
-    authname = authemail = authdate = None
-
-    descr_lines = [line.rstrip() for line in  descr.split('\n')]
-    if not descr_lines:
-        raise CmdException, "Empty patch description"
-
-    lasthdr = 0
-    end = len(descr_lines)
-
-    # Parse the patch header
-    for pos in range(0, end):
-        if not descr_lines[pos]:
-           continue
-        # check for a "From|Author:" line
-        if re.match('\s*(?:from|author):\s+', descr_lines[pos], re.I):
-            auth = re.findall('^.*?:\s+(.*)$', descr_lines[pos])[0]
-            authname, authemail = name_email(auth)
-            lasthdr = pos + 1
-            continue
-        # check for a "Date:" line
-        if re.match('\s*date:\s+', descr_lines[pos], re.I):
-            authdate = re.findall('^.*?:\s+(.*)$', descr_lines[pos])[0]
-            lasthdr = pos + 1
-            continue
-        if subject:
-            break
-        # get the subject
-        subject = descr_lines[pos]
-        lasthdr = pos + 1
-
-    # get the body
-    if lasthdr < end:
-        body = reduce(lambda x, y: x + '\n' + y, descr_lines[lasthdr:], '')
-
-    return (subject + body, authname, authemail, authdate)
-
-def __parse_mail(msg):
-    """Parse the message object and return (description, authname,
-    authemail, authdate, diff)
-    """
-    def __decode_header(header):
-        """Decode a qp-encoded e-mail header as per rfc2047"""
-        try:
-            words_enc = decode_header(header)
-            hobj = make_header(words_enc)
-        except Exception, ex:
-            raise CmdException, 'header decoding error: %s' % str(ex)
-        return unicode(hobj).encode('utf-8')
-
-    # parse the headers
-    if msg.has_key('from'):
-        authname, authemail = name_email(__decode_header(msg['from']))
-    else:
-        authname = authemail = None
-
-    # '\n\t' can be found on multi-line headers
-    descr = __decode_header(msg['subject']).replace('\n\t', ' ')
-    authdate = msg['date']
-
-    # remove the '[*PATCH*]' expression in the subject
-    if descr:
-        descr = re.findall('^(\[.*?[Pp][Aa][Tt][Cc][Hh].*?\])?\s*(.*)$',
-                           descr)[0][1]
-    else:
-        raise CmdException, 'Subject: line not found'
-
-    # the rest of the message
-    msg_text = ''
-    for part in msg.walk():
-        if part.get_content_type() == 'text/plain':
-            msg_text += part.get_payload(decode = True)
-
-    rem_descr, diff = __split_descr_diff(msg_text)
-    if rem_descr:
-        descr += '\n\n' + rem_descr
-
-    # parse the description for author information
-    descr, descr_authname, descr_authemail, descr_authdate = \
-           __parse_description(descr)
-    if descr_authname:
-        authname = descr_authname
-    if descr_authemail:
-        authemail = descr_authemail
-    if descr_authdate:
-       authdate = descr_authdate
-
-    return (descr, authname, authemail, authdate, diff)
-
-def __parse_patch(fobj):
-    """Parse the input file and return (description, authname,
-    authemail, authdate, diff)
-    """
-    descr, diff = __split_descr_diff(fobj.read())
-    descr, authname, authemail, authdate = __parse_description(descr)
-
-    # we don't yet have an agreed place for the creation date.
-    # Just return None
-    return (descr, authname, authemail, authdate, diff)
-
 def __create_patch(filename, message, author_name, author_email,
                    author_date, diff, options):
     """Create a new patch on the stack
@@ -312,10 +186,10 @@ def __import_file(filename, options, patch = None):
         except Exception, ex:
             raise CmdException, 'error parsing the e-mail file: %s' % str(ex)
         message, author_name, author_email, author_date, diff = \
-                 __parse_mail(msg)
+                 parse_mail(msg)
     else:
         message, author_name, author_email, author_date, diff = \
-                 __parse_patch(f)
+                 parse_patch(f)
 
     if filename:
         f.close()
@@ -367,7 +241,7 @@ def __import_mbox(filename, options):
 
     for msg in mbox:
         message, author_name, author_email, author_date, diff = \
-                 __parse_mail(msg)
+                 parse_mail(msg)
         __create_patch(None, message, author_name, author_email,
                        author_date, diff, options)
 
index f70d8082ebc06cfa0f02b3399a0447b7489fdf7e..241f065a46a72860b0e8ddb63c32235995e008e9 100644 (file)
@@ -41,40 +41,17 @@ options = [make_option('-f', '--force',
                        help = 'force the refresh even if HEAD and '\
                        'top differ',
                        action = 'store_true'),
-           make_option('-e', '--edit',
-                       help = 'invoke an editor for the patch '\
-                       'description',
-                       action = 'store_true'),
-           make_option('-s', '--showpatch',
-                       help = 'show the patch content in the editor buffer',
-                       action = 'store_true'),
            make_option('--update',
                        help = 'only update the current patch files',
                        action = 'store_true'),
            make_option('--undo',
                        help = 'revert the commit generated by the last refresh',
                        action = 'store_true'),
-           make_option('-m', '--message',
-                       help = 'use MESSAGE as the patch ' \
-                       'description'),
            make_option('-a', '--annotate', metavar = 'NOTE',
                        help = 'annotate the patch log entry'),
-           make_option('--author', metavar = '"NAME <EMAIL>"',
-                       help = 'use "NAME <EMAIL>" as the author details'),
-           make_option('--authname',
-                       help = 'use AUTHNAME as the author name'),
-           make_option('--authemail',
-                       help = 'use AUTHEMAIL as the author e-mail'),
-           make_option('--authdate',
-                       help = 'use AUTHDATE as the author date'),
-           make_option('--commname',
-                       help = 'use COMMNAME as the committer name'),
-           make_option('--commemail',
-                       help = 'use COMMEMAIL as the committer ' \
-                       'e-mail'),
            make_option('-p', '--patch',
                        help = 'refresh (applied) PATCH instead of the top one')
-           ] + make_sign_options()
+           ]
 
 def func(parser, options, args):
     autoresolved = config.get('stgit.autoresolved')
@@ -103,18 +80,11 @@ def func(parser, options, args):
         out.done()
         return
 
-    if options.author:
-        options.authname, options.authemail = name_email(options.author)
-
     files = [path for (stat,path) in git.tree_status(verbose = True)]
     if args:
         files = [f for f in files if f in args]
 
-    if files or not crt_series.head_top_equal() \
-           or options.edit or options.message \
-           or options.authname or options.authemail or options.authdate \
-           or options.commname or options.commemail or options.sign_str:
-
+    if files or not crt_series.head_top_equal():
         if options.patch:
             applied = crt_series.get_applied()
             between = applied[:applied.index(patch):-1]
@@ -133,16 +103,7 @@ def func(parser, options, args):
         if autoresolved == 'yes':
             resolved_all()
         crt_series.refresh_patch(files = files,
-                                 message = options.message,
-                                 edit = options.edit,
-                                 show_patch = options.showpatch,
-                                 author_name = options.authname,
-                                 author_email = options.authemail,
-                                 author_date = options.authdate,
-                                 committer_name = options.commname,
-                                 committer_email = options.commemail,
-                                 backup = True, sign_str = options.sign_str,
-                                 notes = options.annotate)
+                                 backup = True, notes = options.annotate)
 
         if crt_series.empty_patch(patch):
             out.done('empty patch')
index 539d69904f4071a250c81736e8447be0ffb5811c..6c55127a319f941afbdc4fe99d7a2f1ed33c9cb1 100644 (file)
@@ -762,6 +762,8 @@ def diff(files = None, rev1 = 'HEAD', rev2 = None, diff_flags = []):
     else:
         return ''
 
+# TODO: take another parameter representing a diff string as we
+# usually invoke git.diff() form the calling functions
 def diffstat(files = None, rev1 = 'HEAD', rev2 = None):
     """Return the diffstat between rev1 and rev2."""
     return GRun('git-apply', '--stat', '--summary'
index 5b9d7c48baeb2ef6abd2f657bdf826a4bdbbb23f..a49513db22ce77eccedcf45907699d13abdab1d7 100644 (file)
@@ -68,6 +68,7 @@ commands = Commands({
     'clone':            'clone',
     'commit':           'commit',
     'cp':              'copy',
+    'edit':             'edit',
     'export':           'export',
     'files':            'files',
     'float':            'float',
index 906e6b1ecfe3cb379095719562fd67cb9d4147ff..d6f6a6e613bc3658de5e6eb3a544dd82a8aef35a 100644 (file)
@@ -19,10 +19,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
 import sys, os, re
+from email.Utils import formatdate
 
 from stgit.utils import *
 from stgit.out import *
-from stgit.run import *
 from stgit import git, basedir, templates
 from stgit.config import config
 from shutil import copyfile
@@ -67,6 +67,8 @@ def __clean_comments(f):
     f.seek(0); f.truncate()
     f.writelines(lines)
 
+# TODO: move this out of the stgit.stack module, it is really for
+# higher level commands to handle the user interaction
 def edit_file(series, line, comment, show_patch = True):
     fname = '.stgitmsg.txt'
     tmpl = templates.get_template('patchdescr.tmpl')
@@ -252,7 +254,16 @@ class Patch(StgitObject):
         self._set_field('authemail', email or git.author().email)
 
     def get_authdate(self):
-        return self._get_field('authdate')
+        date = self._get_field('authdate')
+        if not date:
+            return date
+
+        if re.match('[0-9]+\s+[+-][0-9]+', date):
+            # Unix time (seconds) + time zone
+            secs_tz = date.split()
+            date = formatdate(int(secs_tz[0]))[:-5] + secs_tz[1]
+
+        return date
 
     def set_authdate(self, date):
         self._set_field('authdate', date or git.author().date)
@@ -764,6 +775,8 @@ class Series(PatchSet):
         elif message:
             descr = message
 
+        # TODO: move this out of the stgit.stack module, it is really
+        # for higher level commands to handle the user interaction
         if not message and edit:
             descr = edit_file(self, descr.rstrip(), \
                               'Please edit the description for patch "%s" ' \
@@ -842,6 +855,8 @@ class Series(PatchSet):
             if self.patch_exists(name):
                 raise StackException, 'Patch "%s" already exists' % name
 
+        # TODO: move this out of the stgit.stack module, it is really
+        # for higher level commands to handle the user interaction
         if not message and can_edit:
             descr = edit_file(
                 self, None,
index b0602ff159ec4ff0c712ef9573b6e182428c6ebd..5b842d0b116246398a29d4766d4185c3ed98a63c 100755 (executable)
@@ -64,7 +64,7 @@ test_expect_success \
        'Check the "push(f)" log' \
        '
        stg pop &&
-       stg refresh -m "Foo2 Patch" &&
+       stg edit -m "Foo2 Patch" &&
        stg push &&
        stg log --full | grep -q -e "^push(f) "
        '