Esempio n. 1
0
def func(parser, options, args):
    """Create a new patch."""
    stack = directory.repository.current_stack
    if stack.repository.default_index.conflicts():
        raise CmdException(
            'Cannot create a new patch -- resolve conflicts first')

    # Choose a name for the new patch -- or None, which means make one
    # up later when we've gotten hold of the commit message.
    if len(args) == 0:
        name = None
    elif len(args) == 1:
        name = args[0]
        if stack.patches.exists(name):
            raise CmdException('%s: patch already exists' % name)
        elif not stack.patches.is_name_valid(name):
            raise CmdException('Invalid patch name: "%s"' % name)
    else:
        parser.error('incorrect number of arguments')

    cd = CommitData(
        tree=stack.head.data.tree,
        parents=[stack.head],
        message='',
        author=Person.author(),
        committer=Person.committer(),
    )
    cd = update_commit_data(
        cd,
        message=options.message,
        author=options.author(cd.author),
        sign_str=options.sign_str,
        edit=(not options.save_template and options.message is None),
    )

    if options.save_template:
        options.save_template(cd.message)
        return utils.STGIT_SUCCESS

    if not options.no_verify:
        cd = run_commit_msg_hook(stack.repository, cd)

    if name is None:
        name = utils.make_patch_name(cd.message_str, stack.patches.exists)
        assert stack.patches.is_name_valid(name)

    # Write the new patch.
    stack.repository.default_iw
    trans = StackTransaction(stack, 'new: %s' % name)
    trans.patches[name] = stack.repository.commit(cd)
    trans.applied.append(name)
    return trans.run()
Esempio n. 2
0
def _squash(stack, iw, name, msg, save_template, patches, no_verify=False):
    # If a name was supplied on the command line, make sure it's OK.
    def bad_name(pn):
        return pn not in patches and stack.patches.exists(pn)

    def get_name(cd):
        return name or utils.make_patch_name(cd.message, bad_name)

    if name and bad_name(name):
        raise CmdException('Patch name "%s" already taken' % name)

    def make_squashed_patch(trans, new_commit_data):
        name = get_name(new_commit_data)
        trans.patches[name] = stack.repository.commit(new_commit_data)
        trans.unapplied.insert(0, name)

    trans = StackTransaction(stack, 'squash', allow_conflicts=True)
    push_new_patch = bool(set(patches) & set(trans.applied))
    try:
        new_commit_data = _squash_patches(trans, patches, msg, save_template,
                                          no_verify)
        if new_commit_data:
            # We were able to construct the squashed commit
            # automatically. So just delete its constituent patches.
            to_push = trans.delete_patches(lambda pn: pn in patches)
        else:
            # Automatic construction failed. So push the patches
            # consecutively, so that a second construction attempt is
            # guaranteed to work.
            to_push = trans.pop_patches(lambda pn: pn in patches)
            for pn in patches:
                trans.push_patch(pn, iw)
            new_commit_data = _squash_patches(
                trans, patches, msg, save_template, no_verify
            )
            popped_extra = trans.delete_patches(lambda pn: pn in patches)
            assert not popped_extra
        make_squashed_patch(trans, new_commit_data)

        # Push the new patch if necessary, and any unrelated patches we've
        # had to pop out of the way.
        if push_new_patch:
            trans.push_patch(get_name(new_commit_data), iw)
        for pn in to_push:
            trans.push_patch(pn, iw)
    except SaveTemplateDone:
        trans.abort(iw)
        return
    except TransactionHalted:
        pass
    return trans.run(iw)
Esempio n. 3
0
def post_rebase(stack, applied, cmd_name, check_merged):
    iw = stack.repository.default_iw
    trans = StackTransaction(stack, '%s (reapply)' % cmd_name)
    try:
        if check_merged:
            merged = set(trans.check_merged(applied))
        else:
            merged = set()
        for pn in applied:
            trans.push_patch(
                pn, iw, allow_interactive=True, already_merged=pn in merged
            )
    except TransactionHalted:
        pass
    return trans.run(iw)
Esempio n. 4
0
def make_temp_patch(stack, patch_name, tree):
    """Commit tree to temp patch, in a complete transaction."""
    commit = stack.repository.commit(
        CommitData(
            tree=tree,
            parents=[stack.head],
            message='Refresh of %s' % patch_name,
        ))
    temp_name = utils.make_patch_name('refresh-temp', stack.patches.exists)
    trans = StackTransaction(stack, 'refresh (create temporary patch)')
    trans.patches[temp_name] = commit
    trans.applied.append(temp_name)
    return (
        trans.run(stack.repository.default_iw, print_current_patch=False),
        temp_name,
    )
Esempio n. 5
0
def delete_patches(stack, iw, patches):
    def allow_conflicts(trans):
        # Allow conflicts if the topmost patch stays the same.
        if stack.patchorder.applied:
            return trans.applied and trans.applied[
                -1] == stack.patchorder.applied[-1]
        else:
            return not trans.applied

    trans = StackTransaction(stack, 'delete', allow_conflicts=allow_conflicts)
    try:
        to_push = trans.delete_patches(lambda pn: pn in patches)
        for pn in to_push:
            trans.push_patch(pn, iw)
    except TransactionHalted:
        pass
    return trans.run(iw)
Esempio n. 6
0
def prepare_rebase(stack, cmd_name):
    # pop all patches
    iw = stack.repository.default_iw
    trans = StackTransaction(stack, '%s (pop)' % cmd_name, check_clean_iw=iw)
    out.start('Popping all applied patches')
    try:
        trans.reorder_patches(
            applied=[],
            unapplied=trans.applied + trans.unapplied,
            iw=iw,
            allow_interactive=True,
        )
    except TransactionException:
        pass
    retval = trans.run(iw, print_current_patch=False)
    if retval:
        out.done('Failed to pop applied patches')
    else:
        out.done()
    return retval
Esempio n. 7
0
File: refresh.py Progetto: gwd/stgit
def absorb(stack, patch_name, temp_name, edit_fun, annotate=None):
    """Absorb the temp patch into the target patch."""
    if annotate:
        log_msg = 'refresh\n\n' + annotate
    else:
        log_msg = 'refresh'
    trans = StackTransaction(stack, log_msg)
    iw = stack.repository.default_iw
    if patch_name in trans.applied:
        absorb_func = absorb_applied
    else:
        absorb_func = absorb_unapplied
    if absorb_func(trans, iw, patch_name, temp_name, edit_fun):
        def info_msg():
            pass
    else:
        def info_msg():
            out.warn('The new changes did not apply cleanly to %s.'
                     % patch_name, 'They were saved in %s.' % temp_name)
    r = trans.run(iw)
    info_msg()
    return r
Esempio n. 8
0
def func(parser, options, args):
    """Repair inconsistencies in StGit metadata."""
    if args:
        parser.error('incorrect number of arguments')

    repository = directory.repository
    stack = repository.get_stack()

    if stack.protected:
        raise CmdException(
            'This branch is protected. Modification is not permitted.')

    patchorder = stack.patchorder
    patches = [stack.patches.get(pn) for pn in patchorder.all]

    # Find commits that aren't patches, and applied patches.
    patchify = []  # commits to definitely patchify
    maybe_patchify = []  # commits to patchify if we find a patch below them
    applied = []
    c = stack.head
    while len(c.data.parents) == 1:
        for p in patches:
            if p.commit == c:
                applied.append(p)
                patchify.extend(maybe_patchify)
                maybe_patchify = []
                break
        else:
            maybe_patchify.append(c)
        c = c.data.parent
    applied.reverse()
    patchify.reverse()

    # Find patches unreachable behind a merge.
    merge = c
    todo = set([c])
    seen = set()
    unreachable = set()
    while todo:
        c = todo.pop()
        seen.add(c)
        todo |= set(c.data.parents) - seen
        if any(p.commit == c for p in patches):
            unreachable.add(c)
    if unreachable:
        out.warn(('%d patch%s are hidden below the merge commit' %
                  (len(unreachable), ['es', ''][len(unreachable) == 1])),
                 '%s,' % merge.sha1, 'and will be considered unapplied.')

    # Make patches of any linear sequence of commits on top of a patch.
    if applied and patchify:
        out.start('Creating %d new patch%s' %
                  (len(patchify), ['es', ''][len(patchify) == 1]))

        for c in patchify:
            pn = make_patch_name(
                c.data.message,
                unacceptable=lambda name: any(p.name == name for p in patches),
            )
            out.info('Creating patch %s from commit %s' % (pn, c.sha1))
            applied.append(stack.patches.new(pn, c, 'repair'))
        out.done()

    # Figure out hidden
    hidden = [p for p in patches if p.name in patchorder.hidden]

    # Write the applied/unapplied files.
    out.start('Checking patch appliedness')
    unapplied = [p for p in patches if p not in applied and p not in hidden]
    for pn in patchorder.all:
        if all(pn != p.name for p in patches):
            out.info('%s is gone' % pn)
    for p in applied:
        if p.name not in patchorder.applied:
            out.info('%s is now applied' % p.name)
    for p in unapplied:
        if p.name not in patchorder.unapplied:
            out.info('%s is now unapplied' % p.name)
    for p in hidden:
        if p.name not in patchorder.hidden:
            out.info('%s is now hidden' % p.name)
    out.done()

    orig_order = dict((pn, i) for i, pn in enumerate(patchorder.all))

    def patchname_key(p):
        i = orig_order.get(p, len(orig_order))
        return i, p

    trans = StackTransaction(stack,
                             'repair',
                             check_clean_iw=False,
                             allow_bad_head=True)
    try:
        trans.applied = [p.name for p in applied]
        trans.unapplied = sorted((p.name for p in unapplied),
                                 key=patchname_key)
        trans.hidden = sorted((p.name for p in hidden), key=patchname_key)
    except TransactionHalted:
        pass
    return trans.run()
Esempio n. 9
0
def __pick_commit(stack, ref_stack, iw, commit, patchname, options):
    """Pick a commit."""
    repository = stack.repository

    if options.name:
        patchname = options.name
    elif patchname and options.revert:
        patchname = 'revert-' + patchname

    if patchname:
        patchname = find_patch_name(patchname, stack.patches.exists)
    else:
        patchname = make_patch_name(commit.data.message_str,
                                    stack.patches.exists)

    if options.parent:
        parent = git_commit(options.parent, repository, ref_stack.name)
    else:
        parent = commit.data.parent

    if not options.revert:
        bottom = parent
        top = commit
    else:
        bottom = commit
        top = parent

    if options.fold:
        out.start('Folding commit %s' % commit.sha1)

        diff = repository.diff_tree(bottom.data.tree,
                                    top.data.tree,
                                    pathlimits=options.file)

        if diff:
            try:
                # try a direct git apply first
                iw.apply(diff, quiet=True)
            except MergeException:
                if options.file:
                    out.done('conflict(s)')
                    out.error('%s does not apply cleanly' % patchname)
                    return STGIT_CONFLICT
                else:
                    try:
                        iw.merge(
                            bottom.data.tree,
                            stack.head.data.tree,
                            top.data.tree,
                        )
                    except MergeConflictException as e:
                        out.done('%s conflicts' % len(e.conflicts))
                        out.error('%s does not apply cleanly' % patchname,
                                  *e.conflicts)
                        return STGIT_CONFLICT
            out.done()
        else:
            out.done('no changes')
        return STGIT_SUCCESS
    elif options.update:
        files = [
            fn1 for _, _, _, _, _, fn1, fn2 in repository.diff_tree_files(
                stack.top.data.parent.data.tree, stack.top.data.tree)
        ]

        diff = repository.diff_tree(bottom.data.tree,
                                    top.data.tree,
                                    pathlimits=files)

        out.start('Updating with commit %s' % commit.sha1)

        try:
            iw.apply(diff, quiet=True)
        except MergeException:
            out.done('conflict(s)')
            out.error('%s does not apply cleanly' % patchname)
            return STGIT_CONFLICT
        else:
            out.done()
            return STGIT_SUCCESS
    else:
        author = commit.data.author
        message = commit.data.message_str

        if options.revert:
            author = Person.author()
            if message:
                lines = message.splitlines()
                subject = lines[0]
                body = '\n'.join(lines[2:])
            else:
                subject = commit.sha1
                body = ''
            message = 'Revert "%s"\n\nThis reverts commit %s.\n\n%s\n' % (
                subject,
                commit.sha1,
                body,
            )
        elif options.expose:
            fmt = config.get('stgit.pick.expose-format')
            message = Run('git', 'show', '--no-patch', '--pretty=' + fmt,
                          commit.sha1).raw_output()
            message = message.rstrip() + '\n'

        out.start('Importing commit %s' % commit.sha1)

        new_commit = repository.commit(
            CommitData(
                tree=top.data.tree,
                parents=[bottom],
                message=message,
                author=author,
            ))

        trans = StackTransaction(
            stack, 'pick %s from %s' % (patchname, ref_stack.name))
        trans.patches[patchname] = new_commit

        trans.unapplied.append(patchname)
        if not options.unapplied:
            try:
                trans.push_patch(patchname, iw, allow_interactive=True)
            except TransactionHalted:
                pass

        retval = trans.run(iw, print_current_patch=False)

        if retval == STGIT_CONFLICT:
            out.done('conflict(s)')
        elif stack.patches.get(patchname).is_empty():
            out.done('empty patch')
        else:
            out.done()

        return retval
Esempio n. 10
0
def func(parser, options, args):
    repository = directory.repository

    if options.create:
        if len(args) == 0 or len(args) > 2:
            parser.error('incorrect number of arguments')

        branch_name = args[0]
        committish = None if len(args) < 2 else args[1]

        if committish:
            check_local_changes(repository)
        check_conflicts(repository.default_iw)
        try:
            stack = repository.get_stack()
        except (DetachedHeadException, StackException):
            pass
        else:
            check_head_top_equal(stack)

        stack = __create_branch(branch_name, committish)

        out.info('Branch "%s" created' % branch_name)
        log.log_entry(stack, 'branch --create %s' % stack.name)
        return

    elif options.clone:

        cur_branch = Branch(repository, repository.current_branch_name)
        if len(args) == 0:
            clone_name = cur_branch.name + time.strftime('-%C%y%m%d-%H%M%S')
        elif len(args) == 1:
            clone_name = args[0]
        else:
            parser.error('incorrect number of arguments')

        check_local_changes(repository)
        check_conflicts(repository.default_iw)
        try:
            stack = repository.current_stack
        except StackException:
            stack = None
            base = repository.refs.get(repository.head_ref)
        else:
            check_head_top_equal(stack)
            base = stack.base

        out.start('Cloning current branch to "%s"' % clone_name)
        clone = Stack.create(
            repository,
            name=clone_name,
            create_at=base,
            parent_remote=cur_branch.parent_remote,
            parent_branch=cur_branch.name,
        )
        if stack:
            for pn in stack.patchorder.all_visible:
                patch = stack.patches.get(pn)
                clone.patches.new(pn, patch.commit, 'clone %s' % stack.name)
            clone.patchorder.set_order(applied=[],
                                       unapplied=stack.patchorder.all_visible,
                                       hidden=[])
            trans = StackTransaction(clone, 'clone')
            try:
                for pn in stack.patchorder.applied:
                    trans.push_patch(pn)
            except TransactionHalted:
                pass
            trans.run()
        prefix = 'branch.%s.' % cur_branch.name
        new_prefix = 'branch.%s.' % clone.name
        for n, v in list(config.getstartswith(prefix)):
            config.set(n.replace(prefix, new_prefix, 1), v)
        clone.set_description('clone of "%s"' % cur_branch.name)
        clone.switch_to()
        out.done()

        log.copy_log(log.default_repo(), cur_branch.name, clone.name,
                     'branch --clone')
        return

    elif options.delete:

        if len(args) != 1:
            parser.error('incorrect number of arguments')
        __delete_branch(args[0], options.force)
        log.delete_log(log.default_repo(), args[0])
        return

    elif options.cleanup:

        if not args:
            name = repository.current_branch_name
        elif len(args) == 1:
            name = args[0]
        else:
            parser.error('incorrect number of arguments')
        __cleanup_branch(name, options.force)
        log.delete_log(log.default_repo(), name)
        return

    elif options.list:

        if len(args) != 0:
            parser.error('incorrect number of arguments')

        branch_names = sorted(
            ref.replace('refs/heads/', '', 1) for ref in repository.refs
            if ref.startswith('refs/heads/') and not ref.endswith('.stgit'))

        if branch_names:
            out.info('Available branches:')
            max_len = max(len(name) for name in branch_names)
            for branch_name in branch_names:
                __print_branch(branch_name, max_len)
        else:
            out.info('No branches')
        return

    elif options.protect:

        if len(args) == 0:
            branch_name = repository.current_branch_name
        elif len(args) == 1:
            branch_name = args[0]
        else:
            parser.error('incorrect number of arguments')

        try:
            stack = repository.get_stack(branch_name)
        except StackException:
            raise CmdException('Branch "%s" is not controlled by StGIT' %
                               branch_name)

        out.start('Protecting branch "%s"' % branch_name)
        stack.protected = True
        out.done()

        return

    elif options.rename:

        if len(args) == 1:
            stack = repository.current_stack
            new_name = args[0]
        elif len(args) == 2:
            stack = repository.get_stack(args[0])
            new_name = args[1]
        else:
            parser.error('incorrect number of arguments')

        old_name = stack.name
        stack.rename(new_name)

        out.info('Renamed branch "%s" to "%s"' % (old_name, new_name))
        log.rename_log(repository, old_name, new_name, 'branch --rename')
        return

    elif options.unprotect:

        if len(args) == 0:
            branch_name = repository.current_branch_name
        elif len(args) == 1:
            branch_name = args[0]
        else:
            parser.error('incorrect number of arguments')

        try:
            stack = repository.get_stack(branch_name)
        except StackException:
            raise CmdException('Branch "%s" is not controlled by StGIT' %
                               branch_name)

        out.info('Unprotecting branch "%s"' % branch_name)
        stack.protected = False
        out.done()

        return

    elif options.description is not None:

        if len(args) == 0:
            branch_name = repository.current_branch_name
        elif len(args) == 1:
            branch_name = args[0]
        else:
            parser.error('incorrect number of arguments')

        Branch(repository, branch_name).set_description(options.description)
        return

    elif len(args) == 1:
        branch_name = args[0]
        if branch_name == repository.current_branch_name:
            raise CmdException('Branch "%s" is already the current branch' %
                               branch_name)

        if not options.merge:
            check_local_changes(repository)
        check_conflicts(repository.default_iw)
        try:
            stack = repository.get_stack()
        except StackException:
            pass
        else:
            check_head_top_equal(stack)

        out.start('Switching to branch "%s"' % branch_name)
        Branch(repository, branch_name).switch_to()
        out.done()
        return

    # default action: print the current branch
    if len(args) != 0:
        parser.error('incorrect number of arguments')

    out.stdout(directory.repository.current_branch_name)
Esempio n. 11
0
def func(parser, options, args):
    """Synchronise a range of patches
    """
    repository = directory.repository
    stack = repository.get_stack()

    if options.ref_branch:
        remote_stack = repository.get_stack(options.ref_branch)
        if remote_stack.name == stack.name:
            raise CmdException('Cannot synchronise with the current branch')
        remote_patches = remote_stack.patchorder.applied

        def merge_patch(commit, pname):
            return __branch_merge_patch(remote_stack, stack, commit, pname)

    elif options.series:
        patchdir = os.path.dirname(options.series)

        remote_patches = []
        with open(options.series) as f:
            for line in f:
                pn = re.sub('#.*$', '', line).strip()
                if not pn:
                    continue
                remote_patches.append(pn)

        def merge_patch(commit, pname):
            return __series_merge_patch(patchdir, stack, commit, pname)

    else:
        raise CmdException('No remote branch or series specified')

    applied = list(stack.patchorder.applied)
    unapplied = list(stack.patchorder.unapplied)

    if options.all:
        patches = applied
    elif len(args) != 0:
        patches = parse_patches(args,
                                applied + unapplied,
                                len(applied),
                                ordered=True)
    elif applied:
        patches = [applied[-1]]
    else:
        parser.error('no patches applied')

    assert patches

    # only keep the patches to be synchronised
    sync_patches = [p for p in patches if p in remote_patches]
    if not sync_patches:
        raise CmdException('No common patches to be synchronised')

    iw = repository.default_iw

    # pop to the one before the first patch to be synchronised
    first_patch = sync_patches[0]
    if first_patch in applied:
        to_pop = applied[applied.index(first_patch) + 1:]
        if to_pop:
            trans = StackTransaction(stack, 'sync (pop)', check_clean_iw=iw)
            popped_extra = trans.pop_patches(lambda pn: pn in to_pop)
            assert not popped_extra
            retval = trans.run(iw)
            assert not retval
        pushed = [first_patch]
    else:
        to_pop = []
        pushed = []
    popped = to_pop + [p for p in patches if p in unapplied]

    trans = StackTransaction(stack, 'sync', check_clean_iw=iw)
    try:
        for p in pushed + popped:
            if p in popped:
                trans.push_patch(p, iw=iw)

            if p not in sync_patches:
                # nothing to synchronise
                continue

            # the actual sync
            out.start('Synchronising "%s"' % p)

            commit = trans.patches[p]

            # the actual merging (either from a branch or an external file)
            tree = merge_patch(commit, p)
            if tree:
                trans.patches[p] = commit.data.set_tree(tree).commit(
                    repository)
                out.done('updated')
            else:
                out.done()
    except TransactionHalted:
        pass
    return trans.run(iw)
Esempio n. 12
0
def __create_patch(filename, message, author_name, author_email,
                   author_date, diff, options):
    """Create a new patch on the stack
    """
    stack = directory.repository.current_stack

    if options.name:
        name = options.name
        if not stack.patches.is_name_valid(name):
            raise CmdException('Invalid patch name: %s' % name)
    elif filename:
        name = os.path.basename(filename)
    else:
        name = ''
    if options.stripname:
        name = __strip_patch_name(name)

    if not name:
        if options.ignore or options.replace:
            def unacceptable_name(name):
                return False
        else:
            unacceptable_name = stack.patches.exists
        name = make_patch_name(message, unacceptable_name)
    else:
        # fix possible invalid characters in the patch name
        name = re.sub(r'[^\w.]+', '-', name).strip('-')

    assert stack.patches.is_name_valid(name)

    if options.ignore and name in stack.patchorder.applied:
        out.info('Ignoring already applied patch "%s"' % name)
        return

    out.start('Importing patch "%s"' % name)

    author = Person(
        author_name,
        author_email,
        Date.maybe(author_date),
    )
    author = options.author(author)

    try:
        if not diff:
            out.warn('No diff found, creating empty patch')
            tree = stack.head.data.tree
        else:
            iw = stack.repository.default_iw
            iw.apply(
                diff, quiet=False, reject=options.reject, strip=options.strip
            )
            tree = iw.index.write_tree()

        cd = CommitData(
            tree=tree,
            parents=[stack.head],
            author=author,
            message=message,
        )
        cd = update_commit_data(
            cd,
            message=None,
            author=None,
            sign_str=options.sign_str,
            edit=options.edit,
        )
        commit = stack.repository.commit(cd)

        trans = StackTransaction(stack, 'import: %s' % name)

        try:
            if options.replace and name in stack.patchorder.unapplied:
                trans.delete_patches(lambda pn: pn == name, quiet=True)

            trans.patches[name] = commit
            trans.applied.append(name)
        except TransactionHalted:
            pass
        trans.run()
    finally:
        out.done()
Esempio n. 13
0
def __create_patch(
    filename, message, author_name, author_email, author_date, diff, options
):
    """Create a new patch on the stack"""
    stack = directory.repository.current_stack

    if options.name:
        name = options.name
    elif filename:
        name = os.path.basename(filename)
    else:
        name = ''

    if options.stripname:
        # Removing leading numbers and trailing extension
        name = re.sub(
            r'''^
                (?:[0-9]+-)?           # Optional leading patch number
                (.*?)                  # Patch name group (non-greedy)
                (?:\.(?:diff|patch))?  # Optional .diff or .patch extension
                $
            ''',
            r'\g<1>',
            name,
            flags=re.VERBOSE,
        )

    need_unique = not (options.ignore or options.replace)

    if name:
        name = stack.patches.make_name(name, unique=need_unique, lower=False)
    else:
        name = stack.patches.make_name(message, unique=need_unique, lower=True)

    if options.ignore and name in stack.patchorder.applied:
        out.info('Ignoring already applied patch "%s"' % name)
        return

    out.start('Importing patch "%s"' % name)

    author = options.author(
        Person(
            author_name,
            author_email,
            Date.maybe(author_date),
        )
    )

    try:
        if not diff:
            out.warn('No diff found, creating empty patch')
            tree = stack.head.data.tree
        else:
            iw = stack.repository.default_iw
            iw.apply(
                diff,
                quiet=False,
                reject=options.reject,
                strip=options.strip,
                context_lines=options.context_lines,
            )
            tree = iw.index.write_tree()

        cd = CommitData(
            tree=tree,
            parents=[stack.head],
            author=author,
            message=message,
        )
        cd = update_commit_data(
            cd,
            message=None,
            author=None,
            sign_str=options.sign_str,
            edit=options.edit,
        )
        commit = stack.repository.commit(cd)

        trans = StackTransaction(stack, 'import: %s' % name)

        try:
            if options.replace and name in stack.patchorder.unapplied:
                trans.delete_patches(lambda pn: pn == name, quiet=True)

            trans.patches[name] = commit
            trans.applied.append(name)
        except TransactionHalted:
            pass
        trans.run()
    finally:
        out.done()