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, |