Mercurial > public > mercurial-scm > hg
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', |