Mercurial > public > mercurial-scm > hg
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, |