comparison mercurial/cmd_impls/graft.py @ 52505:68dc6cecca32

graft: add a `--to` flag grafting in memory See inline documentation for details.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Mon, 02 Dec 2024 02:45:41 +0100
parents de16800904f9
children
comparison
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,