##// END OF EJS Templates
graft: extract creation of progress report line in a function...
marmoute -
r53400:cef86c1d default
parent child Browse files
Show More
@@ -1,317 +1,321
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 def _build_progress(ui, repo, ctx):
223 rev_sum = b'%d:%s "%s"' % (
224 ctx.rev(),
225 ctx,
226 ctx.description().split(b'\n', 1)[0],
227 )
228 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
229 if names:
230 rev_sum += b' (%s)' % b' '.join(names)
231 return _(b'grafting %s\n') % rev_sum
232
233
222 234 def _graft_revisions(
223 235 ui,
224 236 repo,
225 237 graftstate,
226 238 statedata,
227 239 revs,
228 240 editor,
229 241 cont=False,
230 242 dry_run=False,
231 243 tool=b'',
232 244 ):
233 245 """actually graft some revisions"""
234 246 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
235 desc = b'%d:%s "%s"' % (
236 ctx.rev(),
237 ctx,
238 ctx.description().split(b'\n', 1)[0],
239 )
240 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
241 if names:
242 desc += b' (%s)' % b' '.join(names)
243 ui.status(_(b'grafting %s\n') % desc)
247 ui.status(_build_progress(ui, repo, ctx))
244 248 if dry_run:
245 249 continue
246 250
247 251 source = ctx.extra().get(b'source')
248 252 extra = {}
249 253 if source:
250 254 extra[b'source'] = source
251 255 extra[b'intermediate-source'] = ctx.hex()
252 256 else:
253 257 extra[b'source'] = ctx.hex()
254 258 user = statedata.get(b'user', ctx.user())
255 259 date = statedata.get(b'date', ctx.date())
256 260 message = ctx.description()
257 261 if statedata.get(b'log'):
258 262 message += b'\n(grafted from %s)' % ctx.hex()
259 263
260 264 # we don't merge the first commit when continuing
261 265 if not cont:
262 266 # perform the graft merge with p1(rev) as 'ancestor'
263 267 overrides = {(b'ui', b'forcemerge'): tool}
264 268 if b'base' in statedata:
265 269 base = repo[statedata[b'base']]
266 270 else:
267 271 base = ctx.p1()
268 272 with ui.configoverride(overrides, b'graft'):
269 273 stats = mergemod.graft(
270 274 repo, ctx, base, [b'local', b'graft', b'parent of graft']
271 275 )
272 276 # report any conflicts
273 277 if stats.unresolvedcount > 0:
274 278 # write out state for --continue
275 279 nodes = [repo[rev].hex() for rev in revs[pos:]]
276 280 statedata[b'nodes'] = nodes
277 281 stateversion = 1
278 282 graftstate.save(stateversion, statedata)
279 283 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
280 284 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
281 285 return 1
282 286 else:
283 287 cont = False
284 288
285 289 # commit if --no-commit is false
286 290 if not statedata.get(b'no_commit'):
287 291 node = repo.commit(
288 292 text=message, user=user, date=date, extra=extra, editor=editor
289 293 )
290 294 if node is None:
291 295 ui.warn(
292 296 _(b'note: graft of %d:%s created no changes to commit\n')
293 297 % (ctx.rev(), ctx)
294 298 )
295 299 # checking that newnodes exist because old state files won't have it
296 300 elif statedata.get(b'newnodes') is not None:
297 301 nn = statedata[b'newnodes']
298 302 assert isinstance(nn, list) # list of bytes
299 303 nn.append(node)
300 304
301 305 # remove state when we complete successfully
302 306 if not dry_run:
303 307 graftstate.delete()
304 308
305 309 return 0
306 310
307 311
308 312 def _stopgraft(ui, repo, graftstate):
309 313 """stop the interrupted graft"""
310 314 if not graftstate.exists():
311 315 raise error.StateError(_(b"no interrupted graft found"))
312 316 pctx = repo[b'.']
313 317 mergemod.clean_update(pctx)
314 318 graftstate.delete()
315 319 ui.status(_(b"stopped the interrupted graft\n"))
316 320 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
317 321 return 0
General Comments 0
You need to be logged in to leave comments. Login now