[PATCH 3 of 3] graft: apply get-only changes in-memory
Sean Farley
sean.michael.farley at gmail.com
Mon Nov 19 20:36:47 UTC 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