--- a/mercurial/commands.py Wed Nov 27 22:09:37 2024 -0500
+++ b/mercurial/commands.py Tue Nov 19 15:46:12 2024 +0100
@@ -68,6 +68,9 @@
vfs as vfsmod,
wireprotoserver,
)
+
+from .cmd_impls import graft as graft_impl
+
from .utils import (
dateutil,
procutil,
@@ -3185,285 +3188,7 @@
Returns 0 on successful completion, 1 if there are unresolved files.
"""
with repo.wlock():
- return _dograft(ui, repo, *revs, **opts)
-
-
-def _dograft(ui, repo, *revs, **opts):
- if revs and opts.get('rev'):
- ui.warn(
- _(
- b'warning: inconsistent use of --rev might give unexpected '
- b'revision ordering!\n'
- )
- )
-
- revs = list(revs)
- revs.extend(opts.get('rev'))
- # a dict of data to be stored in state file
- statedata = {}
- # list of new nodes created by ongoing graft
- statedata[b'newnodes'] = []
-
- cmdutil.resolve_commit_options(ui, opts)
-
- editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
-
- cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
-
- cont = False
- if opts.get('no_commit'):
- cmdutil.check_incompatible_arguments(
- opts,
- 'no_commit',
- ['edit', 'currentuser', 'currentdate', 'log'],
- )
-
- graftstate = statemod.cmdstate(repo, b'graftstate')
-
- if opts.get('stop'):
- cmdutil.check_incompatible_arguments(
- opts,
- 'stop',
- [
- 'edit',
- 'log',
- 'user',
- 'date',
- 'currentdate',
- 'currentuser',
- 'rev',
- ],
- )
- return _stopgraft(ui, repo, graftstate)
- elif opts.get('abort'):
- cmdutil.check_incompatible_arguments(
- opts,
- 'abort',
- [
- 'edit',
- 'log',
- 'user',
- 'date',
- 'currentdate',
- 'currentuser',
- 'rev',
- ],
- )
- return cmdutil.abortgraft(ui, repo, graftstate)
- elif opts.get('continue'):
- cont = True
- if revs:
- raise error.InputError(_(b"can't specify --continue and revisions"))
- # read in unfinished revisions
- if graftstate.exists():
- statedata = cmdutil.readgraftstate(repo, graftstate)
- if statedata.get(b'date'):
- opts['date'] = statedata[b'date']
- if statedata.get(b'user'):
- opts['user'] = statedata[b'user']
- if statedata.get(b'log'):
- opts['log'] = True
- if statedata.get(b'no_commit'):
- opts['no_commit'] = statedata.get(b'no_commit')
- if statedata.get(b'base'):
- opts['base'] = statedata.get(b'base')
- nodes = statedata[b'nodes']
- revs = [repo[node].rev() for node in nodes]
- else:
- cmdutil.wrongtooltocontinue(repo, _(b'graft'))
- else:
- if not revs:
- raise error.InputError(_(b'no revisions specified'))
- cmdutil.checkunfinished(repo)
- cmdutil.bailifchanged(repo)
- revs = logcmdutil.revrange(repo, revs)
-
- skipped = set()
- basectx = None
- if opts.get('base'):
- basectx = logcmdutil.revsingle(repo, opts['base'], None)
- if basectx is None:
- # check for merges
- for rev in repo.revs(b'%ld and merge()', revs):
- ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
- skipped.add(rev)
- revs = [r for r in revs if r not in skipped]
- if not revs:
- return -1
- if basectx is not None and len(revs) != 1:
- raise error.InputError(_(b'only one revision allowed with --base '))
-
- # Don't check in the --continue case, in effect retaining --force across
- # --continues. That's because without --force, any revisions we decided to
- # skip would have been filtered out here, so they wouldn't have made their
- # way to the graftstate. With --force, any revisions we would have otherwise
- # skipped would not have been filtered out, and if they hadn't been applied
- # already, they'd have been in the graftstate.
- if not (cont or opts.get('force')) and basectx is None:
- # check for ancestors of dest branch
- ancestors = repo.revs(b'%ld & (::.)', revs)
- for rev in ancestors:
- ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
-
- revs = [r for r in revs if r not in ancestors]
-
- if not revs:
- return -1
-
- # analyze revs for earlier grafts
- ids = {}
- for ctx in repo.set(b"%ld", revs):
- ids[ctx.hex()] = ctx.rev()
- n = ctx.extra().get(b'source')
- if n:
- ids[n] = ctx.rev()
-
- # check ancestors for earlier grafts
- ui.debug(b'scanning for duplicate grafts\n')
-
- # The only changesets we can be sure doesn't contain grafts of any
- # revs, are the ones that are common ancestors of *all* revs:
- for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
- ctx = repo[rev]
- n = ctx.extra().get(b'source')
- if n in ids:
- try:
- r = repo[n].rev()
- except error.RepoLookupError:
- r = None
- if r in revs:
- ui.warn(
- _(
- b'skipping revision %d:%s '
- b'(already grafted to %d:%s)\n'
- )
- % (r, repo[r], rev, ctx)
- )
- revs.remove(r)
- elif ids[n] in revs:
- if r is None:
- ui.warn(
- _(
- b'skipping already grafted revision %d:%s '
- b'(%d:%s also has unknown origin %s)\n'
- )
- % (ids[n], repo[ids[n]], rev, ctx, n[:12])
- )
- else:
- ui.warn(
- _(
- b'skipping already grafted revision %d:%s '
- b'(%d:%s also has origin %d:%s)\n'
- )
- % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
- )
- revs.remove(ids[n])
- elif ctx.hex() in ids:
- r = ids[ctx.hex()]
- if r in revs:
- ui.warn(
- _(
- b'skipping already grafted revision %d:%s '
- b'(was grafted from %d:%s)\n'
- )
- % (r, repo[r], rev, ctx)
- )
- revs.remove(r)
- if not revs:
- return -1
-
- if opts.get('no_commit'):
- statedata[b'no_commit'] = True
- if opts.get('base'):
- statedata[b'base'] = opts['base']
- for pos, ctx in enumerate(repo.set(b"%ld", revs)):
- desc = b'%d:%s "%s"' % (
- ctx.rev(),
- ctx,
- ctx.description().split(b'\n', 1)[0],
- )
- names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
- if names:
- desc += b' (%s)' % b' '.join(names)
- ui.status(_(b'grafting %s\n') % desc)
- if opts.get('dry_run'):
- continue
-
- source = ctx.extra().get(b'source')
- extra = {}
- if source:
- extra[b'source'] = source
- extra[b'intermediate-source'] = ctx.hex()
- else:
- extra[b'source'] = ctx.hex()
- user = ctx.user()
- if opts.get('user'):
- user = opts['user']
- statedata[b'user'] = user
- date = ctx.date()
- if opts.get('date'):
- date = opts['date']
- statedata[b'date'] = date
- message = ctx.description()
- if opts.get('log'):
- message += b'\n(grafted from %s)' % ctx.hex()
- statedata[b'log'] = True
-
- # we don't merge the first commit when continuing
- if not cont:
- # perform the graft merge with p1(rev) as 'ancestor'
- overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
- base = ctx.p1() if basectx is None else basectx
- with ui.configoverride(overrides, b'graft'):
- stats = mergemod.graft(
- repo, ctx, base, [b'local', b'graft', b'parent of graft']
- )
- # report any conflicts
- if stats.unresolvedcount > 0:
- # write out state for --continue
- nodes = [repo[rev].hex() for rev in revs[pos:]]
- statedata[b'nodes'] = nodes
- stateversion = 1
- graftstate.save(stateversion, statedata)
- ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
- ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
- return 1
- else:
- cont = False
-
- # commit if --no-commit is false
- if not opts.get('no_commit'):
- node = repo.commit(
- text=message, user=user, date=date, extra=extra, editor=editor
- )
- if node is None:
- ui.warn(
- _(b'note: graft of %d:%s created no changes to commit\n')
- % (ctx.rev(), ctx)
- )
- # checking that newnodes exist because old state files won't have it
- elif statedata.get(b'newnodes') is not None:
- nn = statedata[b'newnodes']
- assert isinstance(nn, list) # list of bytes
- nn.append(node)
-
- # remove state when we complete successfully
- if not opts.get('dry_run'):
- graftstate.delete()
-
- return 0
-
-
-def _stopgraft(ui, repo, graftstate):
- """stop the interrupted graft"""
- if not graftstate.exists():
- raise error.StateError(_(b"no interrupted graft found"))
- pctx = repo[b'.']
- mergemod.clean_update(pctx)
- graftstate.delete()
- ui.status(_(b"stopped the interrupted graft\n"))
- ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
- return 0
+ return graft_impl.cmd_graft(ui, repo, *revs, **opts)
statemod.addunfinished(