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