mercurial/cmd_impls/graft.py
changeset 52505 68dc6cecca32
parent 52504 de16800904f9
equal deleted inserted replaced
52504:de16800904f9 52505:68dc6cecca32
     9     Tuple,
     9     Tuple,
    10 )
    10 )
    11 
    11 
    12 from ..i18n import _
    12 from ..i18n import _
    13 
    13 
    14 from .. import cmdutil, error, logcmdutil, merge as mergemod, state as statemod
    14 from .. import (
       
    15     cmdutil,
       
    16     context,
       
    17     error,
       
    18     logcmdutil,
       
    19     merge as mergemod,
       
    20     state as statemod,
       
    21 )
    15 
    22 
    16 
    23 
    17 if typing.TYPE_CHECKING:
    24 if typing.TYPE_CHECKING:
    18     _ActionT = str
    25     _ActionT = str
    19     _CmdArgsT = Any  # TODO: (statedata, revs, editor, cont, dry_run, tool)
    26     _CmdArgsT = Any  # TODO: (statedata, revs, editor, cont, dry_run, tool)
    31     elif action == "STOP":
    38     elif action == "STOP":
    32         assert args is None
    39         assert args is None
    33         return _stopgraft(ui, repo, graftstate)
    40         return _stopgraft(ui, repo, graftstate)
    34     elif action == "GRAFT":
    41     elif action == "GRAFT":
    35         return _graft_revisions(ui, repo, graftstate, *args)
    42         return _graft_revisions(ui, repo, graftstate, *args)
       
    43     elif action == "GRAFT-TO":
       
    44         return _graft_revisions_in_memory(ui, repo, graftstate, *args)
    36     else:
    45     else:
    37         raise error.ProgrammingError('unknown action: %s' % action)
    46         msg = b'unknown action: %s' % action.encode('ascii')
       
    47         raise error.ProgrammingError(msg)
    38 
    48 
    39 
    49 
    40 def _process_args(
    50 def _process_args(
    41     ui, repo, *revs, **opts
    51     ui, repo, *revs, **opts
    42 ) -> Tuple[_ActionT, statemod.cmdstate | None, _CmdArgsT | None]:
    52 ) -> Tuple[_ActionT, statemod.cmdstate | None, _CmdArgsT | None]:
    59     statedata = {}
    69     statedata = {}
    60     # list of new nodes created by ongoing graft
    70     # list of new nodes created by ongoing graft
    61     statedata[b'newnodes'] = []
    71     statedata[b'newnodes'] = []
    62 
    72 
    63     # argument incompatible with followup from an interrupted operation
    73     # argument incompatible with followup from an interrupted operation
    64     commit_args = ['edit', 'log', 'user', 'date', 'currentdate', 'currentuser']
    74     commit_args = [
       
    75         'edit',
       
    76         'log',
       
    77         'user',
       
    78         'date',
       
    79         'currentdate',
       
    80         'currentuser',
       
    81         'to',
       
    82     ]
    65     nofollow_args = commit_args + ['base', 'rev']
    83     nofollow_args = commit_args + ['base', 'rev']
    66 
    84 
    67     arg_compatibilities = [
    85     arg_compatibilities = [
    68         ('no_commit', commit_args),
    86         ('no_commit', commit_args),
       
    87         ('continue', ['to']),
    69         ('stop', nofollow_args),
    88         ('stop', nofollow_args),
    70         ('abort', nofollow_args),
    89         ('abort', nofollow_args),
    71     ]
    90     ]
    72     cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
    91     cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
    73     for arg, incompatible in arg_compatibilities:
    92     for arg, incompatible in arg_compatibilities:
    76     cmdutil.resolve_commit_options(ui, opts)
    95     cmdutil.resolve_commit_options(ui, opts)
    77 
    96 
    78     cont = False
    97     cont = False
    79 
    98 
    80     graftstate = statemod.cmdstate(repo, b'graftstate')
    99     graftstate = statemod.cmdstate(repo, b'graftstate')
       
   100 
       
   101     if opts.get('to'):
       
   102         toctx = logcmdutil.revsingle(repo, opts['to'], None)
       
   103         statedata[b'to'] = toctx.hex()
       
   104     else:
       
   105         toctx = repo[None].p1()
    81 
   106 
    82     if opts.get('stop'):
   107     if opts.get('stop'):
    83         return "STOP", graftstate, None
   108         return "STOP", graftstate, None
    84     elif opts.get('abort'):
   109     elif opts.get('abort'):
    85         return "ABORT", graftstate, None
   110         return "ABORT", graftstate, None
   100             cmdutil.wrongtooltocontinue(repo, _(b'graft'))
   125             cmdutil.wrongtooltocontinue(repo, _(b'graft'))
   101     elif not revs:
   126     elif not revs:
   102         raise error.InputError(_(b'no revisions specified'))
   127         raise error.InputError(_(b'no revisions specified'))
   103     else:
   128     else:
   104         cmdutil.checkunfinished(repo)
   129         cmdutil.checkunfinished(repo)
   105         cmdutil.bailifchanged(repo)
   130         if not opts.get('to'):
       
   131             cmdutil.bailifchanged(repo)
   106         revs = logcmdutil.revrange(repo, revs)
   132         revs = logcmdutil.revrange(repo, revs)
   107 
   133 
   108     for o in (
   134     for o in (
   109         b'date',
   135         b'date',
   110         b'user',
   136         b'user',
   140     # way to the graftstate. With --force, any revisions we would have otherwise
   166     # way to the graftstate. With --force, any revisions we would have otherwise
   141     # skipped would not have been filtered out, and if they hadn't been applied
   167     # skipped would not have been filtered out, and if they hadn't been applied
   142     # already, they'd have been in the graftstate.
   168     # already, they'd have been in the graftstate.
   143     if not (cont or opts.get('force')) and basectx is None:
   169     if not (cont or opts.get('force')) and basectx is None:
   144         # check for ancestors of dest branch
   170         # check for ancestors of dest branch
   145         ancestors = repo.revs(b'%ld & (::.)', revs)
   171         ancestors = repo.revs(b'%ld & (::%d)', revs, toctx.rev())
   146         for rev in ancestors:
   172         for rev in ancestors:
   147             ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
   173             ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
   148 
   174 
   149         revs = [r for r in revs if r not in ancestors]
   175         revs = [r for r in revs if r not in ancestors]
   150 
   176 
   162         # check ancestors for earlier grafts
   188         # check ancestors for earlier grafts
   163         ui.debug(b'scanning for duplicate grafts\n')
   189         ui.debug(b'scanning for duplicate grafts\n')
   164 
   190 
   165         # The only changesets we can be sure doesn't contain grafts of any
   191         # The only changesets we can be sure doesn't contain grafts of any
   166         # revs, are the ones that are common ancestors of *all* revs:
   192         # revs, are the ones that are common ancestors of *all* revs:
   167         for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
   193         for rev in repo.revs(b'only(%d,ancestor(%ld))', toctx.rev(), revs):
   168             ctx = repo[rev]
   194             ctx = repo[rev]
   169             n = ctx.extra().get(b'source')
   195             n = ctx.extra().get(b'source')
   170             if n in ids:
   196             if n in ids:
   171                 try:
   197                 try:
   172                     r = repo[n].rev()
   198                     r = repo[n].rev()
   214             return "ERROR", None, None
   240             return "ERROR", None, None
   215 
   241 
   216     editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
   242     editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
   217     dry_run = bool(opts.get("dry_run"))
   243     dry_run = bool(opts.get("dry_run"))
   218     tool = opts.get('tool', b'')
   244     tool = opts.get('tool', b'')
       
   245     if opts.get("to"):
       
   246         return "GRAFT-TO", graftstate, (statedata, revs, editor, dry_run, tool)
   219     return "GRAFT", graftstate, (statedata, revs, editor, cont, dry_run, tool)
   247     return "GRAFT", graftstate, (statedata, revs, editor, cont, dry_run, tool)
   220 
   248 
   221 
   249 
   222 def _build_progress(ui, repo, ctx):
   250 def _build_progress(ui, repo, ctx):
   223     rev_sum = b'%d:%s "%s"' % (
   251     rev_sum = b'%d:%s "%s"' % (
   243     date = statedata.get(b'date', ctx.date())
   271     date = statedata.get(b'date', ctx.date())
   244     message = ctx.description()
   272     message = ctx.description()
   245     if statedata.get(b'log'):
   273     if statedata.get(b'log'):
   246         message += b'\n(grafted from %s)' % ctx.hex()
   274         message += b'\n(grafted from %s)' % ctx.hex()
   247     return (user, date, message, extra)
   275     return (user, date, message, extra)
       
   276 
       
   277 
       
   278 def _graft_revisions_in_memory(
       
   279     ui,
       
   280     repo,
       
   281     graftstate,
       
   282     statedata,
       
   283     revs,
       
   284     editor,
       
   285     dry_run,
       
   286     tool=b'',
       
   287 ):
       
   288     """graft revisions in memory
       
   289 
       
   290     Abort on unresolved conflicts.
       
   291     """
       
   292     with repo.lock(), repo.transaction(b"graft"):
       
   293         target = repo[statedata[b"to"]]
       
   294         for r in revs:
       
   295             ctx = repo[r]
       
   296             ui.status(_build_progress(ui, repo, ctx))
       
   297             if dry_run:
       
   298                 # we might want to actually perform the grafting to detect
       
   299                 # potential conflict in the dry run.
       
   300                 continue
       
   301             wctx = context.overlayworkingctx(repo)
       
   302             wctx.setbase(target)
       
   303             if b'base' in statedata:
       
   304                 base = repo[statedata[b'base']]
       
   305             else:
       
   306                 base = ctx.p1()
       
   307 
       
   308             (user, date, message, extra) = _build_meta(ui, repo, ctx, statedata)
       
   309 
       
   310             # perform the graft merge with p1(rev) as 'ancestor'
       
   311             try:
       
   312                 overrides = {(b'ui', b'forcemerge'): tool}
       
   313                 with ui.configoverride(overrides, b'graft'):
       
   314                     mergemod.graft(
       
   315                         repo,
       
   316                         ctx,
       
   317                         base,
       
   318                         wctx=wctx,
       
   319                     )
       
   320             except error.InMemoryMergeConflictsError as e:
       
   321                 raise error.Abort(
       
   322                     b'cannot graft in memory: merge conflicts',
       
   323                     hint=_(bytes(e)),
       
   324                 )
       
   325             mctx = wctx.tomemctx(
       
   326                 message,
       
   327                 user=user,
       
   328                 date=date,
       
   329                 extra=extra,
       
   330                 editor=editor,
       
   331             )
       
   332             node = repo.commitctx(mctx)
       
   333             target = repo[node]
       
   334         return 0
   248 
   335 
   249 
   336 
   250 def _graft_revisions(
   337 def _graft_revisions(
   251     ui,
   338     ui,
   252     repo,
   339     repo,