comparison mercurial/commands.py @ 52328:f2fc0a91faca

commands: create a "mercurial.cmd_impls" module to host graft The "mercurial.commands" have been overweight for a while. We create a namespace dedicated to host smaller modules containing code revelant to a specific command. This should result in more isolated snd manageable module. We start with moving the code for "hg graft" in "mercurial.cmd_impls.graft" before doing more work on it. Since that code was about 5% of "commands.py" this seems like a success.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Tue, 19 Nov 2024 15:46:12 +0100
parents f4733654f144
children 0a81f3ef054c
comparison
equal deleted inserted replaced
52326:d24731850b2e 52328:f2fc0a91faca
66 util, 66 util,
67 verify as verifymod, 67 verify as verifymod,
68 vfs as vfsmod, 68 vfs as vfsmod,
69 wireprotoserver, 69 wireprotoserver,
70 ) 70 )
71
72 from .cmd_impls import graft as graft_impl
73
71 from .utils import ( 74 from .utils import (
72 dateutil, 75 dateutil,
73 procutil, 76 procutil,
74 stringutil, 77 stringutil,
75 urlutil, 78 urlutil,
3183 See :hg:`help revisions` for more about specifying revisions. 3186 See :hg:`help revisions` for more about specifying revisions.
3184 3187
3185 Returns 0 on successful completion, 1 if there are unresolved files. 3188 Returns 0 on successful completion, 1 if there are unresolved files.
3186 """ 3189 """
3187 with repo.wlock(): 3190 with repo.wlock():
3188 return _dograft(ui, repo, *revs, **opts) 3191 return graft_impl.cmd_graft(ui, repo, *revs, **opts)
3189
3190
3191 def _dograft(ui, repo, *revs, **opts):
3192 if revs and opts.get('rev'):
3193 ui.warn(
3194 _(
3195 b'warning: inconsistent use of --rev might give unexpected '
3196 b'revision ordering!\n'
3197 )
3198 )
3199
3200 revs = list(revs)
3201 revs.extend(opts.get('rev'))
3202 # a dict of data to be stored in state file
3203 statedata = {}
3204 # list of new nodes created by ongoing graft
3205 statedata[b'newnodes'] = []
3206
3207 cmdutil.resolve_commit_options(ui, opts)
3208
3209 editor = cmdutil.getcommiteditor(editform=b'graft', **opts)
3210
3211 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue')
3212
3213 cont = False
3214 if opts.get('no_commit'):
3215 cmdutil.check_incompatible_arguments(
3216 opts,
3217 'no_commit',
3218 ['edit', 'currentuser', 'currentdate', 'log'],
3219 )
3220
3221 graftstate = statemod.cmdstate(repo, b'graftstate')
3222
3223 if opts.get('stop'):
3224 cmdutil.check_incompatible_arguments(
3225 opts,
3226 'stop',
3227 [
3228 'edit',
3229 'log',
3230 'user',
3231 'date',
3232 'currentdate',
3233 'currentuser',
3234 'rev',
3235 ],
3236 )
3237 return _stopgraft(ui, repo, graftstate)
3238 elif opts.get('abort'):
3239 cmdutil.check_incompatible_arguments(
3240 opts,
3241 'abort',
3242 [
3243 'edit',
3244 'log',
3245 'user',
3246 'date',
3247 'currentdate',
3248 'currentuser',
3249 'rev',
3250 ],
3251 )
3252 return cmdutil.abortgraft(ui, repo, graftstate)
3253 elif opts.get('continue'):
3254 cont = True
3255 if revs:
3256 raise error.InputError(_(b"can't specify --continue and revisions"))
3257 # read in unfinished revisions
3258 if graftstate.exists():
3259 statedata = cmdutil.readgraftstate(repo, graftstate)
3260 if statedata.get(b'date'):
3261 opts['date'] = statedata[b'date']
3262 if statedata.get(b'user'):
3263 opts['user'] = statedata[b'user']
3264 if statedata.get(b'log'):
3265 opts['log'] = True
3266 if statedata.get(b'no_commit'):
3267 opts['no_commit'] = statedata.get(b'no_commit')
3268 if statedata.get(b'base'):
3269 opts['base'] = statedata.get(b'base')
3270 nodes = statedata[b'nodes']
3271 revs = [repo[node].rev() for node in nodes]
3272 else:
3273 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3274 else:
3275 if not revs:
3276 raise error.InputError(_(b'no revisions specified'))
3277 cmdutil.checkunfinished(repo)
3278 cmdutil.bailifchanged(repo)
3279 revs = logcmdutil.revrange(repo, revs)
3280
3281 skipped = set()
3282 basectx = None
3283 if opts.get('base'):
3284 basectx = logcmdutil.revsingle(repo, opts['base'], None)
3285 if basectx is None:
3286 # check for merges
3287 for rev in repo.revs(b'%ld and merge()', revs):
3288 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3289 skipped.add(rev)
3290 revs = [r for r in revs if r not in skipped]
3291 if not revs:
3292 return -1
3293 if basectx is not None and len(revs) != 1:
3294 raise error.InputError(_(b'only one revision allowed with --base '))
3295
3296 # Don't check in the --continue case, in effect retaining --force across
3297 # --continues. That's because without --force, any revisions we decided to
3298 # skip would have been filtered out here, so they wouldn't have made their
3299 # way to the graftstate. With --force, any revisions we would have otherwise
3300 # skipped would not have been filtered out, and if they hadn't been applied
3301 # already, they'd have been in the graftstate.
3302 if not (cont or opts.get('force')) and basectx is None:
3303 # check for ancestors of dest branch
3304 ancestors = repo.revs(b'%ld & (::.)', revs)
3305 for rev in ancestors:
3306 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3307
3308 revs = [r for r in revs if r not in ancestors]
3309
3310 if not revs:
3311 return -1
3312
3313 # analyze revs for earlier grafts
3314 ids = {}
3315 for ctx in repo.set(b"%ld", revs):
3316 ids[ctx.hex()] = ctx.rev()
3317 n = ctx.extra().get(b'source')
3318 if n:
3319 ids[n] = ctx.rev()
3320
3321 # check ancestors for earlier grafts
3322 ui.debug(b'scanning for duplicate grafts\n')
3323
3324 # The only changesets we can be sure doesn't contain grafts of any
3325 # revs, are the ones that are common ancestors of *all* revs:
3326 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3327 ctx = repo[rev]
3328 n = ctx.extra().get(b'source')
3329 if n in ids:
3330 try:
3331 r = repo[n].rev()
3332 except error.RepoLookupError:
3333 r = None
3334 if r in revs:
3335 ui.warn(
3336 _(
3337 b'skipping revision %d:%s '
3338 b'(already grafted to %d:%s)\n'
3339 )
3340 % (r, repo[r], rev, ctx)
3341 )
3342 revs.remove(r)
3343 elif ids[n] in revs:
3344 if r is None:
3345 ui.warn(
3346 _(
3347 b'skipping already grafted revision %d:%s '
3348 b'(%d:%s also has unknown origin %s)\n'
3349 )
3350 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3351 )
3352 else:
3353 ui.warn(
3354 _(
3355 b'skipping already grafted revision %d:%s '
3356 b'(%d:%s also has origin %d:%s)\n'
3357 )
3358 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3359 )
3360 revs.remove(ids[n])
3361 elif ctx.hex() in ids:
3362 r = ids[ctx.hex()]
3363 if r in revs:
3364 ui.warn(
3365 _(
3366 b'skipping already grafted revision %d:%s '
3367 b'(was grafted from %d:%s)\n'
3368 )
3369 % (r, repo[r], rev, ctx)
3370 )
3371 revs.remove(r)
3372 if not revs:
3373 return -1
3374
3375 if opts.get('no_commit'):
3376 statedata[b'no_commit'] = True
3377 if opts.get('base'):
3378 statedata[b'base'] = opts['base']
3379 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3380 desc = b'%d:%s "%s"' % (
3381 ctx.rev(),
3382 ctx,
3383 ctx.description().split(b'\n', 1)[0],
3384 )
3385 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3386 if names:
3387 desc += b' (%s)' % b' '.join(names)
3388 ui.status(_(b'grafting %s\n') % desc)
3389 if opts.get('dry_run'):
3390 continue
3391
3392 source = ctx.extra().get(b'source')
3393 extra = {}
3394 if source:
3395 extra[b'source'] = source
3396 extra[b'intermediate-source'] = ctx.hex()
3397 else:
3398 extra[b'source'] = ctx.hex()
3399 user = ctx.user()
3400 if opts.get('user'):
3401 user = opts['user']
3402 statedata[b'user'] = user
3403 date = ctx.date()
3404 if opts.get('date'):
3405 date = opts['date']
3406 statedata[b'date'] = date
3407 message = ctx.description()
3408 if opts.get('log'):
3409 message += b'\n(grafted from %s)' % ctx.hex()
3410 statedata[b'log'] = True
3411
3412 # we don't merge the first commit when continuing
3413 if not cont:
3414 # perform the graft merge with p1(rev) as 'ancestor'
3415 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
3416 base = ctx.p1() if basectx is None else basectx
3417 with ui.configoverride(overrides, b'graft'):
3418 stats = mergemod.graft(
3419 repo, ctx, base, [b'local', b'graft', b'parent of graft']
3420 )
3421 # report any conflicts
3422 if stats.unresolvedcount > 0:
3423 # write out state for --continue
3424 nodes = [repo[rev].hex() for rev in revs[pos:]]
3425 statedata[b'nodes'] = nodes
3426 stateversion = 1
3427 graftstate.save(stateversion, statedata)
3428 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3429 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3430 return 1
3431 else:
3432 cont = False
3433
3434 # commit if --no-commit is false
3435 if not opts.get('no_commit'):
3436 node = repo.commit(
3437 text=message, user=user, date=date, extra=extra, editor=editor
3438 )
3439 if node is None:
3440 ui.warn(
3441 _(b'note: graft of %d:%s created no changes to commit\n')
3442 % (ctx.rev(), ctx)
3443 )
3444 # checking that newnodes exist because old state files won't have it
3445 elif statedata.get(b'newnodes') is not None:
3446 nn = statedata[b'newnodes']
3447 assert isinstance(nn, list) # list of bytes
3448 nn.append(node)
3449
3450 # remove state when we complete successfully
3451 if not opts.get('dry_run'):
3452 graftstate.delete()
3453
3454 return 0
3455
3456
3457 def _stopgraft(ui, repo, graftstate):
3458 """stop the interrupted graft"""
3459 if not graftstate.exists():
3460 raise error.StateError(_(b"no interrupted graft found"))
3461 pctx = repo[b'.']
3462 mergemod.clean_update(pctx)
3463 graftstate.delete()
3464 ui.status(_(b"stopped the interrupted graft\n"))
3465 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3466 return 0
3467 3192
3468 3193
3469 statemod.addunfinished( 3194 statemod.addunfinished(
3470 b'graft', 3195 b'graft',
3471 fname=b'graftstate', 3196 fname=b'graftstate',