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