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