##// END OF EJS Templates
typing: add minimal annotations to cmd_impls/graft.py to pytype with 3.10...
Matt Harbison -
r53248:9042ffea default
parent child Browse files
Show More
@@ -1,303 +1,317
1 # graft.py - implementation of the graft command
1 # graft.py - implementation of the graft command
2
2
3 from __future__ import annotations
3 from __future__ import annotations
4
4
5 import typing
6
7 from typing import (
8 Any,
9 Tuple,
10 )
11
5 from ..i18n import _
12 from ..i18n import _
6
13
7 from .. import cmdutil, error, logcmdutil, merge as mergemod, state as statemod
14 from .. import cmdutil, error, logcmdutil, merge as mergemod, state as statemod
8
15
9
16
10 def cmd_graft(ui, repo, *revs, **opts):
17 if typing.TYPE_CHECKING:
18 _ActionT = str
19 _CmdArgsT = Any # TODO: (statedata, revs, editor, cont, dry_run, tool)
20
21
22 def cmd_graft(ui, repo, *revs, **opts) -> int:
11 """implement the graft command as defined in mercurial/commands.py"""
23 """implement the graft command as defined in mercurial/commands.py"""
12 ret = _process_args(ui, repo, *revs, **opts)
24 ret = _process_args(ui, repo, *revs, **opts)
13 action, graftstate, args = ret
25 action, graftstate, args = ret
14 if action == "ERROR":
26 if action == "ERROR":
15 return -1
27 return -1
16 elif action == "ABORT":
28 elif action == "ABORT":
17 assert args is None
29 assert args is None
18 return cmdutil.abortgraft(ui, repo, graftstate)
30 return cmdutil.abortgraft(ui, repo, graftstate)
19 elif action == "STOP":
31 elif action == "STOP":
20 assert args is None
32 assert args is None
21 return _stopgraft(ui, repo, graftstate)
33 return _stopgraft(ui, repo, graftstate)
22 elif action == "GRAFT":
34 elif action == "GRAFT":
23 return _graft_revisions(ui, repo, graftstate, *args)
35 return _graft_revisions(ui, repo, graftstate, *args)
24 else:
36 else:
25 raise error.ProgrammingError('unknown action: %s' % action)
37 raise error.ProgrammingError('unknown action: %s' % action)
26
38
27
39
28 def _process_args(ui, repo, *revs, **opts):
40 def _process_args(
41 ui, repo, *revs, **opts
42 ) -> Tuple[_ActionT, statemod.cmdstate | None, _CmdArgsT | None]:
29 """process the graft command argument to figure out what to do
43 """process the graft command argument to figure out what to do
30
44
31 This also filter the selected revision to skip the one that cannot be graft
45 This also filter the selected revision to skip the one that cannot be graft
32 or were already grafted.
46 or were already grafted.
33 """
47 """
34 if revs and opts.get('rev'):
48 if revs and opts.get('rev'):
35 ui.warn(
49 ui.warn(
36 _(
50 _(
37 b'warning: inconsistent use of --rev might give unexpected '
51 b'warning: inconsistent use of --rev might give unexpected '
38 b'revision ordering!\n'
52 b'revision ordering!\n'
39 )
53 )
40 )
54 )
41
55
42 revs = list(revs)
56 revs = list(revs)
43 revs.extend(opts.get('rev'))
57 revs.extend(opts.get('rev'))
44 # a dict of data to be stored in state file
58 # a dict of data to be stored in state file
45 statedata = {}
59 statedata = {}
46 # list of new nodes created by ongoing graft
60 # list of new nodes created by ongoing graft
47 statedata[b'newnodes'] = []
61 statedata[b'newnodes'] = []
48
62
49 # argument incompatible with followup from an interrupted operation
63 # argument incompatible with followup from an interrupted operation
50 commit_args = ['edit', 'log', 'user', 'date', 'currentdate', 'currentuser']
64 commit_args = ['edit', 'log', 'user', 'date', 'currentdate', 'currentuser']
51 nofollow_args = commit_args + ['base', 'rev']
65 nofollow_args = commit_args + ['base', 'rev']
52
66
53 arg_compatibilities = [
67 arg_compatibilities = [
54 ('no_commit', commit_args),
68 ('no_commit', commit_args),
55 ('stop', nofollow_args),
69 ('stop', nofollow_args),
56 ('abort', nofollow_args),
70 ('abort', nofollow_args),
57 ]
71 ]
58 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
72 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
59 for arg, incompatible in arg_compatibilities:
73 for arg, incompatible in arg_compatibilities:
60 cmdutil.check_incompatible_arguments(opts, arg, incompatible)
74 cmdutil.check_incompatible_arguments(opts, arg, incompatible)
61
75
62 cmdutil.resolve_commit_options(ui, opts)
76 cmdutil.resolve_commit_options(ui, opts)
63
77
64 cont = False
78 cont = False
65
79
66 graftstate = statemod.cmdstate(repo, b'graftstate')
80 graftstate = statemod.cmdstate(repo, b'graftstate')
67
81
68 if opts.get('stop'):
82 if opts.get('stop'):
69 return "STOP", graftstate, None
83 return "STOP", graftstate, None
70 elif opts.get('abort'):
84 elif opts.get('abort'):
71 return "ABORT", graftstate, None
85 return "ABORT", graftstate, None
72 elif opts.get('continue'):
86 elif opts.get('continue'):
73 cont = True
87 cont = True
74 if revs:
88 if revs:
75 raise error.InputError(_(b"can't specify --continue and revisions"))
89 raise error.InputError(_(b"can't specify --continue and revisions"))
76 # read in unfinished revisions
90 # read in unfinished revisions
77 if graftstate.exists():
91 if graftstate.exists():
78 statedata = cmdutil.readgraftstate(repo, graftstate)
92 statedata = cmdutil.readgraftstate(repo, graftstate)
79 if statedata.get(b'no_commit'):
93 if statedata.get(b'no_commit'):
80 opts['no_commit'] = statedata.get(b'no_commit')
94 opts['no_commit'] = statedata.get(b'no_commit')
81 if statedata.get(b'base'):
95 if statedata.get(b'base'):
82 opts['base'] = statedata.get(b'base')
96 opts['base'] = statedata.get(b'base')
83 nodes = statedata[b'nodes']
97 nodes = statedata[b'nodes']
84 revs = [repo[node].rev() for node in nodes]
98 revs = [repo[node].rev() for node in nodes]
85 else:
99 else:
86 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
100 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
87 elif not revs:
101 elif not revs:
88 raise error.InputError(_(b'no revisions specified'))
102 raise error.InputError(_(b'no revisions specified'))
89 else:
103 else:
90 cmdutil.checkunfinished(repo)
104 cmdutil.checkunfinished(repo)
91 cmdutil.bailifchanged(repo)
105 cmdutil.bailifchanged(repo)
92 revs = logcmdutil.revrange(repo, revs)
106 revs = logcmdutil.revrange(repo, revs)
93
107
94 for o in (
108 for o in (
95 b'date',
109 b'date',
96 b'user',
110 b'user',
97 b'log',
111 b'log',
98 b'no_commit',
112 b'no_commit',
99 b'dry_run',
113 b'dry_run',
100 ):
114 ):
101 v = opts.get(o.decode('ascii'))
115 v = opts.get(o.decode('ascii'))
102 # if statedata is already set, it comes from --continue and test says
116 # if statedata is already set, it comes from --continue and test says
103 # we should honor them above the options (which seems weird).
117 # we should honor them above the options (which seems weird).
104 if v and o not in statedata:
118 if v and o not in statedata:
105 statedata[o] = v
119 statedata[o] = v
106
120
107 skipped = set()
121 skipped = set()
108 basectx = None
122 basectx = None
109 if opts.get('base'):
123 if opts.get('base'):
110 basectx = logcmdutil.revsingle(repo, opts['base'], None)
124 basectx = logcmdutil.revsingle(repo, opts['base'], None)
111 statedata[b'base'] = basectx.hex()
125 statedata[b'base'] = basectx.hex()
112 if basectx is None:
126 if basectx is None:
113 # check for merges
127 # check for merges
114 for rev in repo.revs(b'%ld and merge()', revs):
128 for rev in repo.revs(b'%ld and merge()', revs):
115 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
129 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
116 skipped.add(rev)
130 skipped.add(rev)
117 revs = [r for r in revs if r not in skipped]
131 revs = [r for r in revs if r not in skipped]
118 if not revs:
132 if not revs:
119 return "ERROR", None, None
133 return "ERROR", None, None
120 if basectx is not None and len(revs) != 1:
134 if basectx is not None and len(revs) != 1:
121 raise error.InputError(_(b'only one revision allowed with --base'))
135 raise error.InputError(_(b'only one revision allowed with --base'))
122
136
123 # Don't check in the --continue case, in effect retaining --force across
137 # Don't check in the --continue case, in effect retaining --force across
124 # --continues. That's because without --force, any revisions we decided to
138 # --continues. That's because without --force, any revisions we decided to
125 # skip would have been filtered out here, so they wouldn't have made their
139 # skip would have been filtered out here, so they wouldn't have made their
126 # way to the graftstate. With --force, any revisions we would have otherwise
140 # way to the graftstate. With --force, any revisions we would have otherwise
127 # skipped would not have been filtered out, and if they hadn't been applied
141 # skipped would not have been filtered out, and if they hadn't been applied
128 # already, they'd have been in the graftstate.
142 # already, they'd have been in the graftstate.
129 if not (cont or opts.get('force')) and basectx is None:
143 if not (cont or opts.get('force')) and basectx is None:
130 # check for ancestors of dest branch
144 # check for ancestors of dest branch
131 ancestors = repo.revs(b'%ld & (::.)', revs)
145 ancestors = repo.revs(b'%ld & (::.)', revs)
132 for rev in ancestors:
146 for rev in ancestors:
133 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
147 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
134
148
135 revs = [r for r in revs if r not in ancestors]
149 revs = [r for r in revs if r not in ancestors]
136
150
137 if not revs:
151 if not revs:
138 return "ERROR", None, None
152 return "ERROR", None, None
139
153
140 # analyze revs for earlier grafts
154 # analyze revs for earlier grafts
141 ids = {}
155 ids = {}
142 for ctx in repo.set(b"%ld", revs):
156 for ctx in repo.set(b"%ld", revs):
143 ids[ctx.hex()] = ctx.rev()
157 ids[ctx.hex()] = ctx.rev()
144 n = ctx.extra().get(b'source')
158 n = ctx.extra().get(b'source')
145 if n:
159 if n:
146 ids[n] = ctx.rev()
160 ids[n] = ctx.rev()
147
161
148 # check ancestors for earlier grafts
162 # check ancestors for earlier grafts
149 ui.debug(b'scanning for duplicate grafts\n')
163 ui.debug(b'scanning for duplicate grafts\n')
150
164
151 # The only changesets we can be sure doesn't contain grafts of any
165 # The only changesets we can be sure doesn't contain grafts of any
152 # revs, are the ones that are common ancestors of *all* revs:
166 # revs, are the ones that are common ancestors of *all* revs:
153 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
167 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
154 ctx = repo[rev]
168 ctx = repo[rev]
155 n = ctx.extra().get(b'source')
169 n = ctx.extra().get(b'source')
156 if n in ids:
170 if n in ids:
157 try:
171 try:
158 r = repo[n].rev()
172 r = repo[n].rev()
159 except error.RepoLookupError:
173 except error.RepoLookupError:
160 r = None
174 r = None
161 if r in revs:
175 if r in revs:
162 ui.warn(
176 ui.warn(
163 _(
177 _(
164 b'skipping revision %d:%s '
178 b'skipping revision %d:%s '
165 b'(already grafted to %d:%s)\n'
179 b'(already grafted to %d:%s)\n'
166 )
180 )
167 % (r, repo[r], rev, ctx)
181 % (r, repo[r], rev, ctx)
168 )
182 )
169 revs.remove(r)
183 revs.remove(r)
170 elif ids[n] in revs:
184 elif ids[n] in revs:
171 if r is None:
185 if r is None:
172 ui.warn(
186 ui.warn(
173 _(
187 _(
174 b'skipping already grafted revision %d:%s '
188 b'skipping already grafted revision %d:%s '
175 b'(%d:%s also has unknown origin %s)\n'
189 b'(%d:%s also has unknown origin %s)\n'
176 )
190 )
177 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
191 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
178 )
192 )
179 else:
193 else:
180 ui.warn(
194 ui.warn(
181 _(
195 _(
182 b'skipping already grafted revision %d:%s '
196 b'skipping already grafted revision %d:%s '
183 b'(%d:%s also has origin %d:%s)\n'
197 b'(%d:%s also has origin %d:%s)\n'
184 )
198 )
185 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
199 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
186 )
200 )
187 revs.remove(ids[n])
201 revs.remove(ids[n])
188 elif ctx.hex() in ids:
202 elif ctx.hex() in ids:
189 r = ids[ctx.hex()]
203 r = ids[ctx.hex()]
190 if r in revs:
204 if r in revs:
191 ui.warn(
205 ui.warn(
192 _(
206 _(
193 b'skipping already grafted revision %d:%s '
207 b'skipping already grafted revision %d:%s '
194 b'(was grafted from %d:%s)\n'
208 b'(was grafted from %d:%s)\n'
195 )
209 )
196 % (r, repo[r], rev, ctx)
210 % (r, repo[r], rev, ctx)
197 )
211 )
198 revs.remove(r)
212 revs.remove(r)
199 if not revs:
213 if not revs:
200 return "ERROR", None, None
214 return "ERROR", None, None
201
215
202 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
216 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
203 dry_run = bool(opts.get("dry_run"))
217 dry_run = bool(opts.get("dry_run"))
204 tool = opts.get('tool', b'')
218 tool = opts.get('tool', b'')
205 return "GRAFT", graftstate, (statedata, revs, editor, cont, dry_run, tool)
219 return "GRAFT", graftstate, (statedata, revs, editor, cont, dry_run, tool)
206
220
207
221
208 def _graft_revisions(
222 def _graft_revisions(
209 ui,
223 ui,
210 repo,
224 repo,
211 graftstate,
225 graftstate,
212 statedata,
226 statedata,
213 revs,
227 revs,
214 editor,
228 editor,
215 cont=False,
229 cont=False,
216 dry_run=False,
230 dry_run=False,
217 tool=b'',
231 tool=b'',
218 ):
232 ):
219 """actually graft some revisions"""
233 """actually graft some revisions"""
220 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
234 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
221 desc = b'%d:%s "%s"' % (
235 desc = b'%d:%s "%s"' % (
222 ctx.rev(),
236 ctx.rev(),
223 ctx,
237 ctx,
224 ctx.description().split(b'\n', 1)[0],
238 ctx.description().split(b'\n', 1)[0],
225 )
239 )
226 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
240 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
227 if names:
241 if names:
228 desc += b' (%s)' % b' '.join(names)
242 desc += b' (%s)' % b' '.join(names)
229 ui.status(_(b'grafting %s\n') % desc)
243 ui.status(_(b'grafting %s\n') % desc)
230 if dry_run:
244 if dry_run:
231 continue
245 continue
232
246
233 source = ctx.extra().get(b'source')
247 source = ctx.extra().get(b'source')
234 extra = {}
248 extra = {}
235 if source:
249 if source:
236 extra[b'source'] = source
250 extra[b'source'] = source
237 extra[b'intermediate-source'] = ctx.hex()
251 extra[b'intermediate-source'] = ctx.hex()
238 else:
252 else:
239 extra[b'source'] = ctx.hex()
253 extra[b'source'] = ctx.hex()
240 user = statedata.get(b'user', ctx.user())
254 user = statedata.get(b'user', ctx.user())
241 date = statedata.get(b'date', ctx.date())
255 date = statedata.get(b'date', ctx.date())
242 message = ctx.description()
256 message = ctx.description()
243 if statedata.get(b'log'):
257 if statedata.get(b'log'):
244 message += b'\n(grafted from %s)' % ctx.hex()
258 message += b'\n(grafted from %s)' % ctx.hex()
245
259
246 # we don't merge the first commit when continuing
260 # we don't merge the first commit when continuing
247 if not cont:
261 if not cont:
248 # perform the graft merge with p1(rev) as 'ancestor'
262 # perform the graft merge with p1(rev) as 'ancestor'
249 overrides = {(b'ui', b'forcemerge'): tool}
263 overrides = {(b'ui', b'forcemerge'): tool}
250 if b'base' in statedata:
264 if b'base' in statedata:
251 base = repo[statedata[b'base']]
265 base = repo[statedata[b'base']]
252 else:
266 else:
253 base = ctx.p1()
267 base = ctx.p1()
254 with ui.configoverride(overrides, b'graft'):
268 with ui.configoverride(overrides, b'graft'):
255 stats = mergemod.graft(
269 stats = mergemod.graft(
256 repo, ctx, base, [b'local', b'graft', b'parent of graft']
270 repo, ctx, base, [b'local', b'graft', b'parent of graft']
257 )
271 )
258 # report any conflicts
272 # report any conflicts
259 if stats.unresolvedcount > 0:
273 if stats.unresolvedcount > 0:
260 # write out state for --continue
274 # write out state for --continue
261 nodes = [repo[rev].hex() for rev in revs[pos:]]
275 nodes = [repo[rev].hex() for rev in revs[pos:]]
262 statedata[b'nodes'] = nodes
276 statedata[b'nodes'] = nodes
263 stateversion = 1
277 stateversion = 1
264 graftstate.save(stateversion, statedata)
278 graftstate.save(stateversion, statedata)
265 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
279 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
266 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
280 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
267 return 1
281 return 1
268 else:
282 else:
269 cont = False
283 cont = False
270
284
271 # commit if --no-commit is false
285 # commit if --no-commit is false
272 if not statedata.get(b'no_commit'):
286 if not statedata.get(b'no_commit'):
273 node = repo.commit(
287 node = repo.commit(
274 text=message, user=user, date=date, extra=extra, editor=editor
288 text=message, user=user, date=date, extra=extra, editor=editor
275 )
289 )
276 if node is None:
290 if node is None:
277 ui.warn(
291 ui.warn(
278 _(b'note: graft of %d:%s created no changes to commit\n')
292 _(b'note: graft of %d:%s created no changes to commit\n')
279 % (ctx.rev(), ctx)
293 % (ctx.rev(), ctx)
280 )
294 )
281 # checking that newnodes exist because old state files won't have it
295 # checking that newnodes exist because old state files won't have it
282 elif statedata.get(b'newnodes') is not None:
296 elif statedata.get(b'newnodes') is not None:
283 nn = statedata[b'newnodes']
297 nn = statedata[b'newnodes']
284 assert isinstance(nn, list) # list of bytes
298 assert isinstance(nn, list) # list of bytes
285 nn.append(node)
299 nn.append(node)
286
300
287 # remove state when we complete successfully
301 # remove state when we complete successfully
288 if not dry_run:
302 if not dry_run:
289 graftstate.delete()
303 graftstate.delete()
290
304
291 return 0
305 return 0
292
306
293
307
294 def _stopgraft(ui, repo, graftstate):
308 def _stopgraft(ui, repo, graftstate):
295 """stop the interrupted graft"""
309 """stop the interrupted graft"""
296 if not graftstate.exists():
310 if not graftstate.exists():
297 raise error.StateError(_(b"no interrupted graft found"))
311 raise error.StateError(_(b"no interrupted graft found"))
298 pctx = repo[b'.']
312 pctx = repo[b'.']
299 mergemod.clean_update(pctx)
313 mergemod.clean_update(pctx)
300 graftstate.delete()
314 graftstate.delete()
301 ui.status(_(b"stopped the interrupted graft\n"))
315 ui.status(_(b"stopped the interrupted graft\n"))
302 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
316 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
303 return 0
317 return 0
General Comments 0
You need to be logged in to leave comments. Login now