chiark / gitweb /
Print conflict details with the new infrastructure (bug #11181)
[stgit] / stgit / commands / pick.py
index e8f4dfa2cc59653a9514ed876b25e61e437e83a4..ee08c01c5219114647217ac73df315199f9c3b36 100644 (file)
@@ -16,72 +16,183 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
 import sys, os
-from optparse import OptionParser, make_option
-
+from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
+from stgit.out import *
+from stgit import argparse, stack, git
+from stgit.stack import Series
+
+help = 'Import a patch from a different branch or a commit object'
+kind = 'patch'
+usage = ['[options] ([<patch1>] [<patch2>] [<patch3>..<patch4>])|<commit>']
+description = """
+Import one or more patches from a different branch or a commit object
+into the current series. By default, the name of the imported patch is
+used as the name of the current patch. It can be overridden with the
+'--name' option. A commit object can be reverted with the '--reverse'
+option. The log and author information are those of the commit
+object."""
+
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches,
+                             argparse.hidden_patches)]
+options = [
+    opt('-n', '--name',
+        short = 'Use NAME as the patch name'),
+    opt('-B', '--ref-branch', args = [argparse.stg_branches],
+        short = 'Pick patches from BRANCH'),
+    opt('-r', '--reverse', action = 'store_true',
+        short = 'Reverse the commit object before importing'),
+    opt('-p', '--parent', metavar = 'COMMITID', args = [argparse.commit],
+        short = 'Use COMMITID as parent'),
+    opt('-x', '--expose', action = 'store_true',
+        short = 'Append the imported commit id to the patch log'),
+    opt('--fold', action = 'store_true',
+        short = 'Fold the commit object into the current patch'),
+    opt('--update', action = 'store_true',
+        short = 'Like fold but only update the current patch files'),
+    opt('--unapplied', action = 'store_true',
+        short = 'Keep the patch unapplied')]
+
+directory = DirectoryGotoToplevel(log = True)
+
+def __pick_commit(commit_id, patchname, options):
+    """Pick a commit id.
+    """
+    commit = git.Commit(commit_id)
 
+    if options.name:
+        patchname = options.name
+    if patchname:
+        patchname = find_patch_name(patchname, crt_series.patch_exists)
 
-help = 'import a patch from a different branch or a commit object'
-usage = """%prog [options] [<patch@branch>|<commit>]
+    if options.parent:
+        parent = git_id(crt_series, options.parent)
+    else:
+        parent = commit.get_parent()
 
-Import a patch from a different branch or a commit object into the
-current series. By default, the name of the imported patch is used as
-the name of the current patch. It can be overriden with the '--name'
-option. A commit object can be reverted with the '--reverse'
-option. The log and author information are those of the commit object."""
+    if not options.reverse:
+        bottom = parent
+        top = commit_id
+    else:
+        bottom = commit_id
+        top = parent
 
-options = [make_option('-n', '--name',
-                       help = 'use NAME as the patch name'),
-           make_option('-r', '--reverse',
-                       help = 'reverse the commit object before importing',
-                       action = 'store_true')]
+    if options.fold:
+        out.start('Folding commit %s' % commit_id)
 
+        # try a direct git apply first
+        if not git.apply_diff(bottom, top):
+            git.merge_recursive(bottom, git.get_head(), top)
 
-def func(parser, options, args):
-    """Import a commit object as a new patch
-    """
-    if len(args) != 1:
-        parser.error('incorrect number of arguments')
+        out.done()
+    elif options.update:
+        rev1 = git_id(crt_series, 'HEAD^')
+        rev2 = git_id(crt_series, 'HEAD')
+        files = git.barefiles(rev1, rev2).split('\n')
 
-    check_local_changes()
-    check_conflicts()
-    check_head_top_equal()
+        out.start('Updating with commit %s' % commit_id)
 
-    commit_str = args[0]
-    patch_branch = commit_str.split('@')
+        if not git.apply_diff(bottom, top, files = files):
+            raise CmdException, 'Patch updating failed'
 
-    if len(patch_branch) == 2:
-        patch = patch_branch[0]
-    elif options.name:
-        patch = options.name
+        out.done()
     else:
-        raise CmdException, 'Unkown patch name'
+        message = commit.get_log()
+        if options.expose:
+            message += '(imported from commit %s)\n' % commit.get_id_hash()
+        author_name, author_email, author_date = \
+                     name_email_date(commit.get_author())
+
+        out.start('Importing commit %s' % commit_id)
+
+        newpatch = crt_series.new_patch(patchname, message = message, can_edit = False,
+                                        unapplied = True, bottom = bottom, top = top,
+                                        author_name = author_name,
+                                        author_email = author_email,
+                                        author_date = author_date)
+        # in case the patch name was automatically generated
+        patchname = newpatch.get_name()
+
+        # find a patchlog to fork from
+        refbranchname, refpatchname = parse_rev(patchname)
+        if refpatchname:
+            if refbranchname:
+                # assume the refseries is OK, since we already resolved
+                # commit_str to a git_id
+                refseries = Series(refbranchname)
+            else:
+                refseries = crt_series
+            patch = refseries.get_patch(refpatchname)
+            if patch.get_log():
+                out.info("Log was %s" % newpatch.get_log())
+                out.info("Setting log to %s\n" %  patch.get_log())
+                newpatch.set_log(patch.get_log())
+                out.info("Log is now %s" % newpatch.get_log())
+            else:
+                out.info("No log for %s\n" % patchname)
+
+        if not options.unapplied:
+            modified = crt_series.push_patch(patchname)
+        else:
+            modified = False
+
+        if crt_series.empty_patch(patchname):
+            out.done('empty patch')
+        elif modified:
+            out.done('modified')
+        else:
+            out.done()
 
-    commit_id = git_id(commit_str)
-    commit = git.Commit(commit_id)
-
-    if not options.reverse:
-        bottom = commit.get_parent()
-        top = commit_id
-    else:
-        bottom = commit_id
-        top = commit.get_parent()
 
-    message = commit.get_log()
-    author_name, author_email, author_date = \
-                 name_email_date(commit.get_author())
+def func(parser, options, args):
+    """Import a commit object as a new patch
+    """
+    if not args:
+        parser.error('incorrect number of arguments')
 
-    print 'Importing commit %s...' % commit_id,
-    sys.stdout.flush()
+    if not options.unapplied:
+        check_local_changes()
+        check_conflicts()
+        check_head_top_equal(crt_series)
 
-    crt_series.new_patch(patch, message = message, can_edit = False,
-                         unapplied = True, bottom = bottom, top = top,
-                         author_name = author_name,
-                         author_email = author_email,
-                         author_date = author_date)
-    crt_series.push_patch(patch)
+    if options.ref_branch:
+        remote_series = Series(options.ref_branch)
+    else:
+        remote_series = crt_series
+
+    applied = remote_series.get_applied()
+    unapplied = remote_series.get_unapplied()
+    try:
+        patches = parse_patches(args, applied + unapplied, len(applied))
+        commit_id = None
+    except CmdException:
+        if len(args) > 1:
+            raise
+        # no patches found, try a commit id
+        commit_id = git_id(remote_series, args[0])
+
+    if not commit_id and len(patches) > 1:
+        if options.name:
+            raise CmdException, '--name can only be specified with one patch'
+        if options.parent:
+            raise CmdException, '--parent can only be specified with one patch'
+
+    if options.update and not crt_series.get_current():
+        raise CmdException, 'No patches applied'
+
+    if commit_id:
+        # Try to guess a patch name if the argument was <branch>:<patch>
+        try:
+            patchname = args[0].split(':')[1]
+        except IndexError:
+            patchname = None
+        __pick_commit(commit_id, patchname, options)
+    else:
+        if options.unapplied:
+            patches.reverse()
+        for patch in patches:
+            __pick_commit(git_id(remote_series, patch), patch, options)
 
-    print 'done'
-    print_crt_patch()
+    print_crt_patch(crt_series)