# HG changeset patch # User Anton Shestakov # Date 1615434574 -28800 # Node ID 5341d7c30e6841bfb5d4f19c84a0f23430e53b46 # Parent 87006dcf2bb7a9214872525093d372e3873d4364# Parent 82040a455e71caf61be1e2468322b4ca74df16cb branching: merge into stable in preparation for release # no-check-commit diff -r 87006dcf2bb7 -r 5341d7c30e68 CHANGELOG --- a/CHANGELOG Wed Feb 24 14:30:21 2021 -0800 +++ b/CHANGELOG Thu Mar 11 11:49:34 2021 +0800 @@ -1,10 +1,25 @@ Changelog ========= +10.3.0 - in progress +-------------------- + + * evolve: add a experimental.evolution.in-memory config for running evolve + in memory (hg >= 5.6) + * evolve: improve content-divergence resolution that involves parent changes + * evolve: preserve wdir parent when using `hg evolve --stop` + * obslog: clarify the command name in the help, + * pdiff, pstatus: drop some irrelevant command flags inherited from `hg diff` + and `hg status` respectively + * rewind: detect and abort on cases when we rewind to changesets that are + precessors / successors of each other + * rewind: when user gives only some parts of a fold, include the other parts + as well, or abort if they are missing from local repo + 10.2.1 - in progress -------------------- - * doc: document stack as a substitue for MQ's qseries + * doc: document stack as a substitue for MQ's qseries 10.2.0.post1 -- 2021-02-01 -------------------------- diff -r 87006dcf2bb7 -r 5341d7c30e68 docs/troubles-handling.rst --- a/docs/troubles-handling.rst Wed Feb 24 14:30:21 2021 -0800 +++ b/docs/troubles-handling.rst Thu Mar 11 11:49:34 2021 +0800 @@ -323,6 +323,21 @@ Set the parent of moved changeset as resolution parent. +Special-case: When parent of moved one is obsolete with a successor +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +By default, evolution will set the successor of obsolete parent as resolution +parent and will relocate both the divergent cset on it to perform 3-way merge. +But if the following config is set to True, it will set the obsolete parent as +resolution parent, so now resolved cset will be orphan, as it will be based on +the obsolete parent. Some users might not like the evolve to automatically +resovle this orphan instability as well (while they only wanted to resolve the +divergence), which is why we are providing this config. + +`experimental.evolution.divergence-resolution-minimal=False(default)` + +(The default resolution that automatically evolve the orphan instability as well +seems the best approach for now, but let's also gather user feedback, then we can decide accordingly) + D-A3.2: both moved forward; same branch """"""""""""""""""""""""""""""""""""""" diff -r 87006dcf2bb7 -r 5341d7c30e68 hgext3rd/evolve/__init__.py --- a/hgext3rd/evolve/__init__.py Wed Feb 24 14:30:21 2021 -0800 +++ b/hgext3rd/evolve/__init__.py Thu Mar 11 11:49:34 2021 +0800 @@ -52,7 +52,7 @@ evolution.obsdiscovery = yes Obsolescence Markers Discovery -============================== +------------------------------ The evolve extension containts an experimental new protocol to discover common markers between local and remote repositories. @@ -111,7 +111,7 @@ evolution.obsdiscovery = no Effect Flag Experiment -====================== +---------------------- Evolve also records what changed between two evolutions of a changeset. For example, having this information is helpful to understand what changed between @@ -153,8 +153,21 @@ you create will not cause interference with other clients or servers without the effect flag recording. +In-memory Evolve Experiment +--------------------------- + +The :hg:`evolve` command normally creates new changesets by writing the +files to the working copy and then committing them from there. You can +tell it to create the changesets without touching the working copy by +setting this config:: + + [experimental] + evolution.in-memory = yes + +It will still update the working copy in case of conflicts. + Template keywords -================= +----------------- Evolve provides one template keyword that helps explore obsolescence history: @@ -175,6 +188,28 @@ - precursors (deprecated, use predecessors instead) - successors (deprecated, use successorssets instead) - troubles (deprecated, use instabilities instead) + +Revset predicates +----------------- + +Evolve provides several revset predicates: + + - unstable + - troubled (deprecated, use unstable instead) + - suspended + - predecessors + - precursors (deprecated, use predecessors instead) + - allpredecessors + - allprecursors (deprecated, use allpredecessors instead) + - successors + - allsuccessors + +Note that successors revset in evolve is not the same as successors revset in +core Mercurial 4.3+. In evolve this revset returns only immediate successors, +as opposed to all successors. Use "allsuccessors(set)" to obtain all +successors. + +See :hg:`help revsets -v` for more information. """ evolutionhelptext = b""" @@ -241,22 +276,11 @@ [experimental] evolution=all -""".strip() +""" import sys -try: - from mercurial import registrar - registrar.templatekeyword # new in hg-3.8 -except ImportError: - from . import metadata - raise ImportError(b'evolve needs Mercurial version %s or above' % - min(metadata.testedwith.split())) - import mercurial -from mercurial import util - -from mercurial import obsolete from mercurial import ( bookmarks as bookmarksmod, @@ -267,8 +291,9 @@ hg, lock as lockmod, node as nodemod, + obsolete, pycompat, - revset, + util, ) from mercurial.i18n import _ @@ -287,6 +312,7 @@ obsexchange, obshashtree, obshistory, + revset, rewind, safeguard, templatekw, @@ -337,17 +363,20 @@ eh.merge(cmdrewrite.eh) eh.merge(rewind.eh) eh.merge(headchecking.eh) +eh.merge(revset.eh) uisetup = eh.finaluisetup extsetup = eh.finalextsetup reposetup = eh.finalreposetup cmdtable = eh.cmdtable configtable = eh.configtable +templatekeyword = eh.templatekeyword revsetpredicate = eh.revsetpredicate -templatekeyword = eh.templatekeyword # Configuration eh.configitem(b'experimental', b'evolutioncommands', []) eh.configitem(b'experimental', b'evolution.allnewcommands', None) +eh.configitem(b'experimental', b'evolution.divergence-resolution-minimal', False) +eh.configitem(b'experimental', b'evolution.in-memory', b'false') ##################################################################### ### Option configuration ### @@ -402,12 +431,6 @@ del cmdtable[disabledcmd] ##################################################################### -### experimental behavior ### -##################################################################### - -getrevs = obsolete.getrevs - -##################################################################### ### Additional Utilities ### ##################################################################### @@ -426,7 +449,8 @@ def setupparentcommand(ui): _alias, statuscmd = cmdutil.findcmd(b'status', commands.table) - pstatusopts = [o for o in statuscmd[1] if o[1] != b'rev'] + inapplicable = {b'rev', b'change'} + pstatusopts = [o for o in statuscmd[1] if o[1] not in inapplicable] @eh.command(b'pstatus', pstatusopts, **compat.helpcategorykwargs('CATEGORY_WORKING_DIRECTORY')) @@ -442,7 +466,8 @@ return statuscmd[0](ui, repo, *args, **kwargs) _alias, diffcmd = cmdutil.findcmd(b'diff', commands.table) - pdiffopts = [o for o in diffcmd[1] if o[1] != b'rev'] + inapplicable = {b'rev', b'from', b'to', b'change'} + pdiffopts = [o for o in diffcmd[1] if o[1] not in inapplicable] @eh.command(b'pdiff', pdiffopts, **compat.helpcategorykwargs('CATEGORY_WORKING_DIRECTORY')) @@ -464,179 +489,6 @@ b"diff --hidden --rev 'limit(predecessors(.),1)' --rev .", b'evolve') -### Unstable revset symbol - -@eh.revsetpredicate(b'unstable()') -def revsetunstable(repo, subset, x): - """Changesets with instabilities. - """ - revset.getargs(x, 0, 0, b'unstable takes no arguments') - troubled = set() - troubled.update(getrevs(repo, b'orphan')) - troubled.update(getrevs(repo, b'phasedivergent')) - troubled.update(getrevs(repo, b'contentdivergent')) - troubled = revset.baseset(troubled) - troubled.sort() # set is non-ordered, enforce order - return subset & troubled - -@eh.revsetpredicate(b'troubled()') # legacy name -def revsettroubled(repo, subset, x): - return revsetunstable(repo, subset, x) - -### Obsolescence graph - -# XXX SOME MAJOR CLEAN UP TO DO HERE XXX - -def _precursors(repo, s, includeidentical=False): - """Precursor of a changeset""" - cs = set() - getrev = compat.getgetrev(repo.changelog) - markerbysubj = repo.obsstore.predecessors - node = repo.changelog.node - for r in s: - for p in markerbysubj.get(node(r), ()): - if not includeidentical and p[2] & rewind.identicalflag: - continue - pr = getrev(p[0]) - if pr is not None: - cs.add(pr) - cs -= repo.changelog.filteredrevs # nodemap has no filtering - return cs - -def _allprecursors(repo, s): # XXX we need a better naming - """transitive precursors of a subset""" - node = repo.changelog.node - toproceed = [node(r) for r in s] - seen = set() - allsubjects = repo.obsstore.predecessors - while toproceed: - nc = toproceed.pop() - for mark in allsubjects.get(nc, ()): - np = mark[0] - if np not in seen: - seen.add(np) - toproceed.append(np) - getrev = compat.getgetrev(repo.changelog) - cs = set() - for p in seen: - pr = getrev(p) - if pr is not None: - cs.add(pr) - cs -= repo.changelog.filteredrevs # nodemap has no filtering - return cs - -def _successors(repo, s): - """Successors of a changeset""" - cs = set() - node = repo.changelog.node - getrev = compat.getgetrev(repo.changelog) - markerbyobj = repo.obsstore.successors - for r in s: - for p in markerbyobj.get(node(r), ()): - for sub in p[1]: - sr = getrev(sub) - if sr is not None: - cs.add(sr) - cs -= repo.changelog.filteredrevs # nodemap has no filtering - return cs - -def _allsuccessors(repo, s, haltonflags=0): # XXX we need a better naming - """transitive successors of a subset - - haltonflags allows to provide flags which prevent the evaluation of a - marker. """ - node = repo.changelog.node - toproceed = [node(r) for r in s] - seen = set() - allobjects = repo.obsstore.successors - while toproceed: - nc = toproceed.pop() - for mark in allobjects.get(nc, ()): - if mark[2] & haltonflags: - continue - for sub in mark[1]: - if sub == nullid: - continue # should not be here! - if sub not in seen: - seen.add(sub) - toproceed.append(sub) - getrev = compat.getgetrev(repo.changelog) - cs = set() - for s in seen: - sr = getrev(s) - if sr is not None: - cs.add(sr) - cs -= repo.changelog.filteredrevs # nodemap has no filtering - return cs - -##################################################################### -### Extending revset and template ### -##################################################################### - -# this section add several useful revset symbol not yet in core. -# they are subject to changes - - -### XXX I'm not sure this revset is useful -@eh.revsetpredicate(b'suspended()') -def revsetsuspended(repo, subset, x): - """Obsolete changesets with non-obsolete descendants. - """ - revset.getargs(x, 0, 0, b'suspended takes no arguments') - suspended = revset.baseset(getrevs(repo, b'suspended')) - suspended.sort() - return subset & suspended - - -@eh.revsetpredicate(b'predecessors(set)') -def revsetpredecessors(repo, subset, x): - """Immediate predecessors of changesets in set. - """ - s = revset.getset(repo, revset.fullreposet(repo), x) - s = revset.baseset(_precursors(repo, s)) - s.sort() - return subset & s - - -@eh.revsetpredicate(b'precursors(set)') # legacy name for predecessors -def revsetprecursors(repo, subset, x): - return revsetpredecessors(repo, subset, x) - - -@eh.revsetpredicate(b'allpredecessors(set)') -def revsetallpredecessors(repo, subset, x): - """Transitive predecessors of changesets in set. - """ - s = revset.getset(repo, revset.fullreposet(repo), x) - s = revset.baseset(_allprecursors(repo, s)) - s.sort() - return subset & s - - -@eh.revsetpredicate(b'allprecursors(set)') # legacy name for allpredecessors -def revsetallprecursors(repo, subset, x): - return revsetallpredecessors(repo, subset, x) - - -@eh.revsetpredicate(b'successors(set)') -def revsetsuccessors(repo, subset, x): - """Immediate successors of changesets in set. - """ - s = revset.getset(repo, revset.fullreposet(repo), x) - s = revset.baseset(_successors(repo, s)) - s.sort() - return subset & s - -@eh.revsetpredicate(b'allsuccessors(set)') -def revsetallsuccessors(repo, subset, x): - """Transitive successors of changesets in set. - """ - s = revset.getset(repo, revset.fullreposet(repo), x) - s = revset.baseset(_allsuccessors(repo, s)) - s.sort() - return subset & s - - ##################################################################### ### Various trouble warning ### ##################################################################### @@ -1095,6 +947,10 @@ False)) # making sure a next commit is formed if result[0] and result[1]: + # If using in-memory merge, _solveone() will not have updated the + # working copy, so we need to do that. + if evolvecmd.use_in_memory_merge(repo) and result[1]: + compat.update(repo[result[1]]) ui.status(_(b'working directory is now at %s\n') % ui.label(bytes(repo[b'.']), b'evolve.node')) return 0 diff -r 87006dcf2bb7 -r 5341d7c30e68 hgext3rd/evolve/cmdrewrite.py --- a/hgext3rd/evolve/cmdrewrite.py Wed Feb 24 14:30:21 2021 -0800 +++ b/hgext3rd/evolve/cmdrewrite.py Thu Mar 11 11:49:34 2021 +0800 @@ -579,7 +579,7 @@ c.write(fp) fp.seek(0) - oldnode = node.hex(old.node())[:12] + oldnode = node.short(old.node()) message = b'temporary commit for uncommiting %s' % oldnode tempnode = _patchtocommit(ui, repo, old, fp, message, oldnode) return tempnode @@ -1484,7 +1484,7 @@ pickstate.load() pctxnode = pickstate[b'oldpctx'] ui.status(_(b"aborting pick, updating to %s\n") % - node.hex(pctxnode)[:12]) + node.short(pctxnode)) compat.clean_update(repo[pctxnode]) pickstate.delete() return 0 diff -r 87006dcf2bb7 -r 5341d7c30e68 hgext3rd/evolve/evolvecmd.py --- a/hgext3rd/evolve/evolvecmd.py Wed Feb 24 14:30:21 2021 -0800 +++ b/hgext3rd/evolve/evolvecmd.py Thu Mar 11 11:49:34 2021 +0800 @@ -32,6 +32,8 @@ util, ) +from mercurial.utils import stringutil + from mercurial.i18n import _ from . import ( @@ -164,7 +166,7 @@ progresscb() with state.saver(evolvestate, {b'current': orig.node()}): newid = _relocate(repo, orig, target, evolvestate, pctx, - keepbranch, b'orphan') + keepbranch, b'orphan', update=False) return (True, newid) def _solvephasedivergence(ui, repo, bumped, evolvestate, display, @@ -316,7 +318,7 @@ otherp1 = succsotherp1 = other.p1().rev() divp1 = succsdivp1 = divergent.p1().rev() - basep1 = base.p1().rev() + basep1 = succsbasep1 = base.p1().rev() # finding single successors of divp1 and otherp1 try: @@ -334,7 +336,7 @@ ui.write_err(msg) return (False, b".") try: - utility._singlesuccessor(repo, base.p1()) + succsbasep1 = utility._singlesuccessor(repo, base.p1()) except utility.MultipleSuccessorsError: msg = (b"ambiguous orphan resolution parent for " b"%s (base)\n") % base @@ -343,43 +345,41 @@ # the changeset on which resolution changeset will be based on resolutionparent = succsdivp1 - # divonly: non-obsolete csets which are topological ancestor of "divergent" - # but not "other" - divonly = repo.revs(b"only(%d, %d) - obsolete()" % (divergent.rev(), - other.rev())) - # otheronly: non-obsolete csets which are topological ancestor of "other" - # but not "div" - otheronly = repo.revs(b"only(%d, %d) - obsolete()" % (other.rev(), - divergent.rev())) - # make it exclusive set - divonly = set(divonly) - {divergent.rev()} - otheronly = set(otheronly) - {other.rev()} - # testing how both the divergent changesets are arranged, there can be 4 # possible cases here: # - # 1) both have the same parents - # 2) both have different parents but greatest common anscestor of them is - # parent of one of them - # 3) both have different parents and gca is not parent of any of them - # 4) one of them is parent of other + # 1) both have the same parent (or parent's successor) + # 2) one of them is parent of other + # 3) one of them moved elsewhere + # 4) both of them moved elsewhere # - # we are handling 1) very good now. - # for 2) we will relocate one which is behind to the parent of ahead one and - # then solve the content-divergence the way we solve 1) - # for 3) and 4), we still have to decide + # we are handling 1) 2) 3) very well now. + # for 4), we still have to decide if otherp1 == divp1: # both are on the same parents pass elif divergent == other.p1(): # both are in parent-child relation pass - elif basep1 in (divp1, otherp1): - # only one side moved, set parent of moved one as resolution parent - if basep1 == divp1: + elif succsbasep1 in (succsdivp1, succsotherp1): + # 1) either, only one side moved + # -> set parent of moved one as resolution parent + # 2) or, both moved but one moved to succs of basep1 + # -> set parent of other one as resolution parent + # 3) or, both have same parent's successor + # -> set parent's successor as resolution parent + if succsbasep1 == succsdivp1: resolutionparent = succsotherp1 else: pass + # a special case of 3) where parent of moved one is obsolete with a + # successor. Also, look at section 'D-A3.1' in troubles-handling.rst + minimal_resolution = ( + ui.configbool(b'experimental', b'evolution.divergence-resolution-minimal') + and succsdivp1 == succsotherp1 and basep1 in (divp1, otherp1) + ) + if minimal_resolution: + resolutionparent = otherp1 if basep1 == divp1 else divp1 else: # the nullrev has to be handled specially because -1 is overloaded to both # mean nullrev (this meaning is used for the result of changectx.rev(), as @@ -390,22 +390,7 @@ gca = [nodemod.nullrev] else: gca = repo.revs(b"ancestor(%d, %d)" % (succsotherp1, succsdivp1)) - - if succsotherp1 in gca and succsdivp1 in gca: - # both are not on the same parent but have same parents's succs. - if otheronly and divonly: - # case: we have visible csets on both side diverging from - # tca of "divergent" and "other". We still need to decide what - # to do in this case - pass - if otheronly: - resolutionparent = succsotherp1 - elif divonly: - pass - else: - # no extra cset on either side - pass - elif succsotherp1 in gca and succsdivp1 not in gca: + if succsotherp1 in gca and succsdivp1 not in gca: pass elif succsdivp1 in gca and succsotherp1 not in gca: resolutionparent = succsotherp1 @@ -588,11 +573,11 @@ assert local != other if local not in repo[None].parents(): repo.ui.note(_(b"updating to \"local\" side of the conflict: %s\n") % - local.hex()[:12]) + local) compat.update(local) # merging the two content-divergent changesets repo.ui.note(_(b"merging \"other\" %s changeset '%s'\n") % - (TROUBLES['CONTENTDIVERGENT'], other.hex()[:12])) + (TROUBLES['CONTENTDIVERGENT'], other)) if progresscb: progresscb() with state.saver(evolvestate): @@ -896,29 +881,18 @@ ordering.extend(sorted(dependencies)) return ordering -def _relocate(repo, orig, dest, evolvestate, pctx=None, keepbranch=False, - category=None): - """rewrites the orig rev on dest rev +def _rewrite_commit_message_hashes(repo, commitmsg): + """filter a commit description to update has to their successors + + The goal of this function is to avoid description referencing obsolete + hashes when a stack is rewritten or evolved. - returns the node of new commit which is formed + Each hash in the description will be detected and, if matching an obsolete + changeset, it will be replaced by its successors. + + Note: They might be case were such behavior might be is wrong, for example + if the commit message is explicitely referencing an older, obsolete changesets. """ - if orig.rev() == dest.rev(): - msg = _(b'tried to relocate a node on top of itself') - hint = _(b"This shouldn't happen. If you still need to move changesets, " - b"please do so manually with nothing to rebase - working " - b"directory parent is also destination") - raise error.ProgrammingError(msg, hint=hint) - - if pctx is None: - if len(orig.parents()) == 2: - msg = _(b"tried to relocate a merge commit without specifying which " - b"parent should be moved") - hint = _(b"Specify the parent by passing in pctx") - raise error.ProgrammingError(msg, hint) - pctx = orig.p1() - - commitmsg = orig.description() - cache = {} sha1s = re.findall(sha1re, commitmsg) unfi = repo.unfiltered() @@ -940,17 +914,87 @@ else: repo.ui.note(_(b'The stale commit message reference to %s could ' b'not be updated\n') % sha1) + return commitmsg + +def use_in_memory_merge(repo): + try: + from mercurial import mergestate as mergestatemod + mergestatemod.memmergestate + except (AttributeError, ImportError): + # no in-memory evolve if Mercurial lacks the required code + # hg <= 5.5 (19590b126764) + return False + config_value = repo.ui.config(b'experimental', b'evolution.in-memory') + if config_value == b'force': + return True + if repo.ui.hasconfig(b'hooks', b'precommit'): + return False + return stringutil.parsebool(config_value) + +def _relocate(repo, orig, dest, evolvestate, pctx=None, keepbranch=False, + category=None, update=True): + """rewrites the orig rev on dest rev + + Also updates bookmarks and creates obsmarkers. + + returns the node of new commit which is formed + """ + if orig.rev() == dest.rev(): + msg = _(b'tried to relocate a node on top of itself') + hint = _(b"This shouldn't happen. If you still need to move changesets, " + b"please do so manually with nothing to rebase - working " + b"directory parent is also destination") + raise error.ProgrammingError(msg, hint=hint) + + if pctx is None: + if len(orig.parents()) == 2: + msg = _(b"tried to relocate a merge commit without specifying which " + b"parent should be moved") + hint = _(b"Specify the parent by passing in pctx") + raise error.ProgrammingError(msg, hint) + pctx = orig.p1() + + commitmsg = _rewrite_commit_message_hashes(repo, orig.description()) tr = repo.currenttransaction() assert tr is not None if repo._activebookmark: repo.ui.status(_(b"(leaving bookmark %s)\n") % repo._activebookmark) bookmarksmod.deactivate(repo) - nodenew = _relocatecommit(repo, orig, dest, pctx, keepbranch, commitmsg) + nodenew = _relocatecommit(repo, orig, dest, pctx, keepbranch, commitmsg, + update) _finalizerelocate(repo, orig, dest, nodenew, tr, category, evolvestate) return nodenew -def _relocatecommit(repo, orig, dest, pctx, keepbranch, commitmsg): +def _relocatecommit(repo, orig, dest, pctx, keepbranch, commitmsg, update): + extra = dict(orig.extra()) + if b'branch' in extra: + del extra[b'branch'] + extra[b'rebase_source'] = orig.hex() + targetphase = max(orig.phase(), phases.draft) + configoverrides = { + (b'phases', b'new-commit'): targetphase + } + with repo.ui.configoverride(configoverrides, source=b'evolve'): + if not update and use_in_memory_merge(repo): + try: + return _relocatecommitinmem( + repo, orig, dest, pctx, keepbranch, commitmsg, extra + ) + except error.InMemoryMergeConflictsError: + repo.ui.status( + b'hit merge conflicts; retrying merge in working copy\n') + return _relocatecommitondisk(repo, orig, dest, pctx, keepbranch, + commitmsg, extra) + +def _relocatecommitondisk(repo, orig, dest, pctx, keepbranch, commitmsg, extra): + """rewrites the orig rev on dest rev on disk + + Creates the new commit by using the old-fashioned way of creating + commits by writing files to the working copy. + + returns the node of new commit which is formed + """ if repo[b'.'].rev() != dest.rev(): compat._update(repo, dest, branchmerge=False, force=True) if keepbranch: @@ -971,19 +1015,50 @@ raise error.InterventionRequired(_(b"unresolved merge conflicts"), hint=hint) - extra = dict(orig.extra()) - if b'branch' in extra: - del extra[b'branch'] - extra[b'rebase_source'] = orig.hex() + # Commit might fail if unresolved files exist + return repo.commit(text=commitmsg, user=orig.user(), + date=orig.date(), extra=extra) + +def _relocatecommitinmem(repo, orig, dest, pctx, keepbranch, commitmsg, extra): + """rewrites the orig rev on dest rev in memory + + Creates the new commit by using the modern way of creating + commits without writing files to the working copy. + + returns the node of new commit which is formed + """ + wctx = context.overlayworkingctx(repo) + wctx.setbase(dest) - targetphase = max(orig.phase(), phases.draft) - configoverride = repo.ui.configoverride({ - (b'phases', b'new-commit'): targetphase - }, source=b'evolve') - with configoverride: - # Commit might fail if unresolved files exist - return repo.commit(text=commitmsg, user=orig.user(), - date=orig.date(), extra=extra) + stats = merge.graft( + repo, + orig, + pctx, + [b'destination', b'evolving'], + keepparent=True, + wctx=wctx + ) + + if stats.unresolvedcount: # some conflict + raise error.InMemoryMergeConflictsError() + + branch = None + if keepbranch: + branch = orig.branch() + # FIXME: We call _compact() because it's required to correctly detect + # changed files. This was added to fix a regression shortly before the 5.5 + # release. A proper fix will be done in the default branch. + wctx._compact() + memctx = wctx.tomemctx( + commitmsg, + date=orig.date(), + extra=extra, + user=orig.user(), + branch=branch, + ) + if memctx.isempty() and not repo.ui.configbool(b'ui', b'allowemptycommit'): + return None + return repo.commitctx(memctx) def _finalizerelocate(repo, orig, dest, nodenew, tr, category, evolvestate): if nodenew is not None: @@ -1397,8 +1472,20 @@ return opts -def _cleanup(ui, repo, startnode, shouldupdate): - if not shouldupdate: +def _cleanup(ui, repo, startnode, shouldupdate, headnode): + """Update to the right destination after evolving, if necessary + + headnode is the last node created by the evolve operation (which + we may need to update to when using in-memory merge) + """ + # If --update was passed, we should update to some head of the evolved set, + # but we only need to do that in the in-memory case. If --update was not + # passed, we should still update the working copy to its successor if there + # is one. + if shouldupdate: + if use_in_memory_merge(repo) and headnode: + compat.update(repo[headnode]) + else: # Move back to startnode, or to its successor if the start node is # obsolete (perhaps made obsolete by the current `hg evolve`) unfi = repo.unfiltered() @@ -1593,13 +1680,14 @@ ui.setconfig(b'ui', b'forcemerge', opts.get('tool', r''), b'evolve') + headnode = None evolvestate = state.cmdstate(repo) # Continuation handling if contopt: if not evolvestate: raise error.Abort(_(b'no interrupted evolve to continue')) evolvestate.load() - continueevolve(ui, repo, evolvestate) + headnode = continueevolve(ui, repo, evolvestate) if evolvestate[b'command'] != b'evolve': evolvestate.delete() return @@ -1612,8 +1700,11 @@ raise error.Abort(_(b'no interrupted evolve to stop')) evolvestate.load() stopevolve(ui, repo, evolvestate) + if evolvestate[b'command'] != b'evolve': + evolvestate.delete() + return + startnode = repo.unfiltered()[evolvestate[b'startnode']] evolvestate.delete() - return elif abortopt: if not evolvestate: raise error.Abort(_(b'no interrupted evolve to abort')) @@ -1624,7 +1715,7 @@ compat.clean_update(pctx) ui.status(_(b'evolve aborted\n')) ui.status(_(b'working directory is now at %s\n') - % pctx.hex()[:12]) + % pctx) evolvestate.delete() return 0 return abortevolve(ui, repo, evolvestate) @@ -1672,21 +1763,26 @@ tr = repo.transaction(b"evolve") with util.acceptintervention(tr): for rev in revs: - _solveonerev(ui, repo, rev, evolvestate, activetopic, dryrunopt, - confirmopt, progresscb, targetcat) + (solved, newnode) = _solveonerev(ui, repo, rev, evolvestate, + activetopic, dryrunopt, + confirmopt, progresscb, + targetcat) + if solved: + headnode = newnode seen += 1 if showprogress: compat.progress(ui, _(b'evolve'), None) - _cleanup(ui, repo, startnode, shouldupdate) + _cleanup(ui, repo, startnode, shouldupdate, headnode) def _solveonerev(ui, repo, rev, evolvestate, activetopic, dryrunopt, confirmopt, progresscb, targetcat): """solves one trouble, including orphan merges Like _solveone(), this solves one trouble. Unlike _solveone(), it - stabilizes for both parents of orphan merges. + stabilizes for both parents of orphan merges. Returns the same value as + _solveone(). """ curctx = repo[rev] revtopic = getattr(curctx, 'topic', lambda: b'')() @@ -1719,6 +1815,7 @@ evolvestate[b'skippedrevs'].append(curctx.node()) evolvestate[b'orphanmerge'] = False + return ret def solveobswdp(ui, repo, opts): """this function updates to the successor of obsolete wdir parent""" @@ -1787,7 +1884,6 @@ pctx = repo[b'.'] compat.clean_update(pctx) ui.status(_(b'stopped the interrupted evolve\n')) - ui.status(_(b'working directory is now at %s\n') % pctx) def abortevolve(ui, repo, evolvestate): """ logic for handling of `hg evolve --abort`""" @@ -1862,7 +1958,7 @@ evolvestate.delete() ui.status(_(b'evolve aborted\n')) ui.status(_(b'working directory is now at %s\n') - % nodemod.hex(startnode)[:12]) + % nodemod.short(startnode)) else: raise error.Abort(_(b"unable to abort interrupted evolve, use 'hg " b"evolve --stop' to stop evolve")) @@ -1877,7 +1973,7 @@ compat.clean_update(pctx) ui.status(_(b'evolve aborted\n')) ui.status(_(b'working directory is now at %s\n') - % pctx.hex()[:12]) + % pctx) evolvestate.delete() return 0 return abortevolve(ui, repo, evolvestate) @@ -1911,6 +2007,7 @@ compat.progress(ui, _(b'evolve'), seen, unit=_(b'changesets'), total=count) + headnode = None category = evolvestate[b'category'] confirm = evolvestate[b'confirm'] unfi = repo.unfiltered() @@ -1937,9 +2034,11 @@ if newnode[0]: evolvestate[b'replacements'][curctx.node()] = newnode[1] evolvestate[b'lastsolved'] = newnode[1] + headnode = newnode[1] else: evolvestate[b'skippedrevs'].append(curctx.node()) seen += 1 + return headnode def _continuecontentdivergent(ui, repo, evolvestate, progresscb): """function to continue the interrupted content-divergence resolution.""" diff -r 87006dcf2bb7 -r 5341d7c30e68 hgext3rd/evolve/metadata.py --- a/hgext3rd/evolve/metadata.py Wed Feb 24 14:30:21 2021 -0800 +++ b/hgext3rd/evolve/metadata.py Thu Mar 11 11:49:34 2021 +0800 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -__version__ = b'10.2.1.dev' +__version__ = b'10.3.0.dev' testedwith = b'4.6.2 4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7' minimumhgversion = b'4.6' buglink = b'https://bz.mercurial-scm.org/' diff -r 87006dcf2bb7 -r 5341d7c30e68 hgext3rd/evolve/obshistory.py --- a/hgext3rd/evolve/obshistory.py Wed Feb 24 14:30:21 2021 -0800 +++ b/hgext3rd/evolve/obshistory.py Thu Mar 11 11:49:34 2021 +0800 @@ -61,7 +61,7 @@ (b'f', b'filternonlocal', False, _(b'filter out non local commits')), (b'o', b'origin', True, _(b'show origin of changesets instead of fate')), ] + commands.formatteropts, - _(b'hg olog [OPTION]... [[-r] REV]...'), + _(b'hg obslog [OPTION]... [[-r] REV]...'), **compat.helpcategorykwargs('CATEGORY_CHANGE_NAVIGATION')) def cmdobshistory(ui, repo, *revs, **opts): """show the obsolescence history of the specified revisions diff -r 87006dcf2bb7 -r 5341d7c30e68 hgext3rd/evolve/revset.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext3rd/evolve/revset.py Thu Mar 11 11:49:34 2021 +0800 @@ -0,0 +1,188 @@ +from __future__ import absolute_import + +from mercurial import ( + node as nodemod, + obsolete, + revset, +) + +from . import ( + compat, + exthelper, + rewind, +) + +eh = exthelper.exthelper() + +##################################################################### +### experimental behavior ### +##################################################################### + +getrevs = obsolete.getrevs + +### Unstable revset symbol + +# hg <= 5.3 (48b99af7b4b3) +@eh.revsetpredicate(b'unstable()') +def revsetunstable(repo, subset, x): + """Changesets with instabilities. + """ + revset.getargs(x, 0, 0, b'unstable takes no arguments') + troubled = set() + troubled.update(getrevs(repo, b'orphan')) + troubled.update(getrevs(repo, b'phasedivergent')) + troubled.update(getrevs(repo, b'contentdivergent')) + troubled = revset.baseset(troubled) + troubled.sort() # set is non-ordered, enforce order + return subset & troubled + +@eh.revsetpredicate(b'troubled()') # legacy name +def revsettroubled(repo, subset, x): + return revsetunstable(repo, subset, x) + +### Obsolescence graph + +# XXX SOME MAJOR CLEAN UP TO DO HERE XXX + +def _precursors(repo, s, includeidentical=False): + """Precursor of a changeset""" + cs = set() + getrev = compat.getgetrev(repo.changelog) + markerbysubj = repo.obsstore.predecessors + node = repo.changelog.node + for r in s: + for p in markerbysubj.get(node(r), ()): + if not includeidentical and p[2] & rewind.identicalflag: + continue + pr = getrev(p[0]) + if pr is not None: + cs.add(pr) + cs -= repo.changelog.filteredrevs # nodemap has no filtering + return cs + +def _allprecursors(repo, s): # XXX we need a better naming + """transitive precursors of a subset""" + node = repo.changelog.node + toproceed = [node(r) for r in s] + seen = set() + allsubjects = repo.obsstore.predecessors + while toproceed: + nc = toproceed.pop() + for mark in allsubjects.get(nc, ()): + np = mark[0] + if np not in seen: + seen.add(np) + toproceed.append(np) + getrev = compat.getgetrev(repo.changelog) + cs = set() + for p in seen: + pr = getrev(p) + if pr is not None: + cs.add(pr) + cs -= repo.changelog.filteredrevs # nodemap has no filtering + return cs + +def _successors(repo, s): + """Successors of a changeset""" + cs = set() + node = repo.changelog.node + getrev = compat.getgetrev(repo.changelog) + markerbyobj = repo.obsstore.successors + for r in s: + for p in markerbyobj.get(node(r), ()): + for sub in p[1]: + sr = getrev(sub) + if sr is not None: + cs.add(sr) + cs -= repo.changelog.filteredrevs # nodemap has no filtering + return cs + +def _allsuccessors(repo, s, haltonflags=0): # XXX we need a better naming + """transitive successors of a subset + + haltonflags allows to provide flags which prevent the evaluation of a + marker. """ + node = repo.changelog.node + toproceed = [node(r) for r in s] + seen = set() + allobjects = repo.obsstore.successors + while toproceed: + nc = toproceed.pop() + for mark in allobjects.get(nc, ()): + if mark[2] & haltonflags: + continue + for sub in mark[1]: + if sub == nodemod.nullid: + continue # should not be here! + if sub not in seen: + seen.add(sub) + toproceed.append(sub) + getrev = compat.getgetrev(repo.changelog) + cs = set() + for s in seen: + sr = getrev(s) + if sr is not None: + cs.add(sr) + cs -= repo.changelog.filteredrevs # nodemap has no filtering + return cs + +##################################################################### +### Extending revsets ### +##################################################################### + +# this section adds several useful revset predicates not yet in core. +# they are subject to changes + +### XXX I'm not sure this revset is useful +@eh.revsetpredicate(b'suspended()') +def revsetsuspended(repo, subset, x): + """Obsolete changesets with non-obsolete descendants. + """ + revset.getargs(x, 0, 0, b'suspended takes no arguments') + suspended = revset.baseset(getrevs(repo, b'suspended')) + suspended.sort() + return subset & suspended + +@eh.revsetpredicate(b'predecessors(set)') +def revsetpredecessors(repo, subset, x): + """Immediate predecessors of changesets in set. + """ + s = revset.getset(repo, revset.fullreposet(repo), x) + s = revset.baseset(_precursors(repo, s)) + s.sort() + return subset & s + +@eh.revsetpredicate(b'precursors(set)') # legacy name for predecessors +def revsetprecursors(repo, subset, x): + return revsetpredecessors(repo, subset, x) + +@eh.revsetpredicate(b'allpredecessors(set)') +def revsetallpredecessors(repo, subset, x): + """Transitive predecessors of changesets in set. + """ + s = revset.getset(repo, revset.fullreposet(repo), x) + s = revset.baseset(_allprecursors(repo, s)) + s.sort() + return subset & s + +@eh.revsetpredicate(b'allprecursors(set)') # legacy name for allpredecessors +def revsetallprecursors(repo, subset, x): + return revsetallpredecessors(repo, subset, x) + +@eh.revsetpredicate(b'successors(set)') +def revsetsuccessors(repo, subset, x): + """Immediate successors of changesets in set. + """ + s = revset.getset(repo, revset.fullreposet(repo), x) + s = revset.baseset(_successors(repo, s)) + s.sort() + return subset & s + +@eh.revsetpredicate(b'allsuccessors(set)') +def revsetallsuccessors(repo, subset, x): + """Transitive successors of changesets in set. + """ + s = revset.getset(repo, revset.fullreposet(repo), x) + s = revset.baseset(_allsuccessors(repo, s)) + s.sort() + return subset & s diff -r 87006dcf2bb7 -r 5341d7c30e68 hgext3rd/evolve/rewind.py --- a/hgext3rd/evolve/rewind.py Wed Feb 24 14:30:21 2021 -0800 +++ b/hgext3rd/evolve/rewind.py Thu Mar 11 11:49:34 2021 +0800 @@ -20,6 +20,7 @@ from . import ( compat, exthelper, + obshistory, rewriteutil, ) @@ -93,6 +94,9 @@ targets = _select_rewind_targets(repo, opts) + extratargets = _walk_obsmarkers(ui, unfi, targets) + targets.extend(extratargets) + for rev in targets: ctx = unfi[rev] ssets = obsutil.successorssets(repo, ctx.node(), cache=sscache) @@ -188,6 +192,139 @@ if update_target is not None and not opts.get('keep'): ui.status(_(b'working directory is now at %s\n') % repo[b'.']) +def _check_multiple_predecessors(targetnode, successor, targetset): + """ check if a successor of one rewind target is already another target + + An example obsolescence marker tree: + A0 A1 A2 + x - x - o + + When user tries to rewind A2 to both its predecessors with e.g. + `hg rewind --to A0+A1`, this function will at one point be called with + (A0, A1, [A0, A1]) as arguments. In that case it will raise an Abort + and prevent rewind from succeeding. + """ + if successor in targetset: + msg = _(b'not rewinding, %s is a successor of %s') + msg %= (nodemod.short(successor), nodemod.short(targetnode)) + hint = _(b'pick only one of these changesets, possibly with --exact') + raise error.Abort(msg, hint=hint) + +def _walk_successors(ui, unfi, targetset): + """follow successors of targets and find the latest successors + + While doing that, check and abort if there are multiple unrelated + predecessors in targetset. + + We also keep track of "source": when we reach a successor by following the + obsmarker-graph starting from a certain target, we add `successor: target` + pair to `source`. But we don't care about having more than one `target` + (i.e. predecessor) for each `successor`, because `source` is used later on + for finding "new" successors that we didn't find in this function. + """ + source = {} + latest = set() + for targetnode in targetset: + # following successors for each target node separately + remaining = set([targetnode]) + while remaining: + current = remaining.pop() + markers = unfi.obsstore.successors.get(current, ()) + for marker in markers: + for successor in marker[1]: + if successor in source: + # We have already reached this successor while + # processing this or any other targetnode. This means + # not every target node will get their latest successor + # found if e.g. there's a fold (and that is fine). + # (Also basic cycle protection.) + continue + # TODO: this could be moved to a post-processing stage + _check_multiple_predecessors(targetnode, successor, targetset) + source[successor] = current + remaining.add(successor) + if not markers: + latest.add(current) + + return latest, source + +def _check_unknown_predecessors(unfi, nodes, source): + """ check if any nodes are absent from both source and unfiltered repo + + If node is not in source, we might want it as an extra rewind target. But + if it's also absent from local (unfiltered) repo, we need to warn user and + abort. + """ + missing = { + node for node in nodes + if node not in source and node not in unfi + } + if missing: + msg = _(b'not rewinding, some predecessors are unknown locally: %s') + msg %= b' '.join(nodemod.short(m) for m in missing) + hint = _(b'try selecting all changesets to rewind to manually, ' + b'possibly with --exact') + raise error.Abort(msg, hint=hint) + +def _walk_predecessors(ui, unfi, targetset, latest, source): + """follow predecessors of targets, validate and suggest extra targets + + While doing that, check and abort if any fold components are unknown. + + Skip predecessors that are only reachable by following obsmarkers with + "identical" flag, because these markers are for internal use and actual + users don't want to revive such predecessors. + + Note: picking the first reached (IOW, the latest) predecessor is done on + purpose. We don't want rewind to assume too much, but also, and more + importantly, we don't want rewind to deal with any potential merge + conflicts. And when rewind revives one of the latest predecessors and it is + based on an obsolete changeset that was not revived, the revived changeset + will simply be an orphan, and it will be up to the user to decide how they + want to solve the trouble _after_ rewind is finished. This way of handling + rewinds that may produce merge conflicts means less chance to lose work. + """ + remaining = set(latest) + seen = set(remaining) + extratargets = [] + while remaining: + successor = remaining.pop() + markers = unfi.obsstore.predecessors.get(successor, ()) + data = (((marker[0],), (marker,)) for marker in markers) + for (nodes, markers) in sorted(obshistory.groupbyfoldid(data)): + # TODO: this could be moved to a post-processing stage + _check_unknown_predecessors(unfi, nodes, source) + for node in nodes: + if node in seen: + # basic cycle protection + continue + if node in source: + # we've been here while following successors + seen.add(node) + remaining.add(node) + elif node not in targetset: + # skipping meta obsmarkers from previous rewind operations + identical = [m[2] & identicalflag for m in markers] + if not all(identical): + extratargets.append(unfi[node].rev()) + + return extratargets + +def _walk_obsmarkers(ui, unfi, targets): + """walking of both successors and predecessors of changesets to rewind to + + This function: + - traverses successors of every target, then + - traverses predecessors of every latest successor of each target + - returns reachable predecessors with content changes + """ + node = unfi.changelog.node + targetset = set(node(target) for target in targets) + latest, source = _walk_successors(ui, unfi, targetset) + extratargets = _walk_predecessors(ui, unfi, targetset, latest, source) + + return extratargets + def _select_rewind_targets(repo, opts): """select the revisions we should rewind to """ diff -r 87006dcf2bb7 -r 5341d7c30e68 hgext3rd/topic/__init__.py --- a/hgext3rd/topic/__init__.py Wed Feb 24 14:30:21 2021 -0800 +++ b/hgext3rd/topic/__init__.py Thu Mar 11 11:49:34 2021 +0800 @@ -232,7 +232,7 @@ b'topic.active': b'green', } -__version__ = b'0.21.1.dev' +__version__ = b'0.22.0.dev' testedwith = b'4.6.2 4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7' minimumhgversion = b'4.6' diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-check-sdist.t --- a/tests/test-check-sdist.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-check-sdist.t Thu Mar 11 11:49:34 2021 +0800 @@ -1,5 +1,3 @@ -#require test-repo - Enable obsolescence to avoid the warning issue when obsmarkers are found $ cat << EOF >> "$HGRCPATH" @@ -8,6 +6,16 @@ > EOF $ cd "$TESTDIR"/.. + +Archiving to a separate location to avoid hardlink mess when the repo is shared + +#if test-repo + + $ hg archive "$TESTTMP"/hg-evolve + $ cd "$TESTTMP"/hg-evolve + +#endif + $ "$PYTHON" setup.py sdist --dist-dir "$TESTTMP"/dist > /dev/null */dist.py:*: UserWarning: Unknown distribution option: 'python_requires' (glob) warnings.warn(msg) @@ -27,7 +35,7 @@ $ tar -tzf hg-evolve-*.tar.gz | sed 's|^hg-evolve-[^/]*/||' | sort > files $ wc -l files - 343 files + 347 files $ fgrep debian files tests/test-check-debian.t $ fgrep __init__.py files diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-corrupt.t --- a/tests/test-corrupt.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-corrupt.t Thu Mar 11 11:49:34 2021 +0800 @@ -1,17 +1,9 @@ $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * > [phases] > publish = False > [alias] > qlog = log --template='{rev} - {node|short} {desc} ({phase})\n' - > [diff] - > git = 1 - > unified = 0 > [extensions] > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-abort-orphan.t --- a/tests/test-evolve-abort-orphan.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-abort-orphan.t Thu Mar 11 11:49:34 2021 +0800 @@ -27,6 +27,14 @@ > EOF #endif +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + $ hg init abortrepo $ cd abortrepo $ echo ".*\.orig" > .hgignore @@ -97,6 +105,8 @@ $ hg evolve --all move:[4] added d atop:[5] added c + merging d (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging d warning: conflicts while merging d! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -144,6 +154,8 @@ $ hg evolve --all --update move:[4] added d atop:[5] added c + merging d (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging d warning: conflicts while merging d! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -172,6 +184,8 @@ move:[2] added b atop:[7] added a move:[5] added c + merging c (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging c warning: conflicts while merging c! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -284,6 +298,8 @@ atop:[7] added a move:[6] foo to a atop:[7] added a + merging a (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -349,6 +365,8 @@ atop:[7] added a move:[6] foo to a atop:[7] added a + merging a (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -427,6 +445,8 @@ atop:[9] added c move:[6] foo to a atop:[7] added a + merging a (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -503,6 +523,8 @@ move:[2] added b atop:[4] added a move:[3] added c + merging c (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging c warning: conflicts while merging c! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -550,6 +572,8 @@ $ hg next --evolve move:[3] added c atop:[5] added b + merging c (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging c warning: conflicts while merging c! (edit, then use 'hg resolve --mark') unresolved merge conflicts diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-abort-phasediv.t --- a/tests/test-evolve-abort-phasediv.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-abort-phasediv.t Thu Mar 11 11:49:34 2021 +0800 @@ -27,6 +27,14 @@ > EOF #endif +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + $ hg init abortrepo $ cd abortrepo $ echo ".*\.orig" > .hgignore diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-content-divergent-basic.t --- a/tests/test-evolve-content-divergent-basic.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-content-divergent-basic.t Thu Mar 11 11:49:34 2021 +0800 @@ -8,14 +8,6 @@ $ cat >> $HGRCPATH < [alias] > glog = log -GT "{rev}:{node|short} {desc|firstline}\n ({bookmarks}) [{branch}] {phase}" - > [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * - > [phases] - > publish = False > [diff] > git = 1 > unified = 0 diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-content-divergent-case-A5.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-evolve-content-divergent-case-A5.t Thu Mar 11 11:49:34 2021 +0800 @@ -0,0 +1,183 @@ +=============================================== +Testing content-divergence resolution: Case A.5 +=============================================== + +Independent rewrites of same changeset can lead to content-divergence. In most +common cases, it can occur when multiple users rewrite the same changeset +independently and push it. + +This test belongs to a series of tests checking the resolution of content-divergent +changesets. + +Category A: no parents are obsolete +Testcase 5: one side relocated backward and other rebased to parent's successor +Variants: +# a: "local" is relocated backward +# b: "other" is relocated backward + +A.5 Relocated backward; Rebased to parent's successor +===================================================== + +.. (Divergence reason): +.. local: relocated the changeset backward in the graph +.. other: rebased to the successor of parent +.. Since one side rebased to the successor of parent and other cset relocated backward, +.. the most reasonable behaviour is to set the parent of "backward-relocated" cset +.. as resolution parent of divergence. +.. +.. (local): +.. +.. C ø⇠○ C' +.. | | +.. B ○ | +.. | / +.. A ○ +.. | +.. O ● +.. +.. (other): +.. +.. C ø⇠○ C'' +.. | | +.. B ø⇠○ B' +.. | / +.. A ○ +.. | +.. O ● +.. +.. (Resolution): +.. +.. B'○ ○ C''' +.. | / +.. A ○ +.. | +.. O ● +.. + +Setup +----- + $ . $TESTDIR/testlib/content-divergence-util.sh + $ setuprepos A.5 + creating test repo for test case A.5 + - upstream + - local + - other + cd into `local` and proceed with env setup + +initial + + $ cd upstream + $ mkcommit A + $ mkcommit B + $ mkcommit C + $ cd ../local + $ hg pull -qu + $ hg rebase -r 'desc(C)' -d 'desc(A)' + rebasing 3:d90aa47aa5d3 tip "C" + + $ cd ../other + $ hg pull -qu + $ hg prev + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + [2] B + $ echo newB > B + $ hg amend + 1 new orphan changesets + $ hg next + move:[3] C + atop:[4] B + working directory is now at f085ae420789 + $ hg push -q + + $ cd ../local + $ hg push -fq + 2 new content-divergent changesets + $ hg pull -q + 2 new content-divergent changesets + + +Actual test of resolution +------------------------- + +Variant_a: when "local" is rebased backward +------------------------------------------- + $ hg evolve -l + b80b2bbeb664: C + content-divergent: f085ae420789 (draft) (precursor d90aa47aa5d3) + + f085ae420789: C + content-divergent: b80b2bbeb664 (draft) (precursor d90aa47aa5d3) + + $ hg log -G + * 6:f085ae420789 (draft): C [content-divergent] + | + o 5:7db72af2e30d (draft): B + | + | @ 4:b80b2bbeb664 (draft): C [content-divergent] + |/ + o 1:f5bc6836db60 (draft): A + | + o 0:a9bdc8b26820 (public): O + + $ hg evolve --content-divergent + merge:[4] C + with: [6] C + base: [3] C + rebasing "other" content-divergent changeset f085ae420789 on f5bc6836db60 + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory is now at 88b737dc9dd8 + + $ hg log -G + @ 8:88b737dc9dd8 (draft): C + | + | o 5:7db72af2e30d (draft): B + |/ + o 1:f5bc6836db60 (draft): A + | + o 0:a9bdc8b26820 (public): O + + $ hg evolve -l + + +Variant_b: when "other" is rebased backward +------------------------------------------- + + $ cd ../other + $ hg pull -q + 2 new content-divergent changesets + $ hg evolve -l + f085ae420789: C + content-divergent: b80b2bbeb664 (draft) (precursor d90aa47aa5d3) + + b80b2bbeb664: C + content-divergent: f085ae420789 (draft) (precursor d90aa47aa5d3) + + $ hg log -G + * 6:b80b2bbeb664 (draft): C [content-divergent] + | + | @ 5:f085ae420789 (draft): C [content-divergent] + | | + | o 4:7db72af2e30d (draft): B + |/ + o 1:f5bc6836db60 (draft): A + | + o 0:a9bdc8b26820 (public): O + + $ hg evolve --content-divergent + merge:[5] C + with: [6] C + base: [3] C + rebasing "divergent" content-divergent changeset f085ae420789 on f5bc6836db60 + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory is now at fa4ff8bb3531 + + $ hg log -G + @ 8:fa4ff8bb3531 (draft): C + | + | o 4:7db72af2e30d (draft): B + |/ + o 1:f5bc6836db60 (draft): A + | + o 0:a9bdc8b26820 (public): O + + $ hg evolve -l diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-content-divergent-case-B1.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-evolve-content-divergent-case-B1.t Thu Mar 11 11:49:34 2021 +0800 @@ -0,0 +1,198 @@ +=============================================== +Testing content-divergence resolution: Case B.1 +=============================================== + +Independent rewrites of same changeset can lead to content-divergence. In most +common cases, it can occur when multiple users rewrite the same changeset +independently and push it. + +This test belongs to a series of tests checking the resolution of content-divergent +changesets. + +Category B: parents are obsolete +Testcase 1: one side amended changes and other rebased to in-between successor of basep1 +Variants: +# a: default resolution +# b: minimal resolution using `experimental.evolution.divergence-resolution-minimal=True` + +B.1 Relocated backward; Rebased to parent's successor +===================================================== + +.. (Divergence reason): +.. local: rebased to the 'in-between' successor of basep1 +.. other: amended some changes +.. The default resolution here is that we choose the final successor as resolution parent, +.. but this behavior can be changed to use the 'in-between' successor as resolution parent +.. by using a config option `experimental.evolution.divergence-resolution-minimal=True` +.. +.. This test case is considered complicated and can change its behavior acc. to the user +.. feedback. For more, please look at section 'D-A3.1' in troubles-handling.rst +.. +.. (local): +.. +.. B ø → ○ B' +.. | | +.. A ø → ø A' → ○ A'' +.. | | | +.. |---- | +.. |----------- +.. | +.. O ● +.. +.. (other): +.. +.. B ø→○ B' +.. | / +.. A ○ +.. | +.. O ● +.. +.. (Resolution): +.. +.. ○ B''' +.. | +.. ○ A'' +.. | +.. ● O +.. + +Setup +----- + $ . $TESTDIR/testlib/content-divergence-util.sh + $ setuprepos B.1 + creating test repo for test case B.1 + - upstream + - local + - other + cd into `local` and proceed with env setup + +initial + + $ cd upstream + $ mkcommit A + $ mkcommit B + $ cd ../local + $ hg pull -qu + $ hg prev + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + [1] A + $ echo fooA >> A + $ hg amend -m 'new_A' + 1 new orphan changesets + $ hg evolve + move:[2] B + atop:[3] new_A + $ echo barA >> A + $ hg amend -m 'latest_A' + 1 new orphan changesets + + $ cd ../other + $ hg pull -qu + $ echo fooB > B + $ hg amend -m 'new_B' + $ hg push -q + + $ cd ../local + $ hg push -fq + 2 new orphan changesets + 2 new content-divergent changesets + $ hg pull -q + 1 new orphan changesets + 2 new content-divergent changesets + + +Actual test of resolution +------------------------- + +Variant_a: default resolution +----------------------------- + $ hg evolve -l + 429afd16ac76: B + orphan: 1ffcccee011c (obsolete parent) + content-divergent: 807cc2b37fb3 (draft) (precursor f6fbb35d8ac9) + + 807cc2b37fb3: new_B + orphan: f5bc6836db60 (obsolete parent) + content-divergent: 429afd16ac76 (draft) (precursor f6fbb35d8ac9) + + $ hg log -G + * 6:807cc2b37fb3 (draft): new_B [orphan content-divergent] + | + | @ 5:45ed635c7cfc (draft): latest_A + | | + | | * 4:429afd16ac76 (draft): B [orphan content-divergent] + | | | + | | x 3:1ffcccee011c (draft): new_A + | |/ + x | 1:f5bc6836db60 (draft): A + |/ + o 0:a9bdc8b26820 (public): O + + $ hg evolve --content-divergent + merge:[4] B + with: [6] new_B + base: [2] B + rebasing "divergent" content-divergent changeset 429afd16ac76 on 45ed635c7cfc + rebasing "other" content-divergent changeset 807cc2b37fb3 on 45ed635c7cfc + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg log -G + o 9:6f740085e668 (draft): new_B + | + @ 5:45ed635c7cfc (draft): latest_A + | + o 0:a9bdc8b26820 (public): O + + $ hg evolve -l + + +Variant_b: minimal resolution +----------------------------- + + $ cd ../other + $ hg pull -q + 2 new orphan changesets + 2 new content-divergent changesets + $ hg evolve -l + 807cc2b37fb3: new_B + orphan: f5bc6836db60 (obsolete parent) + content-divergent: 429afd16ac76 (draft) (precursor f6fbb35d8ac9) + + 429afd16ac76: B + orphan: 1ffcccee011c (obsolete parent) + content-divergent: 807cc2b37fb3 (draft) (precursor f6fbb35d8ac9) + + $ hg log -G + o 6:45ed635c7cfc (draft): latest_A + | + | * 5:429afd16ac76 (draft): B [orphan content-divergent] + | | + | x 4:1ffcccee011c (draft): new_A + |/ + | @ 3:807cc2b37fb3 (draft): new_B [orphan content-divergent] + | | + | x 1:f5bc6836db60 (draft): A + |/ + o 0:a9bdc8b26820 (public): O + + $ hg evolve --content-divergent --config experimental.evolution.divergence-resolution-minimal=True + merge:[3] new_B + with: [5] B + base: [2] B + rebasing "divergent" content-divergent changeset 807cc2b37fb3 on 1ffcccee011c + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory is now at 2431e876af63 + + $ hg log -G + @ 8:2431e876af63 (draft): new_B [orphan] + | + | o 6:45ed635c7cfc (draft): latest_A + | | + x | 4:1ffcccee011c (draft): new_A + |/ + o 0:a9bdc8b26820 (public): O + + $ hg evolve -l + 2431e876af63: new_B + orphan: 1ffcccee011c (obsolete parent) + diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-content-divergent-corner-cases.t --- a/tests/test-evolve-content-divergent-corner-cases.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-content-divergent-corner-cases.t Thu Mar 11 11:49:34 2021 +0800 @@ -8,14 +8,6 @@ $ cat >> $HGRCPATH < [alias] > glog = log -GT "{rev}:{node|short} {desc|firstline}\n ({bookmarks}) [{branch}] {phase}" - > [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * - > [phases] - > publish = False > [diff] > git = 1 > unified = 0 diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-content-divergent-interrupted.t --- a/tests/test-evolve-content-divergent-interrupted.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-content-divergent-interrupted.t Thu Mar 11 11:49:34 2021 +0800 @@ -259,12 +259,10 @@ $ echo ".*\.orig" > .hgignore $ hg add .hgignore $ hg ci -m "added hgignore" - $ for ch in a b c d; do echo foo > $ch; hg add $ch; hg ci -qm "added "$ch; done; + $ for ch in a b c; do echo foo > $ch; hg add $ch; hg ci -qm "added "$ch; done; $ hg glog - @ 4:c41c793e0ef1 added d - | () draft - o 3:ca1b80f7960a added c + @ 3:ca1b80f7960a added c | () draft o 2:b1661037fa25 added b | () draft @@ -273,28 +271,36 @@ o 0:8fa14d15e168 added hgignore () draft +changes to get merge conflict during relocation + $ echo "some_changes" >> a + $ hg amend + $ echo foo > d + $ hg add d + $ hg ci -m "added d" + $ hg rebase -r . -d .^^^ --config extensions.rebase= - rebasing 4:c41c793e0ef1 tip "added d" + rebasing 5:f8b09dd867e5 tip "added d" $ echo bar > c $ hg add c $ hg amend $ hg up --hidden 'min(desc("added d"))' - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - updated to hidden changeset c41c793e0ef1 - (hidden revision 'c41c793e0ef1' was rewritten as: 69bdd23a9b0d) - working directory parent is obsolete! (c41c793e0ef1) - (use 'hg evolve' to update to its successor: 69bdd23a9b0d) + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + updated to hidden changeset f8b09dd867e5 + (hidden revision 'f8b09dd867e5' was rewritten as: 6f7eaf1944c0) + working directory parent is obsolete! (f8b09dd867e5) + (use 'hg evolve' to update to its successor: 6f7eaf1944c0) $ echo bar > d + $ echo "latest_changes" >> a $ hg amend 2 new content-divergent changesets $ hg glog - @ 7:e49523854bc8 added d + @ 8:a8673909e314 added d | () draft - | * 6:69bdd23a9b0d added d + | * 7:6f7eaf1944c0 added d | | () draft - o | 3:ca1b80f7960a added c + o | 4:33c16a2e0eb8 added c | | () draft o | 2:b1661037fa25 added b |/ () draft @@ -304,28 +310,26 @@ () draft $ hg evolve --content-divergent - merge:[6] added d - with: [7] added d - base: [4] added d - rebasing "other" content-divergent changeset e49523854bc8 on c7586e2a9264 - file 'c' was deleted in other but was modified in local. - You can use (c)hanged version, (d)elete, or leave (u)nresolved. - What do you want to do? u - 1 files updated, 0 files merged, 0 files removed, 1 files unresolved + merge:[7] added d + with: [8] added d + base: [5] added d + rebasing "other" content-divergent changeset a8673909e314 on c7586e2a9264 + merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved merge conflicts (see 'hg help evolve.interrupted') [240] $ hg evolve --abort evolve aborted - working directory is now at e49523854bc8 + working directory is now at a8673909e314 $ hg glog - @ 7:e49523854bc8 added d + @ 8:a8673909e314 added d | () draft - | * 6:69bdd23a9b0d added d + | * 7:6f7eaf1944c0 added d | | () draft - o | 3:ca1b80f7960a added c + o | 4:33c16a2e0eb8 added c | | () draft o | 2:b1661037fa25 added b |/ () draft @@ -339,17 +343,17 @@ --------------------------------------------------------------------------------- $ hg up 'min(desc("added d"))' - 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + 3 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg rm c $ echo wat > d $ hg amend $ hg glog - @ 8:33e4442acf98 added d + @ 9:b6a3f3ee0c44 added d | () draft - | * 7:e49523854bc8 added d + | * 8:a8673909e314 added d | | () draft - | o 3:ca1b80f7960a added c + | o 4:33c16a2e0eb8 added c | | () draft | o 2:b1661037fa25 added b |/ () draft @@ -359,27 +363,44 @@ () draft $ hg evolve --content-divergent - merge:[7] added d - with: [8] added d - base: [4] added d - rebasing "divergent" content-divergent changeset e49523854bc8 on c7586e2a9264 + merge:[8] added d + with: [9] added d + base: [5] added d + rebasing "divergent" content-divergent changeset a8673909e314 on c7586e2a9264 + merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') + unresolved merge conflicts + (see 'hg help evolve.interrupted') + [240] + +this test case is mainly to test that we hit merge conlict while merging the +two divergent csets, so resolving this one which happened during relocation + $ echo a > a + $ hg res -m + (no more unresolved files) + continue: hg evolve --continue + + $ hg evolve -c + evolving 8:a8673909e314 "added d" + merging a merging d + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') warning: conflicts while merging d! (edit, then use 'hg resolve --mark') - 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + 0 files updated, 0 files merged, 0 files removed, 2 files unresolved unresolved merge conflicts (see 'hg help evolve.interrupted') [240] $ hg evolve --abort evolve aborted - working directory is now at 33e4442acf98 + working directory is now at b6a3f3ee0c44 $ hg glog - @ 8:33e4442acf98 added d + @ 9:b6a3f3ee0c44 added d | () draft - | * 7:e49523854bc8 added d + | * 8:a8673909e314 added d | | () draft - | o 3:ca1b80f7960a added c + | o 4:33c16a2e0eb8 added c | | () draft | o 2:b1661037fa25 added b |/ () draft @@ -517,12 +538,12 @@ $ hg evolve --stop stopped the interrupted evolve - working directory is now at e49523854bc8 + working directory is now at 517d4375cb72 $ hg glog - * 7:517d4375cb72 added d + @ 7:517d4375cb72 added d | () draft - | @ 5:e49523854bc8 added d + | * 5:e49523854bc8 added d | | () draft | o 3:ca1b80f7960a added c | | () draft @@ -537,17 +558,25 @@ relocation --------------------------------------------------------------------------- - $ echo babar > c - $ hg add c - c already tracked! +Making changes to make sure that it hits conflict while relocating + $ hg up -r 3 + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "some_changes" >> a + $ hg amend + 1 new orphan changesets + $ hg next + move:[5] added d + atop:[8] added c + working directory is now at dc9ba677cba1 + $ echo "latest_changes" >> a $ hg amend $ hg glog - @ 8:2d664a4ab749 added d + @ 10:0892835a581f added d + | () draft + o 8:33c16a2e0eb8 added c | () draft | * 7:517d4375cb72 added d | | () draft - o | 3:ca1b80f7960a added c - | | () draft o | 2:b1661037fa25 added b |/ () draft o 1:c7586e2a9264 added a @@ -557,47 +586,43 @@ $ hg evolve --content-divergent merge:[7] added d - with: [8] added d + with: [10] added d base: [4] added d - rebasing "other" content-divergent changeset 2d664a4ab749 on c7586e2a9264 - file 'c' was deleted in local but was modified in other. - You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved. - What do you want to do? u - merging d - warning: conflicts while merging d! (edit, then use 'hg resolve --mark') - 0 files updated, 0 files merged, 0 files removed, 2 files unresolved + rebasing "other" content-divergent changeset 0892835a581f on c7586e2a9264 + merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved merge conflicts (see 'hg help evolve.interrupted') [240] $ hg diff - diff -r 517d4375cb72 c + diff -r c7586e2a9264 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,6 @@ + foo + +<<<<<<< destination: c7586e2a9264 - test: added a + +======= + +some_changes + +latest_changes + +>>>>>>> evolving: 0892835a581f - test: added d + diff -r c7586e2a9264 d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 - +++ b/c Thu Jan 01 00:00:00 1970 +0000 + +++ b/d Thu Jan 01 00:00:00 1970 +0000 @@ -0,0 +1,1 @@ - +babar - diff -r 517d4375cb72 d - --- a/d Thu Jan 01 00:00:00 1970 +0000 - +++ b/d Thu Jan 01 00:00:00 1970 +0000 - @@ -1,1 +1,5 @@ - +<<<<<<< local: 517d4375cb72 - test: added d - foobar - +======= +bar - +>>>>>>> other: e315463d94bd - test: added d $ hg evolve --stop stopped the interrupted evolve - working directory is now at 2d664a4ab749 + working directory is now at 0892835a581f -XXX: we should have preserved the wdir to be at rev 8 $ hg glog - @ 8:2d664a4ab749 added d + @ 10:0892835a581f added d + | () draft + o 8:33c16a2e0eb8 added c | () draft | * 7:517d4375cb72 added d | | () draft - o | 3:ca1b80f7960a added c - | | () draft o | 2:b1661037fa25 added b |/ () draft o 1:c7586e2a9264 added a diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-content-divergent-stack.t --- a/tests/test-evolve-content-divergent-stack.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-content-divergent-stack.t Thu Mar 11 11:49:34 2021 +0800 @@ -749,7 +749,7 @@ $ hg evolve --stop 2 new orphan changesets stopped the interrupted evolve - working directory is now at 2a955e808c53 + working directory is now at 509103439e5e $ hg log -G o changeset: 21:7e67dfb7ee31 | tag: tip @@ -770,7 +770,7 @@ | | instability: orphan, content-divergent | | summary: added c | | - | * changeset: 17:509103439e5e + | @ changeset: 17:509103439e5e | | parent: 5:8e222f257bbf | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 @@ -789,7 +789,7 @@ | | | instability: orphan, content-divergent | | | summary: added c | | | - | | @ changeset: 14:2a955e808c53 + | | * changeset: 14:2a955e808c53 | | | parent: 10:c04ff147ef79 | | | user: test | | | date: Thu Jan 01 00:00:00 1970 +0000 @@ -837,10 +837,10 @@ x c7586e2a9264 (1) added a $ hg obslog -r 'desc("added b")' --all - @ 2a955e808c53 (14) added b + * 2a955e808c53 (14) added b | amended(content) from 6eb54b5af3fb using amend by test (Thu Jan 01 00:00:00 1970 +0000) | - | * 509103439e5e (17) added b + | @ 509103439e5e (17) added b | | amended(content) from d5f148423c16 using amend by test (Thu Jan 01 00:00:00 1970 +0000) | | x | 6eb54b5af3fb (11) added b diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-continue.t --- a/tests/test-evolve-continue.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-continue.t Thu Mar 11 11:49:34 2021 +0800 @@ -10,6 +10,14 @@ > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + Setting up the repo $ hg init repo @@ -56,6 +64,8 @@ $ hg evolve --all move:[4] added d atop:[5] added c + merging d (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging d warning: conflicts while merging d! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -116,6 +126,8 @@ $ hg evolve --update move:[7] added e atop:[8] added d + merging e (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging e warning: conflicts while merging e! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -156,6 +168,8 @@ $ hg evolve --all --update move:[2] added b atop:[9] added a + merging b (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging b warning: conflicts while merging b! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -241,6 +255,8 @@ move:[12] added d atop:[16] added c move:[13] added f + merging f (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging f warning: conflicts while merging f! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -256,6 +272,8 @@ move:[14] added g atop:[18] added f move:[15] added h + merging h (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging h warning: conflicts while merging h! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -303,6 +321,8 @@ move:[19] added g atop:[21] added f perform evolve? [Ny] y + merging g (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging g warning: conflicts while merging g! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -352,6 +372,8 @@ $ hg next --evolve move:[22] added g atop:[24] added f + merging g (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging g warning: conflicts while merging g! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -419,6 +441,8 @@ $ hg evolve move:[3] added d, modified c atop:[5] added c + merging c (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging c warning: conflicts while merging c! (edit, then use 'hg resolve --mark') unresolved merge conflicts diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-inmemory.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-evolve-inmemory.t Thu Mar 11 11:49:34 2021 +0800 @@ -0,0 +1,186 @@ +Tests running `hg evolve` with in-memory merge. + + $ . $TESTDIR/testlib/common.sh + + $ cat >> $HGRCPATH < [extensions] + > evolve = + > drawdag=$RUNTESTDIR/drawdag.py + > [alias] + > glog = log -G -T '{rev}:{node|short} {separate(" ", phase, tags)}\n{desc|firstline}' + > [experimental] + > evolution.in-memory = yes + > EOF + +Test evolving a single orphan + + $ hg init single-orphan + $ cd single-orphan + $ hg debugdrawdag <<'EOS' + > C # C/c = c\n + > B2 | # B2/b = b2\n + > | B # B/b = b\n + > \ / # replace: B -> B2 + > A + > EOS + 1 new orphan changesets + $ hg evolve + move:[3] C + atop:[2] B2 + $ hg glog + o 4:52da76e91abb draft tip + | C + | x 3:bc77848cde3a draft C + | | C + o | 2:377a194b9b8a draft B2 + | | B2 + | x 1:830b6315076c draft B + |/ B + o 0:426bada5c675 draft A + A + $ hg cat -r tip b c + b2 + c + $ cd .. + +Test that in-memory evolve works when there are conflicts +and after continuing. + + $ hg init conflicts + $ cd conflicts + $ hg debugdrawdag <<'EOS' + > E # E/e = e\n + > | + > D # D/b = d\n + > | + > C # C/c = c\n + > B2 | # B2/b = b2\n + > | B # B/b = b\n + > \ / # replace: B -> B2 + > A + > EOS + 3 new orphan changesets + $ hg evolve + move:[3] C + atop:[2] B2 + move:[4] D + merging b + hit merge conflicts; retrying merge in working copy + merging b + warning: conflicts while merging b! (edit, then use 'hg resolve --mark') + unresolved merge conflicts + (see 'hg help evolve.interrupted') + [240] + $ hg glog + @ 6:52da76e91abb draft tip + | C + | * 5:eae7899dd92b draft E + | | E + | % 4:57e51f6a6d36 draft D + | | D + | x 3:bc77848cde3a draft C + | | C + o | 2:377a194b9b8a draft B2 + | | B2 + | x 1:830b6315076c draft B + |/ B + o 0:426bada5c675 draft A + A + $ cat c + c + $ cat b + <<<<<<< destination: 52da76e91abb - test: C + b2 + ======= + d + >>>>>>> evolving: 57e51f6a6d36 D - test: D + $ echo d2 > b + $ hg resolve -m + (no more unresolved files) + continue: hg evolve --continue + $ hg evolve --continue + evolving 4:57e51f6a6d36 "D" + move:[5] E + atop:[7] D + $ hg glog + o 8:3c658574f8ed draft tip + | E + o 7:16e609b952e8 draft + | D + o 6:52da76e91abb draft + | C + | x 5:eae7899dd92b draft E + | | E + | x 4:57e51f6a6d36 draft D + | | D + | x 3:bc77848cde3a draft C + | | C + o | 2:377a194b9b8a draft B2 + | | B2 + | x 1:830b6315076c draft B + |/ B + o 0:426bada5c675 draft A + A + $ hg cat -r tip b c e + d2 + c + e + $ cd .. + +Test that in-memory merge is disabled if there's a precommit hook + + $ hg init precommit-hook + $ cd precommit-hook + $ hg debugdrawdag <<'EOS' + > C # C/c = c\n + > B2 | # B2/b = b2\n + > | B # B/b = b\n + > \ / # replace: B -> B2 + > A + > EOS + 1 new orphan changesets + $ cat >> .hg/hgrc < [hooks] + > precommit = echo "running precommit hook" + > EOF +The hook is not run with in-memory=force + $ hg co B2 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg evolve --config experimental.evolution.in-memory=force --update + move:[3] C + atop:[2] B2 + working directory is now at 52da76e91abb + $ hg glog + @ 4:52da76e91abb draft tip + | C + | x 3:bc77848cde3a draft C + | | C + o | 2:377a194b9b8a draft B2 + | | B2 + | x 1:830b6315076c draft B + |/ B + o 0:426bada5c675 draft A + A + $ hg co tip^ + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg amend -m B3 + 1 new orphan changesets +The hook is run with in-memory=yes + $ hg next --config experimental.evolution.in-memory=yes + move:[4] C + atop:[5] B3 + running precommit hook + working directory is now at aeee7323c054 + $ hg glog + @ 6:aeee7323c054 draft tip + | C + o 5:908ce5f9d7eb draft + | B3 + | x 3:bc77848cde3a draft C + | | C + +---x 2:377a194b9b8a draft B2 + | | B2 + | x 1:830b6315076c draft B + |/ B + o 0:426bada5c675 draft A + A diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-interrupted.t --- a/tests/test-evolve-interrupted.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-interrupted.t Thu Mar 11 11:49:34 2021 +0800 @@ -48,7 +48,7 @@ transaction abort! rollback completed abort: precommit hook exited with status 1 - [255] + [40] $ hg l @ 2 apricot and blueberry @@ -107,7 +107,7 @@ transaction abort! rollback completed abort: precommit hook exited with status 1 - [255] + [40] $ cat b banana $ hg evolve --stop @@ -132,7 +132,7 @@ transaction abort! rollback completed abort: precommit hook exited with status 1 - [255] + [40] $ hg evolve --continue evolving 1:e0486f65907d "banana" working directory is now at bd5ec7dfc2af diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-noupdate.t --- a/tests/test-evolve-noupdate.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-noupdate.t Thu Mar 11 11:49:34 2021 +0800 @@ -18,6 +18,14 @@ > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + $ hg init stoprepo $ cd stoprepo $ echo ".*\.orig" > .hgignore diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-order.t --- a/tests/test-evolve-order.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-order.t Thu Mar 11 11:49:34 2021 +0800 @@ -2,17 +2,6 @@ ----------------------- $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * - > [phases] - > publish = False - > [diff] - > git = 1 - > unified = 0 > [ui] > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n > [experimental] diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-phase.t --- a/tests/test-evolve-phase.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-phase.t Thu Mar 11 11:49:34 2021 +0800 @@ -9,6 +9,14 @@ > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + Testing when there are no conflicts during evolve $ hg init noconflict @@ -83,6 +91,8 @@ $ hg evolve move:[2] c atop:[3] b + merging a (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging a warning: conflicts while merging a! (edit, then use 'hg resolve --mark') unresolved merge conflicts diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-serveronly-bundle2.t --- a/tests/test-evolve-serveronly-bundle2.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-serveronly-bundle2.t Thu Mar 11 11:49:34 2021 +0800 @@ -2,8 +2,6 @@ $ . ${TESTDIR}/testlib/pythonpath.sh $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" > [web] > push_ssl = false > allow_push = * diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-serveronly-legacy.t --- a/tests/test-evolve-serveronly-legacy.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-serveronly-legacy.t Thu Mar 11 11:49:34 2021 +0800 @@ -2,8 +2,6 @@ $ . ${TESTDIR}/testlib/pythonpath.sh $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" > [web] > push_ssl = false > allow_push = * diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-split.t --- a/tests/test-evolve-split.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-split.t Thu Mar 11 11:49:34 2021 +0800 @@ -1,17 +1,6 @@ Check that evolve shows error while handling split commits -------------------------------------- $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * - > [phases] - > publish = False - > [diff] - > git = 1 - > unified = 0 > [ui] > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n > [extensions] diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-stop-orphan.t --- a/tests/test-evolve-stop-orphan.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-stop-orphan.t Thu Mar 11 11:49:34 2021 +0800 @@ -16,6 +16,14 @@ > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + $ hg init stoprepo $ cd stoprepo $ echo ".*\.orig" > .hgignore @@ -88,6 +96,8 @@ $ hg evolve move:[4] added d atop:[5] added c + merging d (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging d warning: conflicts while merging d! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -134,6 +144,8 @@ $ hg next --evolve move:[4] added d atop:[5] added c + merging d (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging d warning: conflicts while merging d! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -153,7 +165,6 @@ $ hg evolve --stop stopped the interrupted evolve - working directory is now at cb6a2ab625bb $ hg glog @ 5:cb6a2ab625bb added c @@ -196,6 +207,8 @@ $ hg evolve --update move:[4] added d atop:[5] added c + merging d (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging d warning: conflicts while merging d! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -246,6 +259,8 @@ atop:[7] added hgignore move:[2] added b move:[5] added c + merging c (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging c warning: conflicts while merging c! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -258,15 +273,15 @@ $ hg evolve --stop stopped the interrupted evolve - working directory is now at aec285328e90 + working directory is now at 21817cd42526 Only changeset which has a successor now are obsoleted $ hg glog - @ 9:aec285328e90 added b + o 9:aec285328e90 added b | () draft o 8:fd00db71edca added a | () draft - o 7:21817cd42526 added hgignore + @ 7:21817cd42526 added hgignore () draft * 6:2a4e03d422e2 added d | () draft @@ -284,6 +299,8 @@ $ hg evolve --all move:[5] added c atop:[9] added b + merging c (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging c warning: conflicts while merging c! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -303,11 +320,11 @@ | () draft o 10:cb1dd1086ef6 added c | () draft - @ 9:aec285328e90 added b + o 9:aec285328e90 added b | () draft o 8:fd00db71edca added a | () draft - o 7:21817cd42526 added hgignore + @ 7:21817cd42526 added hgignore () draft Bookmarks should only be moved of the changesets which have been evolved, @@ -315,7 +332,7 @@ ------------------------------------------------------------------------- $ hg up tip^ - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bookmark b1 $ hg up .^ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved @@ -359,6 +376,8 @@ move:[9] added b atop:[12] added a move:[10] added c + merging c (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging c warning: conflicts while merging c! (edit, then use 'hg resolve --mark') unresolved merge conflicts @@ -367,13 +386,13 @@ $ hg evolve --stop stopped the interrupted evolve - working directory is now at a3f4b95da934 + working directory is now at a3cc2042492f Bookmarks of only the changeset which are evolved is moved $ hg glog - @ 13:a3f4b95da934 added b + o 13:a3f4b95da934 added b | (b2) draft - o 12:a3cc2042492f added a + @ 12:a3cc2042492f added a | () draft | * 11:cd0909a30222 added d | | () draft diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-stop-phasediv.t --- a/tests/test-evolve-stop-phasediv.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-stop-phasediv.t Thu Mar 11 11:49:34 2021 +0800 @@ -16,6 +16,14 @@ > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + $ hg init stoprepo $ cd stoprepo $ echo ".*\.orig" > .hgignore @@ -90,15 +98,14 @@ $ hg evolve --stop stopped the interrupted evolve - working directory is now at ca1b80f7960a + working directory is now at ddba58020bc0 -XXX: maybe we should update wdir to where it was $ hg glog - * 6:ddba58020bc0 added d + @ 6:ddba58020bc0 added d | () draft | o 4:c41c793e0ef1 added d | | () public - | @ 3:ca1b80f7960a added c + | o 3:ca1b80f7960a added c | | () public | o 2:b1661037fa25 added b |/ () public diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-topic.t --- a/tests/test-evolve-topic.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-topic.t Thu Mar 11 11:49:34 2021 +0800 @@ -2,16 +2,8 @@ Check we can find the topic extensions $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > [phases] - > publish = False > [ui] > logtemplate = {rev} - \{{get(namespaces, "topics")}} {node|short} {desc} ({phase})\n - > [diff] - > git = 1 - > unified = 0 > [extensions] > rebase = > EOF diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve-wdir.t --- a/tests/test-evolve-wdir.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve-wdir.t Thu Mar 11 11:49:34 2021 +0800 @@ -35,6 +35,14 @@ > glog = log --graph --template "{rev}:{node|short} ({phase}): {desc|firstline} {if(troubles, '[{troubles}]')}\n" > EOF +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + $ hg init repo $ cd repo $ mkcommit c_A diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-evolve.t --- a/tests/test-evolve.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-evolve.t Thu Mar 11 11:49:34 2021 +0800 @@ -1,11 +1,4 @@ $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > metaedit=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * > [phases] > publish = False > [alias] @@ -16,6 +9,15 @@ > [extensions] > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH + +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + $ mkcommit() { > echo "$1" > "$1" > hg add "$1" @@ -1407,6 +1409,8 @@ move:[30] will be evolved safely atop:[32] amended move:[31] will cause conflict at evolve + merging newfile (inmemory !) + hit merge conflicts; retrying merge in working copy (inmemory !) merging newfile warning: conflicts while merging newfile! (edit, then use 'hg resolve --mark') unresolved merge conflicts diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-fold.t --- a/tests/test-fold.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-fold.t Thu Mar 11 11:49:34 2021 +0800 @@ -3,8 +3,6 @@ setup $ cat >> $HGRCPATH < [defaults] - > fold=-d "0 0" > [extensions] > evolve= > [alias] diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-obsolete-push.t --- a/tests/test-obsolete-push.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-obsolete-push.t Thu Mar 11 11:49:34 2021 +0800 @@ -1,6 +1,4 @@ $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" > [extensions] > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-oldconvert.t --- a/tests/test-oldconvert.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-oldconvert.t Thu Mar 11 11:49:34 2021 +0800 @@ -1,11 +1,4 @@ $ cat >> $HGRCPATH < [web] - > push_ssl = false - > allow_push = * - > [phases] - > publish=False - > [alias] - > odiff=diff --rev 'limit(obsparents(.),1)' --rev . > [extensions] > EOF $ mkcommit() { diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-prev-next.t --- a/tests/test-prev-next.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-prev-next.t Thu Mar 11 11:49:34 2021 +0800 @@ -431,7 +431,7 @@ $ hg prev abort: conflicting changes (do you want --merge?) - [255] + [20] $ echo hi > bar $ hg prev @@ -443,7 +443,7 @@ $ hg next abort: conflicting changes (do you want --merge?) - [255] + [20] Test that --merge still works fine with commands.update.check set @@ -454,7 +454,7 @@ $ hg next abort: conflicting changes (do you want --merge?) - [255] + [20] $ hg next --merge merging bar warning: conflicts while merging bar! (edit, then use 'hg resolve --mark') diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-rewind.t --- a/tests/test-rewind.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-rewind.t Thu Mar 11 11:49:34 2021 +0800 @@ -8,6 +8,7 @@ > publish = false > [alias] > glf = log -GT "{rev}: {desc} ({files})" + > glhf = log -GT "{rev}:{node|short} {desc} ({files})" > [extensions] > evolve = > EOF @@ -666,16 +667,13 @@ rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851 $ hg rewind --to '9576e80d6851' --hidden --dry-run rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851 - -XXX this should also give us 2 changesets - $ hg rewind --to 'a0316c4c5417' --hidden --dry-run - rewinding 4535d0af405c to 1 changesets: a0316c4c5417 + rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851 $ hg rewind --to '9576e80d6851' --exact --hidden --dry-run - rewinding 4535d0af405c to 1 changesets: 9576e80d6851 + rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851 $ hg rewind --to 'a0316c4c5417' --exact --hidden --dry-run - rewinding 4535d0af405c to 1 changesets: a0316c4c5417 + rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851 actual rewind @@ -869,6 +867,19 @@ summary: c_ROOT + $ hg rewind --hidden --to 'allpredecessors(desc("c_B0"))' --dry-run + abort: not rewinding, a65fceb2324a is a successor of 7e594302a05d + (pick only one of these changesets, possibly with --exact) + [255] + $ hg rewind --hidden --to 'allpredecessors(desc("c_B0"))' --dry-run --exact + abort: not rewinding, a65fceb2324a is a successor of 7e594302a05d + (pick only one of these changesets, possibly with --exact) + [255] + $ hg rewind --hidden --to 'allpredecessors(desc("c_B0"))' --dry-run --as-divergence + abort: not rewinding, a65fceb2324a is a successor of 7e594302a05d + (pick only one of these changesets, possibly with --exact) + [255] + Testing the defaults -------------------- @@ -1198,3 +1209,436 @@ $ hg rewind --keep --to 'desc("amended")' --hidden abort: uncommitted changes [20] + + $ cd .. + +Extra cases related to folds +============================ + +folding with a changeset created after the rewind target +-------------------------------------------------------- + +.. B0 ⇠\ +.. | ⇠ AB2 +.. A0 ⇠ A1 ⇠/ + +this simple test case introduces the idea of making rewind consider different +evolutions of fold components: "parent" evolution of A has more predecessors + + $ hg init extra-fold-case-1 + $ cd extra-fold-case-1 + + $ echo R > R + $ hg ci -qAm R + $ echo A > A + $ hg ci -qAm A0 + $ hg amend -m A1 + $ echo B > B + $ hg ci -qAm B0 + $ hg fold -r 'desc("A1")::' -m AB2 --exact -q + + $ hg glhf --hidden + @ 4:7f9a5314ef94 AB2 (A B) + | + | x 3:16429ed4b6cb B0 (B) + | | + | x 2:3748b241cad8 A1 (A) + |/ + | x 1:fa8956746c52 A0 (A) + |/ + o 0:167e04d3d1b2 R (R) + + +target selection + +when rewinding from a fold, rewind to all of its components (at various points +in their evolution) to not lose work + + $ hg rewind --hidden --to 'desc("A0")' --dry-run + rewinding 7f9a5314ef94 to 2 changesets: fa8956746c52 16429ed4b6cb + $ hg rewind --hidden --to 'desc("B0")' --dry-run + rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 16429ed4b6cb + $ hg rewind --from 'desc("AB2")' --dry-run + rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 16429ed4b6cb + + $ hg rewind --hidden --to 'allpredecessors(desc("AB2"))' --dry-run + abort: not rewinding, 3748b241cad8 is a successor of fa8956746c52 + (pick only one of these changesets, possibly with --exact) + [255] + + $ cd .. + +folding with a changeset we rebased onto +---------------------------------------- + +.. A0 ⇠ A1 ⇠\ +.. | ⇠ AB2 +.. B0 ⇠/ + +similar to the previous case, but this time evolution of A has more +predecessors and at some point starts to be based on B + + $ hg init extra-fold-case-2 + $ cd extra-fold-case-2 + + $ echo R > R + $ hg ci -qAm R + $ echo A > A + $ hg ci -qAm A0 + $ hg up 'desc("R")' -q + $ echo B > B + $ hg ci -qAm B0 + $ echo A > A + $ hg ci -qAm A1 + $ hg prune -r 'desc("A0")' -s 'desc("A1")' + 1 changesets pruned + + $ hg fold -r 'desc("B0")::' -m AB2 --exact -q + + $ hg glhf --hidden + @ 4:1988e9fe9517 AB2 (A B) + | + | x 3:7175ff74409b A1 (A) + | | + | x 2:d6ed1d624918 B0 (B) + |/ + | x 1:fa8956746c52 A0 (A) + |/ + o 0:167e04d3d1b2 R (R) + + +target selection + +when rewinding from a fold, rewind to all of its components (at various points +in their evolution) to not lose work + + $ hg rewind --hidden --to 'desc("A0")' --dry-run + rewinding 1988e9fe9517 to 2 changesets: fa8956746c52 d6ed1d624918 + $ hg rewind --hidden --to 'desc("B0")' --dry-run + rewinding 1988e9fe9517 to 2 changesets: d6ed1d624918 7175ff74409b + $ hg rewind --from 'desc("AB2")' --dry-run + rewinding 1988e9fe9517 to 2 changesets: d6ed1d624918 7175ff74409b + + $ cd .. + +folding with a changeset that rebased on us +------------------------------------------- + +.. B0 ⇠⇠⇠⇠ B1 ⇠\ +.. | | ⇠ AB2 +.. | A0 ⇠ A1 ⇠/ + +now evolutions of A and B have the same amount of changesets, but at point 0 +they aren't yet related + + $ hg init extra-fold-case-3 + $ cd extra-fold-case-3 + + $ echo R > R + $ hg ci -qAm R + $ echo A > A + $ hg ci -qAm A0 + $ hg amend -m A1 + $ hg up 'desc("R")' -q + $ echo B > B + $ hg ci -qAm B0 + $ hg up 'desc("A1")' -q + $ echo B > B + $ hg ci -qAm B1 + $ hg prune -r 'desc("B0")' -s 'desc("B1")' + 1 changesets pruned + + $ hg fold -r 'desc("A1")::' -m AB2 --exact -q + + $ hg glhf --hidden + @ 5:7f9a5314ef94 AB2 (A B) + | + | x 4:fe7a7d317e16 B1 (B) + | | + +---x 3:d6ed1d624918 B0 (B) + | | + | x 2:3748b241cad8 A1 (A) + |/ + | x 1:fa8956746c52 A0 (A) + |/ + o 0:167e04d3d1b2 R (R) + + +target selection + + $ hg rewind --hidden --to 'desc("A0")' --dry-run + rewinding 7f9a5314ef94 to 2 changesets: fa8956746c52 fe7a7d317e16 + $ hg rewind --hidden --to 'desc("B0")' --dry-run + rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 d6ed1d624918 + $ hg rewind --hidden --to 'desc("A1")' --dry-run + rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 fe7a7d317e16 + $ hg rewind --hidden --to 'desc("B1")' --dry-run + rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 fe7a7d317e16 + $ hg rewind --from 'desc("AB2")' --dry-run + rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 fe7a7d317e16 + +actual rewind + + $ hg rewind --hidden --to 'desc("A0")' + 1 new orphan changesets + rewound to 2 changesets + (1 changesets obsoleted) + working directory is now at e492d2f9be46 + +after rewind to A0: +- A0' and B1' are successors to AB2 (split using rewind) +- A0' is a successor of A0 (operation: rewind) +- A0' is a child of R (just like A0) +- B1' is a successor of B1 (operation: rewind) +- B1' is a child of A1 (just like B1), and therefore an orphan + + $ hg obslog -a + o 54b340ce1d87 (6) A0 + |\ split(description, meta, parent, content) from 7f9a5314ef94 using rewind by test (Thu Jan 01 00:00:06 1970 +0000) + | | meta-changed(meta) from fa8956746c52 using rewind by test (Thu Jan 01 00:00:06 1970 +0000) + | | + +---@ e492d2f9be46 (7) B1 + | | | split(description, meta, parent, content) from 7f9a5314ef94 using rewind by test (Thu Jan 01 00:00:06 1970 +0000) + | | | meta-changed(meta) from fe7a7d317e16 using rewind by test (Thu Jan 01 00:00:06 1970 +0000) + | | | + x---+ 7f9a5314ef94 (5) AB2 + | | | folded(description, parent, content) from 3748b241cad8, fe7a7d317e16 using fold by test (Thu Jan 01 00:00:06 1970 +0000) + | | | + x | | 3748b241cad8 (2) A1 + |/ / reworded(description) from fa8956746c52 using amend by test (Thu Jan 01 00:00:06 1970 +0000) + | | + | x fe7a7d317e16 (4) B1 + | | rewritten(description, parent) from d6ed1d624918 using prune by test (Thu Jan 01 00:00:06 1970 +0000) + | | + | x d6ed1d624918 (3) B0 + | + x fa8956746c52 (1) A0 + + $ hg glhf + @ 7:e492d2f9be46 B1 (B) + | + | o 6:54b340ce1d87 A0 (A) + | | + x | 2:3748b241cad8 A1 (A) + |/ + o 0:167e04d3d1b2 R (R) + + + $ hg debugobsolete --exclusive -r 'first(head())' + 7f9a5314ef94f5856ee90661268194cc5ce9b332 54b340ce1d87f3593fd9de2a742e7b444e5136ed e492d2f9be46b73c0cfa51709e92db864b8f3ed9 0 (Thu Jan 01 00:00:06 1970 +0000) {'ef1': '15', 'operation': 'rewind', 'user': 'test'} + fa8956746c5294ce3351309133b450c5930f30f5 54b340ce1d87f3593fd9de2a742e7b444e5136ed 4 (Thu Jan 01 00:00:06 1970 +0000) {'ef1': '2', 'operation': 'rewind', 'user': 'test'} + $ hg debugobsolete --exclusive -r 'last(head())' + 7f9a5314ef94f5856ee90661268194cc5ce9b332 54b340ce1d87f3593fd9de2a742e7b444e5136ed e492d2f9be46b73c0cfa51709e92db864b8f3ed9 0 (Thu Jan 01 00:00:06 1970 +0000) {'ef1': '15', 'operation': 'rewind', 'user': 'test'} + fe7a7d317e168a15e8aa43131b54d3256443d728 e492d2f9be46b73c0cfa51709e92db864b8f3ed9 4 (Thu Jan 01 00:00:06 1970 +0000) {'ef1': '2', 'operation': 'rewind', 'user': 'test'} + + $ cd .. + +simple fold with a missing part +------------------------------- + +.. B0 ⇠ (B1) ⇠\ +.. | | ⇠ AB2 +.. A0 ⇠ A1 ⇠/ + +a stack was rewritten, but then a part of it became unknown locally + + $ hg init extra-fold-case-4 + $ cd extra-fold-case-4 + + $ echo R > R + $ hg ci -qAm R + $ echo A > A + $ hg ci -qAm A0 + $ echo B > B + $ hg ci -qAm B0 + $ hg up 'desc("R")' -q + $ echo A > A + $ hg ci -qAm A1 + $ echo B > B + $ hg ci -qAm B1 + $ hg prune -r 'desc("A0")+desc("B0")' -s 'desc("A1")+desc("B1")' --biject + 2 changesets pruned + + $ hg fold -r 'desc("A1") + desc("B1")' -m AB2 --exact -q + + $ hg glhf --hidden + @ 5:1988e9fe9517 AB2 (A B) + | + | x 4:25210d726f52 B1 (B) + | | + | x 3:9c76368ab336 A1 (A) + |/ + | x 2:a07c12c45197 B0 (B) + | | + | x 1:fa8956746c52 A0 (A) + |/ + o 0:167e04d3d1b2 R (R) + + +target selection + + $ hg rewind --hidden --to 'desc("A0")' --dry-run + rewinding 1988e9fe9517 to 2 changesets: fa8956746c52 25210d726f52 + $ hg rewind --hidden --to 'desc("A1")' --dry-run + rewinding 1988e9fe9517 to 2 changesets: 9c76368ab336 25210d726f52 + $ hg rewind --hidden --to 'desc("B1")' --dry-run + rewinding 1988e9fe9517 to 2 changesets: 9c76368ab336 25210d726f52 + +because B0 is a child of A0, we use A0 instead of A1 unless --exact is given + +XXX the semantic of --exact might need clarification here, +XXX for example, shouln't --exact make sure we only rewind to the `--to` target ? + + $ hg rewind --hidden --to 'desc("B0")' --dry-run + rewinding 1988e9fe9517 to 2 changesets: fa8956746c52 a07c12c45197 + $ hg rewind --hidden --to 'desc("B0")' --exact --dry-run + rewinding 1988e9fe9517 to 2 changesets: a07c12c45197 9c76368ab336 + +stripping one of the fold parts + + $ hg strip --config extensions.strip= -r 'desc("B1")' --hidden -q + + $ hg glhf --hidden + @ 4:1988e9fe9517 AB2 (A B) + | + | x 3:9c76368ab336 A1 (A) + |/ + | x 2:a07c12c45197 B0 (B) + | | + | x 1:fa8956746c52 A0 (A) + |/ + o 0:167e04d3d1b2 R (R) + + +target selection + +the obvious challenge here is to somehow work around the missing fold +component, but we can't do much because it is one of the latest predecessors of +AB2 fold + +in future we might have a way to allow rewind to skip changesets unknown +locally and still proceed (and lose the least amount of work possible) + + $ hg rewind --hidden --to 'desc("A0")+desc("B0")' --exact --dry-run + rewinding 1988e9fe9517 to 2 changesets: fa8956746c52 a07c12c45197 + $ hg rewind --hidden --to 'desc("A0")' --dry-run + abort: not rewinding, some predecessors are unknown locally: 25210d726f52 + (try selecting all changesets to rewind to manually, possibly with --exact) + [255] + $ hg rewind --hidden --to 'desc("A1")' --dry-run + abort: not rewinding, some predecessors are unknown locally: 25210d726f52 + (try selecting all changesets to rewind to manually, possibly with --exact) + [255] + +XXX the semantic of --exact might need clarification here, +XXX for example, shouln't --exact make sure we only rewind to the `--to` target ? + + $ hg rewind --hidden --to 'desc("A1")' --exact --dry-run + abort: not rewinding, some predecessors are unknown locally: 25210d726f52 + (try selecting all changesets to rewind to manually, possibly with --exact) + [255] + $ hg rewind --from 'desc("AB2")' --dry-run + abort: not rewinding, some predecessors are unknown locally: 25210d726f52 + (try selecting all changesets to rewind to manually, possibly with --exact) + [255] + $ hg rewind --from 'desc("AB2")' --exact --dry-run + abort: not rewinding, some predecessors are unknown locally: 25210d726f52 + (try selecting all changesets to rewind to manually, possibly with --exact) + [255] + + $ cd .. + +split and then fold with a missing part +--------------------------------------- + +.. /⇠ (C1) ⇠\ +.. BC0 ⇠ | \ +.. | \⇠ B1 ⇠ AC2 +.. A0 ⇠⇠⇠⇠⇠⇠⇠⇠⇠⇠⇠/ + +here we have a case when walking successors and then predecessors of target +revisions just once might not be enough, because it's a more complex DAG with a +changeset missing from local repo + + $ hg init extra-fold-case-5 + $ cd extra-fold-case-5 + + $ echo R > R + $ hg ci -qAm R + $ echo A > A + $ hg ci -qAm A0 + $ echo B > B + $ echo C > C + $ hg ci -qAm BC0 + $ hg up 'desc("A0")' -q + $ echo B > B + $ hg ci -qAm B1 + $ echo C > C + $ hg ci -qAm C1 + + $ hg prune -r 'desc("BC0")' -s 'desc("B1")+desc("C1")' --split + 1 changesets pruned + + $ hg up 'desc("R")' -q + $ echo A > A + $ echo C > C + $ hg ci -qAm AC2 + + $ hg prune -r 'desc("A0")+desc("C1")' -s 'desc("AC2")' --fold + 2 changesets pruned + 1 new orphan changesets + + $ hg glhf --hidden + @ 5:9ccaac2e5fbb AC2 (A C) + | + | x 4:2e4ab803d8ae C1 (C) + | | + | * 3:44774eafdc1c B1 (B) + | | + | | x 2:883d75400657 BC0 (B C) + | |/ + | x 1:fa8956746c52 A0 (A) + |/ + o 0:167e04d3d1b2 R (R) + + +target selection + + $ hg rewind --hidden --to 'desc("A0")' --dry-run + rewinding 9ccaac2e5fbb to 2 changesets: fa8956746c52 2e4ab803d8ae + $ hg rewind --hidden --to 'desc("BC0")' --dry-run + rewinding 44774eafdc1c to 1 changesets: 883d75400657 + rewinding 9ccaac2e5fbb to 2 changesets: fa8956746c52 883d75400657 + $ hg rewind --from 'desc("AC2")' --dry-run + rewinding 9ccaac2e5fbb to 2 changesets: fa8956746c52 2e4ab803d8ae + +stripping a component of AC2 fold + + $ hg strip --config extensions.strip= --hidden -r 'desc("C1")' -q + warning: ignoring unknown working parent 9ccaac2e5fbb! + +target selection + +at the moment, there's not much that we can do here because of missing C1 + +in future we might have a way to allow rewind to skip changesets unknown +locally and still proceed (and lose the least amount of work possible) + +XXX the semantic of --exact might need clarification here, +XXX for example, shouln't --exact make sure we only rewind to the `--to` target ? + + $ hg rewind --hidden --to 'desc("A0")' --dry-run + abort: not rewinding, some predecessors are unknown locally: 2e4ab803d8ae + (try selecting all changesets to rewind to manually, possibly with --exact) + [255] + $ hg rewind --hidden --to 'desc("BC0")' --dry-run + rewinding 44774eafdc1c to 1 changesets: 883d75400657 + rewinding 9ccaac2e5fbb to 2 changesets: fa8956746c52 883d75400657 + $ hg rewind --from 'desc("AC2")' --dry-run + abort: not rewinding, some predecessors are unknown locally: 2e4ab803d8ae + (try selecting all changesets to rewind to manually, possibly with --exact) + [255] + $ hg rewind --from 'desc("AC2")' --exact --dry-run + abort: not rewinding, some predecessors are unknown locally: 2e4ab803d8ae + (try selecting all changesets to rewind to manually, possibly with --exact) + [255] diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-split.t --- a/tests/test-split.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-split.t Thu Mar 11 11:49:34 2021 +0800 @@ -6,16 +6,6 @@ $ cat >> $HGRCPATH < [alias] > glog = log -G -T "{rev}:{node|short} {desc|firstline} ({phase})\n" - > [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > split=-d "0 0" - > amend=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * - > [phases] - > publish = False > [diff] > git = 1 > unified = 0 diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-stabilize-conflict.t --- a/tests/test-stabilize-conflict.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-stabilize-conflict.t Thu Mar 11 11:49:34 2021 +0800 @@ -9,8 +9,6 @@ > interactive=false > merge=internal:merge > promptecho = True - > [defaults] - > amend=-d "0 0" > [merge-tools] > touch.checkchanged=true > touch.gui=true diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-stabilize-order.t --- a/tests/test-stabilize-order.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-stabilize-order.t Thu Mar 11 11:49:34 2021 +0800 @@ -1,10 +1,16 @@ $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" > [extensions] > EOF $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH +#testcases inmemory ondisk +#if inmemory + $ cat >> $HGRCPATH < [experimental] + > evolution.in-memory = yes + > EOF +#endif + $ glog() { > hg log -G --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@" > } @@ -72,8 +78,8 @@ b committing manifest committing changelog - resolving manifests - removing b + resolving manifests (ondisk !) + removing b (ondisk !) $ glog o 6:81b8bbcd5892@default(draft) addb | @@ -95,14 +101,17 @@ move:[3] addc atop:[6] addb hg rebase -r 7a7552255fb5 -d 81b8bbcd5892 - resolving manifests - getting b + resolving manifests (ondisk !) + getting b (ondisk !) resolving manifests getting c committing files: c committing manifest committing changelog + resolving manifests (inmemory !) + getting b (inmemory !) + getting c (inmemory !) working directory is now at 0f691739f917 $ hg debugobsolete > successors.new $ diff -u successors.old successors.new @@ -157,15 +166,17 @@ move:[7] addc atop:[8] addb hg rebase -r 0f691739f917 -d 7a68bc4596ea - resolving manifests - removing c - getting b + resolving manifests (ondisk !) + removing c (ondisk !) + getting b (ondisk !) resolving manifests getting c committing files: c committing manifest committing changelog + resolving manifests (inmemory !) + getting b (inmemory !) working directory is now at 2256dae6521f $ glog @ 9:2256dae6521f@default(draft) addc diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-topic-fold.t --- a/tests/test-topic-fold.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-topic-fold.t Thu Mar 11 11:49:34 2021 +0800 @@ -2,19 +2,6 @@ ------------------------ $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > split=-d "0 0" - > amend=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * - > [phases] - > publish = False - > [diff] - > git = 1 - > unified = 0 > [ui] > interactive = true > [extensions] diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-topic-rebase.t --- a/tests/test-topic-rebase.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-topic-rebase.t Thu Mar 11 11:49:34 2021 +0800 @@ -2,19 +2,6 @@ -------------------------- $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > split=-d "0 0" - > amend=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * - > [phases] - > publish = False - > [diff] - > git = 1 - > unified = 0 > [ui] > interactive = true > [extensions] diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-touch.t --- a/tests/test-touch.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-touch.t Thu Mar 11 11:49:34 2021 +0800 @@ -2,8 +2,6 @@ $ cat >> $HGRCPATH < [ui] > logtemplate={rev}:{node|short} {desc}\n - > [defaults] - > amend=-d "0 0" > [alias] > glog = log -GT "{rev}: {desc}" > [extensions] diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-tutorial.t --- a/tests/test-tutorial.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-tutorial.t Thu Mar 11 11:49:34 2021 +0800 @@ -13,11 +13,6 @@ > [diff] > # use "git" diff format, clearer and smarter format > git = 1 - > [alias] - > # "-d '0 0'" means that the new commit will be at January 1st 1970. - > # This is used for stable hash during test - > # (this tutorial is automatically tested.) - > amend = amend -d '0 0' > EOF $ hg init local diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-unstability-resolution-result.t --- a/tests/test-unstability-resolution-result.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-unstability-resolution-result.t Thu Mar 11 11:49:34 2021 +0800 @@ -8,8 +8,6 @@ XXX dispatching each these test case in appropriate file would make sense. $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" > [extensions] > hgext.rebase= > EOF diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-unstable-orphan.t --- a/tests/test-unstable-orphan.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-unstable-orphan.t Thu Mar 11 11:49:34 2021 +0800 @@ -6,17 +6,6 @@ instability happens when a changesets has obsolete ancestors. $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" - > fold=-d "0 0" - > [web] - > push_ssl = false - > allow_push = * - > [phases] - > publish = False - > [diff] - > git = 1 - > unified = 0 > [ui] > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n > [extensions] diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-wireproto-bundle1.t --- a/tests/test-wireproto-bundle1.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-wireproto-bundle1.t Thu Mar 11 11:49:34 2021 +0800 @@ -1,7 +1,5 @@ $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" > [ui] > ssh = "$PYTHON" "$RUNTESTDIR/dummyssh" > [phases] diff -r 87006dcf2bb7 -r 5341d7c30e68 tests/test-wireproto.t --- a/tests/test-wireproto.t Wed Feb 24 14:30:21 2021 -0800 +++ b/tests/test-wireproto.t Thu Mar 11 11:49:34 2021 +0800 @@ -1,7 +1,5 @@ $ cat >> $HGRCPATH < [defaults] - > amend=-d "0 0" > [experimental] > obsmarkers-exchange-debug=true > bundle2-exp=true