Mercurial > public > mercurial-scm > hg-stable
view mercurial/cmd_impls/graft.py @ 52360: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 | |
children | 5ab77b93567c |
line wrap: on
line source
# graft.py - implementation of the graft command from ..i18n import _ from .. import cmdutil, error, logcmdutil, merge as mergemod, state as statemod def cmd_graft(ui, repo, *revs, **opts): """implement the graft command as defined in mercuria/commands.py""" if revs and opts.get('rev'): ui.warn( _( b'warning: inconsistent use of --rev might give unexpected ' b'revision ordering!\n' ) ) revs = list(revs) revs.extend(opts.get('rev')) # a dict of data to be stored in state file statedata = {} # list of new nodes created by ongoing graft statedata[b'newnodes'] = [] cmdutil.resolve_commit_options(ui, opts) editor = cmdutil.getcommiteditor(editform=b'graft', **opts) cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue') cont = False if opts.get('no_commit'): cmdutil.check_incompatible_arguments( opts, 'no_commit', ['edit', 'currentuser', 'currentdate', 'log'], ) graftstate = statemod.cmdstate(repo, b'graftstate') if opts.get('stop'): cmdutil.check_incompatible_arguments( opts, 'stop', [ 'edit', 'log', 'user', 'date', 'currentdate', 'currentuser', 'rev', ], ) return _stopgraft(ui, repo, graftstate) elif opts.get('abort'): cmdutil.check_incompatible_arguments( opts, 'abort', [ 'edit', 'log', 'user', 'date', 'currentdate', 'currentuser', 'rev', ], ) return cmdutil.abortgraft(ui, repo, graftstate) elif opts.get('continue'): cont = True if revs: raise error.InputError(_(b"can't specify --continue and revisions")) # read in unfinished revisions if graftstate.exists(): statedata = cmdutil.readgraftstate(repo, graftstate) if statedata.get(b'date'): opts['date'] = statedata[b'date'] if statedata.get(b'user'): opts['user'] = statedata[b'user'] if statedata.get(b'log'): opts['log'] = True if statedata.get(b'no_commit'): opts['no_commit'] = statedata.get(b'no_commit') if statedata.get(b'base'): opts['base'] = statedata.get(b'base') nodes = statedata[b'nodes'] revs = [repo[node].rev() for node in nodes] else: cmdutil.wrongtooltocontinue(repo, _(b'graft')) else: if not revs: raise error.InputError(_(b'no revisions specified')) cmdutil.checkunfinished(repo) cmdutil.bailifchanged(repo) revs = logcmdutil.revrange(repo, revs) skipped = set() basectx = None if opts.get('base'): basectx = logcmdutil.revsingle(repo, opts['base'], None) if basectx is None: # check for merges for rev in repo.revs(b'%ld and merge()', revs): ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev) skipped.add(rev) revs = [r for r in revs if r not in skipped] if not revs: return -1 if basectx is not None and len(revs) != 1: raise error.InputError(_(b'only one revision allowed with --base ')) # Don't check in the --continue case, in effect retaining --force across # --continues. That's because without --force, any revisions we decided to # skip would have been filtered out here, so they wouldn't have made their # way to the graftstate. With --force, any revisions we would have otherwise # skipped would not have been filtered out, and if they hadn't been applied # already, they'd have been in the graftstate. if not (cont or opts.get('force')) and basectx is None: # check for ancestors of dest branch ancestors = repo.revs(b'%ld & (::.)', revs) for rev in ancestors: ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev])) revs = [r for r in revs if r not in ancestors] if not revs: return -1 # analyze revs for earlier grafts ids = {} for ctx in repo.set(b"%ld", revs): ids[ctx.hex()] = ctx.rev() n = ctx.extra().get(b'source') if n: ids[n] = ctx.rev() # check ancestors for earlier grafts ui.debug(b'scanning for duplicate grafts\n') # The only changesets we can be sure doesn't contain grafts of any # revs, are the ones that are common ancestors of *all* revs: for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs): ctx = repo[rev] n = ctx.extra().get(b'source') if n in ids: try: r = repo[n].rev() except error.RepoLookupError: r = None if r in revs: ui.warn( _( b'skipping revision %d:%s ' b'(already grafted to %d:%s)\n' ) % (r, repo[r], rev, ctx) ) revs.remove(r) elif ids[n] in revs: if r is None: ui.warn( _( b'skipping already grafted revision %d:%s ' b'(%d:%s also has unknown origin %s)\n' ) % (ids[n], repo[ids[n]], rev, ctx, n[:12]) ) else: ui.warn( _( b'skipping already grafted revision %d:%s ' b'(%d:%s also has origin %d:%s)\n' ) % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]) ) revs.remove(ids[n]) elif ctx.hex() in ids: r = ids[ctx.hex()] if r in revs: ui.warn( _( b'skipping already grafted revision %d:%s ' b'(was grafted from %d:%s)\n' ) % (r, repo[r], rev, ctx) ) revs.remove(r) if not revs: return -1 if opts.get('no_commit'): statedata[b'no_commit'] = True if opts.get('base'): statedata[b'base'] = opts['base'] for pos, ctx in enumerate(repo.set(b"%ld", revs)): desc = b'%d:%s "%s"' % ( ctx.rev(), ctx, ctx.description().split(b'\n', 1)[0], ) names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node()) if names: desc += b' (%s)' % b' '.join(names) ui.status(_(b'grafting %s\n') % desc) if opts.get('dry_run'): continue source = ctx.extra().get(b'source') extra = {} if source: extra[b'source'] = source extra[b'intermediate-source'] = ctx.hex() else: extra[b'source'] = ctx.hex() user = ctx.user() if opts.get('user'): user = opts['user'] statedata[b'user'] = user date = ctx.date() if opts.get('date'): date = opts['date'] statedata[b'date'] = date message = ctx.description() if opts.get('log'): message += b'\n(grafted from %s)' % ctx.hex() statedata[b'log'] = True # we don't merge the first commit when continuing if not cont: # perform the graft merge with p1(rev) as 'ancestor' overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')} base = ctx.p1() if basectx is None else basectx with ui.configoverride(overrides, b'graft'): stats = mergemod.graft( repo, ctx, base, [b'local', b'graft', b'parent of graft'] ) # report any conflicts if stats.unresolvedcount > 0: # write out state for --continue nodes = [repo[rev].hex() for rev in revs[pos:]] statedata[b'nodes'] = nodes stateversion = 1 graftstate.save(stateversion, statedata) ui.error(_(b"abort: unresolved conflicts, can't continue\n")) ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n")) return 1 else: cont = False # commit if --no-commit is false if not opts.get('no_commit'): node = repo.commit( text=message, user=user, date=date, extra=extra, editor=editor ) if node is None: ui.warn( _(b'note: graft of %d:%s created no changes to commit\n') % (ctx.rev(), ctx) ) # checking that newnodes exist because old state files won't have it elif statedata.get(b'newnodes') is not None: nn = statedata[b'newnodes'] assert isinstance(nn, list) # list of bytes nn.append(node) # remove state when we complete successfully if not opts.get('dry_run'): graftstate.delete() return 0 def _stopgraft(ui, repo, graftstate): """stop the interrupted graft""" if not graftstate.exists(): raise error.StateError(_(b"no interrupted graft found")) pctx = repo[b'.'] mergemod.clean_update(pctx) graftstate.delete() ui.status(_(b"stopped the interrupted graft\n")) ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12]) return 0