##// END OF EJS Templates
interfaces: convert the repository zope interfaces to Protocol classes...
interfaces: convert the repository zope interfaces to Protocol classes This is the same transformation as 382d9629cede did for dirstate. The same caveat applies- the code may not be valid, since the functions are missing the `self` arg, and the attrs should be plain attrs, not zope `Attribute`. These classes are pretty intertwined however, so making the same transformation to everything makes it easier to change and review. Additionally, there are some classes that subclass multiple protocol classes, and should themselves subclass `typing.Protocol` to be a protocol class. But defer that for now for clarity.

File last commit:

r53248:9042ffea default
r53342:26dd402c default
Show More
graft.py
317 lines | 10.9 KiB | text/x-python | PythonLexer
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 # graft.py - implementation of the graft command
Matt Harbison
typing: add missing `from __future__ import annotations` to core modules
r53247 from __future__ import annotations
Matt Harbison
typing: add minimal annotations to cmd_impls/graft.py to pytype with 3.10...
r53248 import typing
from typing import (
Any,
Tuple,
)
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 from ..i18n import _
from .. import cmdutil, error, logcmdutil, merge as mergemod, state as statemod
Matt Harbison
typing: add minimal annotations to cmd_impls/graft.py to pytype with 3.10...
r53248 if typing.TYPE_CHECKING:
_ActionT = str
_CmdArgsT = Any # TODO: (statedata, revs, editor, cont, dry_run, tool)
def cmd_graft(ui, repo, *revs, **opts) -> int:
Matt Harbison
graft: fix a few typos in doc comments
r53244 """implement the graft command as defined in mercurial/commands.py"""
graft: split the argument processing from the grafting...
r53226 ret = _process_args(ui, repo, *revs, **opts)
graft: clarify the args passing depending of variants...
r53231 action, graftstate, args = ret
if action == "ERROR":
graft: split the argument processing from the grafting...
r53226 return -1
graft: clarify the args passing depending of variants...
r53231 elif action == "ABORT":
assert args is None
return cmdutil.abortgraft(ui, repo, graftstate)
graft: split the argument processing from the grafting...
r53226 elif action == "STOP":
graft: clarify the args passing depending of variants...
r53231 assert args is None
return _stopgraft(ui, repo, graftstate)
graft: split the argument processing from the grafting...
r53226 elif action == "GRAFT":
graft: clarify the args passing depending of variants...
r53231 return _graft_revisions(ui, repo, graftstate, *args)
graft: split the argument processing from the grafting...
r53226 else:
Matt Harbison
graft: fix interpolation in a ProgrammingError case...
r53243 raise error.ProgrammingError('unknown action: %s' % action)
graft: split the argument processing from the grafting...
r53226
Matt Harbison
typing: add minimal annotations to cmd_impls/graft.py to pytype with 3.10...
r53248 def _process_args(
ui, repo, *revs, **opts
) -> Tuple[_ActionT, statemod.cmdstate | None, _CmdArgsT | None]:
graft: split the argument processing from the grafting...
r53226 """process the graft command argument to figure out what to do
This also filter the selected revision to skip the one that cannot be graft
Matt Harbison
graft: fix a few typos in doc comments
r53244 or were already grafted.
graft: split the argument processing from the grafting...
r53226 """
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 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'] = []
graft: gather arg compatibility code...
r53233 # argument incompatible with followup from an interrupted operation
commit_args = ['edit', 'log', 'user', 'date', 'currentdate', 'currentuser']
graft: also forbid "--base" with "--stop" and the like...
r53234 nofollow_args = commit_args + ['base', 'rev']
commands: create a "mercurial.cmd_impls" module to host graft...
r53225
graft: gather arg compatibility code...
r53233 arg_compatibilities = [
('no_commit', commit_args),
('stop', nofollow_args),
('abort', nofollow_args),
]
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
graft: gather arg compatibility code...
r53233 for arg, incompatible in arg_compatibilities:
cmdutil.check_incompatible_arguments(opts, arg, incompatible)
cmdutil.resolve_commit_options(ui, opts)
commands: create a "mercurial.cmd_impls" module to host graft...
r53225
cont = False
graftstate = statemod.cmdstate(repo, b'graftstate')
if opts.get('stop'):
graft: clarify the args passing depending of variants...
r53231 return "STOP", graftstate, None
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 elif opts.get('abort'):
graft: clarify the args passing depending of variants...
r53231 return "ABORT", graftstate, None
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 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'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'))
graft: split the argument processing from the grafting...
r53226 elif not revs:
raise error.InputError(_(b'no revisions specified'))
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 else:
cmdutil.checkunfinished(repo)
cmdutil.bailifchanged(repo)
revs = logcmdutil.revrange(repo, revs)
graft: assign computed configuration value to statedata instead of opts...
r53227 for o in (
b'date',
b'user',
b'log',
graft: move no_commit into "statedata" too...
r53228 b'no_commit',
graft: move "dry_run" and "base" in statedate...
r53229 b'dry_run',
graft: assign computed configuration value to statedata instead of opts...
r53227 ):
v = opts.get(o.decode('ascii'))
# if statedata is already set, it comes from --continue and test says
# we should honor them above the options (which seems weird).
if v and o not in statedata:
statedata[o] = v
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 skipped = set()
basectx = None
if opts.get('base'):
basectx = logcmdutil.revsingle(repo, opts['base'], None)
graft: move "dry_run" and "base" in statedate...
r53229 statedata[b'base'] = basectx.hex()
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 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:
graft: clarify the args passing depending of variants...
r53231 return "ERROR", None, None
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 if basectx is not None and len(revs) != 1:
Matt Harbison
graft: trim an InputError message...
r53245 raise error.InputError(_(b'only one revision allowed with --base'))
commands: create a "mercurial.cmd_impls" module to host graft...
r53225
# 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:
graft: clarify the args passing depending of variants...
r53231 return "ERROR", None, None
commands: create a "mercurial.cmd_impls" module to host graft...
r53225
# 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:
graft: clarify the args passing depending of variants...
r53231 return "ERROR", None, None
commands: create a "mercurial.cmd_impls" module to host graft...
r53225
graft: get the editor later...
r53232 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
graft: move "dry_run" and "base" in statedate...
r53229 dry_run = bool(opts.get("dry_run"))
graft: explicitly pass the "tool" argument...
r53230 tool = opts.get('tool', b'')
graft: clarify the args passing depending of variants...
r53231 return "GRAFT", graftstate, (statedata, revs, editor, cont, dry_run, tool)
graft: split the argument processing from the grafting...
r53226
def _graft_revisions(
ui,
repo,
graftstate,
statedata,
revs,
editor,
cont=False,
graft: move "dry_run" and "base" in statedate...
r53229 dry_run=False,
graft: explicitly pass the "tool" argument...
r53230 tool=b'',
graft: split the argument processing from the grafting...
r53226 ):
"""actually graft some revisions"""
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 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)
graft: move "dry_run" and "base" in statedate...
r53229 if dry_run:
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 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()
graft: assign computed configuration value to statedata instead of opts...
r53227 user = statedata.get(b'user', ctx.user())
date = statedata.get(b'date', ctx.date())
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 message = ctx.description()
graft: assign computed configuration value to statedata instead of opts...
r53227 if statedata.get(b'log'):
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 message += b'\n(grafted from %s)' % ctx.hex()
# we don't merge the first commit when continuing
if not cont:
# perform the graft merge with p1(rev) as 'ancestor'
graft: explicitly pass the "tool" argument...
r53230 overrides = {(b'ui', b'forcemerge'): tool}
graft: move "dry_run" and "base" in statedate...
r53229 if b'base' in statedata:
base = repo[statedata[b'base']]
else:
base = ctx.p1()
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 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
graft: move no_commit into "statedata" too...
r53228 if not statedata.get(b'no_commit'):
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 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
graft: move "dry_run" and "base" in statedate...
r53229 if not dry_run:
commands: create a "mercurial.cmd_impls" module to host graft...
r53225 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