[PATCH 3 of 3] graft: apply get-only changes in-memory

Sean Farley sean.michael.farley at gmail.com
Mon Nov 19 14:36:47 CST 2012


On Mon, Nov 19, 2012 at 2:01 PM, David Schleimer <dschleimer at fb.com> wrote:
> # HG changeset patch
> # User David Schleimer <dschleimer at fb.com>
> # Date 1353354272 28800
> # Node ID d4f1efa15442572e7ea658b50a02e093492797d7
> # Parent  a77f8405c800c27ba5cb97b7482410c6438c81c8
> graft: apply get-only changes in-memory
>
> This changes the graft code to apply the simplest-possible changes
> in-memory.  Specifically, changes where all we need to do is set the
> contents of files to the version in the commit being grafted.  This
> means that no files were deleted and no files were modified on by both
> the target branch and the change we're grafting.
>
> Based on one of our real-world branches, with 497 grafts,
> approximately 80% of cherry-picked commits will fall into the fast
> case here.  This patch takes a graft with all 497 revisions on the
> branch in question from 5250 seconds to 3251 seconds.
>
> The revisions that fall in the fast case were more common near the
> beginning of the branch than the end.  Longer lived branches are
> likely to see less of an improvement from this patch, but they should
> be unlikely to see a regression.

Nice, +1! Would this also work for the 'import' command? Or even
better, for incoming changesets via 'pull' so that the working files
aren't touched if there is no merge / conflict?

>
> diff --git a/mercurial/commands.py b/mercurial/commands.py
> --- a/mercurial/commands.py
> +++ b/mercurial/commands.py
> @@ -2823,45 +2823,20 @@
>
>      wlock = repo.wlock()
>      try:
> +        current = repo['.']
>          for pos, ctx in enumerate(repo.set("%ld", revs)):
> -            current = repo['.']
>
>              ui.status(_('grafting revision %s\n') % ctx.rev())
>              if opts.get('dry_run'):
>                  continue
>
> -            # we don't merge the first commit when continuing
> -            if not cont:
> -                # perform the graft merge with p1(rev) as 'ancestor'
> -                try:
> -                    # ui.forcemerge is an internal variable, do not document
> -                    repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
> -                    stats = mergemod.update(repo, ctx.node(), True, True, False,
> -                                            ctx.p1().node())
> -                finally:
> -                    repo.ui.setconfig('ui', 'forcemerge', '')
> -                # report any conflicts
> -                if stats and stats[3] > 0:
> -                    # write out state for --continue
> -                    nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
> -                    repo.opener.write('graftstate', ''.join(nodelines))
> -                    raise util.Abort(
> -                        _("unresolved conflicts, can't continue"),
> -                        hint=_('use hg resolve and hg graft --continue'))
> -            else:
> -                cont = False
> -
> -            # drop the second merge parent
> -            repo.setparents(current.node(), nullid)
> -            repo.dirstate.write()
> -            # fix up dirstate for copies and renames
> -            cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
> -
> -            # commit
>              source = ctx.extra().get('source')
>              if not source:
>                  source = ctx.hex()
> -            extra = {'source': source}
> +            extra = {
> +                'source': source,
> +                'branch': current.extra().get('branch', 'default'),
> +                }
>              user = ctx.user()
>              if opts.get('user'):
>                  user = opts['user']
> @@ -2871,10 +2846,114 @@
>              message = ctx.description()
>              if opts.get('log'):
>                  message += '\n(grafted from %s)' % ctx.hex()
> -            node = repo.commit(text=message, user=user,
> -                        date=date, extra=extra, editor=editor)
> +
> +            memoryapply = False
> +            # we don't merge the first commit when continuing
> +            if not cont:
> +                # perform the graft merge with p1(rev) as 'ancestor'
> +
> +                try:
> +                    repo.ui.setconfig('ui', 'forcemerge',
> +                                      opts.get('tool', ''))
> +
> +                    action = mergemod.calculateupdates(repo, current, ctx,
> +                                                       ctx.p1(),
> +                                                       branchmerge=True,
> +                                                       force=True,
> +                                                       partial=False)
> +                finally:
> +                    repo.ui.setconfig('ui', 'forcemerge', '')
> +
> +                supportedactions = set(['g'])
> +
> +                actionmap = {}
> +                for a in action:
> +                    if a[1] in supportedactions:
> +                            memoryapply = True
> +                            actionmap[a[0]] = a
> +                    else:
> +                        memoryapply = False
> +                        break
> +
> +                if memoryapply:
> +
> +                    copymap = copies.pathcopies(ctx.p1(), ctx)
> +
> +                    def filectxfn(repo, memctx, path):
> +                        a = actionmap[path]
> +                        flags = a[2]
> +                        islink = 'l' in flags
> +                        isexec = 'x' in flags
> +                        mfilectx = ctx.filectx(path)
> +                        return context.memfilectx(path, mfilectx.data(),
> +                                                  islink=islink,
> +                                                  isexec=isexec,
> +                                                  copied=copymap.get(path))
> +
> +
> +                    memctx = context.memctx(repo, (current.node(), None),
> +                                            text=message,
> +                                            files=actionmap.keys(),
> +                                            filectxfn=filectxfn,
> +                                            user=user,
> +                                            date=date,
> +                                            extra=extra)
> +                else:
> +                    try:
> +                        repo.ui.debug("falling back to on-disk merge\n")
> +                        # first update to the current rev if we've
> +                        # commited anything since the last update
> +                        if current.node() != repo['.'].node():
> +                            mergemod.update(repo, current.node(),
> +                                            branchmerge=False,
> +                                            force=True,
> +                                            partial=None)
> +                        # ui.forcemerge is an internal variable, do not document
> +                        repo.ui.setconfig('ui', 'forcemerge',
> +                                          opts.get('tool', ''))
> +                        stats = mergemod.applyupdates(repo, action,
> +                                                      wctx=repo[None],
> +                                                      mctx=ctx,
> +                                                      actx=ctx.p1(),
> +                                                      overwrite=False)
> +                        repo.setparents(current.node(), ctx.node())
> +                        mergemod.recordupdates(repo, action, branchmerge=True)
> +                    finally:
> +                        repo.ui.setconfig('ui', 'forcemerge', '')
> +                    # report any conflicts
> +                    if stats and stats[3] > 0:
> +                        # write out state for --continue
> +                        nodelines = [repo[rev].hex() + "\n"
> +                                     for rev in revs[pos:]]
> +                        repo.opener.write('graftstate', ''.join(nodelines))
> +                        raise util.Abort(
> +                            _("unresolved conflicts, can't continue"),
> +                            hint=_('use hg resolve and hg graft --continue'))
> +
> +            if cont or not memoryapply:
> +                cont = False
> +                repo.setparents(current.node(), nullid)
> +                repo.dirstate.write()
> +                # fix up dirstate for copies and renames
> +                cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
> +
> +            # commit
> +                node = repo.commit(text=message, user=user,
> +                                   date=date, extra=extra, editor=editor)
> +            else:
> +                node = repo.commitctx(memctx)
> +
>              if node is None:
>                  ui.status(_('graft for revision %s is empty\n') % ctx.rev())
> +            else:
> +                current = repo[node]
> +
> +        # if we've committed at least one revision, update to the most recent
> +        if current.node() != repo['.'].node():
> +            mergemod.update(repo, current.node(),
> +                            branchmerge=False,
> +                            force=True,
> +                            partial=False)
>      finally:
>          wlock.release()
>
> diff --git a/tests/test-graft.t b/tests/test-graft.t
> --- a/tests/test-graft.t
> +++ b/tests/test-graft.t
> @@ -135,8 +135,9 @@
>      checking for directory renames
>    resolving manifests
>     overwrite: False, partial: False
> -   ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
> +   ancestor: 68795b066622, local: ef0ef43d49e7, remote: 5d205f8b35b6
>     b: local copied/moved to a -> m
> +  falling back to on-disk merge
>    preserving b for resolve of b
>    updating: b 1/1 files (100.00%)
>    picked tool 'internal:merge' for b (binary False symlink False)
> @@ -148,18 +149,23 @@
>      searching for copies back to rev 1
>    resolving manifests
>     overwrite: False, partial: False
> -   ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
> +   ancestor: 4c60f11aa304, local: 6b9e5368ca4e, remote: 97f8bfe72746
>     e: remote is newer -> g
> -  updating: e 1/1 files (100.00%)
> -  getting e
>    e
>    grafting revision 4
>      searching for copies back to rev 1
>    resolving manifests
>     overwrite: False, partial: False
> -   ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
> +   ancestor: 4c60f11aa304, local: 1905859650ec, remote: 9c233e8e184d
>     e: versions differ -> m
>     d: remote is newer -> g
> +  falling back to on-disk merge
> +  resolving manifests
> +   overwrite: True, partial: False
> +   ancestor: 6b9e5368ca4e+, local: 6b9e5368ca4e+, remote: 1905859650ec
> +   e: remote is newer -> g
> +  updating: e 1/1 files (100.00%)
> +  getting e
>    preserving e for resolve of e
>    updating: d 1/2 files (50.00%)
>    getting d
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel


More information about the Mercurial-devel mailing list