# HG changeset patch # User Pierre-Yves David # Date 1496233694 -7200 # Node ID 7abea6ea172cfae0c9116d4c1bab4adc00e5ed69 # Parent 8fadd87da07e41243018374effb6b47f78748304# Parent ca157d06b2dc0e509fa4f6471695be3de8d7ad80 test-compat: merge with mercurial-4.0 branch The new 'olog' command is not working with 3.9 so we skill all mentions of it in the tests. This also apply to the topic extensions. diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -50,3 +50,4 @@ 5ef112a6eb875633a7925cde61b7d2d9e65b3a56 6.0.1 8510d3fd7c3b312dc731f4c29badc415d504558a 6.1.0 d4ee0274a8efbaf3d73a659998248c379c61c2bf 6.2.0 +0af99106b0754426913b5c82fb52dc70d4d299f6 6.2.1 diff --git a/README b/README --- a/README +++ b/README @@ -112,7 +112,32 @@ Changelog ========= -6.2.1 - in progress +6.3.0 - in progress +------------------- + + - olog: add an 'obslog' alias + - olog: add an '--all' option to show the whole obsolescence history tree. + - evolution: add an experiment to track the effect of rewrites. + (See hg help - evolve for details) + - exchange: fix the "relevant-markers" algorithm to include inline prune. + This will impact discovery of obsmarkers between server and client if one + still uses the old algorithm. Please upgrade both clients and servers as + soon as possible. + (See changeset 176d1a0ce385 in core Mercurial for details) + - obsdiscovery: add a config flag to disable all obsmarkers discovery + (See hg help - evolve for details) + - template: add a 'precursors' template that display the closests precursors of changesets + - template: add a 'successors' template that display the closests successors of changesets + - template: add a 'obsfate' template that display how a changeset has evolved + - new discovery experiment: add options to restrict memory consumption on + large repository (see "hg help -e evolve" for details). + +6.2.2 - in progress +------------------- + + * evolve: fix --rev handling in --list mode + +6.2.1 -- 2017-05-23 ------------------- - prune: fix a crash related to color handling, diff --git a/debian/changelog b/debian/changelog --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +mercurial-evolve (6.2.1-1) unstable; urgency=medium + + * new upstream release + + -- Pierre-Yves David Tue, 23 May 2017 10:47:36 +0200 + mercurial-evolve (6.2.0-1) unstable; urgency=medium * new upstream release diff --git a/hgext3rd/evolve/__init__.py b/hgext3rd/evolve/__init__.py --- a/hgext3rd/evolve/__init__.py +++ b/hgext3rd/evolve/__init__.py @@ -44,6 +44,12 @@ # * abort: abort the push auto-publish = ignore + # For some large repository with few markers, the current for obsolescence + # markers discovery can get in the way. You can disable it with the + # configuration option below. This means all pushes and pulls will + # re-exchange all markers every time. + evolution.obsdiscovery = yes + Obsolescence Markers Discovery Experiment ========================================= @@ -70,6 +76,37 @@ # (recommended 'off' for developer repositories) # (recommended 'yes' for server (default)) obshashrange.warm-cache = no + +It is recommended to enable the blackbox extension to gather useful +data about the experiment. It is shipped with Mercurial so no extra +install needed. + + [extensions] + blackbox = + +Finally some extra option are available to help tame the experimental +implementation of some of the algorithms: + + [experimental] + # restrict cache size to reduce memory consumption + obshashrange.lru-size = 2000 # default is 2000 + +Effect Flag Experiment +====================== + +We are experimenting with a way to register what changed between a precursor +and its successors (content, description, parent, etc...). For example, having +this information is helpful to show what changed between an obsolete changeset +and its tipmost successors. + +The following config control the experiment:: + + [experimental] + # activate the registration of effect flags in obs markers + evolution.effect-flags = yes + +The effect flags are shown in the obglog command output without particular +configuration of you want to inspect them. """ evolutionhelptext = """ @@ -153,12 +190,11 @@ lock as lockmod, merge, node, + obsolete, patch, phases, revset, scmutil, - templatekw, - obsolete ) from mercurial.commands import walkopts, commitopts, commitopts2, mergetoolopts @@ -167,14 +203,16 @@ from . import ( checkheads, + compat, debugcmd, exthelper, metadata, obscache, obsexchange, + obshistory, safeguard, + templatekw, utility, - obshistory ) __version__ = metadata.__version__ @@ -217,6 +255,8 @@ eh.merge(safeguard.eh) eh.merge(obscache.eh) eh.merge(obshistory.eh) +eh.merge(templatekw.eh) +eh.merge(compat.eh) uisetup = eh.final_uisetup extsetup = eh.final_extsetup reposetup = eh.final_reposetup @@ -508,29 +548,6 @@ s.sort() return subset & s -### template keywords -# XXX it does not handle troubles well :-/ - -@eh.templatekw('obsolete') -def obsoletekw(repo, ctx, templ, **args): - """:obsolete: String. Whether the changeset is ``obsolete``. - """ - if ctx.obsolete(): - return 'obsolete' - return '' - -@eh.templatekw('troubles') -def showtroubles(**args): - """:troubles: List of strings. Evolution troubles affecting the changeset - (zero or more of "unstable", "divergent" or "bumped").""" - ctx = args['ctx'] - try: - # specify plural= explicitly to trigger TypeError on hg < 4.2 - return templatekw.showlist('trouble', ctx.troubles(), args, - plural='troubles') - except TypeError: - return templatekw.showlist('trouble', ctx.troubles(), plural='troubles', - **args) ##################################################################### ### Various trouble warning ### @@ -538,29 +555,6 @@ # This section take care of issue warning to the user when troubles appear - -def _getobsoletereason(repo, revnode): - """ Return a tuple containing: - - the reason a revision is obsolete (diverged, pruned or superseed) - - the list of successors short node if the revision is neither pruned - or has diverged - """ - successorssets = obsolete.successorssets(repo, revnode) - - if len(successorssets) == 0: - # The commit has been pruned - return ('pruned', []) - elif len(successorssets) > 1: - return ('diverged', []) - else: - # No divergence, only one set of successors - successors = [node.short(node_id) for node_id in successorssets[0]] - - if len(successors) == 1: - return ('superseed', successors) - else: - return ('superseed_split', successors) - def _warnobsoletewc(ui, repo): rev = repo['.'] @@ -577,7 +571,7 @@ return # Show a warning for helping the user to solve the issue - reason, successors = _getobsoletereason(repo, rev.node()) + reason, successors = obshistory._getobsfateandsuccs(repo, rev.node()) if reason == 'pruned': solvemsg = _("use 'hg evolve' to update to its parent successor") @@ -615,7 +609,7 @@ unfilteredrepo = repo.unfiltered() rev = unfilteredrepo[changeid] - reason, successors = _getobsoletereason(unfilteredrepo, rev.node()) + reason, successors = obshistory._getobsfateandsuccs(unfilteredrepo, rev.node()) # Be more precise in cqse the revision is superseed if reason == 'superseed': @@ -810,7 +804,10 @@ message = old.description() user = commitopts.get('user') or old.user() - date = commitopts.get('date') or None # old.date() + # TODO: In case not date is given, we should take the old commit date + # if we are working one one changeset or mimic the fold behavior about + # date + date = commitopts.get('date') or None extra = dict(commitopts.get('extra', old.extra())) extra['branch'] = head.branch() @@ -1310,7 +1307,7 @@ revs = repo.revs('+'.join("%s()" % t for t in troublecategories)) if opts.get('rev'): - revs = revs & repo.revs(opts.get('rev')) + revs = scmutil.revrange(repo, opts.get('rev')) fm = ui.formatter('evolvelist', opts) for rev in revs: diff --git a/hgext3rd/evolve/compat.py b/hgext3rd/evolve/compat.py new file mode 100644 --- /dev/null +++ b/hgext3rd/evolve/compat.py @@ -0,0 +1,57 @@ +# Copyright 2017 Octobus +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. +""" +Compatibility module +""" + +from mercurial import ( + hg, + obsolete +) + +from . import ( + exthelper, +) + +eh = exthelper.exthelper() + +if not hasattr(hg, '_copycache'): + # exact copy of relevantmarkers as in Mercurial-176d1a0ce385 + # this fixes relevant markers computation for version < hg-4.3 + @eh.wrapfunction(obsolete.obsstore, 'relevantmarkers') + def relevantmarkers(orig, self, nodes): + """return a set of all obsolescence markers relevant to a set of nodes. + + "relevant" to a set of nodes mean: + + - marker that use this changeset as successor + - prune marker of direct children on this changeset + - recursive application of the two rules on precursors of these markers + + It is a set so you cannot rely on order. + + Backport of mercurial changeset 176d1a0ce385 for version < 4.3 + """ + + pendingnodes = set(nodes) + seenmarkers = set() + seennodes = set(pendingnodes) + precursorsmarkers = self.precursors + succsmarkers = self.successors + children = self.children + while pendingnodes: + direct = set() + for current in pendingnodes: + direct.update(precursorsmarkers.get(current, ())) + pruned = [m for m in children.get(current, ()) if not m[1]] + direct.update(pruned) + pruned = [m for m in succsmarkers.get(current, ()) if not m[1]] + direct.update(pruned) + direct -= seenmarkers + pendingnodes = set([m[0] for m in direct]) + seenmarkers |= direct + pendingnodes -= seennodes + seennodes |= pendingnodes + return seenmarkers diff --git a/hgext3rd/evolve/debugcmd.py b/hgext3rd/evolve/debugcmd.py --- a/hgext3rd/evolve/debugcmd.py +++ b/hgext3rd/evolve/debugcmd.py @@ -10,7 +10,10 @@ # * We could have the same code in core as `hg debugobsolete --stat`, # * We probably want a way for the extension to hook in for extra data. -from mercurial import node +from mercurial import ( + obsolete, + node, +) from mercurial.i18n import _ @@ -41,7 +44,8 @@ store = repo.obsstore unfi = repo.unfiltered() nm = unfi.changelog.nodemap - ui.write(_('markers total: %9i\n') % len(store._all)) + nbmarkers = len(store._all) + ui.write(_('markers total: %9i\n') % nbmarkers) sucscount = [0, 0, 0, 0] known = 0 parentsdata = 0 @@ -51,6 +55,8 @@ clustersmap = {} # same data using parent information pclustersmap = {} + size_v0 = [] + size_v1 = [] for mark in store: if mark[0] in nm: known += 1 @@ -72,6 +78,8 @@ # same with parent data nodes.update(parents) _updateclustermap(nodes, mark, pclustersmap) + size_v0.append(len(obsolete._fm0encodeonemarker(mark))) + size_v1.append(len(obsolete._fm1encodeonemarker(mark))) # freezing the result for c in clustersmap.values(): @@ -95,6 +103,27 @@ for key in sorted(metakeys): ui.write((' %15s: %9i\n' % (key, metakeys[key]))) + size_v0.sort() + size_v1.sort() + if size_v0: + ui.write('marker size:\n') + # format v1 + ui.write(' format v1:\n') + ui.write((' smallest length: %9i\n' % size_v1[0])) + ui.write((' longer length: %9i\n' % size_v1[-1])) + median = size_v1[nbmarkers // 2] + ui.write((' median length: %9i\n' % median)) + mean = sum(size_v1) // nbmarkers + ui.write((' mean length: %9i\n' % mean)) + # format v0 + ui.write(' format v0:\n') + ui.write((' smallest length: %9i\n' % size_v0[0])) + ui.write((' longer length: %9i\n' % size_v0[-1])) + median = size_v0[nbmarkers // 2] + ui.write((' median length: %9i\n' % median)) + mean = sum(size_v0) // nbmarkers + ui.write((' mean length: %9i\n' % mean)) + allclusters = list(set(clustersmap.values())) allclusters.sort(key=lambda x: len(x[1])) ui.write(('disconnected clusters: %9i\n' % len(allclusters))) diff --git a/hgext3rd/evolve/metadata.py b/hgext3rd/evolve/metadata.py --- a/hgext3rd/evolve/metadata.py +++ b/hgext3rd/evolve/metadata.py @@ -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__ = '6.2.1.dev' +__version__ = '6.3.0.dev' testedwith = '3.8.4 3.9.2 4.0.2 4.1.2 4.2' minimumhgversion = '3.8' buglink = 'https://bz.mercurial-scm.org/' diff --git a/hgext3rd/evolve/obsdiscovery.py b/hgext3rd/evolve/obsdiscovery.py --- a/hgext3rd/evolve/obsdiscovery.py +++ b/hgext3rd/evolve/obsdiscovery.py @@ -72,97 +72,6 @@ eh.merge(stablerange.eh) obsexcmsg = utility.obsexcmsg -########################################## -### trigger discovery during exchange ### -########################################## - -@eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers') -def _pushdiscoveryobsmarkers(orig, pushop): - if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt) - and pushop.repo.obsstore - and 'obsolete' in pushop.remote.listkeys('namespaces')): - repo = pushop.repo - obsexcmsg(repo.ui, "computing relevant nodes\n") - revs = list(repo.revs('::%ln', pushop.futureheads)) - unfi = repo.unfiltered() - cl = unfi.changelog - if not pushop.remote.capable('_evoext_obshash_0'): - # do not trust core yet - # return orig(pushop) - nodes = [cl.node(r) for r in revs] - if nodes: - obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" - % len(nodes)) - pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) - else: - obsexcmsg(repo.ui, "markers already in sync\n") - pushop.outobsmarkers = [] - pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) - return - - common = [] - missing = None - obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" - % len(revs)) - commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads)) - if _canobshashrange(repo, pushop.remote): - missing = findmissingrange(pushop.ui, unfi, pushop.remote, - commonrevs) - else: - common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, - commonrevs) - if missing is None: - revs = list(unfi.revs('%ld - (::%ln)', revs, common)) - nodes = [cl.node(r) for r in revs] - else: - revs = list(repo.revs('only(%ln, %ln)', pushop.futureheads, - pushop.outgoing.commonheads)) - nodes = [cl.node(r) for r in revs] - nodes += missing - - if nodes: - obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" - % len(nodes)) - pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) - else: - obsexcmsg(repo.ui, "markers already in sync\n") - pushop.outobsmarkers = [] - -@eh.extsetup -def _installobsmarkersdiscovery(ui): - olddisco = exchange.pushdiscoverymapping['obsmarker'] - - def newdisco(pushop): - _pushdiscoveryobsmarkers(olddisco, pushop) - exchange.pushdiscoverymapping['obsmarker'] = newdisco - -def buildpullobsmarkersboundaries(pullop, bundle2=True): - """small function returning the argument for pull markers call - may to contains 'heads' and 'common'. skip the key for None. - - It is a separed function to play around with strategy for that.""" - repo = pullop.repo - remote = pullop.remote - unfi = repo.unfiltered() - revs = unfi.revs('::(%ln - null)', pullop.common) - boundaries = {'heads': pullop.pulledsubset} - if not revs: # nothing common - boundaries['common'] = [node.nullid] - return boundaries - - if bundle2 and _canobshashrange(repo, remote): - obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" - % len(revs)) - boundaries['missing'] = findmissingrange(repo.ui, repo, pullop.remote, - revs) - elif remote.capable('_evoext_obshash_0'): - obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" - % len(revs)) - boundaries['common'] = findcommonobsmarkers(repo.ui, repo, remote, revs) - else: - boundaries['common'] = [node.nullid] - return boundaries - ################################## ### Code performing discovery ### ################################## @@ -439,7 +348,7 @@ class _obshashcache(obscache.dualsourcecache): - _schemaversion = 1 + _schemaversion = 2 _cachename = 'evo-ext-obshashrange' # used for error message @@ -794,6 +703,9 @@ # the obshash of its parents. This is similar to what happend for changeset # node where the parent is used in the computation +def _canobshashtree(repo, remote): + return remote.capable('_evoext_obshash_0') + @eh.command( 'debugobsrelsethashtree', [('', 'v0', None, 'hash on marker format "0"'), @@ -925,3 +837,126 @@ def newcap(repo, proto): return _obshash_capabilities(oldcap, repo, proto) wireproto.commands['capabilities'] = (newcap, args) + +########################################## +### trigger discovery during exchange ### +########################################## + +def _dopushmarkers(pushop): + return (# we have any markers to push + pushop.repo.obsstore + # exchange of obsmarkers is enabled locally + and obsolete.isenabled(pushop.repo, obsolete.exchangeopt) + # remote server accept markers + and 'obsolete' in pushop.remote.listkeys('namespaces')) + +def _pushobshashrange(pushop, commonrevs): + repo = pushop.repo.unfiltered() + remote = pushop.remote + missing = findmissingrange(pushop.ui, repo, remote, commonrevs) + missing += pushop.outgoing.missing + return missing + +def _pushobshashtree(pushop, commonrevs): + repo = pushop.repo.unfiltered() + remote = pushop.remote + node = repo.changelog.node + common = findcommonobsmarkers(pushop.ui, repo, remote, commonrevs) + revs = list(repo.revs('only(%ln, %ln)', pushop.futureheads, common)) + return [node(r) for r in revs] + +# available discovery method, first valid is used +# tuple (canuse, perform discovery)) +obsdiscoveries = [ + (_canobshashrange, _pushobshashrange), + (_canobshashtree, _pushobshashtree), +] + +obsdiscovery_skip_message = """\ +(skipping discovery of obsolescence markers, will exchange everything) +(controled by 'experimental.evolution.obsdiscovery' configuration) +""" + +def usediscovery(repo): + return repo.ui.configbool('experimental', 'evolution.obsdiscovery', True) + +@eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers') +def _pushdiscoveryobsmarkers(orig, pushop): + if _dopushmarkers(pushop): + repo = pushop.repo + remote = pushop.remote + obsexcmsg(repo.ui, "computing relevant nodes\n") + revs = list(repo.revs('::%ln', pushop.futureheads)) + unfi = repo.unfiltered() + + if not usediscovery(repo): + # discovery disabled by user + repo.ui.status(obsdiscovery_skip_message) + return orig(pushop) + + # look for an obs-discovery protocol we can use + discovery = None + for candidate in obsdiscoveries: + if candidate[0](repo, remote): + discovery = candidate[1] + break + + if discovery is None: + # no discovery available, rely on core to push all relevants + # obs markers. + return orig(pushop) + + obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" + % len(revs)) + commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads)) + # find the nodes where the relevant obsmarkers mismatches + nodes = discovery(pushop, commonrevs) + + if nodes: + obsexcmsg(repo.ui, "computing markers relevant to %i nodes\n" + % len(nodes)) + pushop.outobsmarkers = repo.obsstore.relevantmarkers(nodes) + else: + obsexcmsg(repo.ui, "markers already in sync\n") + pushop.outobsmarkers = [] + +@eh.extsetup +def _installobsmarkersdiscovery(ui): + olddisco = exchange.pushdiscoverymapping['obsmarker'] + + def newdisco(pushop): + _pushdiscoveryobsmarkers(olddisco, pushop) + exchange.pushdiscoverymapping['obsmarker'] = newdisco + +def buildpullobsmarkersboundaries(pullop, bundle2=True): + """small function returning the argument for pull markers call + may to contains 'heads' and 'common'. skip the key for None. + + It is a separed function to play around with strategy for that.""" + repo = pullop.repo + remote = pullop.remote + unfi = repo.unfiltered() + revs = unfi.revs('::(%ln - null)', pullop.common) + boundaries = {'heads': pullop.pulledsubset} + if not revs: # nothing common + boundaries['common'] = [node.nullid] + return boundaries + + if not usediscovery(repo): + # discovery disabled by users. + repo.ui.status(obsdiscovery_skip_message) + boundaries['common'] = [node.nullid] + return boundaries + + if bundle2 and _canobshashrange(repo, remote): + obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" + % len(revs)) + boundaries['missing'] = findmissingrange(repo.ui, repo, pullop.remote, + revs) + elif remote.capable('_evoext_obshash_0'): + obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" + % len(revs)) + boundaries['common'] = findcommonobsmarkers(repo.ui, repo, remote, revs) + else: + boundaries['common'] = [node.nullid] + return boundaries diff --git a/hgext3rd/evolve/obsexchange.py b/hgext3rd/evolve/obsexchange.py --- a/hgext3rd/evolve/obsexchange.py +++ b/hgext3rd/evolve/obsexchange.py @@ -18,6 +18,7 @@ import socket from mercurial import ( + bundle2, error, exchange, extensions, @@ -98,7 +99,10 @@ subset = [c.node() for c in repo.unfiltered().set('only(%ln, %ln)', heads, common)] subset += kwargs['evo_missing_nodes'] markers = repo.obsstore.relevantmarkers(subset) - exchange.buildobsmarkerspart(bundler, markers) + if util.safehasattr(bundle2, 'buildobsmarkerspart'): + bundle2.buildobsmarkerspart(bundler, markers) + else: + exchange.buildobsmarkerspart(bundler, markers) # manual wrap up in extsetup because of the wireproto.commands mapping def _obscommon_capabilities(orig, repo, proto): diff --git a/hgext3rd/evolve/obshistory.py b/hgext3rd/evolve/obshistory.py --- a/hgext3rd/evolve/obshistory.py +++ b/hgext3rd/evolve/obshistory.py @@ -7,11 +7,14 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +import re + from mercurial import ( cmdutil, commands, error, graphmod, + obsolete, node as nodemod, scmutil, ) @@ -25,9 +28,10 @@ eh = exthelper.exthelper() @eh.command( - 'olog', + 'obslog|olog', [('G', 'graph', True, _("show the revision DAG")), - ('r', 'rev', [], _('show the specified revision or revset'), _('REV')) + ('r', 'rev', [], _('show the specified revision or revset'), _('REV')), + ('a', 'all', False, _('show all related changesets, not only precursors')) ] + commands.formatteropts, _('hg olog [OPTION]... [REV]')) def cmdobshistory(ui, repo, *revs, **opts): @@ -157,14 +161,14 @@ stack.pop() return False -def _obshistorywalker(repo, revs): +def _obshistorywalker(repo, revs, walksuccessors=False): """ Directly inspired by graphmod.dagwalker, walk the obs marker tree and yield (id, CHANGESET, ctx, [parentinfo]) tuples """ # Get the list of nodes and links between them - candidates, nodesucc, nodeprec = _obshistorywalker_links(repo, revs) + candidates, nodesucc, nodeprec = _obshistorywalker_links(repo, revs, walksuccessors) # Shown, set of nodes presents in items shown = set() @@ -215,15 +219,19 @@ childrens = [(graphmod.PARENT, x) for x in nodeprec.get(cand, ())] yield (cand, 'M', changectx, childrens) -def _obshistorywalker_links(repo, revs): +def _obshistorywalker_links(repo, revs, walksuccessors=False): """ Iterate the obs history tree starting from revs, traversing each revision precursors recursively. + If walksuccessors is True, also check that every successor has been + walked, which ends up walking on all connected obs markers. It helps + getting a better view with splits and divergences. Return a tuple of: - The list of node crossed - The dictionnary of each node successors, values are a set - The dictionnary of each node precursors, values are a list """ precursors = repo.obsstore.precursors + successors = repo.obsstore.successors nodec = repo.changelog.node # Parents, set of parents nodes seen during walking the graph for node @@ -256,12 +264,21 @@ seen.add(precnode) nodes.append(precnode) + # Also walk on successors if the option is enabled + if walksuccessors: + for successor in successors.get(node, ()): + for succnodeid in successor[1]: + if succnodeid not in seen: + seen.add(succnodeid) + nodes.append(succnodeid) + return sorted(seen), nodesucc, nodeprec def _debugobshistorygraph(ui, repo, revs, opts): displayer = obsmarker_printer(ui, repo.unfiltered(), None, opts, buffered=True) edges = graphmod.asciiedges - cmdutil.displaygraph(ui, repo, _obshistorywalker(repo.unfiltered(), revs), displayer, edges) + walker = _obshistorywalker(repo.unfiltered(), revs, opts.get('all', False)) + cmdutil.displaygraph(ui, repo, walker, displayer, edges) def _debugobshistorysingle(fm, repo, revs): """ Display the obsolescence history for a single revision @@ -337,6 +354,36 @@ fm.write('debugobshistory.verb', '%s', verb, label="evolve.verb") + + effectflag = metadata.get('ef1') + if effectflag is not None: + try: + effectflag = int(effectflag) + except ValueError: + effectflag = None + if effectflag: + effect = [] + + # XXX should be a dict + if effectflag & DESCCHANGED: + effect.append('description') + if effectflag & METACHANGED: + effect.append('meta') + if effectflag & USERCHANGED: + effect.append('user') + if effectflag & DATECHANGED: + effect.append('date') + if effectflag & BRANCHCHANGED: + effect.append('branch') + if effectflag & PARENTCHANGED: + effect.append('parent') + if effectflag & DIFFCHANGED: + effect.append('content') + + if effect: + fmteffect = fm.formatlist(effect, 'debugobshistory.effect', sep=', ') + fm.write('debugobshistory.effect', '(%s)', fmteffect) + fm.plain(' by ') fm.write('debugobshistory.marker_user', '%s', metadata['user'], @@ -355,3 +402,205 @@ label="evolve.node") fm.plain("\n") + +# logic around storing and using effect flags +DESCCHANGED = 1 << 0 # action changed the description +METACHANGED = 1 << 1 # action change the meta +PARENTCHANGED = 1 << 2 # action change the parent +DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset +USERCHANGED = 1 << 4 # the user changed +DATECHANGED = 1 << 5 # the date changed +BRANCHCHANGED = 1 << 6 # the branch changed + +METABLACKLIST = [ + re.compile('^__touch-noise__$'), + re.compile('^branch$'), + re.compile('^.*-source$'), + re.compile('^.*_source$'), + re.compile('^source$'), +] + +def ismetablacklisted(metaitem): + """ Check that the key of a meta item (extrakey, extravalue) does not + match at least one of the blacklist pattern + """ + metakey = metaitem[0] + for pattern in METABLACKLIST: + if pattern.match(metakey): + return False + + return True + +def geteffectflag(relation): + """compute the effect flag by comparing the source and destination""" + effects = 0 + + source = relation[0] + + for changectx in relation[1]: + # Check if description has changed + if changectx.description() != source.description(): + effects |= DESCCHANGED + + # Check if known meta has changed + if changectx.user() != source.user(): + effects |= USERCHANGED + + if changectx.date() != source.date(): + effects |= DATECHANGED + + if changectx.branch() != source.branch(): + effects |= BRANCHCHANGED + + # Check if other meta has changed + changeextra = changectx.extra().items() + ctxmeta = filter(ismetablacklisted, changeextra) + + sourceextra = source.extra().items() + srcmeta = filter(ismetablacklisted, sourceextra) + + if ctxmeta != srcmeta: + effects |= METACHANGED + + # Check if at least one of the parent has changes + if changectx.parents() != source.parents(): + effects |= PARENTCHANGED + + if not _cmpdiff(source, changectx): + effects |= DIFFCHANGED + + return effects + +def _prepare_hunk(hunk): + """Drop all information but the username and patch""" + cleanunk = [] + for line in hunk.splitlines(): + if line.startswith(b'# User') or not line.startswith(b'#'): + if line.startswith(b'@@'): + line = b'@@\n' + cleanunk.append(line) + return cleanunk + +def _getdifflines(iterdiff): + """return a cleaned up lines""" + try: + lines = iterdiff.next() + except StopIteration: + return None + return _prepare_hunk(lines) + +def _cmpdiff(leftctx, rightctx): + """return True if both ctx introduce the "same diff" + + This is a first and basic implementation, with many shortcoming. + """ + leftdiff = leftctx.diff(git=1) + rightdiff = rightctx.diff(git=1) + left, right = (0, 0) + while None not in (left, right): + left = _getdifflines(leftdiff) + right = _getdifflines(rightdiff) + + if left != right: + return False + return True + +@eh.wrapfunction(obsolete, 'createmarkers') +def createmarkerswithbits(orig, repo, relations, flag=0, date=None, + metadata=None, **kwargs): + """compute 'effect-flag' and augment the created markers + + Wrap obsolete.createmarker in order to compute the effect of each + relationship and store them as flag in the metadata. + + While we experiment, we store flag in a metadata field. This field is + "versionned" to easilly allow moving to other meaning for flags. + + The comparison of description or other infos just before creating the obs + marker might induce overhead in some cases. However it is a good place to + start since it automatically makes all markers creation recording more + meaningful data. In the future, we can introduce way for commands to + provide precomputed effect to avoid the overhead. + """ + if not repo.ui.configbool('experimental', 'evolution.effect-flags', False): + return orig(repo, relations, flag, date, metadata, **kwargs) + if metadata is None: + metadata = {} + tr = repo.transaction('add-obsolescence-marker') + try: + for r in relations: + # Compute the effect flag for each obsmarker + effect = geteffectflag(r) + + # Copy the metadata in order to add them, we copy because the + # effect flag might be different per relation + m = metadata.copy() + # we store the effect even if "0". This disctinct markers created + # without the feature with markers recording a no-op. + m['ef1'] = "%d" % effect + + # And call obsolete.createmarkers for creating the obsmarker for real + orig(repo, [r], flag, date, m, **kwargs) + + tr.close() + finally: + tr.release() + +def _getobsfate(successorssets): + """ Compute a changeset obsolescence fate based on his successorssets. + Successors can be the tipmost ones or the immediate ones. + Returns one fate in the following list: + - pruned + - diverged + - superseed + - superseed_split + """ + + if len(successorssets) == 0: + # The commit has been pruned + return 'pruned' + elif len(successorssets) > 1: + return 'diverged' + else: + # No divergence, only one set of successors + successors = successorssets[0] + + if len(successors) == 1: + return 'superseed' + else: + return 'superseed_split' + +def _getobsfateandsuccs(repo, revnode, successorssets=None): + """ Return a tuple containing: + - the reason a revision is obsolete (diverged, pruned or superseed) + - the list of successors short node if the revision is neither pruned + or has diverged + """ + if successorssets is None: + successorssets = obsolete.successorssets(repo, revnode) + + fate = _getobsfate(successorssets) + + # Apply node.short if we have no divergence + if len(successorssets) == 1: + successors = [nodemod.short(node_id) for node_id in successorssets[0]] + else: + successors = [] + for succset in successorssets: + successors.append([nodemod.short(node_id) for node_id in succset]) + + return (fate, successors) + +def _humanizedobsfate(fate, successors): + """ Returns a humanized string for a changeset fate and its successors + """ + + if fate == 'pruned': + return 'pruned' + elif fate == 'diverged': + msgs = [] + for successorsset in successors: + msgs.append('superseed as %s' % ','.join(successorsset)) + return ' + '.join(msgs) + elif fate in ('superseed', 'superseed_split'): + return 'superseed as %s' % ','.join(successors) diff --git a/hgext3rd/evolve/stablerange.py b/hgext3rd/evolve/stablerange.py --- a/hgext3rd/evolve/stablerange.py +++ b/hgext3rd/evolve/stablerange.py @@ -196,6 +196,8 @@ depth = stablerange.depthrev length = stablerange.rangelength subranges = stablerange.subranges + if not revs: + raise error.Abort('no revisions specified') repo.stablerange.warmup(repo, max(revs)) if opts['subranges']: ranges = subrangesclosure(repo, revs) @@ -241,7 +243,7 @@ class stablerange(object): - def __init__(self): + def __init__(self, lrusize=2000): # The point up to which we have data in cache self._tiprev = None self._tipnode = None @@ -254,10 +256,10 @@ # and then use the relevant top most part. This order is going to be # the same for all ranges headed at the same merge. So we cache these # value to reuse them accross the same invocation. - self._stablesortcache = {} + self._stablesortcache = util.lrucachedict(lrusize) # something useful to compute the above # mergerev -> stablesort, length - self._stablesortprepared = {} + self._stablesortprepared = util.lrucachedict(lrusize) # caching parent call # as we do so many of them self._parentscache = {} # The first part of the stable sorted list of revision of a merge will @@ -304,12 +306,21 @@ original = set(rangeheap) seen = 0 + # progress report is showing up in the profile for small and fast + # repository so we build that complicated work around. + progress_each = 100 + progress_last = time.time() heapify(rangeheap) while rangeheap: value = heappop(rangeheap) if value in original: - if not seen % 1000: + if not seen % progress_each: + # if a lot of time passed, report more often + progress_new = time.time() + if (1 < progress_each) and (0.1 < progress_new - progress_last): + progress_each /= 10 ui.progress(_("filling stablerange cache"), seen, total=nbrevs) + progress_last = progress_new seen += 1 original.remove(value) # might have been added from other source __, rangeid = value @@ -383,7 +394,14 @@ # note: In the general case we can just walk down and then request # data about the merge. But I'm not sure this function will be even # call for the general case. - allrevs = self._stablesortcache.get(headrev) + + # Lrudict.get in hg-3.9 returns the lrunode instead of the + # value, use __getitem__ instead and catch the exception directly + try: + allrevs = self._stablesortcache[headrev] + except KeyError: + allrevs = None + if allrevs is None: allrevs = self._getrevsfrommerge(repo, headrev) if allrevs is None: @@ -432,8 +450,11 @@ self._stablesortprepared[merge] = (sortedrevs, len(sortedrevs)) def _getrevsfrommerge(self, repo, merge): - prepared = self._stablesortprepared.get(merge) - if prepared is None: + # Lrudict.get in hg-3.9 returns the lrunode instead of the + # value, use __getitem__ instead and catch the exception directly + try: + prepared = self._stablesortprepared[merge] + except KeyError: return None mergedepth = self.depthrev(repo, merge) @@ -731,7 +752,9 @@ _schemaversion = 0 def __init__(self, repo): - super(sqlstablerange, self).__init__() + lrusize = repo.ui.configint('experimental', 'obshashrange.lru-size', + 2000) + super(sqlstablerange, self).__init__(lrusize=lrusize) self._vfs = repo.vfs self._path = repo.vfs.join('cache/evoext_stablerange_v0.sqlite') self._cl = repo.unfiltered().changelog # (okay to keep an old one) @@ -922,6 +945,9 @@ tr = super(stablerangerepo, self).transaction(*args, **kwargs) if not repo.ui.configbool('experimental', 'obshashrange', False): return tr + if not repo.ui.configbool('experimental', 'obshashrange.warm-cache', + True): + return tr reporef = weakref.ref(self) def _warmcache(tr): diff --git a/hgext3rd/evolve/__init__.py b/hgext3rd/evolve/templatekw.py copy from hgext3rd/evolve/__init__.py copy to hgext3rd/evolve/templatekw.py --- a/hgext3rd/evolve/__init__.py +++ b/hgext3rd/evolve/templatekw.py @@ -5,508 +5,20 @@ # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -"""extends Mercurial feature related to Changeset Evolution - -This extension: - -- provides several commands to mutate history and deal with resulting issues, -- enable the changeset-evolution feature for Mercurial, -- improves some aspect of the early implementation in Mercurial core, - -Note that a version dedicated to server usage only (no local working copy) is -available as 'evolve.serveronly'. - -While many feature related to changeset evolution are directly handled by core -this extensions contains significant additions recommended to any user of -changeset evolution. - -With the extensions various evolution events will display warning (new unstable -changesets, obsolete working copy parent, improved error when accessing hidden -revision, etc). - -In addition, the extension contains better discovery protocol for obsolescence -markers. This means less obs-markers will have to be pushed and pulled around, -speeding up such operation. - -Some improvement and bug fixes available in newer version of Mercurial are also -backported to older version of Mercurial by this extension. Some older -experimental protocol are also supported for a longer time in the extensions to -help people transitioning. (The extensions is currently compatible down to -Mercurial version 3.8). - -New Config: - - [experimental] - # Set to control the behavior when pushing draft changesets to a publishing - # repository. Possible value: - # * ignore: current core behavior (default) - # * warn: proceed with the push, but issue a warning - # * abort: abort the push - auto-publish = ignore - -Obsolescence Markers Discovery Experiment -========================================= - -We are experimenting with a new protocol to discover common markers during the -local and remote repository. This experiment is still at an early stage but is -already raising better result than the previous version when usable. - -Large" repositories (hundreds of thousand) are currently unsupported. Some key -algorithm has a naive implementation with too agressive caching, creating -memory consumption issue (this will get fixed). - -Medium sized repositories works fine, but be prepared for a noticable initial -cache filling. for the Mercurial repository, this is around 20 seconds - -The following config control the experiment:: - - [experimental] - - # enable new discovery protocol - # (needed on both client and server) - obshashrange = yes - - # avoid cache warming after transaction - # (recommended 'off' for developer repositories) - # (recommended 'yes' for server (default)) - obshashrange.warm-cache = no +"""evolve templates """ -evolutionhelptext = """ -Obsolescence markers make it possible to mark changesets that have been -deleted or superset in a new version of the changeset. - -Unlike the previous way of handling such changes, by stripping the old -changesets from the repository, obsolescence markers can be propagated -between repositories. This allows for a safe and simple way of exchanging -mutable history and altering it after the fact. Changeset phases are -respected, such that only draft and secret changesets can be altered (see -:hg:`help phases` for details). - -Obsolescence is tracked using "obsolete markers", a piece of metadata -tracking which changesets have been made obsolete, potential successors for -a given changeset, the moment the changeset was marked as obsolete, and the -user who performed the rewriting operation. The markers are stored -separately from standard changeset data can be exchanged without any of the -precursor changesets, preventing unnecessary exchange of obsolescence data. - -The complete set of obsolescence markers describes a history of changeset -modifications that is orthogonal to the repository history of file -modifications. This changeset history allows for detection and automatic -resolution of edge cases arising from multiple users rewriting the same part -of history concurrently. - -Current feature status -====================== - -This feature is still in development. If you see this help, you have enabled an -extension that turned this feature on. - -Obsolescence markers will be exchanged between repositories that explicitly -assert support for the obsolescence feature (this can currently only be done -via an extension).""".strip() - - -import os -import sys -import random -import re -import collections -import errno -import struct - -try: - import StringIO as io - StringIO = io.StringIO -except ImportError: - import io - StringIO = io.StringIO - - -try: - from mercurial import registrar - registrar.templatekeyword # new in hg-3.8 -except ImportError: - from . import metadata - raise ImportError('evolve needs version %s or above' % - min(metadata.testedwith.split())) - -import mercurial -from mercurial import util -from mercurial import repair - -from mercurial import obsolete -if not obsolete._enabled: - obsolete._enabled = True - -from mercurial import ( - bookmarks as bookmarksmod, - cmdutil, - commands, - context, - copies, - dirstate, - error, - extensions, - help, - hg, - lock as lockmod, - merge, - node, - patch, - phases, - revset, - scmutil, - templatekw, - obsolete -) - -from mercurial.commands import walkopts, commitopts, commitopts2, mergetoolopts -from mercurial.i18n import _ -from mercurial.node import nullid - from . import ( - checkheads, - debugcmd, exthelper, - metadata, - obscache, - obsexchange, - safeguard, - utility, obshistory ) -__version__ = metadata.__version__ -testedwith = metadata.testedwith -minimumhgversion = metadata.minimumhgversion -buglink = metadata.buglink - -sha1re = re.compile(r'\b[0-9a-f]{6,40}\b') - -# Flags for enabling optional parts of evolve -commandopt = 'allnewcommands' - -obsexcmsg = utility.obsexcmsg - -colortable = {'evolve.node': 'yellow', - 'evolve.user': 'green', - 'evolve.rev': 'blue', - 'evolve.short_description': '', - 'evolve.date': 'cyan', - 'evolve.current_rev': 'bold', - 'evolve.verb': '', - } - -_pack = struct.pack -_unpack = struct.unpack - -aliases, entry = cmdutil.findcmd('commit', commands.table) -interactiveopt = [['i', 'interactive', None, _('use interactive mode')]] -# This extension contains the following code -# -# - Extension Helper code -# - Obsolescence cache -# - ... -# - Older format compat +from mercurial import ( + templatekw, + node, +) eh = exthelper.exthelper() -eh.merge(debugcmd.eh) -eh.merge(obsexchange.eh) -eh.merge(checkheads.eh) -eh.merge(safeguard.eh) -eh.merge(obscache.eh) -eh.merge(obshistory.eh) -uisetup = eh.final_uisetup -extsetup = eh.final_extsetup -reposetup = eh.final_reposetup -cmdtable = eh.cmdtable - -# pre hg 4.0 compat - -if not util.safehasattr(dirstate.dirstate, 'parentchange'): - import contextlib - - @contextlib.contextmanager - def parentchange(self): - '''Context manager for handling dirstate parents. - - If an exception occurs in the scope of the context manager, - the incoherent dirstate won't be written when wlock is - released. - ''' - self._parentwriters += 1 - yield - # Typically we want the "undo" step of a context manager in a - # finally block so it happens even when an exception - # occurs. In this case, however, we only want to decrement - # parentwriters if the code in the with statement exits - # normally, so we don't have a try/finally here on purpose. - self._parentwriters -= 1 - dirstate.dirstate.parentchange = parentchange - -##################################################################### -### Option configuration ### -##################################################################### - -@eh.reposetup # must be the first of its kin. -def _configureoptions(ui, repo): - # If no capabilities are specified, enable everything. - # This is so existing evolve users don't need to change their config. - evolveopts = ui.configlist('experimental', 'evolution') - if not evolveopts: - evolveopts = ['all'] - ui.setconfig('experimental', 'evolution', evolveopts, 'evolve') - -@eh.uisetup -def _configurecmdoptions(ui): - # Unregister evolve commands if the command capability is not specified. - # - # This must be in the same function as the option configuration above to - # guarantee it happens after the above configuration, but before the - # extsetup functions. - evolvecommands = ui.configlist('experimental', 'evolutioncommands') - evolveopts = ui.configlist('experimental', 'evolution') - if evolveopts and (commandopt not in evolveopts and - 'all' not in evolveopts): - # We build whitelist containing the commands we want to enable - whitelist = set() - for cmd in evolvecommands: - matchingevolvecommands = [e for e in cmdtable.keys() if cmd in e] - if not matchingevolvecommands: - raise error.Abort(_('unknown command: %s') % cmd) - elif len(matchingevolvecommands) > 1: - msg = _('ambiguous command specification: "%s" matches %r') - raise error.Abort(msg % (cmd, matchingevolvecommands)) - else: - whitelist.add(matchingevolvecommands[0]) - for disabledcmd in set(cmdtable) - whitelist: - del cmdtable[disabledcmd] - -##################################################################### -### experimental behavior ### -##################################################################### - -commitopts3 = [ - ('D', 'current-date', None, - _('record the current date as commit date')), - ('U', 'current-user', None, - _('record the current user as committer')), -] - -def _resolveoptions(ui, opts): - """modify commit options dict to handle related options - - For now, all it does is figure out the commit date: respect -D unless - -d was supplied. - """ - # N.B. this is extremely similar to setupheaderopts() in mq.py - if not opts.get('date') and opts.get('current_date'): - opts['date'] = '%d %d' % util.makedate() - if not opts.get('user') and opts.get('current_user'): - opts['user'] = ui.username() - -getrevs = obsolete.getrevs - -##################################################################### -### Additional Utilities ### -##################################################################### - -# This section contains a lot of small utility function and method - -# - Function to create markers -# - useful alias pstatus and pdiff (should probably go in evolve) -# - "troubles" method on changectx -# - function to travel through the obsolescence graph -# - function to find useful changeset to stabilize - - -### Useful alias - -@eh.uisetup -def _installalias(ui): - if ui.config('alias', 'pstatus', None) is None: - ui.setconfig('alias', 'pstatus', 'status --rev .^', 'evolve') - if ui.config('alias', 'pdiff', None) is None: - ui.setconfig('alias', 'pdiff', 'diff --rev .^', 'evolve') - if ui.config('alias', 'odiff', None) is None: - ui.setconfig('alias', 'odiff', - "diff --hidden --rev 'limit(precursors(.),1)' --rev .", - 'evolve') - if ui.config('alias', 'grab', None) is None: - if os.name == 'nt': - ui.setconfig('alias', 'grab', - "! " + util.hgexecutable() - + " rebase --dest . --rev $@ && " - + util.hgexecutable() + " up tip", - 'evolve') - else: - ui.setconfig('alias', 'grab', - "! $HG rebase --dest . --rev $@ && $HG up tip", - 'evolve') - - -### Troubled revset symbol - -@eh.revset('troubled') -def revsettroubled(repo, subset, x): - """``troubled()`` - Changesets with troubles. - """ - revset.getargs(x, 0, 0, 'troubled takes no arguments') - troubled = set() - troubled.update(getrevs(repo, 'unstable')) - troubled.update(getrevs(repo, 'bumped')) - troubled.update(getrevs(repo, 'divergent')) - troubled = revset.baseset(troubled) - troubled.sort() # set is non-ordered, enforce order - return subset & troubled - -### Obsolescence graph - -# XXX SOME MAJOR CLEAN UP TO DO HERE XXX - -def _precursors(repo, s): - """Precursor of a changeset""" - cs = set() - nm = repo.changelog.nodemap - markerbysubj = repo.obsstore.precursors - node = repo.changelog.node - for r in s: - for p in markerbysubj.get(node(r), ()): - pr = nm.get(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.precursors - 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) - nm = repo.changelog.nodemap - cs = set() - for p in seen: - pr = nm.get(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 - nm = repo.changelog.nodemap - markerbyobj = repo.obsstore.successors - for r in s: - for p in markerbyobj.get(node(r), ()): - for sub in p[1]: - sr = nm.get(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) - nm = repo.changelog.nodemap - cs = set() - for s in seen: - sr = nm.get(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.revset('suspended') -def revsetsuspended(repo, subset, x): - """``suspended()`` - Obsolete changesets with non-obsolete descendants. - """ - revset.getargs(x, 0, 0, 'suspended takes no arguments') - suspended = revset.baseset(getrevs(repo, 'suspended')) - suspended.sort() - return subset & suspended - - -@eh.revset('precursors') -def revsetprecursors(repo, subset, x): - """``precursors(set)`` - Immediate precursors of changesets in set. - """ - s = revset.getset(repo, revset.fullreposet(repo), x) - s = revset.baseset(_precursors(repo, s)) - s.sort() - return subset & s - - -@eh.revset('allprecursors') -def revsetallprecursors(repo, subset, x): - """``allprecursors(set)`` - Transitive precursors of changesets in set. - """ - s = revset.getset(repo, revset.fullreposet(repo), x) - s = revset.baseset(_allprecursors(repo, s)) - s.sort() - return subset & s - - -@eh.revset('successors') -def revsetsuccessors(repo, subset, x): - """``successors(set)`` - 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.revset('allsuccessors') -def revsetallsuccessors(repo, subset, x): - """``allsuccessors(set)`` - 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 ### template keywords # XXX it does not handle troubles well :-/ @@ -532,2774 +44,155 @@ return templatekw.showlist('trouble', ctx.troubles(), plural='troubles', **args) -##################################################################### -### Various trouble warning ### -##################################################################### - -# This section take care of issue warning to the user when troubles appear - - -def _getobsoletereason(repo, revnode): - """ Return a tuple containing: - - the reason a revision is obsolete (diverged, pruned or superseed) - - the list of successors short node if the revision is neither pruned - or has diverged - """ - successorssets = obsolete.successorssets(repo, revnode) - - if len(successorssets) == 0: - # The commit has been pruned - return ('pruned', []) - elif len(successorssets) > 1: - return ('diverged', []) - else: - # No divergence, only one set of successors - successors = [node.short(node_id) for node_id in successorssets[0]] - - if len(successors) == 1: - return ('superseed', successors) - else: - return ('superseed_split', successors) - -def _warnobsoletewc(ui, repo): - rev = repo['.'] - - if not rev.obsolete(): - return - - msg = _("working directory parent is obsolete! (%s)\n") - shortnode = node.short(rev.node()) - - ui.warn(msg % shortnode) - - # Check that evolve is activated for performance reasons - if ui.quiet or not obsolete.isenabled(repo, commandopt): - return - - # Show a warning for helping the user to solve the issue - reason, successors = _getobsoletereason(repo, rev.node()) - - if reason == 'pruned': - solvemsg = _("use 'hg evolve' to update to its parent successor") - elif reason == 'diverged': - debugcommand = "hg evolve -list --divergent" - basemsg = _("%s has diverged, use '%s' to resolve the issue") - solvemsg = basemsg % (shortnode, debugcommand) - elif reason == 'superseed': - msg = _("use 'hg evolve' to update to its successor: %s") - solvemsg = msg % successors[0] - elif reason == 'superseed_split': - msg = _("use 'hg evolve' to update to its tipmost successor: %s") - - if len(successors) <= 2: - solvemsg = msg % ", ".join(successors) - else: - firstsuccessors = ", ".join(successors[:2]) - remainingnumber = len(successors) - 2 - successorsmsg = _("%s and %d more") % (firstsuccessors, remainingnumber) - solvemsg = msg % successorsmsg - else: - raise ValueError(reason) - - ui.warn("(%s)\n" % solvemsg) - -if util.safehasattr(context, '_filterederror'): - # if < hg-4.2 we do not update the message - @eh.wrapfunction(context, '_filterederror') - def evolve_filtererror(original, repo, changeid): - """build an exception to be raised about a filtered changeid - - This is extracted in a function to help extensions (eg: evolve) to - experiment with various message variants.""" - if repo.filtername.startswith('visible'): - - unfilteredrepo = repo.unfiltered() - rev = unfilteredrepo[changeid] - reason, successors = _getobsoletereason(unfilteredrepo, rev.node()) - - # Be more precise in cqse the revision is superseed - if reason == 'superseed': - reason = _("successor: %s") % successors[0] - elif reason == 'superseed_split': - if len(successors) <= 2: - reason = _("successors: %s") % ", ".join(successors) - else: - firstsuccessors = ", ".join(successors[:2]) - remainingnumber = len(successors) - 2 - successorsmsg = _("%s and %d more") % (firstsuccessors, remainingnumber) - reason = _("successors: %s") % successorsmsg - - msg = _("hidden revision '%s'") % changeid - hint = _('use --hidden to access hidden revisions; %s') % reason - return error.FilteredRepoLookupError(msg, hint=hint) - msg = _("filtered revision '%s' (not in '%s' subset)") - msg %= (changeid, repo.filtername) - return error.FilteredRepoLookupError(msg) - -@eh.wrapcommand("update") -@eh.wrapcommand("pull") -def wrapmayobsoletewc(origfn, ui, repo, *args, **opts): - """Warn that the working directory parent is an obsolete changeset""" - def warnobsolete(): - _warnobsoletewc(ui, repo) - wlock = None - try: - wlock = repo.wlock() - repo._afterlock(warnobsolete) - res = origfn(ui, repo, *args, **opts) - finally: - lockmod.release(wlock) - return res - -@eh.wrapcommand("parents") -def wrapparents(origfn, ui, repo, *args, **opts): - res = origfn(ui, repo, *args, **opts) - _warnobsoletewc(ui, repo) - return res - -# XXX this could wrap transaction code -# XXX (but this is a bit a layer violation) -@eh.wrapcommand("commit") -@eh.wrapcommand("import") -@eh.wrapcommand("push") -@eh.wrapcommand("pull") -@eh.wrapcommand("graft") -@eh.wrapcommand("phase") -@eh.wrapcommand("unbundle") -def warnobserrors(orig, ui, repo, *args, **kwargs): - """display warning is the command resulted in more instable changeset""" - # part of the troubled stuff may be filtered (stash ?) - # This needs a better implementation but will probably wait for core. - filtered = repo.changelog.filteredrevs - priorunstables = len(set(getrevs(repo, 'unstable')) - filtered) - priorbumpeds = len(set(getrevs(repo, 'bumped')) - filtered) - priordivergents = len(set(getrevs(repo, 'divergent')) - filtered) - ret = orig(ui, repo, *args, **kwargs) - filtered = repo.changelog.filteredrevs - newunstables = \ - len(set(getrevs(repo, 'unstable')) - filtered) - priorunstables - newbumpeds = \ - len(set(getrevs(repo, 'bumped')) - filtered) - priorbumpeds - newdivergents = \ - len(set(getrevs(repo, 'divergent')) - filtered) - priordivergents - if newunstables > 0: - ui.warn(_('%i new unstable changesets\n') % newunstables) - if newbumpeds > 0: - ui.warn(_('%i new bumped changesets\n') % newbumpeds) - if newdivergents > 0: - ui.warn(_('%i new divergent changesets\n') % newdivergents) - return ret - -@eh.wrapfunction(mercurial.exchange, 'push') -def push(orig, repo, *args, **opts): - """Add a hint for "hg evolve" when troubles make push fails - """ - try: - return orig(repo, *args, **opts) - except error.Abort as ex: - hint = _("use 'hg evolve' to get a stable history " - "or --force to ignore warnings") - if (len(ex.args) >= 1 - and ex.args[0].startswith('push includes ') - and ex.hint is None): - ex.hint = hint - raise - -def summaryhook(ui, repo): - def write(fmt, count): - s = fmt % count - if count: - ui.write(s) - else: - ui.note(s) - - state = _evolvestateread(repo) - if state is not None: - # i18n: column positioning for "hg summary" - ui.write(_('evolve: (evolve --continue)\n')) - -@eh.extsetup -def obssummarysetup(ui): - cmdutil.summaryhooks.add('evolve', summaryhook) - - -##################################################################### -### Core Other extension compat ### -##################################################################### - - -@eh.extsetup -def _rebasewrapping(ui): - # warning about more obsolete - try: - rebase = extensions.find('rebase') - if rebase: - extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors) - except KeyError: - pass # rebase not found - try: - histedit = extensions.find('histedit') - if histedit: - extensions.wrapcommand(histedit.cmdtable, 'histedit', warnobserrors) - except KeyError: - pass # histedit not found - -##################################################################### -### Old Evolve extension content ### -##################################################################### - -# XXX need clean up and proper sorting in other section - -### changeset rewriting logic -############################# - -def rewrite(repo, old, updates, head, newbases, commitopts): - """Return (nodeid, created) where nodeid is the identifier of the - changeset generated by the rewrite process, and created is True if - nodeid was actually created. If created is False, nodeid - references a changeset existing before the rewrite call. - """ - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction('rewrite') - if len(old.parents()) > 1: # XXX remove this unnecessary limitation. - raise error.Abort(_('cannot amend merge changesets')) - base = old.p1() - updatebookmarks = _bookmarksupdater(repo, old.node(), tr) - - # commit a new version of the old changeset, including the update - # collect all files which might be affected - files = set(old.files()) - for u in updates: - files.update(u.files()) - - # Recompute copies (avoid recording a -> b -> a) - copied = copies.pathcopies(base, head) - - # prune files which were reverted by the updates - def samefile(f): - if f in head.manifest(): - a = head.filectx(f) - if f in base.manifest(): - b = base.filectx(f) - return (a.data() == b.data() - and a.flags() == b.flags()) - else: - return False - else: - return f not in base.manifest() - files = [f for f in files if not samefile(f)] - # commit version of these files as defined by head - headmf = head.manifest() - - def filectxfn(repo, ctx, path): - if path in headmf: - fctx = head[path] - flags = fctx.flags() - mctx = context.memfilectx(repo, fctx.path(), fctx.data(), - islink='l' in flags, - isexec='x' in flags, - copied=copied.get(path)) - return mctx - return None - - message = cmdutil.logmessage(repo.ui, commitopts) - if not message: - message = old.description() - - user = commitopts.get('user') or old.user() - date = commitopts.get('date') or None # old.date() - extra = dict(commitopts.get('extra', old.extra())) - extra['branch'] = head.branch() - - new = context.memctx(repo, - parents=newbases, - text=message, - files=files, - filectxfn=filectxfn, - user=user, - date=date, - extra=extra) - - if commitopts.get('edit'): - new._text = cmdutil.commitforceeditor(repo, new, []) - revcount = len(repo) - newid = repo.commitctx(new) - new = repo[newid] - created = len(repo) != revcount - updatebookmarks(newid) - - tr.close() - return newid, created - finally: - lockmod.release(tr, lock, wlock) - -class MergeFailure(error.Abort): - pass - -def relocate(repo, orig, dest, pctx=None, keepbranch=False): - """rewrite on dest""" - if orig.rev() == dest.rev(): - raise error.Abort(_('tried to relocate a node on top of itself'), - hint=_("This shouldn't happen. If you still " - "need to move changesets, please do so " - "manually with nothing to rebase - working " - "directory parent is also destination")) - - if pctx is None: - if len(orig.parents()) == 2: - raise error.Abort(_("tried to relocate a merge commit without " - "specifying which parent should be moved"), - hint=_("Specify the parent by passing in pctx")) - pctx = orig.p1() - - commitmsg = orig.description() - - cache = {} - sha1s = re.findall(sha1re, commitmsg) - unfi = repo.unfiltered() - for sha1 in sha1s: - ctx = None - try: - ctx = unfi[sha1] - except error.RepoLookupError: - continue - - if not ctx.obsolete(): - continue - - successors = obsolete.successorssets(repo, ctx.node(), cache) - - # We can't make any assumptions about how to update the hash if the - # cset in question was split or diverged. - if len(successors) == 1 and len(successors[0]) == 1: - newsha1 = node.hex(successors[0][0]) - commitmsg = commitmsg.replace(sha1, newsha1[:len(sha1)]) - else: - repo.ui.note(_('The stale commit message reference to %s could ' - 'not be updated\n') % sha1) - - tr = repo.currenttransaction() - assert tr is not None - try: - r = _evolvemerge(repo, orig, dest, pctx, keepbranch) - if r[-1]: # some conflict - raise error.Abort(_('unresolved merge conflicts ' - '(see hg help resolve)')) - nodenew = _relocatecommit(repo, orig, commitmsg) - except error.Abort as exc: - with repo.dirstate.parentchange(): - repo.setparents(repo['.'].node(), nullid) - repo.dirstate.write(tr) - # fix up dirstate for copies and renames - copies.duplicatecopies(repo, dest.rev(), orig.p1().rev()) - - class LocalMergeFailure(MergeFailure, exc.__class__): - pass - exc.__class__ = LocalMergeFailure - tr.close() # to keep changes in this transaction (e.g. dirstate) - raise - _finalizerelocate(repo, orig, dest, nodenew, tr) - return nodenew - -def _bookmarksupdater(repo, oldid, tr): - """Return a callable update(newid) updating the current bookmark - and bookmarks bound to oldid to newid. - """ - def updatebookmarks(newid): - dirty = False - oldbookmarks = repo.nodebookmarks(oldid) - if oldbookmarks: - for b in oldbookmarks: - repo._bookmarks[b] = newid - dirty = True - if dirty: - repo._bookmarks.recordchange(tr) - return updatebookmarks - -### new command -############################# -metadataopts = [ - ('d', 'date', '', - _('record the specified date in metadata'), _('DATE')), - ('u', 'user', '', - _('record the specified user in metadata'), _('USER')), -] - -@eh.uisetup -def _installimportobsolete(ui): - entry = cmdutil.findcmd('import', commands.table)[1] - entry[1].append(('', 'obsolete', False, - _('mark the old node as obsoleted by ' - 'the created commit'))) - -@eh.wrapfunction(mercurial.cmdutil, 'tryimportone') -def tryimportone(orig, ui, repo, hunk, parents, opts, *args, **kwargs): - extracted = patch.extract(ui, hunk) - expected = extracted.get('nodeid') - if expected is not None: - expected = node.bin(expected) - oldextract = patch.extract - try: - patch.extract = lambda ui, hunk: extracted - ret = orig(ui, repo, hunk, parents, opts, *args, **kwargs) - finally: - patch.extract = oldextract - created = ret[1] - if (opts['obsolete'] and None not in (created, expected) - and created != expected): - tr = repo.transaction('import-obs') - try: - metadata = {'user': ui.username()} - repo.obsstore.create(tr, expected, (created,), - metadata=metadata) - tr.close() - finally: - tr.release() - return ret - - -def _deprecatealias(oldalias, newalias): - '''Deprecates an alias for a command in favour of another - - Creates a new entry in the command table for the old alias. It creates a - wrapper that has its synopsis set to show that is has been deprecated. - The documentation will be replace with a pointer to the new alias. - If a user invokes the command a deprecation warning will be printed and - the command of the *new* alias will be invoked. - - This function is loosely based on the extensions.wrapcommand function. - ''' - try: - aliases, entry = cmdutil.findcmd(newalias, cmdtable) - except error.UnknownCommand: - # Commands may be disabled - return - for alias, e in cmdtable.items(): - if e is entry: - break - - synopsis = '(DEPRECATED)' - if len(entry) > 2: - fn, opts, _syn = entry - else: - fn, opts, = entry - deprecationwarning = _('%s have been deprecated in favor of %s\n') % ( - oldalias, newalias) - - def newfn(*args, **kwargs): - ui = args[0] - ui.warn(deprecationwarning) - util.checksignature(fn)(*args, **kwargs) - newfn.__doc__ = deprecationwarning - cmdwrapper = eh.command(oldalias, opts, synopsis) - cmdwrapper(newfn) - -@eh.extsetup -def deprecatealiases(ui): - _deprecatealias('gup', 'next') - _deprecatealias('gdown', 'previous') - -def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category): - """Resolve the troubles affecting one revision""" - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction("evolve") - if 'unstable' == category: - result = _solveunstable(ui, repo, ctx, dryrun, confirm, progresscb) - elif 'bumped' == category: - result = _solvebumped(ui, repo, ctx, dryrun, confirm, progresscb) - elif 'divergent' == category: - result = _solvedivergent(ui, repo, ctx, dryrun, confirm, - progresscb) - else: - assert False, "unknown trouble category: %s" % (category) - tr.close() - return result - finally: - lockmod.release(tr, lock, wlock) - -def _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat): - """Used by the evolve function to display an error message when - no troubles can be resolved""" - troublecategories = ['bumped', 'divergent', 'unstable'] - unselectedcategories = [c for c in troublecategories if c != targetcat] - msg = None - hint = None - - troubled = { - "unstable": repo.revs("unstable()"), - "divergent": repo.revs("divergent()"), - "bumped": repo.revs("bumped()"), - "all": repo.revs("troubled()"), - } - - hintmap = { - 'bumped': _("do you want to use --bumped"), - 'bumped+divergent': _("do you want to use --bumped or --divergent"), - 'bumped+unstable': _("do you want to use --bumped or --unstable"), - 'divergent': _("do you want to use --divergent"), - 'divergent+unstable': _("do you want to use --divergent" - " or --unstable"), - 'unstable': _("do you want to use --unstable"), - 'any+bumped': _("do you want to use --any (or --rev) and --bumped"), - 'any+bumped+divergent': _("do you want to use --any (or --rev) and" - " --bumped or --divergent"), - 'any+bumped+unstable': _("do you want to use --any (or --rev) and" - "--bumped or --unstable"), - 'any+divergent': _("do you want to use --any (or --rev) and" - " --divergent"), - 'any+divergent+unstable': _("do you want to use --any (or --rev)" - " and --divergent or --unstable"), - 'any+unstable': _("do you want to use --any (or --rev)" - "and --unstable"), - } - - if revopt: - revs = scmutil.revrange(repo, revopt) - if not revs: - msg = _("set of specified revisions is empty") - else: - msg = _("no %s changesets in specified revisions") % targetcat - othertroubles = [] - for cat in unselectedcategories: - if revs & troubled[cat]: - othertroubles.append(cat) - if othertroubles: - hint = hintmap['+'.join(othertroubles)] - - elif anyopt: - msg = _("no %s changesets to evolve") % targetcat - othertroubles = [] - for cat in unselectedcategories: - if troubled[cat]: - othertroubles.append(cat) - if othertroubles: - hint = hintmap['+'.join(othertroubles)] - - else: - # evolve without any option = relative to the current wdir - if targetcat == 'unstable': - msg = _("nothing to evolve on current working copy parent") - else: - msg = _("current working copy parent is not %s") % targetcat - - p1 = repo['.'].rev() - othertroubles = [] - for cat in unselectedcategories: - if p1 in troubled[cat]: - othertroubles.append(cat) - if othertroubles: - hint = hintmap['+'.join(othertroubles)] - else: - l = len(troubled[targetcat]) - if l: - hint = _("%d other %s in the repository, do you want --any " - "or --rev") % (l, targetcat) - else: - othertroubles = [] - for cat in unselectedcategories: - if troubled[cat]: - othertroubles.append(cat) - if othertroubles: - hint = hintmap['any+' + ('+'.join(othertroubles))] - else: - msg = _("no troubled changesets") - - assert msg is not None - ui.write_err("%s\n" % msg) - if hint: - ui.write_err("(%s)\n" % hint) - return 2 - else: - return 1 - -def _cleanup(ui, repo, startnode, showprogress): - if showprogress: - ui.progress(_('evolve'), None) - if repo['.'] != startnode: - ui.status(_('working directory is now at %s\n') % repo['.']) - -class MultipleSuccessorsError(RuntimeError): - """Exception raised by _singlesuccessor when multiple successor sets exists - - The object contains the list of successorssets in its 'successorssets' - attribute to call to easily recover. +def closestprecursors(repo, nodeid): + """ Yield the list of next precursors pointing on visible changectx nodes """ - def __init__(self, successorssets): - self.successorssets = successorssets - -def _singlesuccessor(repo, p): - """returns p (as rev) if not obsolete or its unique latest successors - - fail if there are no such successor""" + precursors = repo.obsstore.precursors + stack = [nodeid] - if not p.obsolete(): - return p.rev() - obs = repo[p] - ui = repo.ui - newer = obsolete.successorssets(repo, obs.node()) - # search of a parent which is not killed - while not newer: - ui.debug("stabilize target %s is plain dead," - " trying to stabilize on its parent\n" % - obs) - obs = obs.parents()[0] - newer = obsolete.successorssets(repo, obs.node()) - if len(newer) > 1 or len(newer[0]) > 1: - raise MultipleSuccessorsError(newer) - - return repo[newer[0][0]].rev() - -def builddependencies(repo, revs): - """returns dependency graphs giving an order to solve instability of revs - (see _orderrevs for more information on usage)""" + while stack: + current = stack.pop() + currentpreccs = precursors.get(current, ()) - # For each troubled revision we keep track of what instability if any should - # be resolved in order to resolve it. Example: - # dependencies = {3: [6], 6:[]} - # Means that: 6 has no dependency, 3 depends on 6 to be solved - dependencies = {} - # rdependencies is the inverted dict of dependencies - rdependencies = collections.defaultdict(set) + for prec in currentpreccs: + precnodeid = prec[0] - for r in revs: - dependencies[r] = set() - for p in repo[r].parents(): - try: - succ = _singlesuccessor(repo, p) - except MultipleSuccessorsError as exc: - dependencies[r] = exc.successorssets - continue - if succ in revs: - dependencies[r].add(succ) - rdependencies[succ].add(r) - return dependencies, rdependencies - -def _dedupedivergents(repo, revs): - """Dedupe the divergents revs in revs to get one from each group with the - lowest revision numbers - """ - repo = repo.unfiltered() - res = set() - # To not reevaluate divergents of the same group once one is encountered - discarded = set() - for rev in revs: - if rev in discarded: - continue - divergent = repo[rev] - base, others = divergentdata(divergent) - othersrevs = [o.rev() for o in others] - res.add(min([divergent.rev()] + othersrevs)) - discarded.update(othersrevs) - return res + if precnodeid in repo: + yield precnodeid + else: + stack.append(precnodeid) -def _selectrevs(repo, allopt, revopt, anyopt, targetcat): - """select troubles in repo matching according to given options""" - revs = set() - if allopt or revopt: - revs = repo.revs("%s()" % targetcat) - if revopt: - revs = scmutil.revrange(repo, revopt) & revs - elif not anyopt: - topic = getattr(repo, 'currenttopic', '') - if topic: - revs = repo.revs('topic(%s)', topic) & revs - elif targetcat == 'unstable': - revs = _aspiringdescendant(repo, - repo.revs('(.::) - obsolete()::')) - revs = set(revs) - if targetcat == 'divergent': - # Pick one divergent per group of divergents - revs = _dedupedivergents(repo, revs) - elif anyopt: - revs = repo.revs('first(%s())' % (targetcat)) - elif targetcat == 'unstable': - revs = set(_aspiringchildren(repo, repo.revs('(.::) - obsolete()::'))) - if 1 < len(revs): - msg = "multiple evolve candidates" - hint = (_("select one of %s with --rev") - % ', '.join([str(repo[r]) for r in sorted(revs)])) - raise error.Abort(msg, hint=hint) - elif targetcat in repo['.'].troubles(): - revs = set([repo['.'].rev()]) - return revs - - -def _orderrevs(repo, revs): - """Compute an ordering to solve instability for the given revs - - revs is a list of unstable revisions. - - Returns the same revisions ordered to solve their instability from the - bottom to the top of the stack that the stabilization process will produce - eventually. - - This ensures the minimal number of stabilizations, as we can stabilize each - revision on its final stabilized destination. +@eh.templatekw("precursors") +def shownextvisibleprecursors(repo, ctx, **args): + """Returns a string containing the list if the closest successors + displayed """ - # Step 1: Build the dependency graph - dependencies, rdependencies = builddependencies(repo, revs) - # Step 2: Build the ordering - # Remove the revisions with no dependency(A) and add them to the ordering. - # Removing these revisions leads to new revisions with no dependency (the - # one depending on A) that we can remove from the dependency graph and add - # to the ordering. We progress in a similar fashion until the ordering is - # built - solvablerevs = collections.deque([r for r in sorted(dependencies.keys()) - if not dependencies[r]]) - ordering = [] - while solvablerevs: - rev = solvablerevs.popleft() - for dependent in rdependencies[rev]: - dependencies[dependent].remove(rev) - if not dependencies[dependent]: - solvablerevs.append(dependent) - del dependencies[rev] - ordering.append(rev) - - ordering.extend(sorted(dependencies)) - return ordering + precursors = sorted(closestprecursors(repo, ctx.node())) -def divergentsets(repo, ctx): - """Compute sets of commits divergent with a given one""" - cache = {} - base = {} - for n in obsolete.allprecursors(repo.obsstore, [ctx.node()]): - if n == ctx.node(): - # a node can't be a base for divergence with itself - continue - nsuccsets = obsolete.successorssets(repo, n, cache) - for nsuccset in nsuccsets: - if ctx.node() in nsuccset: - # we are only interested in *other* successor sets - continue - if tuple(nsuccset) in base: - # we already know the latest base for this divergency - continue - base[tuple(nsuccset)] = n - divergence = [] - for divset, b in base.iteritems(): - divergence.append({ - 'divergentnodes': divset, - 'commonprecursor': b - }) + # <= hg-4.1 requires an explicite gen. + # we can use None once the support is dropped + # + # They also requires an iterator instead of an iterable. + gen = iter(" ".join(map(node.short, precursors))) + return templatekw._hybrid(gen.__iter__(), precursors, lambda x: {'precursor': x}, + lambda d: "%s" % node.short(d['precursor'])) - return divergence - -def _preparelistctxs(items, condition): - return [item.hex() for item in items if condition(item)] +def closestsuccessors(repo, nodeid): + """ returns the closest visible successors sets instead. + """ + return directsuccessorssets(repo, nodeid) -def _formatctx(fm, ctx): - fm.data(node=ctx.hex()) - fm.data(desc=ctx.description()) - fm.data(date=ctx.date()) - fm.data(user=ctx.user()) - -def listtroubles(ui, repo, troublecategories, **opts): - """Print all the troubles for the repo (or given revset)""" - troublecategories = troublecategories or ['divergent', 'unstable', 'bumped'] - showunstable = 'unstable' in troublecategories - showbumped = 'bumped' in troublecategories - showdivergent = 'divergent' in troublecategories - - revs = repo.revs('+'.join("%s()" % t for t in troublecategories)) - if opts.get('rev'): - revs = revs & repo.revs(opts.get('rev')) - - fm = ui.formatter('evolvelist', opts) - for rev in revs: - ctx = repo[rev] - unpars = _preparelistctxs(ctx.parents(), lambda p: p.unstable()) - obspars = _preparelistctxs(ctx.parents(), lambda p: p.obsolete()) - imprecs = _preparelistctxs(repo.set("allprecursors(%n)", ctx.node()), - lambda p: not p.mutable()) - dsets = divergentsets(repo, ctx) - - fm.startitem() - # plain formatter section - hashlen, desclen = 12, 60 - desc = ctx.description() - if desc: - desc = desc.splitlines()[0] - desc = (desc[:desclen] + '...') if len(desc) > desclen else desc - fm.plain('%s: ' % ctx.hex()[:hashlen]) - fm.plain('%s\n' % desc) - fm.data(node=ctx.hex(), rev=ctx.rev(), desc=desc, phase=ctx.phasestr()) +@eh.templatekw("successors") +def shownextvisiblesuccessors(repo, ctx, templ, **args): + """Returns a string of sets of successors for a changectx in this format: + [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2 while + also diverged into ctx3""" + if not ctx.obsolete(): + return '' - for unpar in unpars if showunstable else []: - fm.plain(' unstable: %s (unstable parent)\n' % unpar[:hashlen]) - for obspar in obspars if showunstable else []: - fm.plain(' unstable: %s (obsolete parent)\n' % obspar[:hashlen]) - for imprec in imprecs if showbumped else []: - fm.plain(' bumped: %s (immutable precursor)\n' % imprec[:hashlen]) - - if dsets and showdivergent: - for dset in dsets: - fm.plain(' divergent: ') - first = True - for n in dset['divergentnodes']: - t = "%s (%s)" if first else " %s (%s)" - first = False - fm.plain(t % (node.hex(n)[:hashlen], repo[n].phasestr())) - comprec = node.hex(dset['commonprecursor'])[:hashlen] - fm.plain(" (precursor %s)\n" % comprec) - fm.plain("\n") - - # templater-friendly section - _formatctx(fm, ctx) - troubles = [] - for unpar in unpars: - troubles.append({'troubletype': 'unstable', 'sourcenode': unpar, - 'sourcetype': 'unstableparent'}) - for obspar in obspars: - troubles.append({'troubletype': 'unstable', 'sourcenode': obspar, - 'sourcetype': 'obsoleteparent'}) - for imprec in imprecs: - troubles.append({'troubletype': 'bumped', 'sourcenode': imprec, - 'sourcetype': 'immutableprecursor'}) - for dset in dsets: - divnodes = [{'node': node.hex(n), - 'phase': repo[n].phasestr(), - } for n in dset['divergentnodes']] - troubles.append({'troubletype': 'divergent', - 'commonprecursor': node.hex(dset['commonprecursor']), - 'divergentnodes': divnodes}) - fm.data(troubles=troubles) - - fm.end() + ssets = closestsuccessors(repo, ctx.node()) -@eh.command( - '^evolve|stabilize|solve', - [('n', 'dry-run', False, - _('do not perform actions, just print what would be done')), - ('', 'confirm', False, - _('ask for confirmation before performing the action')), - ('A', 'any', False, - _('also consider troubled changesets unrelated to current working ' - 'directory')), - ('r', 'rev', [], _('solves troubles of these revisions')), - ('', 'bumped', False, _('solves only bumped changesets')), - ('', 'divergent', False, _('solves only divergent changesets')), - ('', 'unstable', False, _('solves only unstable changesets (default)')), - ('a', 'all', False, _('evolve all troubled changesets related to the ' - 'current working directory and its descendants')), - ('c', 'continue', False, _('continue an interrupted evolution')), - ('l', 'list', False, 'provide details on troubled changesets in the repo'), - ] + mergetoolopts, - _('[OPTIONS]...') -) -def evolve(ui, repo, **opts): - """solve troubled changesets in your repository + data = [] + gen = [] + for ss in ssets: + subgen = '[%s]' % ', '.join(map(node.short, ss)) + gen.append(subgen) + h = templatekw._hybrid(iter(subgen), ss, lambda x: {'successor': x}, + lambda d: "%s" % d["successor"]) + data.append(h) - Modifying history can lead to various types of troubled changesets: - unstable, bumped, or divergent. The evolve command resolves your troubles - by executing one of the following actions: - - - update working copy to a successor - - rebase an unstable changeset - - extract the desired changes from a bumped changeset - - fuse divergent changesets back together - - If you pass no arguments, evolve works in automatic mode: it will execute a - single action to reduce instability related to your working copy. There are - two cases for this action. First, if the parent of your working copy is - obsolete, evolve updates to the parent's successor. Second, if the working - copy parent is not obsolete but has obsolete predecessors, then evolve - determines if there is an unstable changeset that can be rebased onto the - working copy parent in order to reduce instability. - If so, evolve rebases that changeset. If not, evolve refuses to guess your - intention, and gives a hint about what you might want to do next. + gen = ', '.join(gen) + return templatekw._hybrid(iter(gen), data, lambda x: {'successorset': x}, + lambda d: d["successorset"]) - Any time evolve creates a changeset, it updates the working copy to the new - changeset. (Currently, every successful evolve operation involves an update - as well; this may change in future.) - - Automatic mode only handles common use cases. For example, it avoids taking - action in the case of ambiguity, and it ignores unstable changesets that - are not related to your working copy. - It also refuses to solve bumped or divergent changesets unless you explicity - request such behavior (see below). - - Eliminating all instability around your working copy may require multiple - invocations of :hg:`evolve`. Alternately, use ``--all`` to recursively - select and evolve all unstable changesets that can be rebased onto the - working copy parent. - This is more powerful than successive invocations, since ``--all`` handles - ambiguous cases (e.g. unstable changesets with multiple children) by - evolving all branches. +@eh.templatekw("obsfate_quiet") +def showobsfate_quiet(repo, ctx, templ, **args): + if not ctx.obsolete(): + return '' - When your repository cannot be handled by automatic mode, you might need to - use ``--rev`` to specify a changeset to evolve. For example, if you have - an unstable changeset that is not related to the working copy parent, - you could use ``--rev`` to evolve it. Or, if some changeset has multiple - unstable children, evolve in automatic mode refuses to guess which one to - evolve; you have to use ``--rev`` in that case. - - Alternately, ``--any`` makes evolve search for the next evolvable changeset - regardless of whether it is related to the working copy parent. + successorssets = closestsuccessors(repo, ctx.node()) + return obshistory._humanizedobsfate(*obshistory._getobsfateandsuccs(repo, ctx, successorssets)) - You can supply multiple revisions to evolve multiple troubled changesets - in a single invocation. In revset terms, ``--any`` is equivalent to ``--rev - first(unstable())``. ``--rev`` and ``--all`` are mutually exclusive, as are - ``--rev`` and ``--any``. - - ``hg evolve --any --all`` is useful for cleaning up instability across all - branches, letting evolve figure out the appropriate order and destination. +# copy from mercurial.obsolete with a small change to stop at first known changeset. - When you have troubled changesets that are not unstable, :hg:`evolve` - refuses to consider them unless you specify the category of trouble you - wish to resolve, with ``--bumped`` or ``--divergent``. These options are - currently mutually exclusive with each other and with ``--unstable`` - (the default). You can combine ``--bumped`` or ``--divergent`` with - ``--rev``, ``--all``, or ``--any``. - - You can also use the evolve command to list the troubles affecting your - repository by using the --list flag. You can choose to display only some - categories of troubles with the --unstable, --divergent or --bumped flags. +def directsuccessorssets(repo, initialnode, cache=None): + """return set of all direct successors of initial nodes """ - # Options - listopt = opts['list'] - contopt = opts['continue'] - anyopt = opts['any'] - allopt = opts['all'] - startnode = repo['.'] - dryrunopt = opts['dry_run'] - confirmopt = opts['confirm'] - revopt = opts['rev'] - troublecategories = ['bumped', 'divergent', 'unstable'] - specifiedcategories = [t for t in troublecategories if opts[t]] - if listopt: - listtroubles(ui, repo, specifiedcategories, **opts) - return - - targetcat = 'unstable' - if 1 < len(specifiedcategories): - msg = _('cannot specify more than one trouble category to solve (yet)') - raise error.Abort(msg) - elif len(specifiedcategories) == 1: - targetcat = specifiedcategories[0] - elif repo['.'].obsolete(): - displayer = cmdutil.show_changeset(ui, repo, - {'template': shorttemplate}) - # no args and parent is obsolete, update to successors - try: - ctx = repo[_singlesuccessor(repo, repo['.'])] - except MultipleSuccessorsError as exc: - repo.ui.write_err('parent is obsolete with multiple successors:\n') - for ln in exc.successorssets: - for n in ln: - displayer.show(repo[n]) - return 2 - - ui.status(_('update:')) - if not ui.quiet: - displayer.show(ctx) - - if dryrunopt: - return 0 - res = hg.update(repo, ctx.rev()) - if ctx != startnode: - ui.status(_('working directory is now at %s\n') % ctx) - return res - - ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'evolve') - troubled = set(repo.revs('troubled()')) - - # Progress handling - seen = 1 - count = allopt and len(troubled) or 1 - showprogress = allopt - - def progresscb(): - if revopt or allopt: - ui.progress(_('evolve'), seen, unit=_('changesets'), total=count) - - # Continuation handling - if contopt: - if anyopt: - raise error.Abort('cannot specify both "--any" and "--continue"') - if allopt: - raise error.Abort('cannot specify both "--all" and "--continue"') - state = _evolvestateread(repo) - if state is None: - raise error.Abort('no evolve to continue') - orig = repo[state['current']] - # XXX This is a terrible terrible hack, please get rid of it. - lock = repo.wlock() - try: - repo.vfs.write('graftstate', orig.hex() + '\n') - try: - graftcmd = commands.table['graft'][0] - ret = graftcmd(ui, repo, old_obsolete=True, **{'continue': True}) - _evolvestatedelete(repo) - return ret - finally: - util.unlinkpath(repo.vfs.join('graftstate'), ignoremissing=True) - finally: - lock.release() - cmdutil.bailifchanged(repo) - - if revopt and allopt: - raise error.Abort('cannot specify both "--rev" and "--all"') - if revopt and anyopt: - raise error.Abort('cannot specify both "--rev" and "--any"') - - revs = _selectrevs(repo, allopt, revopt, anyopt, targetcat) - - if not revs: - return _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat) - - # For the progress bar to show - count = len(revs) - # Order the revisions - if targetcat == 'unstable': - revs = _orderrevs(repo, revs) - for rev in revs: - progresscb() - _solveone(ui, repo, repo[rev], dryrunopt, confirmopt, - progresscb, targetcat) - seen += 1 - progresscb() - _cleanup(ui, repo, startnode, showprogress) - -def _possibledestination(repo, rev): - """return all changesets that may be a new parent for REV""" - tonode = repo.changelog.node - parents = repo.changelog.parentrevs - torev = repo.changelog.rev - dest = set() - tovisit = list(parents(rev)) - while tovisit: - r = tovisit.pop() - succsets = obsolete.successorssets(repo, tonode(r)) - if not succsets: - tovisit.extend(parents(r)) - else: - # We should probably pick only one destination from split - # (case where '1 < len(ss)'), This could be the currently tipmost - # but logic is less clear when result of the split are now on - # multiple branches. - for ss in succsets: - for n in ss: - dest.add(torev(n)) - return dest - -def _aspiringchildren(repo, revs): - """Return a list of changectx which can be stabilized on top of pctx or - one of its descendants. Empty list if none can be found.""" - target = set(revs) - result = [] - for r in repo.revs('unstable() - %ld', revs): - dest = _possibledestination(repo, r) - if target & dest: - result.append(r) - return result - -def _aspiringdescendant(repo, revs): - """Return a list of changectx which can be stabilized on top of pctx or - one of its descendants recursively. Empty list if none can be found.""" - target = set(revs) - result = set(target) - paths = collections.defaultdict(set) - for r in repo.revs('unstable() - %ld', revs): - for d in _possibledestination(repo, r): - paths[d].add(r) - - result = set(target) - tovisit = list(revs) - while tovisit: - base = tovisit.pop() - for unstable in paths[base]: - if unstable not in result: - tovisit.append(unstable) - result.add(unstable) - return sorted(result - target) - -def _solveunstable(ui, repo, orig, dryrun=False, confirm=False, - progresscb=None): - """Stabilize an unstable changeset""" - pctx = orig.p1() - if len(orig.parents()) == 2: - if not pctx.obsolete(): - pctx = orig.p2() # second parent is obsolete ? - elif orig.p2().obsolete(): - hint = _("Redo the merge (%s) and use `hg prune " - "--succ ` to obsolete the old one") % orig.hex()[:12] - ui.warn(_("warning: no support for evolving merge changesets " - "with two obsolete parents yet\n") + - _("(%s)\n") % hint) - return False - - if not pctx.obsolete(): - ui.warn(_("cannot solve instability of %s, skipping\n") % orig) - return False - obs = pctx - newer = obsolete.successorssets(repo, obs.node()) - # search of a parent which is not killed - while not newer or newer == [()]: - ui.debug("stabilize target %s is plain dead," - " trying to stabilize on its parent\n" % - obs) - obs = obs.parents()[0] - newer = obsolete.successorssets(repo, obs.node()) - if len(newer) > 1: - msg = _("skipping %s: divergent rewriting. can't choose " - "destination\n") % obs - ui.write_err(msg) - return 2 - targets = newer[0] - assert targets - if len(targets) > 1: - # split target, figure out which one to pick, are they all in line? - targetrevs = [repo[r].rev() for r in targets] - roots = repo.revs('roots(%ld)', targetrevs) - heads = repo.revs('heads(%ld)', targetrevs) - if len(roots) > 1 or len(heads) > 1: - msg = "cannot solve split accross two branches\n" - ui.write_err(msg) - return 2 - target = repo[heads.first()] - else: - target = targets[0] - displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) - target = repo[target] - if not ui.quiet or confirm: - repo.ui.write(_('move:')) - displayer.show(orig) - repo.ui.write(_('atop:')) - displayer.show(target) - if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y': - raise error.Abort(_('evolve aborted by user')) - if progresscb: - progresscb() - todo = 'hg rebase -r %s -d %s\n' % (orig, target) - if dryrun: - repo.ui.write(todo) - else: - repo.ui.note(todo) - if progresscb: - progresscb() - keepbranch = orig.p1().branch() != orig.branch() - try: - relocate(repo, orig, target, pctx, keepbranch) - except MergeFailure: - _evolvestatewrite(repo, {'current': orig.node()}) - repo.ui.write_err(_('evolve failed!\n')) - repo.ui.write_err( - _("fix conflict and run 'hg evolve --continue'" - " or use 'hg update -C .' to abort\n")) - raise - -def _solvebumped(ui, repo, bumped, dryrun=False, confirm=False, - progresscb=None): - """Stabilize a bumped changeset""" - repo = repo.unfiltered() - bumped = repo[bumped.rev()] - # For now we deny bumped merge - if len(bumped.parents()) > 1: - msg = _('skipping %s : we do not handle merge yet\n') % bumped - ui.write_err(msg) - return 2 - prec = repo.set('last(allprecursors(%d) and public())', bumped).next() - # For now we deny target merge - if len(prec.parents()) > 1: - msg = _('skipping: %s: public version is a merge, ' - 'this is not handled yet\n') % prec - ui.write_err(msg) - return 2 - - displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) - if not ui.quiet or confirm: - repo.ui.write(_('recreate:')) - displayer.show(bumped) - repo.ui.write(_('atop:')) - displayer.show(prec) - if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y': - raise error.Abort(_('evolve aborted by user')) - if dryrun: - todo = 'hg rebase --rev %s --dest %s;\n' % (bumped, prec.p1()) - repo.ui.write(todo) - repo.ui.write(('hg update %s;\n' % prec)) - repo.ui.write(('hg revert --all --rev %s;\n' % bumped)) - repo.ui.write(('hg commit --msg "bumped update to %s"')) - return 0 - if progresscb: - progresscb() - newid = tmpctx = None - tmpctx = bumped - # Basic check for common parent. Far too complicated and fragile - tr = repo.currenttransaction() - assert tr is not None - bmupdate = _bookmarksupdater(repo, bumped.node(), tr) - if not list(repo.set('parents(%d) and parents(%d)', bumped, prec)): - # Need to rebase the changeset at the right place - repo.ui.status( - _('rebasing to destination parent: %s\n') % prec.p1()) - try: - tmpid = relocate(repo, bumped, prec.p1()) - if tmpid is not None: - tmpctx = repo[tmpid] - obsolete.createmarkers(repo, [(bumped, (tmpctx,))]) - except MergeFailure: - repo.vfs.write('graftstate', bumped.hex() + '\n') - repo.ui.write_err(_('evolution failed!\n')) - msg = _("fix conflict and run 'hg evolve --continue'\n") - repo.ui.write_err(msg) - raise - # Create the new commit context - repo.ui.status(_('computing new diff\n')) - files = set() - copied = copies.pathcopies(prec, bumped) - precmanifest = prec.manifest().copy() - # 3.3.2 needs a list. - # future 3.4 don't detect the size change during iteration - # this is fishy - for key, val in list(bumped.manifest().iteritems()): - precvalue = precmanifest.get(key, None) - if precvalue is not None: - del precmanifest[key] - if precvalue != val: - files.add(key) - files.update(precmanifest) # add missing files - # commit it - if files: # something to commit! - def filectxfn(repo, ctx, path): - if path in bumped: - fctx = bumped[path] - flags = fctx.flags() - mctx = context.memfilectx(repo, fctx.path(), fctx.data(), - islink='l' in flags, - isexec='x' in flags, - copied=copied.get(path)) - return mctx - return None - text = 'bumped update to %s:\n\n' % prec - text += bumped.description() - - new = context.memctx(repo, - parents=[prec.node(), node.nullid], - text=text, - files=files, - filectxfn=filectxfn, - user=bumped.user(), - date=bumped.date(), - extra=bumped.extra()) - - newid = repo.commitctx(new) - if newid is None: - obsolete.createmarkers(repo, [(tmpctx, ())]) - newid = prec.node() - else: - phases.retractboundary(repo, tr, bumped.phase(), [newid]) - obsolete.createmarkers(repo, [(tmpctx, (repo[newid],))], - flag=obsolete.bumpedfix) - bmupdate(newid) - repo.ui.status(_('committed as %s\n') % node.short(newid)) - # reroute the working copy parent to the new changeset - with repo.dirstate.parentchange(): - repo.dirstate.setparents(newid, node.nullid) - -def _solvedivergent(ui, repo, divergent, dryrun=False, confirm=False, - progresscb=None): - repo = repo.unfiltered() - divergent = repo[divergent.rev()] - base, others = divergentdata(divergent) - if len(others) > 1: - othersstr = "[%s]" % (','.join([str(i) for i in others])) - msg = _("skipping %d:divergent with a changeset that got splitted" - " into multiple ones:\n" - "|[%s]\n" - "| This is not handled by automatic evolution yet\n" - "| You have to fallback to manual handling with commands " - "such as:\n" - "| - hg touch -D\n" - "| - hg prune\n" - "| \n" - "| You should contact your local evolution Guru for help.\n" - ) % (divergent, othersstr) - ui.write_err(msg) - return 2 - other = others[0] - if len(other.parents()) > 1: - msg = _("skipping %s: divergent changeset can't be " - "a merge (yet)\n") % divergent - ui.write_err(msg) - hint = _("You have to fallback to solving this by hand...\n" - "| This probably means redoing the merge and using \n" - "| `hg prune` to kill older version.\n") - ui.write_err(hint) - return 2 - if other.p1() not in divergent.parents(): - msg = _("skipping %s: have a different parent than %s " - "(not handled yet)\n") % (divergent, other) - hint = _("| %(d)s, %(o)s are not based on the same changeset.\n" - "| With the current state of its implementation, \n" - "| evolve does not work in that case.\n" - "| rebase one of them next to the other and run \n" - "| this command again.\n" - "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n" - "| - or: hg rebase --dest 'p1(%(o)s)' -r %(d)s\n" - ) % {'d': divergent, 'o': other} - ui.write_err(msg) - ui.write_err(hint) - return 2 - - displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) - if not ui.quiet or confirm: - ui.write(_('merge:')) - displayer.show(divergent) - ui.write(_('with: ')) - displayer.show(other) - ui.write(_('base: ')) - displayer.show(base) - if confirm and ui.prompt(_('perform evolve? [Ny]'), 'n') != 'y': - raise error.Abort(_('evolve aborted by user')) - if dryrun: - ui.write(('hg update -c %s &&\n' % divergent)) - ui.write(('hg merge %s &&\n' % other)) - ui.write(('hg commit -m "auto merge resolving conflict between ' - '%s and %s"&&\n' % (divergent, other))) - ui.write(('hg up -C %s &&\n' % base)) - ui.write(('hg revert --all --rev tip &&\n')) - ui.write(('hg commit -m "`hg log -r %s --template={desc}`";\n' - % divergent)) - return - if divergent not in repo[None].parents(): - repo.ui.status(_('updating to "local" conflict\n')) - hg.update(repo, divergent.rev()) - repo.ui.note(_('merging divergent changeset\n')) - if progresscb: - progresscb() - stats = merge.update(repo, - other.node(), - branchmerge=True, - force=False, - ancestor=base.node(), - mergeancestor=True) - hg._showstats(repo, stats) - if stats[3]: - repo.ui.status(_("use 'hg resolve' to retry unresolved file merges " - "or 'hg update -C .' to abort\n")) - if stats[3] > 0: - raise error.Abort('merge conflict between several amendments ' - '(this is not automated yet)', - hint="""/!\ You can try: -/!\ * manual merge + resolve => new cset X -/!\ * hg up to the parent of the amended changeset (which are named W and Z) -/!\ * hg revert --all -r X -/!\ * hg ci -m "same message as the amended changeset" => new cset Y -/!\ * hg prune -n Y W Z -""") - if progresscb: - progresscb() - emtpycommitallowed = repo.ui.backupconfig('ui', 'allowemptycommit') - tr = repo.currenttransaction() - assert tr is not None - try: - repo.ui.setconfig('ui', 'allowemptycommit', True, 'evolve') - with repo.dirstate.parentchange(): - repo.dirstate.setparents(divergent.node(), node.nullid) - oldlen = len(repo) - amend(ui, repo, message='', logfile='') - if oldlen == len(repo): - new = divergent - # no changes - else: - new = repo['.'] - obsolete.createmarkers(repo, [(other, (new,))]) - phases.retractboundary(repo, tr, other.phase(), [new.node()]) - finally: - repo.ui.restoreconfig(emtpycommitallowed) - -def divergentdata(ctx): - """return base, other part of a conflict - - This only return the first one. - - XXX this woobly function won't survive XXX - """ - repo = ctx._repo.unfiltered() - for base in repo.set('reverse(allprecursors(%d))', ctx): - newer = obsolete.successorssets(ctx._repo, base.node()) - # drop filter and solution including the original ctx - newer = [n for n in newer if n and ctx.node() not in n] - if newer: - return base, tuple(ctx._repo[o] for o in newer[0]) - raise error.Abort("base of divergent changeset %s not found" % ctx, - hint='this case is not yet handled') - -shorttemplate = "[{label('evolve.rev', rev)}] {desc|firstline}\n" - -@eh.command( - '^previous', - [('B', 'move-bookmark', False, - _('move active bookmark after update')), - ('', 'merge', False, _('bring uncommitted change along')), - ('', 'no-topic', False, _('ignore topic and move topologically')), - ('n', 'dry-run', False, - _('do not perform actions, just print what would be done'))], - '[OPTION]...') -def cmdprevious(ui, repo, **opts): - """update to parent revision - - Displays the summary line of the destination for clarity.""" - wlock = None - dryrunopt = opts['dry_run'] - if not dryrunopt: - wlock = repo.wlock() - try: - wkctx = repo[None] - wparents = wkctx.parents() - if len(wparents) != 1: - raise error.Abort('merge in progress') - if not opts['merge']: - try: - cmdutil.bailifchanged(repo) - except error.Abort as exc: - exc.hint = _('do you want --merge?') - raise - - parents = wparents[0].parents() - topic = getattr(repo, 'currenttopic', '') - if topic and not opts.get("no_topic", False): - parents = [ctx for ctx in parents if ctx.topic() == topic] - displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) - if not parents: - ui.warn(_('no parent in topic "%s"\n') % topic) - ui.warn(_('(do you want --no-topic)\n')) - elif len(parents) == 1: - p = parents[0] - bm = repo._activebookmark - shouldmove = opts.get('move_bookmark') and bm is not None - if dryrunopt: - ui.write(('hg update %s;\n' % p.rev())) - if shouldmove: - ui.write(('hg bookmark %s -r %s;\n' % (bm, p.rev()))) - else: - ret = hg.update(repo, p.rev()) - if not ret: - tr = lock = None - try: - lock = repo.lock() - tr = repo.transaction('previous') - if shouldmove: - repo._bookmarks[bm] = p.node() - repo._bookmarks.recordchange(tr) - else: - bookmarksmod.deactivate(repo) - tr.close() - finally: - lockmod.release(tr, lock) - - displayer.show(p) - return 0 - else: - for p in parents: - displayer.show(p) - ui.warn(_('multiple parents, explicitly update to one\n')) - return 1 - finally: - lockmod.release(wlock) - -@eh.command( - '^next', - [('B', 'move-bookmark', False, - _('move active bookmark after update')), - ('', 'merge', False, _('bring uncommitted change along')), - ('', 'evolve', False, _('evolve the next changeset if necessary')), - ('', 'no-topic', False, _('ignore topic and move topologically')), - ('n', 'dry-run', False, - _('do not perform actions, just print what would be done'))], - '[OPTION]...') -def cmdnext(ui, repo, **opts): - """update to next child revision - - Use the ``--evolve`` flag to evolve unstable children on demand. - - Displays the summary line of the destination for clarity. - """ - wlock = None - dryrunopt = opts['dry_run'] - if not dryrunopt: - wlock = repo.wlock() - try: - wkctx = repo[None] - wparents = wkctx.parents() - if len(wparents) != 1: - raise error.Abort('merge in progress') - if not opts['merge']: - try: - cmdutil.bailifchanged(repo) - except error.Abort as exc: - exc.hint = _('do you want --merge?') - raise - - children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()] - topic = getattr(repo, 'currenttopic', '') - filtered = [] - if topic and not opts.get("no_topic", False): - filtered = [ctx for ctx in children if ctx.topic() != topic] - # XXX N-square membership on children - children = [ctx for ctx in children if ctx not in filtered] - displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) - if len(children) == 1: - c = children[0] - bm = repo._activebookmark - shouldmove = opts.get('move_bookmark') and bm is not None - if dryrunopt: - ui.write(('hg update %s;\n' % c.rev())) - if shouldmove: - ui.write(('hg bookmark %s -r %s;\n' % (bm, c.rev()))) - else: - ret = hg.update(repo, c.rev()) - if not ret: - lock = tr = None - try: - lock = repo.lock() - tr = repo.transaction('next') - if shouldmove: - repo._bookmarks[bm] = c.node() - repo._bookmarks.recordchange(tr) - else: - bookmarksmod.deactivate(repo) - tr.close() - finally: - lockmod.release(tr, lock) - displayer.show(c) - result = 0 - elif children: - ui.warn(_("ambigious next changeset:\n")) - for c in children: - displayer.show(c) - ui.warn(_('explicitly update to one of them\n')) - result = 1 - else: - aspchildren = _aspiringchildren(repo, [repo['.'].rev()]) - if topic: - filtered.extend(repo[c] for c in children - if repo[c].topic() != topic) - # XXX N-square membership on children - aspchildren = [ctx for ctx in aspchildren if ctx not in filtered] - if not opts['evolve'] or not aspchildren: - if filtered: - ui.warn(_('no children on topic "%s"\n') % topic) - ui.warn(_('do you want --no-topic\n')) - else: - ui.warn(_('no children\n')) - if aspchildren: - msg = _('(%i unstable changesets to be evolved here, ' - 'do you want --evolve?)\n') - ui.warn(msg % len(aspchildren)) - result = 1 - elif 1 < len(aspchildren): - ui.warn(_("ambigious next (unstable) changeset:\n")) - for c in aspchildren: - displayer.show(repo[c]) - ui.warn(_("(run 'hg evolve --rev REV' on one of them)\n")) - return 1 - else: - cmdutil.bailifchanged(repo) - result = _solveone(ui, repo, repo[aspchildren[0]], dryrunopt, - False, lambda: None, category='unstable') - if not result: - ui.status(_('working directory now at %s\n') - % ui.label(str(repo['.']), 'evolve.node')) - return result - return 1 - return result - finally: - lockmod.release(wlock) - -def _reachablefrombookmark(repo, revs, bookmarks): - """filter revisions and bookmarks reachable from the given bookmark - yoinked from mq.py - """ - repomarks = repo._bookmarks - if not bookmarks.issubset(repomarks): - raise error.Abort(_("bookmark '%s' not found") % - ','.join(sorted(bookmarks - set(repomarks.keys())))) - - # If the requested bookmark is not the only one pointing to a - # a revision we have to only delete the bookmark and not strip - # anything. revsets cannot detect that case. - nodetobookmarks = {} - for mark, bnode in repomarks.iteritems(): - nodetobookmarks.setdefault(bnode, []).append(mark) - for marks in nodetobookmarks.values(): - if bookmarks.issuperset(marks): - rsrevs = repair.stripbmrevset(repo, marks[0]) - revs = set(revs) - revs.update(set(rsrevs)) - revs = sorted(revs) - return repomarks, revs - -def _deletebookmark(repo, repomarks, bookmarks): - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction('prune') - for bookmark in bookmarks: - del repomarks[bookmark] - repomarks.recordchange(tr) - tr.close() - for bookmark in sorted(bookmarks): - repo.ui.write(_("bookmark '%s' deleted\n") % bookmark) - finally: - lockmod.release(tr, lock, wlock) - -def _getmetadata(**opts): - metadata = {} - date = opts.get('date') - user = opts.get('user') - if date: - metadata['date'] = '%i %i' % util.parsedate(date) - if user: - metadata['user'] = user - return metadata - -@eh.command( - '^prune|obsolete', - [('n', 'new', [], _("successor changeset (DEPRECATED)")), - ('s', 'succ', [], _("successor changeset")), - ('r', 'rev', [], _("revisions to prune")), - ('k', 'keep', None, _("does not modify working copy during prune")), - ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")), - ('', 'fold', False, - _("record a fold (multiple precursors, one successors)")), - ('', 'split', False, - _("record a split (on precursor, multiple successors)")), - ('B', 'bookmark', [], _("remove revs only reachable from given" - " bookmark"))] + metadataopts, - _('[OPTION] [-r] REV...')) -# XXX -U --noupdate option to prevent wc update and or bookmarks update ? -def cmdprune(ui, repo, *revs, **opts): - """hide changesets by marking them obsolete - - Pruned changesets are obsolete with no successors. If they also have no - descendants, they are hidden (invisible to all commands). - - Non-obsolete descendants of pruned changesets become "unstable". Use :hg:`evolve` - to handle this situation. - - When you prune the parent of your working copy, Mercurial updates the working - copy to a non-obsolete parent. - - You can use ``--succ`` to tell Mercurial that a newer version (successor) of the - pruned changeset exists. Mercurial records successor revisions in obsolescence - markers. - - You can use the ``--biject`` option to specify a 1-1 mapping (bijection) between - revisions to pruned (precursor) and successor changesets. This option may be - removed in a future release (with the functionality provided automatically). - - If you specify multiple revisions in ``--succ``, you are recording a "split" and - must acknowledge it by passing ``--split``. Similarly, when you prune multiple - changesets with a single successor, you must pass the ``--fold`` option. - """ - revs = scmutil.revrange(repo, list(revs) + opts.get('rev')) - succs = opts['new'] + opts['succ'] - bookmarks = set(opts.get('bookmark')) - metadata = _getmetadata(**opts) - biject = opts.get('biject') - fold = opts.get('fold') - split = opts.get('split') - - options = [o for o in ('biject', 'fold', 'split') if opts.get(o)] - if 1 < len(options): - raise error.Abort(_("can only specify one of %s") % ', '.join(options)) - - if bookmarks: - repomarks, revs = _reachablefrombookmark(repo, revs, bookmarks) - if not revs: - # no revisions to prune - delete bookmark immediately - _deletebookmark(repo, repomarks, bookmarks) - - if not revs: - raise error.Abort(_('nothing to prune')) - - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction('prune') - # defines pruned changesets - precs = [] - revs.sort() - for p in revs: - cp = repo[p] - if not cp.mutable(): - # note: createmarkers() would have raised something anyway - raise error.Abort('cannot prune immutable changeset: %s' % cp, - hint="see 'hg help phases' for details") - precs.append(cp) - if not precs: - raise error.Abort('nothing to prune') - - if _disallowednewunstable(repo, revs): - raise error.Abort(_("cannot prune in the middle of a stack"), - hint=_("new unstable changesets are not allowed")) - - # defines successors changesets - sucs = scmutil.revrange(repo, succs) - sucs.sort() - sucs = tuple(repo[n] for n in sucs) - if not biject and len(sucs) > 1 and len(precs) > 1: - msg = "Can't use multiple successors for multiple precursors" - hint = _("use --biject to mark a series as a replacement" - " for another") - raise error.Abort(msg, hint=hint) - elif biject and len(sucs) != len(precs): - msg = "Can't use %d successors for %d precursors" \ - % (len(sucs), len(precs)) - raise error.Abort(msg) - elif (len(precs) == 1 and len(sucs) > 1) and not split: - msg = "please add --split if you want to do a split" - raise error.Abort(msg) - elif len(sucs) == 1 and len(precs) > 1 and not fold: - msg = "please add --fold if you want to do a fold" - raise error.Abort(msg) - elif biject: - relations = [(p, (s,)) for p, s in zip(precs, sucs)] - else: - relations = [(p, sucs) for p in precs] - - wdp = repo['.'] - - if len(sucs) == 1 and len(precs) == 1 and wdp in precs: - # '.' killed, so update to the successor - newnode = sucs[0] - else: - # update to an unkilled parent - newnode = wdp - - while newnode in precs or newnode.obsolete(): - newnode = newnode.parents()[0] - - if newnode.node() != wdp.node(): - if opts.get('keep', False): - # This is largely the same as the implementation in - # strip.stripcmd(). We might want to refactor this somewhere - # common at some point. - - # only reset the dirstate for files that would actually change - # between the working context and uctx - descendantrevs = repo.revs("%d::." % newnode.rev()) - changedfiles = [] - for rev in descendantrevs: - # blindly reset the files, regardless of what actually - # changed - changedfiles.extend(repo[rev].files()) - - # reset files that only changed in the dirstate too - dirstate = repo.dirstate - dirchanges = [f for f in dirstate if dirstate[f] != 'n'] - changedfiles.extend(dirchanges) - repo.dirstate.rebuild(newnode.node(), newnode.manifest(), - changedfiles) - dirstate.write(tr) - else: - bookactive = repo._activebookmark - # Active bookmark that we don't want to delete (with -B option) - # we deactivate and move it before the update and reactivate it - # after - movebookmark = bookactive and not bookmarks - if movebookmark: - bookmarksmod.deactivate(repo) - repo._bookmarks[bookactive] = newnode.node() - repo._bookmarks.recordchange(tr) - commands.update(ui, repo, newnode.rev()) - ui.status(_('working directory now at %s\n') - % ui.label(str(newnode), 'evolve.node')) - if movebookmark: - bookmarksmod.activate(repo, bookactive) - - # update bookmarks - if bookmarks: - _deletebookmark(repo, repomarks, bookmarks) - - # create markers - obsolete.createmarkers(repo, relations, metadata=metadata) - - # informs that changeset have been pruned - ui.status(_('%i changesets pruned\n') % len(precs)) - - for ctx in repo.unfiltered().set('bookmark() and %ld', precs): - # used to be: - # - # ldest = list(repo.set('max((::%d) - obsolete())', ctx)) - # if ldest: - # c = ldest[0] - # - # but then revset took a lazy arrow in the knee and became much - # slower. The new forms makes as much sense and a much faster. - for dest in ctx.ancestors(): - if not dest.obsolete(): - updatebookmarks = _bookmarksupdater(repo, ctx.node(), tr) - updatebookmarks(dest.node()) - break - - tr.close() - finally: - lockmod.release(tr, lock, wlock) - -@eh.command( - 'amend|refresh', - [('A', 'addremove', None, - _('mark new/missing files as added/removed before committing')), - ('e', 'edit', False, _('invoke editor on commit messages')), - ('', 'close-branch', None, - _('mark a branch as closed, hiding it from the branch list')), - ('s', 'secret', None, _('use the secret phase for committing')), - ] + walkopts + commitopts + commitopts2 + commitopts3 + interactiveopt, - _('[OPTION]... [FILE]...')) -def amend(ui, repo, *pats, **opts): - """combine a changeset with updates and replace it with a new one - - Commits a new changeset incorporating both the changes to the given files - and all the changes from the current parent changeset into the repository. - - See :hg:`commit` for details about committing changes. - - If you don't specify -m, the parent's message will be reused. - - Behind the scenes, Mercurial first commits the update as a regular child - of the current parent. Then it creates a new commit on the parent's parents - with the updated contents. Then it changes the working copy parent to this - new combined changeset. Finally, the old changeset and its update are hidden - from :hg:`log` (unless you use --hidden with log). + succmarkers = repo.obsstore.successors - Returns 0 on success, 1 if nothing changed. - """ - opts = opts.copy() - edit = opts.pop('edit', False) - log = opts.get('logfile') - opts['amend'] = True - if not (edit or opts['message'] or log): - opts['message'] = repo['.'].description() - _resolveoptions(ui, opts) - _alias, commitcmd = cmdutil.findcmd('commit', commands.table) - return commitcmd[0](ui, repo, *pats, **opts) - - -def _touchedbetween(repo, source, dest, match=None): - touched = set() - for files in repo.status(source, dest, match=match)[:3]: - touched.update(files) - return touched - -def _commitfiltered(repo, ctx, match, target=None): - """Recommit ctx with changed files not in match. Return the new - node identifier, or None if nothing changed. - """ - base = ctx.p1() - if target is None: - target = base - # ctx - initialfiles = _touchedbetween(repo, base, ctx) - if base == target: - affected = set(f for f in initialfiles if match(f)) - newcontent = set() - else: - affected = _touchedbetween(repo, target, ctx, match=match) - newcontent = _touchedbetween(repo, target, base, match=match) - # The commit touchs all existing files - # + all file that needs a new content - # - the file affected bny uncommit with the same content than base. - files = (initialfiles - affected) | newcontent - if not newcontent and files == initialfiles: - return None - - # Filter copies - copied = copies.pathcopies(target, ctx) - copied = dict((dst, src) for dst, src in copied.iteritems() - if dst in files) - - def filectxfn(repo, memctx, path, contentctx=ctx, redirect=newcontent): - if path in redirect: - return filectxfn(repo, memctx, path, contentctx=target, redirect=()) - if path not in contentctx: - return None - fctx = contentctx[path] - flags = fctx.flags() - mctx = context.memfilectx(repo, fctx.path(), fctx.data(), - islink='l' in flags, - isexec='x' in flags, - copied=copied.get(path)) - return mctx - - new = context.memctx(repo, - parents=[base.node(), node.nullid], - text=ctx.description(), - files=files, - filectxfn=filectxfn, - user=ctx.user(), - date=ctx.date(), - extra=ctx.extra()) - # commitctx always create a new revision, no need to check - newid = repo.commitctx(new) - return newid - -def _uncommitdirstate(repo, oldctx, match): - """Fix the dirstate after switching the working directory from - oldctx to a copy of oldctx not containing changed files matched by - match. - """ - ctx = repo['.'] - ds = repo.dirstate - copies = dict(ds.copies()) - m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3] - for f in m: - if ds[f] == 'r': - # modified + removed -> removed - continue - ds.normallookup(f) - - for f in a: - if ds[f] == 'r': - # added + removed -> unknown - ds.drop(f) - elif ds[f] != 'a': - ds.add(f) - - for f in r: - if ds[f] == 'a': - # removed + added -> normal - ds.normallookup(f) - elif ds[f] != 'r': - ds.remove(f) - - # Merge old parent and old working dir copies - oldcopies = {} - for f in (m + a): - src = oldctx[f].renamed() - if src: - oldcopies[f] = src[0] - oldcopies.update(copies) - copies = dict((dst, oldcopies.get(src, src)) - for dst, src in oldcopies.iteritems()) - # Adjust the dirstate copies - for dst, src in copies.iteritems(): - if (src not in ctx or dst in ctx or ds[dst] != 'a'): - src = None - ds.copy(src, dst) - -@eh.command( - '^uncommit', - [('a', 'all', None, _('uncommit all changes when no arguments given')), - ('r', 'rev', '', _('revert commit content to REV instead')), - ] + commands.walkopts, - _('[OPTION]... [NAME]')) -def uncommit(ui, repo, *pats, **opts): - """move changes from parent revision to working directory - - Changes to selected files in the checked out revision appear again as - uncommitted changed in the working directory. A new revision - without the selected changes is created, becomes the checked out - revision, and obsoletes the previous one. - - The --include option specifies patterns to uncommit. - The --exclude option specifies patterns to keep in the commit. - - The --rev argument let you change the commit file to a content of another - revision. It still does not change the content of your file in the working - directory. - - Return 0 if changed files are uncommitted. - """ - - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - wctx = repo[None] - if len(wctx.parents()) <= 0: - raise error.Abort(_("cannot uncommit null changeset")) - if len(wctx.parents()) > 1: - raise error.Abort(_("cannot uncommit while merging")) - old = repo['.'] - if old.phase() == phases.public: - raise error.Abort(_("cannot rewrite immutable changeset")) - if len(old.parents()) > 1: - raise error.Abort(_("cannot uncommit merge changeset")) - oldphase = old.phase() - - rev = None - if opts.get('rev'): - rev = scmutil.revsingle(repo, opts.get('rev')) - ctx = repo[None] - if ctx.p1() == rev or ctx.p2() == rev: - raise error.Abort(_("cannot uncommit to parent changeset")) - - onahead = old.rev() in repo.changelog.headrevs() - disallowunstable = not obsolete.isenabled(repo, - obsolete.allowunstableopt) - if disallowunstable and not onahead: - raise error.Abort(_("cannot uncommit in the middle of a stack")) - - # Recommit the filtered changeset - tr = repo.transaction('uncommit') - updatebookmarks = _bookmarksupdater(repo, old.node(), tr) - newid = None - includeorexclude = opts.get('include') or opts.get('exclude') - if (pats or includeorexclude or opts.get('all')): - match = scmutil.match(old, pats, opts) - newid = _commitfiltered(repo, old, match, target=rev) - if newid is None: - raise error.Abort(_('nothing to uncommit'), - hint=_("use --all to uncommit all files")) - # Move local changes on filtered changeset - obsolete.createmarkers(repo, [(old, (repo[newid],))]) - phases.retractboundary(repo, tr, oldphase, [newid]) - with repo.dirstate.parentchange(): - repo.dirstate.setparents(newid, node.nullid) - _uncommitdirstate(repo, old, match) - updatebookmarks(newid) - if not repo[newid].files(): - ui.warn(_("new changeset is empty\n")) - ui.status(_("(use 'hg prune .' to remove it)\n")) - tr.close() - finally: - lockmod.release(tr, lock, wlock) - -@eh.wrapcommand('commit') -def commitwrapper(orig, ui, repo, *arg, **kwargs): - tr = None - if kwargs.get('amend', False): - wlock = lock = None - else: - wlock = repo.wlock() - lock = repo.lock() - try: - obsoleted = kwargs.get('obsolete', []) - if obsoleted: - obsoleted = repo.set('%lr', obsoleted) - result = orig(ui, repo, *arg, **kwargs) - if not result: # commit succeeded - new = repo['-1'] - oldbookmarks = [] - markers = [] - for old in obsoleted: - oldbookmarks.extend(repo.nodebookmarks(old.node())) - markers.append((old, (new,))) - if markers: - obsolete.createmarkers(repo, markers) - for book in oldbookmarks: - repo._bookmarks[book] = new.node() - if oldbookmarks: - if not wlock: - wlock = repo.wlock() - if not lock: - lock = repo.lock() - tr = repo.transaction('commit') - repo._bookmarks.recordchange(tr) - tr.close() - return result - finally: - lockmod.release(tr, lock, wlock) - -@eh.command( - '^split', - [('r', 'rev', [], _("revision to split")), - ] + commitopts + commitopts2, - _('hg split [OPTION]... [-r] REV')) -def cmdsplit(ui, repo, *revs, **opts): - """split a changeset into smaller changesets - - By default, split the current revision by prompting for all its hunks to be - redistributed into new changesets. - - Use --rev to split a given changeset instead. - """ - tr = wlock = lock = None - newcommits = [] - - revarg = (list(revs) + opts.get('rev')) or ['.'] - if len(revarg) != 1: - msg = _("more than one revset is given") - hnt = _("use either `hg split ` or `hg split --rev `, not both") - raise error.Abort(msg, hint=hnt) - - rev = scmutil.revsingle(repo, revarg[0]) - try: - wlock = repo.wlock() - lock = repo.lock() - cmdutil.bailifchanged(repo) - tr = repo.transaction('split') - ctx = repo[rev] - r = ctx.rev() - disallowunstable = not obsolete.isenabled(repo, - obsolete.allowunstableopt) - if disallowunstable: - # XXX We should check head revs - if repo.revs("(%d::) - %d", rev, rev): - raise error.Abort(_("cannot split commit: %s not a head") % ctx) - - if len(ctx.parents()) > 1: - raise error.Abort(_("cannot split merge commits")) - prev = ctx.p1() - bmupdate = _bookmarksupdater(repo, ctx.node(), tr) - bookactive = repo._activebookmark - if bookactive is not None: - repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark) - bookmarksmod.deactivate(repo) - hg.update(repo, prev) - - commands.revert(ui, repo, rev=r, all=True) - - def haschanges(): - modified, added, removed, deleted = repo.status()[:4] - return modified or added or removed or deleted - msg = ("HG: This is the original pre-split commit message. " - "Edit it as appropriate.\n\n") - msg += ctx.description() - opts['message'] = msg - opts['edit'] = True - while haschanges(): - pats = () - cmdutil.dorecord(ui, repo, commands.commit, 'commit', False, - cmdutil.recordfilter, *pats, **opts) - # TODO: Does no seem like the best way to do this - # We should make dorecord return the newly created commit - newcommits.append(repo['.']) - if haschanges(): - if ui.prompt('Done splitting? [yN]', default='n') == 'y': - commands.commit(ui, repo, **opts) - newcommits.append(repo['.']) - break + # Stack of nodes we search successors sets for + toproceed = [initialnode] + # set version of above list for fast loop detection + # element added to "toproceed" must be added here + stackedset = set(toproceed) + if cache is None: + cache = {} + while toproceed: + current = toproceed[-1] + if current in cache: + stackedset.remove(toproceed.pop()) + elif current != initialnode and current in repo: + # We have a valid direct successors. + cache[current] = [(current,)] + elif current not in succmarkers: + if current in repo: + # We have a valid last successors. + cache[current] = [(current,)] else: - ui.status(_("no more change to split\n")) - - if newcommits: - tip = repo[newcommits[-1]] - bmupdate(tip.node()) - if bookactive is not None: - bookmarksmod.activate(repo, bookactive) - obsolete.createmarkers(repo, [(repo[r], newcommits)]) - tr.close() - finally: - lockmod.release(tr, lock, wlock) - - -@eh.wrapcommand('strip', extension='strip', opts=[ - ('', 'bundle', None, _("delete the commit entirely and move it to a " - "backup bundle")), - ]) -def stripwrapper(orig, ui, repo, *revs, **kwargs): - if (not ui.configbool('experimental', 'prunestrip') or - kwargs.get('bundle', False)): - return orig(ui, repo, *revs, **kwargs) - - if kwargs.get('force'): - ui.warn(_("warning: --force has no effect during strip with evolve " - "enabled\n")) - if kwargs.get('no_backup', False): - ui.warn(_("warning: --no-backup has no effect during strips with " - "evolve enabled\n")) - - revs = list(revs) + kwargs.pop('rev', []) - revs = set(scmutil.revrange(repo, revs)) - revs = repo.revs("(%ld)::", revs) - kwargs['rev'] = [] - kwargs['new'] = [] - kwargs['succ'] = [] - kwargs['biject'] = False - return cmdprune(ui, repo, *revs, **kwargs) - -@eh.command( - '^touch', - [('r', 'rev', [], 'revision to update'), - ('D', 'duplicate', False, - 'do not mark the new revision as successor of the old one'), - ('A', 'allowdivergence', False, - 'mark the new revision as successor of the old one potentially creating ' - 'divergence')], - # allow to choose the seed ? - _('[-r] revs')) -def touch(ui, repo, *revs, **opts): - """create successors that are identical to their predecessors except - for the changeset ID - - This is used to "resurrect" changesets - """ - duplicate = opts['duplicate'] - allowdivergence = opts['allowdivergence'] - revs = list(revs) - revs.extend(opts['rev']) - if not revs: - revs = ['.'] - revs = scmutil.revrange(repo, revs) - if not revs: - ui.write_err('no revision to touch\n') - return 1 - if not duplicate and repo.revs('public() and %ld', revs): - raise error.Abort("can't touch public revision") - displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) - wlock = lock = tr = None - try: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction('touch') - revs.sort() # ensure parent are run first - newmapping = {} - for r in revs: - ctx = repo[r] - extra = ctx.extra().copy() - extra['__touch-noise__'] = random.randint(0, 0xffffffff) - # search for touched parent - p1 = ctx.p1().node() - p2 = ctx.p2().node() - p1 = newmapping.get(p1, p1) - p2 = newmapping.get(p2, p2) - - if not (duplicate or allowdivergence): - # The user hasn't yet decided what to do with the revived - # cset, let's ask - sset = obsolete.successorssets(repo, ctx.node()) - nodivergencerisk = (len(sset) == 0 or - (len(sset) == 1 and - len(sset[0]) == 1 and - repo[sset[0][0]].rev() == ctx.rev() - )) - if nodivergencerisk: - duplicate = False + # Final obsolete version is unknown locally. + # Do not count that as a valid successors + cache[current] = [] + else: + for mark in sorted(succmarkers[current]): + for suc in mark[1]: + if suc not in cache: + if suc in stackedset: + # cycle breaking + cache[suc] = [] + else: + # case (3) If we have not computed successors sets + # of one of those successors we add it to the + # `toproceed` stack and stop all work for this + # iteration. + toproceed.append(suc) + stackedset.add(suc) + break else: - displayer.show(ctx) - index = ui.promptchoice( - _("reviving this changeset will create divergence" - " unless you make a duplicate.\n(a)llow divergence or" - " (d)uplicate the changeset? $$ &Allowdivergence $$ " - "&Duplicate"), 0) - choice = ['allowdivergence', 'duplicate'][index] - if choice == 'allowdivergence': - duplicate = False + continue + break + else: + succssets = [] + for mark in sorted(succmarkers[current]): + # successors sets contributed by this marker + markss = [[]] + for suc in mark[1]: + # cardinal product with previous successors + productresult = [] + for prefix in markss: + for suffix in cache[suc]: + newss = list(prefix) + for part in suffix: + # do not duplicated entry in successors set + # first entry wins. + if part not in newss: + newss.append(part) + productresult.append(newss) + markss = productresult + succssets.extend(markss) + # remove duplicated and subset + seen = [] + final = [] + candidate = sorted(((set(s), s) for s in succssets if s), + key=lambda x: len(x[1]), reverse=True) + for setversion, listversion in candidate: + for seenset in seen: + if setversion.issubset(seenset): + break else: - duplicate = True - - new, unusedvariable = rewrite(repo, ctx, [], ctx, - [p1, p2], - commitopts={'extra': extra}) - # store touched version to help potential children - newmapping[ctx.node()] = new - - if not duplicate: - obsolete.createmarkers(repo, [(ctx, (repo[new],))]) - phases.retractboundary(repo, tr, ctx.phase(), [new]) - if ctx in repo[None].parents(): - with repo.dirstate.parentchange(): - repo.dirstate.setparents(new, node.nullid) - tr.close() - finally: - lockmod.release(tr, lock, wlock) - -@eh.command( - '^fold|squash', - [('r', 'rev', [], _("revision to fold")), - ('', 'exact', None, _("only fold specified revisions")), - ('', 'from', None, _("fold revisions linearly to working copy parent")) - ] + commitopts + commitopts2, - _('hg fold [OPTION]... [-r] REV')) -def fold(ui, repo, *revs, **opts): - """fold multiple revisions into a single one - - With --from, folds all the revisions linearly between the given revisions - and the parent of the working directory. - - With --exact, folds only the specified revisions while ignoring the - parent of the working directory. In this case, the given revisions must - form a linear unbroken chain. - - .. container:: verbose - - Some examples: - - - Fold the current revision with its parent:: - - hg fold --from .^ - - - Fold all draft revisions with working directory parent:: - - hg fold --from 'draft()' - - See :hg:`help phases` for more about draft revisions and - :hg:`help revsets` for more about the `draft()` keyword - - - Fold revisions between 3 and 6 with the working directory parent:: - - hg fold --from 3::6 - - - Fold revisions 3 and 4: - - hg fold "3 + 4" --exact - - - Only fold revisions linearly between foo and @:: - - hg fold foo::@ --exact - """ - revs = list(revs) - revs.extend(opts['rev']) - if not revs: - raise error.Abort(_('no revisions specified')) - - revs = scmutil.revrange(repo, revs) - - if opts['from'] and opts['exact']: - raise error.Abort(_('cannot use both --from and --exact')) - elif opts['from']: - # Try to extend given revision starting from the working directory - extrevs = repo.revs('(%ld::.) or (.::%ld)', revs, revs) - discardedrevs = [r for r in revs if r not in extrevs] - if discardedrevs: - msg = _("cannot fold non-linear revisions") - hint = _("given revisions are unrelated to parent of working" - " directory") - raise error.Abort(msg, hint=hint) - revs = extrevs - elif opts['exact']: - # Nothing to do; "revs" is already set correctly - pass - else: - raise error.Abort(_('must specify either --from or --exact')) - - if not revs: - raise error.Abort(_('specified revisions evaluate to an empty set'), - hint=_('use different revision arguments')) - elif len(revs) == 1: - ui.write_err(_('single revision specified, nothing to fold\n')) - return 1 - - wlock = lock = None - try: - wlock = repo.wlock() - lock = repo.lock() - - root, head = _foldcheck(repo, revs) - - tr = repo.transaction('fold') - try: - commitopts = opts.copy() - allctx = [repo[r] for r in revs] - targetphase = max(c.phase() for c in allctx) - - if commitopts.get('message') or commitopts.get('logfile'): - commitopts['edit'] = False - else: - msgs = ["HG: This is a fold of %d changesets." % len(allctx)] - msgs += ["HG: Commit message of changeset %s.\n\n%s\n" % - (c.rev(), c.description()) for c in allctx] - commitopts['message'] = "\n".join(msgs) - commitopts['edit'] = True - - newid, unusedvariable = rewrite(repo, root, allctx, head, - [root.p1().node(), - root.p2().node()], - commitopts=commitopts) - phases.retractboundary(repo, tr, targetphase, [newid]) - obsolete.createmarkers(repo, [(ctx, (repo[newid],)) - for ctx in allctx]) - tr.close() - finally: - tr.release() - ui.status('%i changesets folded\n' % len(revs)) - if repo['.'].rev() in revs: - hg.update(repo, newid) - finally: - lockmod.release(lock, wlock) - -@eh.command( - '^metaedit', - [('r', 'rev', [], _("revision to edit")), - ('', 'fold', None, _("also fold specified revisions into one")), - ] + commitopts + commitopts2, - _('hg metaedit [OPTION]... [-r] [REV]')) -def metaedit(ui, repo, *revs, **opts): - """edit commit information - - Edits the commit information for the specified revisions. By default, edits - commit information for the working directory parent. - - With --fold, also folds multiple revisions into one if necessary. In this - case, the given revisions must form a linear unbroken chain. - - .. container:: verbose - - Some examples: - - - Edit the commit message for the working directory parent:: - - hg metaedit - - - Change the username for the working directory parent:: - - hg metaedit --user 'New User ' - - - Combine all draft revisions that are ancestors of foo but not of @ into - one:: - - hg metaedit --fold 'draft() and only(foo,@)' - - See :hg:`help phases` for more about draft revisions, and - :hg:`help revsets` for more about the `draft()` and `only()` keywords. - """ - revs = list(revs) - revs.extend(opts['rev']) - if not revs: - if opts['fold']: - raise error.Abort(_('revisions must be specified with --fold')) - revs = ['.'] - - wlock = lock = None - try: - wlock = repo.wlock() - lock = repo.lock() - - revs = scmutil.revrange(repo, revs) - if not opts['fold'] and len(revs) > 1: - # TODO: handle multiple revisions. This is somewhat tricky because - # if we want to edit a series of commits: - # - # a ---- b ---- c - # - # we need to rewrite a first, then directly rewrite b on top of the - # new a, then rewrite c on top of the new b. So we need to handle - # revisions in topological order. - raise error.Abort(_('editing multiple revisions without --fold is ' - 'not currently supported')) - - if opts['fold']: - root, head = _foldcheck(repo, revs) - else: - if repo.revs("%ld and public()", revs): - raise error.Abort(_('cannot edit commit information for public ' - 'revisions')) - newunstable = _disallowednewunstable(repo, revs) - if newunstable: - msg = _('cannot edit commit information in the middle' - ' of a stack') - hint = _('%s will become unstable and new unstable changes' - ' are not allowed') - hint %= repo[newunstable.first()] - raise error.Abort(msg, hint=hint) - root = head = repo[revs.first()] - - wctx = repo[None] - p1 = wctx.p1() - tr = repo.transaction('metaedit') - newp1 = None - try: - commitopts = opts.copy() - allctx = [repo[r] for r in revs] - targetphase = max(c.phase() for c in allctx) - - if commitopts.get('message') or commitopts.get('logfile'): - commitopts['edit'] = False - else: - if opts['fold']: - msgs = ["HG: This is a fold of %d changesets." % len(allctx)] - msgs += ["HG: Commit message of changeset %s.\n\n%s\n" % - (c.rev(), c.description()) for c in allctx] - else: - msgs = [head.description()] - commitopts['message'] = "\n".join(msgs) - commitopts['edit'] = True - - # TODO: if the author and message are the same, don't create a new - # hash. Right now we create a new hash because the date can be - # different. - newid, created = rewrite(repo, root, allctx, head, - [root.p1().node(), root.p2().node()], - commitopts=commitopts) - if created: - if p1.rev() in revs: - newp1 = newid - phases.retractboundary(repo, tr, targetphase, [newid]) - obsolete.createmarkers(repo, [(ctx, (repo[newid],)) - for ctx in allctx]) - else: - ui.status(_("nothing changed\n")) - tr.close() - finally: - tr.release() - - if opts['fold']: - ui.status('%i changesets folded\n' % len(revs)) - if newp1 is not None: - hg.update(repo, newp1) - finally: - lockmod.release(lock, wlock) - -def _foldcheck(repo, revs): - roots = repo.revs('roots(%ld)', revs) - if len(roots) > 1: - raise error.Abort(_("cannot fold non-linear revisions " - "(multiple roots given)")) - root = repo[roots.first()] - if root.phase() <= phases.public: - raise error.Abort(_("cannot fold public revisions")) - heads = repo.revs('heads(%ld)', revs) - if len(heads) > 1: - raise error.Abort(_("cannot fold non-linear revisions " - "(multiple heads given)")) - head = repo[heads.first()] - if _disallowednewunstable(repo, revs): - msg = _("cannot fold chain not ending with a head or with branching") - hint = _("new unstable changesets are not allowed") - raise error.Abort(msg, hint=hint) - return root, head - -def _disallowednewunstable(repo, revs): - allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt) - if allowunstable: - return revset.baseset() - return repo.revs("(%ld::) - %ld", revs, revs) - -@eh.wrapcommand('graft') -def graftwrapper(orig, ui, repo, *revs, **kwargs): - kwargs = dict(kwargs) - revs = list(revs) + kwargs.get('rev', []) - kwargs['rev'] = [] - obsoleted = kwargs.setdefault('obsolete', []) - - wlock = lock = None - try: - wlock = repo.wlock() - lock = repo.lock() - if kwargs.get('old_obsolete'): - if kwargs.get('continue'): - obsoleted.extend(repo.vfs.read('graftstate').splitlines()) - else: - obsoleted.extend(revs) - # convert obsolete target into revs to avoid alias joke - obsoleted[:] = [str(i) for i in repo.revs('%lr', obsoleted)] - if obsoleted and len(revs) > 1: - - raise error.Abort(_('cannot graft multiple revisions while ' - 'obsoleting (for now).')) - - return commitwrapper(orig, ui, repo, *revs, **kwargs) - finally: - lockmod.release(lock, wlock) - -@eh.extsetup -def oldevolveextsetup(ui): - for cmd in ['prune', 'uncommit', 'touch', 'fold']: - try: - entry = extensions.wrapcommand(cmdtable, cmd, - warnobserrors) - except error.UnknownCommand: - # Commands may be disabled - continue - - entry = cmdutil.findcmd('commit', commands.table)[1] - entry[1].append(('o', 'obsolete', [], - _("make commit obsolete this revision (DEPRECATED)"))) - entry = cmdutil.findcmd('graft', commands.table)[1] - entry[1].append(('o', 'obsolete', [], - _("make graft obsoletes this revision (DEPRECATED)"))) - entry[1].append(('O', 'old-obsolete', False, - _("make graft obsoletes its source (DEPRECATED)"))) - -@eh.wrapfunction(obsolete, '_checkinvalidmarkers') -def _checkinvalidmarkers(orig, markers): - """search for marker with invalid data and raise error if needed - - Exist as a separated function to allow the evolve extension for a more - subtle handling. - """ - if 'debugobsconvert' in sys.argv: - return - for mark in markers: - if node.nullid in mark[1]: - msg = _('bad obsolescence marker detected: invalid successors nullid') - hint = _('You should run `hg debugobsconvert`') - raise error.Abort(msg, hint=hint) - -@eh.command( - 'debugobsconvert', - [('', 'new-format', obsexchange._bestformat, _('Destination format for markers.'))], - '') -def debugobsconvert(ui, repo, new_format): - origmarkers = repo.obsstore._all # settle version - if new_format == repo.obsstore._version: - msg = _('New format is the same as the old format, not upgrading!') - raise error.Abort(msg) - f = repo.svfs('obsstore', 'wb', atomictemp=True) - known = set() - markers = [] - for m in origmarkers: - # filter out invalid markers - if nullid in m[1]: - m = list(m) - m[1] = tuple(s for s in m[1] if s != nullid) - m = tuple(m) - if m in known: - continue - known.add(m) - markers.append(m) - ui.write(_('Old store is version %d, will rewrite in version %d\n') % ( - repo.obsstore._version, new_format)) - map(f.write, obsolete.encodemarkers(markers, True, new_format)) - f.close() - ui.write(_('Done!\n')) - - -def _helploader(ui): - return help.gettext(evolutionhelptext) - -@eh.uisetup -def _setuphelp(ui): - for entry in help.helptable: - if entry[0] == "evolution": - break - else: - help.helptable.append((["evolution"], _("Safely Rewriting History"), - _helploader)) - help.helptable.sort() - -def _relocatecommit(repo, orig, commitmsg): - if commitmsg is None: - commitmsg = orig.description() - extra = dict(orig.extra()) - if 'branch' in extra: - del extra['branch'] - extra['rebase_source'] = orig.hex() - - backup = repo.ui.backupconfig('phases', 'new-commit') - try: - targetphase = max(orig.phase(), phases.draft) - repo.ui.setconfig('phases', 'new-commit', targetphase, 'evolve') - # Commit might fail if unresolved files exist - nodenew = repo.commit(text=commitmsg, user=orig.user(), - date=orig.date(), extra=extra) - finally: - repo.ui.restoreconfig(backup) - return nodenew - -def _finalizerelocate(repo, orig, dest, nodenew, tr): - destbookmarks = repo.nodebookmarks(dest.node()) - nodesrc = orig.node() - destphase = repo[nodesrc].phase() - oldbookmarks = repo.nodebookmarks(nodesrc) - if nodenew is not None: - phases.retractboundary(repo, tr, destphase, [nodenew]) - obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))]) - for book in oldbookmarks: - repo._bookmarks[book] = nodenew - else: - obsolete.createmarkers(repo, [(repo[nodesrc], ())]) - # Behave like rebase, move bookmarks to dest - for book in oldbookmarks: - repo._bookmarks[book] = dest.node() - for book in destbookmarks: # restore bookmark that rebase move - repo._bookmarks[book] = dest.node() - if oldbookmarks or destbookmarks: - repo._bookmarks.recordchange(tr) - -evolvestateversion = 0 - -@eh.uisetup -def setupevolveunfinished(ui): - data = ('evolvestate', True, False, _('evolve in progress'), - _("use 'hg evolve --continue' or 'hg update -C .' to abort")) - cmdutil.unfinishedstates.append(data) - -@eh.wrapfunction(hg, 'clean') -def clean(orig, repo, *args, **kwargs): - ret = orig(repo, *args, **kwargs) - util.unlinkpath(repo.vfs.join('evolvestate'), ignoremissing=True) - return ret - -def _evolvestatewrite(repo, state): - # [version] - # [type][length][content] - # - # `version` is a 4 bytes integer (handled at higher level) - # `type` is a single character, `length` is a 4 byte integer, and - # `content` is an arbitrary byte sequence of length `length`. - f = repo.vfs('evolvestate', 'w') - try: - f.write(_pack('>I', evolvestateversion)) - current = state['current'] - key = 'C' # as in 'current' - format = '>sI%is' % len(current) - f.write(_pack(format, key, len(current), current)) - finally: - f.close() - -def _evolvestateread(repo): - try: - f = repo.vfs('evolvestate') - except IOError as err: - if err.errno != errno.ENOENT: - raise - return None - try: - versionblob = f.read(4) - if len(versionblob) < 4: - repo.ui.debug('ignoring corrupted evolvestte (file contains %i bits)' - % len(versionblob)) - return None - version = _unpack('>I', versionblob)[0] - if version != evolvestateversion: - msg = _('unknown evolvestate version %i') % version - raise error.Abort(msg, hint=_('upgrade your evolve')) - records = [] - data = f.read() - off = 0 - end = len(data) - while off < end: - rtype = data[off] - off += 1 - length = _unpack('>I', data[off:(off + 4)])[0] - off += 4 - record = data[off:(off + length)] - off += length - if rtype == 't': - rtype, record = record[0], record[1:] - records.append((rtype, record)) - state = {} - for rtype, rdata in records: - if rtype == 'C': - state['current'] = rdata - elif rtype.lower(): - repo.ui.debug('ignore evolve state record type %s' % rtype) - else: - raise error.Abort(_('unknown evolvestate field type %r') - % rtype, hint=_('upgrade your evolve')) - return state - finally: - f.close() - -def _evolvestatedelete(repo): - util.unlinkpath(repo.vfs.join('evolvestate'), ignoremissing=True) - -def _evolvemerge(repo, orig, dest, pctx, keepbranch): - """Used by the evolve function to merge dest on top of pctx. - return the same tuple as merge.graft""" - if repo['.'].rev() != dest.rev(): - merge.update(repo, - dest, - branchmerge=False, - force=True) - if repo._activebookmark: - repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark) - bookmarksmod.deactivate(repo) - if keepbranch: - repo.dirstate.setbranch(orig.branch()) - if util.safehasattr(repo, 'currenttopic'): - # uurrgs - # there no other topic setter yet - if not orig.topic() and repo.vfs.exists('topic'): - repo.vfs.unlink('topic') - else: - with repo.vfs.open('topic', 'w') as f: - f.write(orig.topic()) - - return merge.graft(repo, orig, pctx, ['local', 'graft'], True) + final.append(listversion) + seen.append(setversion) + final.reverse() # put small successors set first + cache[current] = final + return cache[initialnode] diff --git a/tests/test-discovery-obshashrange.t b/tests/test-discovery-obshashrange.t --- a/tests/test-discovery-obshashrange.t +++ b/tests/test-discovery-obshashrange.t @@ -519,17 +519,17 @@ $ hg debugobshashrange --subranges --rev 'heads(all())' rev node index size depth obshash 7 f69452c5b1af 0 7 7 000000000000 - 5 45f8b879de92 0 6 6 b8a4206b0fc6 + 5 45f8b879de92 0 6 6 7c49a958a9ac 3 2dc09a01254d 0 4 4 8932bf980bb4 7 f69452c5b1af 4 3 7 000000000000 3 2dc09a01254d 2 2 4 ce1937ca1278 - 5 45f8b879de92 4 2 6 31fc49d36a59 + 5 45f8b879de92 4 2 6 c6795525c540 1 66f7d451a68b 0 2 2 327c7dd73d29 6 c8d03c1b5e94 4 2 6 89755fd39e6d 2 01241442b3c2 2 1 3 1ed3c61fb39a 0 1ea73414a91b 0 1 1 000000000000 3 2dc09a01254d 3 1 4 26f996446ecb - 5 45f8b879de92 5 1 6 1a0c08180b65 + 5 45f8b879de92 5 1 6 796507769034 1 66f7d451a68b 1 1 2 327c7dd73d29 4 bebd167eb94d 4 1 5 b21465ecb790 6 c8d03c1b5e94 5 1 6 446c2dc3bce5 @@ -571,17 +571,17 @@ $ hg debugobshashrange --subranges --rev 'heads(all())' rev node index size depth obshash 7 f69452c5b1af 0 7 7 000000000000 - 5 45f8b879de92 0 6 6 b8a4206b0fc6 + 5 45f8b879de92 0 6 6 7c49a958a9ac 3 2dc09a01254d 0 4 4 8932bf980bb4 7 f69452c5b1af 4 3 7 000000000000 3 2dc09a01254d 2 2 4 ce1937ca1278 - 5 45f8b879de92 4 2 6 31fc49d36a59 + 5 45f8b879de92 4 2 6 c6795525c540 1 66f7d451a68b 0 2 2 327c7dd73d29 6 c8d03c1b5e94 4 2 6 89755fd39e6d 2 01241442b3c2 2 1 3 1ed3c61fb39a 0 1ea73414a91b 0 1 1 000000000000 3 2dc09a01254d 3 1 4 26f996446ecb - 5 45f8b879de92 5 1 6 1a0c08180b65 + 5 45f8b879de92 5 1 6 796507769034 1 66f7d451a68b 1 1 2 327c7dd73d29 4 bebd167eb94d 4 1 5 b21465ecb790 6 c8d03c1b5e94 5 1 6 446c2dc3bce5 @@ -616,18 +616,18 @@ $ hg debugobshashrange --subranges --rev 'heads(all())' rev node index size depth obshash 8 4de32a90b66c 0 8 8 c7f1f7e9925b - 5 45f8b879de92 0 6 6 b8a4206b0fc6 + 5 45f8b879de92 0 6 6 7c49a958a9ac 3 2dc09a01254d 0 4 4 8932bf980bb4 8 4de32a90b66c 4 4 8 c681c3e58c27 3 2dc09a01254d 2 2 4 ce1937ca1278 - 5 45f8b879de92 4 2 6 31fc49d36a59 + 5 45f8b879de92 4 2 6 c6795525c540 8 4de32a90b66c 6 2 8 033544c939f0 1 66f7d451a68b 0 2 2 327c7dd73d29 6 c8d03c1b5e94 4 2 6 89755fd39e6d 2 01241442b3c2 2 1 3 1ed3c61fb39a 0 1ea73414a91b 0 1 1 000000000000 3 2dc09a01254d 3 1 4 26f996446ecb - 5 45f8b879de92 5 1 6 1a0c08180b65 + 5 45f8b879de92 5 1 6 796507769034 8 4de32a90b66c 7 1 8 033544c939f0 1 66f7d451a68b 1 1 2 327c7dd73d29 4 bebd167eb94d 4 1 5 b21465ecb790 @@ -745,18 +745,18 @@ $ hg debugobshashrange --subranges --rev 'heads(all())' rev node index size depth obshash 7 4de32a90b66c 0 8 8 c7f1f7e9925b - 8 45f8b879de92 0 6 6 b8a4206b0fc6 + 8 45f8b879de92 0 6 6 7c49a958a9ac 3 2dc09a01254d 0 4 4 8932bf980bb4 7 4de32a90b66c 4 4 8 c681c3e58c27 3 2dc09a01254d 2 2 4 ce1937ca1278 - 8 45f8b879de92 4 2 6 31fc49d36a59 + 8 45f8b879de92 4 2 6 c6795525c540 7 4de32a90b66c 6 2 8 033544c939f0 1 66f7d451a68b 0 2 2 327c7dd73d29 5 c8d03c1b5e94 4 2 6 89755fd39e6d 2 01241442b3c2 2 1 3 1ed3c61fb39a 0 1ea73414a91b 0 1 1 000000000000 3 2dc09a01254d 3 1 4 26f996446ecb - 8 45f8b879de92 5 1 6 1a0c08180b65 + 8 45f8b879de92 5 1 6 796507769034 7 4de32a90b66c 7 1 8 033544c939f0 1 66f7d451a68b 1 1 2 327c7dd73d29 4 bebd167eb94d 4 1 5 b21465ecb790 diff --git a/tests/test-evolve-list.t b/tests/test-evolve-list.t --- a/tests/test-evolve-list.t +++ b/tests/test-evolve-list.t @@ -72,6 +72,10 @@ a922b3733e98: b divergent: c882616e9d84 (draft) (precursor d2ae7f538514) + $ hg evolve --list --rev c882616e9d84 + c882616e9d84: b + divergent: a922b3733e98 (draft) (precursor d2ae7f538514) + $ hg phase -p a922b3733e98 $ hg evolve --list c882616e9d84: b diff --git a/tests/test-evolve-templates.t b/tests/test-evolve-templates.t new file mode 100644 --- /dev/null +++ b/tests/test-evolve-templates.t @@ -0,0 +1,773 @@ +This test file test the various templates for precursors and successors. + +Global setup +============ + + $ . $TESTDIR/testlib/common.sh + $ cat >> $HGRCPATH < [ui] + > interactive = true + > [phases] + > publish=False + > [extensions] + > evolve = + > [alias] + > tlog = log -G -T '{node|short}\ + > {if(precursors, "\n Precursors: {precursors}")}\ + > {if(precursors, "\n semi-colon: {join(precursors, "; ")}")}\ + > {if(successors, "\n Successors: {successors}")}\ + > {if(successors, "\n semi-colon: {join(successors, "; ")}")}\ + > {if(successors, "\n Fate: {obsfate_quiet}")}\n' + > EOF + +Test templates on amended commit +================================ + +Test setup +---------- + + $ hg init $TESTTMP/templates-local-amend + $ cd $TESTTMP/templates-local-amend + $ mkcommit ROOT + $ mkcommit A0 + $ echo 42 >> A0 + $ hg amend -m "A1" + $ hg amend -m "A2" + $ hg log --hidden -G + @ changeset: 4:d004c8f274b9 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A2 + | + | x changeset: 3:a468dc9b3633 + |/ parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A1 + | + | x changeset: 2:f137d23bb3e1 + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: temporary amend commit for 471f378eab4c + | | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + +Check templates +--------------- + $ hg up 'desc(A0)' --hidden + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: d004c8f274b9) + +Precursors template should show current revision as it is the working copy + $ hg tlog + o d004c8f274b9 + | Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | @ 471f378eab4c + |/ Successors: [d004c8f274b9] + | semi-colon: [d004c8f274b9] + | Fate: superseed as d004c8f274b9 + o ea207398892e + + $ hg up 'desc(A1)' --hidden + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (a468dc9b3633) + (use 'hg evolve' to update to its successor: d004c8f274b9) + +Precursors template should show current revision as it is the working copy + $ hg tlog + o d004c8f274b9 + | Precursors: a468dc9b3633 + | semi-colon: a468dc9b3633 + | @ a468dc9b3633 + |/ Successors: [d004c8f274b9] + | semi-colon: [d004c8f274b9] + | Fate: superseed as d004c8f274b9 + o ea207398892e + +Precursors template should show the precursor as we force its display with +--hidden + $ hg tlog --hidden + o d004c8f274b9 + | Precursors: a468dc9b3633 + | semi-colon: a468dc9b3633 + | @ a468dc9b3633 + |/ Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | Successors: [d004c8f274b9] + | semi-colon: [d004c8f274b9] + | Fate: superseed as d004c8f274b9 + | x f137d23bb3e1 + | | + | x 471f378eab4c + |/ Successors: [a468dc9b3633] + | semi-colon: [a468dc9b3633] + | Fate: superseed as a468dc9b3633 + o ea207398892e + + + $ hg up 'desc(A2)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg tlog + @ d004c8f274b9 + | + o ea207398892e + + $ hg tlog --hidden + @ d004c8f274b9 + | Precursors: a468dc9b3633 + | semi-colon: a468dc9b3633 + | x a468dc9b3633 + |/ Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | Successors: [d004c8f274b9] + | semi-colon: [d004c8f274b9] + | Fate: superseed as d004c8f274b9 + | x f137d23bb3e1 + | | + | x 471f378eab4c + |/ Successors: [a468dc9b3633] + | semi-colon: [a468dc9b3633] + | Fate: superseed as a468dc9b3633 + o ea207398892e + + +Test templates with splitted commit +=================================== + + $ hg init $TESTTMP/templates-local-split + $ cd $TESTTMP/templates-local-split + $ mkcommit ROOT + $ echo 42 >> a + $ echo 43 >> b + $ hg commit -A -m "A0" + adding a + adding b + $ hg log --hidden -G + @ changeset: 1:471597cad322 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg split -r 'desc(A0)' -d "0 0" << EOF + > y + > y + > n + > n + > y + > y + > EOF + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + adding a + adding b + diff --git a/a b/a + new file mode 100644 + examine changes to 'a'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +42 + record change 1/2 to 'a'? [Ynesfdaq?] y + + diff --git a/b b/b + new file mode 100644 + examine changes to 'b'? [Ynesfdaq?] n + + created new head + Done splitting? [yN] n + diff --git a/b b/b + new file mode 100644 + examine changes to 'b'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +43 + record this change to 'b'? [Ynesfdaq?] y + + no more change to split + + $ hg log --hidden -G + @ changeset: 3:f257fde29c7a + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 2:337fec4d2edc + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + | x changeset: 1:471597cad322 + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + +Check templates +--------------- + + $ hg up 'obsolete()' --hidden + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (471597cad322) + (use 'hg evolve' to update to its tipmost successor: 337fec4d2edc, f257fde29c7a) + +Precursors template should show current revision as it is the working copy + $ hg tlog + o f257fde29c7a + | Precursors: 471597cad322 + | semi-colon: 471597cad322 + o 337fec4d2edc + | Precursors: 471597cad322 + | semi-colon: 471597cad322 + | @ 471597cad322 + |/ Successors: [337fec4d2edc, f257fde29c7a] + | semi-colon: [337fec4d2edc, f257fde29c7a] + | Fate: superseed as 337fec4d2edc,f257fde29c7a + o ea207398892e + + $ hg up f257fde29c7a + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + +Precursors template should not show a precursor as it's not displayed in the +log + $ hg tlog + @ f257fde29c7a + | + o 337fec4d2edc + | + o ea207398892e + +Precursors template should show the precursor as we force its display with +--hidden + $ hg tlog --hidden + @ f257fde29c7a + | Precursors: 471597cad322 + | semi-colon: 471597cad322 + o 337fec4d2edc + | Precursors: 471597cad322 + | semi-colon: 471597cad322 + | x 471597cad322 + |/ Successors: [337fec4d2edc, f257fde29c7a] + | semi-colon: [337fec4d2edc, f257fde29c7a] + | Fate: superseed as 337fec4d2edc,f257fde29c7a + o ea207398892e + +Test templates with folded commit +============================== + +Test setup +---------- + + $ hg init $TESTTMP/templates-local-fold + $ cd $TESTTMP/templates-local-fold + $ mkcommit ROOT + $ mkcommit A0 + $ mkcommit B0 + $ hg log --hidden -G + @ changeset: 2:0dec01379d3b + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: B0 + | + o changeset: 1:471f378eab4c + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg fold --exact -r 'desc(A0) + desc(B0)' --date "0 0" -m "C0" + 2 changesets folded + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log --hidden -G + @ changeset: 3:eb5a0daa2192 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: C0 + | + | x changeset: 2:0dec01379d3b + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: B0 + | | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + +Check templates +--------------- + + $ hg up 'desc(A0)' --hidden + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + +Precursors template should show current revision as it is the working copy + $ hg tlog + o eb5a0daa2192 + | Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | @ 471f378eab4c + |/ Successors: [eb5a0daa2192] + | semi-colon: [eb5a0daa2192] + | Fate: superseed as eb5a0daa2192 + o ea207398892e + + $ hg up 'desc(B0)' --hidden + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (0dec01379d3b) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + +Precursors template should show both precursors as they should be both +displayed + $ hg tlog + o eb5a0daa2192 + | Precursors: 0dec01379d3b 471f378eab4c + | semi-colon: 0dec01379d3b; 471f378eab4c + | @ 0dec01379d3b + | | Successors: [eb5a0daa2192] + | | semi-colon: [eb5a0daa2192] + | | Fate: superseed as eb5a0daa2192 + | x 471f378eab4c + |/ Successors: [eb5a0daa2192] + | semi-colon: [eb5a0daa2192] + | Fate: superseed as eb5a0daa2192 + o ea207398892e + + $ hg up 'desc(C0)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + +Precursors template should not show precursors as it's not displayed in the +log + $ hg tlog + @ eb5a0daa2192 + | + o ea207398892e + +Precursors template should show both precursors as we force its display with +--hidden + $ hg tlog --hidden + @ eb5a0daa2192 + | Precursors: 0dec01379d3b 471f378eab4c + | semi-colon: 0dec01379d3b; 471f378eab4c + | x 0dec01379d3b + | | Successors: [eb5a0daa2192] + | | semi-colon: [eb5a0daa2192] + | | Fate: superseed as eb5a0daa2192 + | x 471f378eab4c + |/ Successors: [eb5a0daa2192] + | semi-colon: [eb5a0daa2192] + | Fate: superseed as eb5a0daa2192 + o ea207398892e + + +Test templates with divergence +============================== + +Test setup +---------- + + $ hg init $TESTTMP/templates-local-divergence + $ cd $TESTTMP/templates-local-divergence + $ mkcommit ROOT + $ mkcommit A0 + $ hg amend -m "A1" + $ hg log --hidden -G + @ changeset: 2:fdf9bde5129a + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A1 + | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg update --hidden 'desc(A0)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: fdf9bde5129a) + $ hg amend -m "A2" + 2 new divergent changesets + $ hg log --hidden -G + @ changeset: 3:65b757b745b9 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A2 + | + | o changeset: 2:fdf9bde5129a + |/ parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A1 + | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg amend -m 'A3' + +Check templates +--------------- + + $ hg up 'desc(A0)' --hidden + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (471f378eab4c) + (471f378eab4c has diverged, use 'hg evolve -list --divergent' to resolve the issue) + +Precursors template should show current revision as it is the working copy + $ hg tlog + o 019fadeab383 + | Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | o fdf9bde5129a + |/ Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | @ 471f378eab4c + |/ Successors: [fdf9bde5129a], [019fadeab383] + | semi-colon: [fdf9bde5129a]; [019fadeab383] + | Fate: superseed as fdf9bde5129a + superseed as 019fadeab383 + o ea207398892e + + $ hg up 'desc(A1)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved +Precursors template should not show precursors as it's not displayed in the +log + $ hg tlog + o 019fadeab383 + | + | @ fdf9bde5129a + |/ + o ea207398892e + +Precursors template should a precursor as we force its display with --hidden + $ hg tlog --hidden + o 019fadeab383 + | Precursors: 65b757b745b9 + | semi-colon: 65b757b745b9 + | x 65b757b745b9 + |/ Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | Successors: [019fadeab383] + | semi-colon: [019fadeab383] + | Fate: superseed as 019fadeab383 + | @ fdf9bde5129a + |/ Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | x 471f378eab4c + |/ Successors: [fdf9bde5129a], [65b757b745b9] + | semi-colon: [fdf9bde5129a]; [65b757b745b9] + | Fate: superseed as fdf9bde5129a + superseed as 65b757b745b9 + o ea207398892e + +Test templates with amended + folded commit +=========================================== + +Test setup +---------- + + $ hg init $TESTTMP/templates-local-amend-fold + $ cd $TESTTMP/templates-local-amend-fold + $ mkcommit ROOT + $ mkcommit A0 + $ mkcommit B0 + $ hg amend -m "B1" + $ hg log --hidden -G + @ changeset: 3:b7ea6d14e664 + | tag: tip + | parent: 1:471f378eab4c + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: B1 + | + | x changeset: 2:0dec01379d3b + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: B0 + | + o changeset: 1:471f378eab4c + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg fold --exact -r 'desc(A0) + desc(B1)' --date "0 0" -m "C0" + 2 changesets folded + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log --hidden -G + @ changeset: 4:eb5a0daa2192 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: C0 + | + | x changeset: 3:b7ea6d14e664 + | | parent: 1:471f378eab4c + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: B1 + | | + | | x changeset: 2:0dec01379d3b + | |/ user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: B0 + | | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + +Check templates +--------------- + + $ hg up 'desc(A0)' --hidden + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + $ hg tlog + o eb5a0daa2192 + | Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | @ 471f378eab4c + |/ Successors: [eb5a0daa2192] + | semi-colon: [eb5a0daa2192] + | Fate: superseed as eb5a0daa2192 + o ea207398892e + + $ hg up 'desc(B0)' --hidden + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (0dec01379d3b) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + $ hg tlog + o eb5a0daa2192 + | Precursors: 0dec01379d3b 471f378eab4c + | semi-colon: 0dec01379d3b; 471f378eab4c + | @ 0dec01379d3b + | | Successors: [eb5a0daa2192] + | | semi-colon: [eb5a0daa2192] + | | Fate: superseed as eb5a0daa2192 + | x 471f378eab4c + |/ Successors: [eb5a0daa2192] + | semi-colon: [eb5a0daa2192] + | Fate: superseed as eb5a0daa2192 + o ea207398892e + + $ hg up 'desc(B1)' --hidden + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (b7ea6d14e664) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + $ hg tlog + o eb5a0daa2192 + | Precursors: 471f378eab4c b7ea6d14e664 + | semi-colon: 471f378eab4c; b7ea6d14e664 + | @ b7ea6d14e664 + | | Successors: [eb5a0daa2192] + | | semi-colon: [eb5a0daa2192] + | | Fate: superseed as eb5a0daa2192 + | x 471f378eab4c + |/ Successors: [eb5a0daa2192] + | semi-colon: [eb5a0daa2192] + | Fate: superseed as eb5a0daa2192 + o ea207398892e + + $ hg up 'desc(C0)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg tlog + @ eb5a0daa2192 + | + o ea207398892e + + $ hg tlog --hidden + @ eb5a0daa2192 + | Precursors: 471f378eab4c b7ea6d14e664 + | semi-colon: 471f378eab4c; b7ea6d14e664 + | x b7ea6d14e664 + | | Precursors: 0dec01379d3b + | | semi-colon: 0dec01379d3b + | | Successors: [eb5a0daa2192] + | | semi-colon: [eb5a0daa2192] + | | Fate: superseed as eb5a0daa2192 + | | x 0dec01379d3b + | |/ Successors: [b7ea6d14e664] + | | semi-colon: [b7ea6d14e664] + | | Fate: superseed as b7ea6d14e664 + | x 471f378eab4c + |/ Successors: [eb5a0daa2192] + | semi-colon: [eb5a0daa2192] + | Fate: superseed as eb5a0daa2192 + o ea207398892e + + +Test template with pushed and pulled obs markers +============================================== + +Test setup +---------- + + $ hg init $TESTTMP/templates-local-remote-markers-1 + $ cd $TESTTMP/templates-local-remote-markers-1 + $ mkcommit ROOT + $ mkcommit A0 + $ hg clone $TESTTMP/templates-local-remote-markers-1 $TESTTMP/templates-local-remote-markers-2 + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd $TESTTMP/templates-local-remote-markers-2 + $ hg log --hidden -G + @ changeset: 1:471f378eab4c + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ cd $TESTTMP/templates-local-remote-markers-1 + $ hg amend -m "A1" + $ hg amend -m "A2" + $ hg log --hidden -G + @ changeset: 3:7a230b46bf61 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A2 + | + | x changeset: 2:fdf9bde5129a + |/ parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A1 + | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ cd $TESTTMP/templates-local-remote-markers-2 + $ hg pull + pulling from $TESTTMP/templates-local-remote-markers-1 + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 0 changes to 1 files (+1 heads) + 2 new obsolescence markers + (run 'hg heads' to see heads, 'hg merge' to merge) + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: 7a230b46bf61) + $ hg log --hidden -G + o changeset: 2:7a230b46bf61 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A2 + | + | @ changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + +Check templates +--------------- + + $ hg tlog + o 7a230b46bf61 + | Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | @ 471f378eab4c + |/ Successors: [7a230b46bf61] + | semi-colon: [7a230b46bf61] + | Fate: superseed as 7a230b46bf61 + o ea207398892e + + $ hg up 'desc(A2)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg tlog + @ 7a230b46bf61 + | + o ea207398892e + + $ hg tlog --hidden + @ 7a230b46bf61 + | Precursors: 471f378eab4c + | semi-colon: 471f378eab4c + | x 471f378eab4c + |/ Successors: [7a230b46bf61] + | semi-colon: [7a230b46bf61] + | Fate: superseed as 7a230b46bf61 + o ea207398892e + diff --git a/tests/test-evolve.t b/tests/test-evolve.t --- a/tests/test-evolve.t +++ b/tests/test-evolve.t @@ -779,6 +779,17 @@ more than 2 successors: 0 available keys: user: 10 + marker size: + format v1: + smallest length: 69 + longer length: 69 + median length: 69 + mean length: 69 + format v0: + smallest length: * (glob) + longer length: * (glob) + median length: * (glob) + mean length: * (glob) disconnected clusters: 1 any known node: 1 smallest length: 10 @@ -1477,121 +1488,3 @@ $ hg status newlyadded A newlyadded - -hg metaedit ------------ - - $ hg update --clean . - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ rm newlyadded - $ hg metaedit -r 0 - abort: cannot edit commit information for public revisions - [255] - $ hg metaedit --fold - abort: revisions must be specified with --fold - [255] - $ hg metaedit -r 0 --fold - abort: cannot fold public revisions - [255] - $ hg metaedit '36 + 42' --fold - abort: cannot fold non-linear revisions (multiple roots given) - [255] - $ hg metaedit '36::39 + 41' --fold - abort: cannot fold non-linear revisions (multiple heads given) - [255] -check that metaedit respects allowunstable - $ hg metaedit '.^' --config 'experimental.evolution=createmarkers, allnewcommands' - abort: cannot edit commit information in the middle of a stack - (c904da5245b0 will become unstable and new unstable changes are not allowed) - [255] - $ hg metaedit '18::20' --fold --config 'experimental.evolution=createmarkers, allnewcommands' - abort: cannot fold chain not ending with a head or with branching - (new unstable changesets are not allowed) - [255] - $ hg metaedit --user foobar - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg log --template '{rev}: {author}\n' -r '42:' --hidden - 42: test - 43: foobar - $ hg log --template '{rev}: {author}\n' -r . - 43: foobar - -TODO: support this - $ hg metaedit '.^::.' - abort: editing multiple revisions without --fold is not currently supported - [255] - - $ HGEDITOR=cat hg metaedit '.^::.' --fold - HG: This is a fold of 2 changesets. - HG: Commit message of changeset 41. - - amended - - HG: Commit message of changeset 43. - - will be evolved safely - - - - HG: Enter commit message. Lines beginning with 'HG:' are removed. - HG: Leave message empty to abort commit. - HG: -- - HG: user: test - HG: branch 'default' - HG: changed a - HG: changed newfile - 2 changesets folded - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - - $ glog -r . - @ 44:41bf1183869c@default(draft) amended - | - ~ - -no new commit is created here because the date is the same - $ HGEDITOR=cat hg metaedit - amended - - - will be evolved safely - - - HG: Enter commit message. Lines beginning with 'HG:' are removed. - HG: Leave message empty to abort commit. - HG: -- - HG: user: test - HG: branch 'default' - HG: changed a - HG: changed newfile - nothing changed - - $ glog -r '.^::.' - @ 44:41bf1183869c@default(draft) amended - | - o 36:43c3f5ef149f@default(draft) add uu - | - ~ - -TODO: don't create a new commit in this case - $ hg metaedit --config defaults.metaedit= - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n' - 36: add uu - 45: amended - - $ hg up .^ - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg metaedit --user foobar2 45 - $ hg log --template '{rev}: {author}\n' -r '42:' --hidden - 42: test - 43: foobar - 44: test - 45: test - 46: foobar2 - $ hg diff -r 45 -r 46 --hidden - -'fold' one commit - $ hg metaedit 39 --fold --user foobar3 - 1 changesets folded - $ hg log -r 47 --template '{rev}: {author}\n' - 47: foobar3 diff --git a/tests/test-exchange-obsmarkers-case-B1.t b/tests/test-exchange-obsmarkers-case-B1.t --- a/tests/test-exchange-obsmarkers-case-B1.t +++ b/tests/test-exchange-obsmarkers-case-B1.t @@ -75,7 +75,7 @@ =========== a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 0000000000000000000000000000000000000000 f5bc6836db60e308a17ba08bf050154ba9c4fad7 926d9d84b97b3483891ae983990ad87c1f7827e9 - f6fbb35d8ac958bbe70035e4c789c18471cdc0af e041f7ff1c7bd5501c7ab602baa35f0873128021 + f6fbb35d8ac958bbe70035e4c789c18471cdc0af c2398c6305068b6b377f36402c507b713a7c586f obshashrange ============ rev node index size depth obshash diff --git a/tests/test-exchange-obsmarkers-case-B2.t b/tests/test-exchange-obsmarkers-case-B2.t --- a/tests/test-exchange-obsmarkers-case-B2.t +++ b/tests/test-exchange-obsmarkers-case-B2.t @@ -65,7 +65,7 @@ obshashtree =========== a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 52a5380bc04783a9ad43bb2ab2f47a02ef02adcc - f5bc6836db60e308a17ba08bf050154ba9c4fad7 c5a567339e205e8cc4c494e4fb82944daaec449c + f5bc6836db60e308a17ba08bf050154ba9c4fad7 6892277170c13fec6712303d639965a454b5cabf obshashrange ============ rev node index size depth obshash diff --git a/tests/test-exchange-obsmarkers-case-B3.t b/tests/test-exchange-obsmarkers-case-B3.t --- a/tests/test-exchange-obsmarkers-case-B3.t +++ b/tests/test-exchange-obsmarkers-case-B3.t @@ -78,7 +78,7 @@ a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 0000000000000000000000000000000000000000 f5bc6836db60e308a17ba08bf050154ba9c4fad7 0000000000000000000000000000000000000000 35b1839966785d5703a01607229eea932db42f87 631ab4cd02ffa1d144dc8f32a18be574076031e3 - e56289ab6378dc752fd7965f8bf66b58bda740bd 47c9d2d8db5d4b1eddd0266329ad260ccc84772c + e56289ab6378dc752fd7965f8bf66b58bda740bd a37fa02ad96f2e8ecce7080e73cfe22af8e1b14f obshashrange ============ rev node index size depth obshash diff --git a/tests/test-exchange-obsmarkers-case-B4.t b/tests/test-exchange-obsmarkers-case-B4.t --- a/tests/test-exchange-obsmarkers-case-B4.t +++ b/tests/test-exchange-obsmarkers-case-B4.t @@ -93,7 +93,7 @@ a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 1900882e85db10a1dc5bc7748f436a8a834356c6 f5bc6836db60e308a17ba08bf050154ba9c4fad7 c27e764c783f451ef3aa40daf2a3795e6674cd06 f6fbb35d8ac958bbe70035e4c789c18471cdc0af 907beff79fdff2b82b5d3bed7989107a6d744508 - 7f7f229b13a629a5b20581c6cb723f4e2ca54bed c27e764c783f451ef3aa40daf2a3795e6674cd06 + 7f7f229b13a629a5b20581c6cb723f4e2ca54bed 86e6b5a5ad828d83b3205ea4ca11154972da19a1 obshashrange ============ rev node index size depth obshash diff --git a/tests/test-exchange-obsmarkers-case-B5.t b/tests/test-exchange-obsmarkers-case-B5.t --- a/tests/test-exchange-obsmarkers-case-B5.t +++ b/tests/test-exchange-obsmarkers-case-B5.t @@ -100,7 +100,7 @@ 6e72f0a95b5e01a7504743aa941f69cb1fbef8b0 13bd00d88332fcd3fe634ed42f9d35c9cfc06398 1d0f3cd253006f014c7687a78abbc9287db4101d 01d985a82467333a4de7a5b4e8a0de3286f8bda8 e5ea8f9c73143125d36658e90ef70c6d2027a5b7 d2b1159bcf3694aabc3674785b1859544c35357d - 069b05c3876d56f62895e853a501ea58ea85f68d d00e3201fcf83a1bf42e70757f07b45bdd77a220 + 069b05c3876d56f62895e853a501ea58ea85f68d a07163ee7e7cda4065f7593715b7b12f833f7065 obshashrange ============ rev node index size depth obshash diff --git a/tests/test-exchange-obsmarkers-case-B6.t b/tests/test-exchange-obsmarkers-case-B6.t --- a/tests/test-exchange-obsmarkers-case-B6.t +++ b/tests/test-exchange-obsmarkers-case-B6.t @@ -77,7 +77,7 @@ a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 86e41541149f4b6cccc5fd131d744d8e83a681e5 f5bc6836db60e308a17ba08bf050154ba9c4fad7 f2e05412d3f1d5bc1ae647cf9efc43e0399c26ca 962ecf6b1afc94e15c7e48fdfb76ef8abd11372b 974507d1c466d0aa86d288836194339ed3b98736 - f6298a8ac3a4b78bbeae5f1d3dc5bc3c3812f0f3 04e03a8959d8a39984e6a8f4a16fba975b364747 + f6298a8ac3a4b78bbeae5f1d3dc5bc3c3812f0f3 1e0b6dce792c229c865a543c6f8964b4bba5fb5a obshashrange ============ rev node index size depth obshash diff --git a/tests/test-exchange-obsmarkers-case-B7.t b/tests/test-exchange-obsmarkers-case-B7.t --- a/tests/test-exchange-obsmarkers-case-B7.t +++ b/tests/test-exchange-obsmarkers-case-B7.t @@ -71,7 +71,7 @@ =========== a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 0000000000000000000000000000000000000000 f5bc6836db60e308a17ba08bf050154ba9c4fad7 926d9d84b97b3483891ae983990ad87c1f7827e9 - f6fbb35d8ac958bbe70035e4c789c18471cdc0af e041f7ff1c7bd5501c7ab602baa35f0873128021 + f6fbb35d8ac958bbe70035e4c789c18471cdc0af c2398c6305068b6b377f36402c507b713a7c586f obshashrange ============ rev node index size depth obshash diff --git a/tests/test-exchange-obsmarkers-case-C1.t b/tests/test-exchange-obsmarkers-case-C1.t --- a/tests/test-exchange-obsmarkers-case-C1.t +++ b/tests/test-exchange-obsmarkers-case-C1.t @@ -75,8 +75,8 @@ obshashtree =========== a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 1ce18e5a71f78d443a80c819f2f7197c4706af70 - f5bc6836db60e308a17ba08bf050154ba9c4fad7 92af733686ce7e0469d8b2b87b4612a4c2d33468 - f6fbb35d8ac958bbe70035e4c789c18471cdc0af 3800aeba3728457abb9c508c94f6abc59e698c55 + f5bc6836db60e308a17ba08bf050154ba9c4fad7 5c3c935df6f4007c633c3386d6f58b22c274809e + f6fbb35d8ac958bbe70035e4c789c18471cdc0af bccb3d243420ef26782fa11f209830db360c34b2 obshashrange ============ rev node index size depth obshash diff --git a/tests/test-exchange-obsmarkers-case-C2.t b/tests/test-exchange-obsmarkers-case-C2.t --- a/tests/test-exchange-obsmarkers-case-C2.t +++ b/tests/test-exchange-obsmarkers-case-C2.t @@ -80,7 +80,7 @@ =========== a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 0000000000000000000000000000000000000000 28b51eb45704506b5c603decd6bf7ac5e0f6a52f 72f95b7b9fa12243aeb90433d211f2c38263da31 - 06055a7959d4128e6e3bccfd01482e83a2db8a3a 58ecf9a107b10986d88da605eb0d03b7f24ae486 + 06055a7959d4128e6e3bccfd01482e83a2db8a3a 9d758cd0bf02f27e9b7de5665b10ceb1dc8bb1f1 e5ea8f9c73143125d36658e90ef70c6d2027a5b7 289cb0d058c81c763eca8bb438657dba9a7ba646 obshashrange ============ diff --git a/tests/test-exchange-obsmarkers-case-C3.t b/tests/test-exchange-obsmarkers-case-C3.t --- a/tests/test-exchange-obsmarkers-case-C3.t +++ b/tests/test-exchange-obsmarkers-case-C3.t @@ -84,8 +84,8 @@ =========== a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 40be80b35671116f2c61ef25797806536a6eb5a0 28b51eb45704506b5c603decd6bf7ac5e0f6a52f beac7228bbe708bc7c9181c3c27f8a17f21dbd9f - 06055a7959d4128e6e3bccfd01482e83a2db8a3a 8b648bd67281e9e525919285ac7b3bb2836c2f02 - e5ea8f9c73143125d36658e90ef70c6d2027a5b7 dcd2b566ad0983333be704afdc205066e1a6b742 + 06055a7959d4128e6e3bccfd01482e83a2db8a3a 04b20150f38991d297ecfe0bf0f77deb816aed9d + e5ea8f9c73143125d36658e90ef70c6d2027a5b7 a59f7d5ea437389a33d2e98b9b85ba1d568a743e obshashrange ============ rev node index size depth obshash diff --git a/tests/test-exchange-obsmarkers-case-C4.t b/tests/test-exchange-obsmarkers-case-C4.t --- a/tests/test-exchange-obsmarkers-case-C4.t +++ b/tests/test-exchange-obsmarkers-case-C4.t @@ -87,7 +87,7 @@ a9bdc8b26820b1b87d585b82eb0ceb4a2ecdbc04 a9c02d134f5b98acc74d1dc4eb28fd59f958a2bd f5bc6836db60e308a17ba08bf050154ba9c4fad7 619b4d13bd9878f04d7208dcfcf1e89da826f6be 35b1839966785d5703a01607229eea932db42f87 ddeb7b7a87378f59cecb36d5146df0092b6b3327 - 7f7f229b13a629a5b20581c6cb723f4e2ca54bed 58ef2e726c5bd89bceffb6243294b38eadbf3d60 + 7f7f229b13a629a5b20581c6cb723f4e2ca54bed 0ef78fef48a60c677f55ba6efa4126dab2d6caf2 obshashrange ============ rev node index size depth obshash diff --git a/tests/test-evolve.t b/tests/test-metaedit.t copy from tests/test-evolve.t copy to tests/test-metaedit.t --- a/tests/test-evolve.t +++ b/tests/test-metaedit.t @@ -20,7 +20,7 @@ $ mkcommit() { > echo "$1" > "$1" > hg add "$1" - > hg ci -m "add $1" + > hg ci -m "$1" > } $ mkstack() { @@ -42,143 +42,6 @@ > for i in $@; do mkcommit $i ; done > } -Test the evolution test topic is installed - - $ hg help evolution - Safely Rewriting History - """""""""""""""""""""""" - - Obsolescence markers make it possible to mark changesets that have been - deleted or superset in a new version of the changeset. - - Unlike the previous way of handling such changes, by stripping the old - changesets from the repository, obsolescence markers can be propagated - between repositories. This allows for a safe and simple way of exchanging - mutable history and altering it after the fact. Changeset phases are - respected, such that only draft and secret changesets can be altered (see - 'hg help phases' for details). - - Obsolescence is tracked using "obsolete markers", a piece of metadata - tracking which changesets have been made obsolete, potential successors - for a given changeset, the moment the changeset was marked as obsolete, - and the user who performed the rewriting operation. The markers are stored - separately from standard changeset data can be exchanged without any of - the precursor changesets, preventing unnecessary exchange of obsolescence - data. - - The complete set of obsolescence markers describes a history of changeset - modifications that is orthogonal to the repository history of file - modifications. This changeset history allows for detection and automatic - resolution of edge cases arising from multiple users rewriting the same - part of history concurrently. - - Current feature status - ====================== - - This feature is still in development. If you see this help, you have - enabled an extension that turned this feature on. - - Obsolescence markers will be exchanged between repositories that - explicitly assert support for the obsolescence feature (this can currently - only be done via an extension). - -various init - - $ hg init local - $ cd local - $ mkcommit a - $ mkcommit b - $ cat >> .hg/hgrc << EOF - > [phases] - > publish = True - > EOF - $ hg pull -q . # make 1 public - $ rm .hg/hgrc - $ mkcommit c - $ mkcommit d - $ hg up 1 - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ mkcommit e -q - created new head - $ mkcommit f - $ hg qlog - 5 - e44648563c73 add f (draft) - 4 - fbb94e3a0ecf add e (draft) - 3 - 47d2a3944de8 add d (draft) - 2 - 4538525df7e2 add c (draft) - 1 - 7c3bad9141dc add b (public) - 0 - 1f0dee641bb7 add a (public) - -test kill and immutable changeset - - $ hg log -r 1 --template '{rev} {phase} {obsolete}\n' - 1 public - $ hg prune 1 - abort: cannot prune immutable changeset: 7c3bad9141dc - (see 'hg help phases' for details) - [255] - $ hg log -r 1 --template '{rev} {phase} {obsolete}\n' - 1 public - -test simple kill - - $ hg id -n - 5 - $ hg prune . - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - working directory now at fbb94e3a0ecf - 1 changesets pruned - $ hg qlog - 4 - fbb94e3a0ecf add e (draft) - 3 - 47d2a3944de8 add d (draft) - 2 - 4538525df7e2 add c (draft) - 1 - 7c3bad9141dc add b (public) - 0 - 1f0dee641bb7 add a (public) - -test multiple kill - - $ hg prune 4 -r 3 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - working directory now at 7c3bad9141dc - 2 changesets pruned - $ hg qlog - 2 - 4538525df7e2 add c (draft) - 1 - 7c3bad9141dc add b (public) - 0 - 1f0dee641bb7 add a (public) - -test kill with dirty changes - - $ hg up 2 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ echo 4 > g - $ hg add g - $ hg prune . - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - working directory now at 7c3bad9141dc - 1 changesets pruned - $ hg st - A g - -Smoketest debugobsrelsethashtree: - - $ hg debugobsrelsethashtree - 1f0dee641bb7258c56bd60e93edfa2405381c41e 0000000000000000000000000000000000000000 - 7c3bad9141dcb46ff89abf5f61856facd56e476c * (glob) - 4538525df7e2b9f09423636c61ef63a4cb872a2d * (glob) - 47d2a3944de8b013de3be9578e8e344ea2e6c097 * (glob) - fbb94e3a0ecf6d20c2cc31152ef162ce45af982f * (glob) - e44648563c73f75950076031c6fdf06629de95f1 * (glob) - -Smoketest stablerange.obshash: - - $ hg debugobshashrange --subranges --rev 'head()' - rev node index size depth obshash - 1 7c3bad9141dc 0 2 2 * (glob) - 0 1f0dee641bb7 0 1 1 000000000000 - 1 7c3bad9141dc 1 1 2 * (glob) - - $ cd .. - ########################## importing Parren test ########################## @@ -188,7 +51,7 @@ > logtemplate = "{rev}\t{bookmarks}: {desc|firstline} - {author|user}\n" > EOF -Creating And Updating Changeset +HG METAEDIT =============================== Setup the Base Repo @@ -196,1294 +59,44 @@ We start with a plain base repo:: - $ hg init main; cd main - $ cat >main-file-1 <<-EOF - > One - > - > Two - > - > Three - > EOF - $ echo Two >main-file-2 - $ hg add - adding main-file-1 - adding main-file-2 - $ hg commit --message base - $ cd .. - -and clone this into a new repo where we do our work:: - - $ hg clone main work - updating to branch default - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cd work - - -Create First Patch ------------------- - -To begin with, we just do the changes that will be the initial version of the changeset:: - - $ echo One >file-from-A - $ sed -i'' -e s/One/Eins/ main-file-1 - $ hg add file-from-A - -So this is what we would like our changeset to be:: - - $ hg diff - diff --git a/file-from-A b/file-from-A - new file mode 100644 - --- /dev/null - +++ b/file-from-A - @@ -0,0 +1,1 @@ - +One - diff --git a/main-file-1 b/main-file-1 - --- a/main-file-1 - +++ b/main-file-1 - @@ -1,1 +1,1 @@ - -One - +Eins - -To commit it we just - commit it:: - - $ hg commit --message "a nifty feature" - -and place a bookmark so we can easily refer to it again (which we could have done before the commit):: - - $ hg book feature-A - - -Create Second Patch -------------------- - -Let's do this again for the second changeset:: - - $ echo Two >file-from-B - $ sed -i'' -e s/Two/Zwie/ main-file-1 - $ hg add file-from-B - -Before committing, however, we need to switch to a new bookmark for the second -changeset. Otherwise we would inadvertently move the bookmark for our first changeset. -It is therefore advisable to always set the bookmark before committing:: - - $ hg book feature-B - $ hg commit --message "another feature (child of $(hg log -r . -T '{node|short}'))" - -So here we are:: - - $ hg book - feature-A 1:568a468b60fc - * feature-B 2:73296a82292a - - -Fix The Second Patch --------------------- - -There's a typo in feature-B. We spelled *Zwie* instead of *Zwei*:: - - $ hg diff --change tip | grep -F Zwie - +Zwie - -Fixing this is very easy. Just change:: - - $ sed -i'' -e s/Zwie/Zwei/ main-file-1 - -and **amend**:: - - $ hg amend - -This results in a new single changeset for our amended changeset, and the old -changeset plus the updating changeset are hidden from view by default:: - - $ hg log - 4 feature-B: another feature (child of 568a468b60fc) - test - 1 feature-A: a nifty feature - test - 0 : base - test - - $ hg up feature-A -q - $ hg bookmark -i feature-A - $ sed -i'' -e s/Eins/Un/ main-file-1 - -(amend of public changeset denied) - - $ hg phase --public 0 -v - phase changed for 1 changesets - - -(amend of on ancestors) - - $ hg amend - 1 new unstable changesets - $ hg log - 6 feature-A: a nifty feature - test - 4 feature-B: another feature (child of 568a468b60fc) - test - 1 : a nifty feature - test - 0 : base - test - $ hg up -q 0 - $ glog --hidden - o 6:ba0ec09b1bab@default(draft) a nifty feature - | - | x 5:c296b79833d1@default(draft) temporary amend commit for 568a468b60fc - | | - | | o 4:6992c59c6b06@default(draft) another feature (child of 568a468b60fc) - | |/ - | | x 3:c97947cdc7a2@default(draft) temporary amend commit for 73296a82292a - | | | - | | x 2:73296a82292a@default(draft) another feature (child of 568a468b60fc) - | |/ - | x 1:568a468b60fc@default(draft) a nifty feature - |/ - @ 0:e55e0562ee93@default(public) base - - $ hg debugobsolete - 73296a82292a76fb8a7061969d2489ec0d84cd5e 6992c59c6b06a1b4a92e24ff884829ae026d018b 0 (*) {'user': 'test'} (glob) - c97947cdc7a2a11cf78419f5c2c3dd3944ec79e8 0 {73296a82292a76fb8a7061969d2489ec0d84cd5e} (*) {'user': 'test'} (glob) - 568a468b60fc99a42d5d4ddbe181caff1eef308d ba0ec09b1babf3489b567853807f452edd46704f 0 (*) {'user': 'test'} (glob) - c296b79833d1d497f33144786174bf35e04e44a3 0 {568a468b60fc99a42d5d4ddbe181caff1eef308d} (*) {'user': 'test'} (glob) - $ hg evolve - move:[4] another feature (child of 568a468b60fc) - atop:[6] a nifty feature - merging main-file-1 - working directory is now at 99833d22b0c6 - $ hg log - 7 feature-B: another feature (child of ba0ec09b1bab) - test - 6 feature-A: a nifty feature - test - 0 : base - test - -Test commit -o options - - $ hg up 6 - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg revert -r 7 --all - adding file-from-B - reverting main-file-1 - $ sed -i'' -e s/Zwei/deux/ main-file-1 - $ hg commit -m 'another feature that rox' -o 7 + $ hg init $TESTTMP/metaedit; cd $TESTTMP/metaedit + $ mkcommit "ROOT" + $ hg phase --public "desc(ROOT)" + $ mkcommit "A" + $ mkcommit "B" + $ hg up "desc(A)" + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ mkcommit "C" created new head - $ hg log - 8 feature-B: another feature that rox - test - 6 feature-A: a nifty feature - test - 0 : base - test - -phase change turning obsolete changeset public issue a bumped warning - - $ hg phase --hidden --public 7 - 1 new bumped changesets - -all solving bumped troubled - - $ hg glog - @ 8 feature-B: another feature that rox - test - | - | o 7 : another feature (child of ba0ec09b1bab) - test - |/ - o 6 feature-A: a nifty feature - test - | - o 0 : base - test - - $ hg evolve --any --traceback --bumped - recreate:[8] another feature that rox - atop:[7] another feature (child of ba0ec09b1bab) - computing new diff - committed as 6707c5e1c49d - working directory is now at 6707c5e1c49d - $ hg glog - @ 9 feature-B: bumped update to 99833d22b0c6: - test - | - o 7 : another feature (child of ba0ec09b1bab) - test - | - o 6 feature-A: a nifty feature - test - | - o 0 : base - test - - $ hg diff --hidden -r 9 -r 8 - $ hg diff -r 9^ -r 9 - diff --git a/main-file-1 b/main-file-1 - --- a/main-file-1 - +++ b/main-file-1 - @@ -3,1 +3,1 @@ - -Zwei - +deux - $ hg log -r 'bumped()' # no more bumped - -test evolve --all - $ sed -i'' -e s/deux/to/ main-file-1 - $ hg commit -m 'dansk 2!' - $ sed -i'' -e s/Three/tre/ main-file-1 - $ hg commit -m 'dansk 3!' - $ hg update 9 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ sed -i'' -e s/Un/Én/ main-file-1 - $ hg commit --amend -m 'dansk!' - 2 new unstable changesets - -(ninja test for the {trouble} template: - - $ hg log -G --template '{rev} {troubles}\n' - @ 13 - | - | o 11 unstable - | | - | o 10 unstable - | | - | x 9 - |/ - o 7 - | - o 6 - | - o 0 - - - -(/ninja) - - $ hg evolve --all --traceback - move:[10] dansk 2! - atop:[13] dansk! - merging main-file-1 - move:[11] dansk 3! - atop:[14] dansk 2! - merging main-file-1 - working directory is now at 68557e4f0048 - $ hg glog - @ 15 : dansk 3! - test - | - o 14 : dansk 2! - test - | - o 13 feature-B: dansk! - test - | - o 7 : another feature (child of ba0ec09b1bab) - test - | - o 6 feature-A: a nifty feature - test - | - o 0 : base - test - - - $ cd .. - -enable general delta - - $ cat << EOF >> $HGRCPATH - > [format] - > generaldelta=1 - > EOF - - - - $ hg init alpha - $ cd alpha - $ echo 'base' > firstfile - $ hg add firstfile - $ hg ci -m 'base' - - $ cd .. - $ hg clone -Ur 0 alpha beta - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - $ cd alpha - - $ cat << EOF > A - > We - > need - > some - > kind - > of - > file - > big - > enough - > to - > prevent - > snapshot - > . - > yes - > new - > lines - > are - > useless - > . - > EOF - $ hg add A - $ hg commit -m 'adding A' - $ hg mv A B - $ echo '.' >> B - $ hg amend -m 'add B' - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 3 files, 4 changesets, 4 total revisions - $ hg --config extensions.hgext.mq= strip 'extinct()' - abort: empty revision set - [255] - $ hg --config extensions.hgext.mq= strip --hidden 'extinct()' - saved backup bundle to $TESTTMP/alpha/.hg/strip-backup/e87767087a57-d7bd82e9-backup.hg (glob) - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 2 files, 2 changesets, 2 total revisions - $ cd .. - -Clone just this branch - - $ cd beta - $ hg pull -r tip ../alpha - pulling from ../alpha - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - 2 new obsolescence markers - (run 'hg update' to get a working copy) - $ hg up - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - - $ cd .. - -Test graft --obsolete/--old-obsolete - - $ hg init test-graft - $ cd test-graft - $ mkcommit 0 - $ mkcommit 1 - $ mkcommit 2 - $ mkcommit 3 - $ hg up -qC 0 - $ mkcommit 4 - created new head - $ glog --hidden - @ 4:ce341209337f@default(draft) add 4 - | - | o 3:0e84df4912da@default(draft) add 3 - | | - | o 2:db038628b9e5@default(draft) add 2 - | | - | o 1:73d38bb17fd7@default(draft) add 1 - |/ - o 0:8685c6d34325@default(draft) add 0 - - $ hg graft -r3 -O - grafting 3:0e84df4912da "add 3" - $ hg graft -r1 -o 2 - grafting 1:73d38bb17fd7 "add 1" - $ glog --hidden - @ 6:acb28cd497b7@default(draft) add 1 - | - o 5:0b9e50c35132@default(draft) add 3 - | - o 4:ce341209337f@default(draft) add 4 - | - | x 3:0e84df4912da@default(draft) add 3 - | | - | x 2:db038628b9e5@default(draft) add 2 - | | - | o 1:73d38bb17fd7@default(draft) add 1 - |/ - o 0:8685c6d34325@default(draft) add 0 - - $ hg debugobsolete - 0e84df4912da4c7cad22a3b4fcfd58ddfb7c8ae9 0b9e50c35132ff548ec0065caea6a87e1ebcef32 0 (*) {'user': 'test'} (glob) - db038628b9e56f51a454c0da0c508df247b41748 acb28cd497b7f8767e01ef70f68697a959573c2d 0 (*) {'user': 'test'} (glob) - -Test graft --continue - - $ hg up -qC 0 - $ echo 2 > 1 - $ hg ci -Am conflict 1 + $ mkcommit "D" + $ echo "D'" > D + $ hg amend -m "D2" + $ hg up "desc(C)" + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ mkcommit "E" created new head - $ hg up -qC 6 - $ hg graft -O 7 - grafting 7:a5bfd90a2f29 "conflict" (tip) - merging 1 - warning: conflicts while merging 1! (edit, then use 'hg resolve --mark') - abort: unresolved conflicts, can't continue - (use 'hg resolve' and 'hg graft --continue') - [255] - $ hg log -r7 --template '{rev}:{node|short} {obsolete}\n' - 7:a5bfd90a2f29 - $ echo 3 > 1 - $ hg resolve -m 1 - (no more unresolved files) - continue: hg graft --continue - $ hg graft --continue -O - grafting 7:a5bfd90a2f29 "conflict" (tip) - $ glog --hidden - @ 8:920e58bb443b@default(draft) conflict - | - | x 7:a5bfd90a2f29@default(draft) conflict - | | - o | 6:acb28cd497b7@default(draft) add 1 - | | - o | 5:0b9e50c35132@default(draft) add 3 - | | - o | 4:ce341209337f@default(draft) add 4 - |/ - | x 3:0e84df4912da@default(draft) add 3 - | | - | x 2:db038628b9e5@default(draft) add 2 - | | - | o 1:73d38bb17fd7@default(draft) add 1 - |/ - o 0:8685c6d34325@default(draft) add 0 - - $ hg debugobsolete - 0e84df4912da4c7cad22a3b4fcfd58ddfb7c8ae9 0b9e50c35132ff548ec0065caea6a87e1ebcef32 0 (*) {'user': 'test'} (glob) - db038628b9e56f51a454c0da0c508df247b41748 acb28cd497b7f8767e01ef70f68697a959573c2d 0 (*) {'user': 'test'} (glob) - a5bfd90a2f29c7ccb8f917ff4e5013a9053d0a04 920e58bb443b73eea9d6d65570b4241051ea3229 0 (*) {'user': 'test'} (glob) - -Test touch - - $ glog - @ 8:920e58bb443b@default(draft) conflict - | - o 6:acb28cd497b7@default(draft) add 1 - | - o 5:0b9e50c35132@default(draft) add 3 - | - o 4:ce341209337f@default(draft) add 4 - | - | o 1:73d38bb17fd7@default(draft) add 1 - |/ - o 0:8685c6d34325@default(draft) add 0 - - $ hg touch - $ glog - @ 9:*@default(draft) conflict (glob) - | - o 6:acb28cd497b7@default(draft) add 1 - | - o 5:0b9e50c35132@default(draft) add 3 - | - o 4:ce341209337f@default(draft) add 4 - | - | o 1:73d38bb17fd7@default(draft) add 1 - |/ - o 0:8685c6d34325@default(draft) add 0 - - $ hg touch . - $ glog - @ 10:*@default(draft) conflict (glob) - | - o 6:acb28cd497b7@default(draft) add 1 - | - o 5:0b9e50c35132@default(draft) add 3 - | - o 4:ce341209337f@default(draft) add 4 - | - | o 1:73d38bb17fd7@default(draft) add 1 - |/ - o 0:8685c6d34325@default(draft) add 0 - - -Test fold - - $ rm *.orig - $ hg fold - abort: no revisions specified - [255] - $ hg fold --from - abort: no revisions specified - [255] - $ hg fold . - abort: must specify either --from or --exact - [255] - $ hg fold --from . --exact - abort: cannot use both --from and --exact - [255] - $ hg fold --from . - single revision specified, nothing to fold - [1] - $ hg fold 0::10 --rev 1 --exact - abort: cannot fold non-linear revisions (multiple heads given) - [255] - $ hg fold -r 4 -r 6 --exact - abort: cannot fold non-linear revisions (multiple roots given) - [255] - $ hg fold --from 10 1 - abort: cannot fold non-linear revisions - (given revisions are unrelated to parent of working directory) - [255] - $ hg fold --exact -r "4 and not 4" - abort: specified revisions evaluate to an empty set - (use different revision arguments) - [255] - $ hg phase --public 0 - $ hg fold --from -r 0 - abort: cannot fold public revisions - [255] - $ hg fold --from -r 5 - 3 changesets folded - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg fold --from 6 # want to run hg fold 6 - abort: hidden revision '6'! - (use --hidden to access hidden revisions) - [255] - $ hg log -r 11 --template '{desc}\n' - add 3 - - - add 1 - - - conflict - $ hg debugrebuildstate - $ hg st - -Test fold with wc parent is not the head of the folded revision - - $ hg up 4 - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg fold --rev 4::11 --user victor --exact - 2 changesets folded - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ glog - @ 12:d26d339c513f@default(draft) add 4 - | - | o 1:73d38bb17fd7@default(draft) add 1 - |/ - o 0:8685c6d34325@default(public) add 0 - - $ hg log --template '{rev}: {author}\n' - 12: victor - 1: test - 0: test - $ hg log -r 12 --template '{desc}\n' - add 4 - - - add 3 - - - add 1 - - - conflict - $ hg debugrebuildstate - $ hg st - -Test obsstore stat - - $ hg debugobsstorestat - markers total: 10 - for known precursors: 10 - with parents data: 0 - markers with no successors: 0 - 1 successors: 10 - 2 successors: 0 - more than 2 successors: 0 - available keys: - user: 10 - disconnected clusters: 1 - any known node: 1 - smallest length: 10 - longer length: 10 - median length: 10 - mean length: 10 - using parents data: 1 - any known node: 1 - smallest length: 10 - longer length: 10 - median length: 10 - mean length: 10 - - -Test evolving renames - - $ hg up null - 0 files updated, 0 files merged, 4 files removed, 0 files unresolved - $ echo a > a - $ hg ci -Am a - adding a - created new head - $ echo b > b - $ hg ci -Am b - adding b - $ hg mv a c - $ hg ci -m c - $ hg prune .^ - 1 changesets pruned - 1 new unstable changesets - $ hg stab --any - move:[15] c - atop:[13] a - working directory is now at 3742bde73477 - $ hg st -C --change=tip - A c - a - R a - -Test fold with commit messages - - $ cd ../work - $ hg fold --from .^ --message "Folding with custom commit message" - 2 changesets folded - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ glog - @ 16:98cb758db56d@default(draft) Folding with custom commit message - | - o 13:0a2f9b959bb4@default(draft) dansk! - | - o 7:99833d22b0c6@default(public) another feature (child of ba0ec09b1bab) - | - o 6:ba0ec09b1bab@default(public) a nifty feature - | - o 0:e55e0562ee93@default(public) base - - $ cat > commit-message < A longer - > commit message - > EOF + $ mkcommit "F" - $ hg fold --from .^ --logfile commit-message - 2 changesets folded - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg qlog - 17 - a00182c58888 A longer - commit message (draft) - 7 - 99833d22b0c6 another feature (child of ba0ec09b1bab) (public) - 6 - ba0ec09b1bab a nifty feature (public) - 0 - e55e0562ee93 base (public) - - $ cd .. - -Test branch preservation: -=========================== - - $ hg init evolving-branch - $ cd evolving-branch - $ touch a - $ hg add a - $ hg ci -m 'a0' - $ echo 1 > a - $ hg ci -m 'a1' - $ echo 2 > a - $ hg ci -m 'a2' - $ echo 3 > a - $ hg ci -m 'a3' - - $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n' - @ 3 [default] a3 - | - o 2 [default] a2 - | - o 1 [default] a1 - | - o 0 [default] a0 - - -branch change propagated - - $ hg up 'desc(a2)' - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg branch mybranch - marked working directory as branch mybranch - (branches are permanent and global, did you want a bookmark?) - $ hg amend - 1 new unstable changesets - - $ hg evolve - move:[3] a3 - atop:[5] a2 - working directory is now at 7c5649f73d11 - - $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n' - @ 6 [mybranch] a3 - | - o 5 [mybranch] a2 - | - o 1 [default] a1 - | - o 0 [default] a0 - - -branch change preserved +Test +---- - $ hg up 'desc(a1)' - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg amend -m 'a1_' - 2 new unstable changesets - $ hg evolve - move:[5] a2 - atop:[7] a1_ - working directory is now at eb07e22a0e63 - $ hg evolve - move:[6] a3 - atop:[8] a2 - working directory is now at 777c26ca5e78 - $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n' - @ 9 [mybranch] a3 - | - o 8 [mybranch] a2 - | - o 7 [default] a1_ - | - o 0 [default] a0 - - -Evolve from the middle of a stack pick the right changesets. - - $ hg up 7 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg ci --amend -m 'a1__' - 2 new unstable changesets - - $ hg up 8 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n' - o 10 [default] a1__ + $ hg log -G + @ 8 : F - test | - | o 9 [mybranch] a3 - | | - | @ 8 [mybranch] a2 - | | - | x 7 [default] a1_ - |/ - o 0 [default] a0 - - $ hg evolve - nothing to evolve on current working copy parent - (2 other unstable in the repository, do you want --any or --rev) - [2] - - -Evolve disables active bookmarks. - - $ hg up 10 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg bookmark testbookmark - $ ls .hg/bookmarks* - .hg/bookmarks - .hg/bookmarks.* (glob) - $ hg evolve - move:[8] a2 - atop:[10] a1__ - (leaving bookmark testbookmark) - working directory is now at d952e93add6f - $ ls .hg/bookmarks* - .hg/bookmarks - -Possibility to select what trouble to solve first, asking for bumped before -divergent - $ hg up 10 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg revert -r 11 --all - reverting a - $ hg log -G --template '{rev} [{branch}] {desc|firstline}\n' - o 11 [mybranch] a2 + o 7 : E - test | - @ 10 [default] a1__ - | - | o 9 [mybranch] a3 - | | - | x 8 [mybranch] a2 - | | - | x 7 [default] a1_ + | o 6 : D2 - test |/ - o 0 [default] a0 - - $ echo "hello world" > newfile - $ hg add newfile - $ hg commit -m "add new file bumped" -o 11 - $ hg phase --public --hidden 11 - 1 new bumped changesets - $ hg glog - @ 12 : add new file bumped - test - | - | o 11 : a2 - test - |/ - o 10 testbookmark: a1__ - test - | - | o 9 : a3 - test - | | - | x 8 : a2 - test - | | - | x 7 : a1_ - test - |/ - o 0 : a0 - test - - -Now we have a bumped and an unstable changeset, we solve the bumped first -normally the unstable changeset would be solve first - - $ hg glog - @ 12 : add new file bumped - test - | - | o 11 : a2 - test - |/ - o 10 testbookmark: a1__ - test - | - | o 9 : a3 - test - | | - | x 8 : a2 - test - | | - | x 7 : a1_ - test - |/ - o 0 : a0 - test - - $ hg evolve -r 12 --bumped - recreate:[12] add new file bumped - atop:[11] a2 - computing new diff - committed as f15d32934071 - working directory is now at f15d32934071 - $ hg evolve --any - move:[9] a3 - atop:[13] bumped update to d952e93add6f: - working directory is now at cce26b684bfe -Check that we can resolve troubles in a revset with more than one commit - $ hg up 14 -C - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ mkcommit gg - $ hg up 14 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ mkcommit gh - created new head - $ hg up 14 - 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ printf "newline\nnewline\n" >> a - $ hg glog - o 16 : add gh - test - | - | o 15 : add gg - test - |/ - @ 14 : a3 - test - | - o 13 : bumped update to d952e93add6f: - test - | - o 11 : a2 - test - | - o 10 testbookmark: a1__ - test - | - o 0 : a0 - test - - $ hg amend - 2 new unstable changesets - $ hg glog - @ 18 : a3 - test + o 3 : C - test | - | o 16 : add gh - test - | | - | | o 15 : add gg - test - | |/ - | x 14 : a3 - test + | o 2 : B - test |/ - o 13 : bumped update to d952e93add6f: - test - | - o 11 : a2 - test - | - o 10 testbookmark: a1__ - test - | - o 0 : a0 - test - - -Evolving an empty revset should do nothing - $ hg evolve --rev "16 and 15" - set of specified revisions is empty - [1] - - $ hg evolve --rev "14::" --bumped - no bumped changesets in specified revisions - (do you want to use --unstable) - [2] - $ hg evolve --rev "14::" --unstable - move:[15] add gg - atop:[18] a3 - move:[16] add gh - atop:[18] a3 - working directory is now at e02107f98737 - $ hg glog - @ 20 : add gh - test - | - | o 19 : add gg - test - |/ - o 18 : a3 - test - | - o 13 : bumped update to d952e93add6f: - test - | - o 11 : a2 - test + o 1 : A - test | - o 10 testbookmark: a1__ - test - | - o 0 : a0 - test - -Enabling commands selectively, no command enabled, next and fold and unknown - $ cat >> $HGRCPATH < [experimental] - > evolution=createmarkers - > EOF - $ hg next - hg: unknown command 'next' - Mercurial Distributed SCM - - basic commands: - - add add the specified files on the next commit - annotate show changeset information by line for each file - clone make a copy of an existing repository - commit commit the specified files or all outstanding changes - diff diff repository (or selected files) - export dump the header and diffs for one or more changesets - forget forget the specified files on the next commit - init create a new repository in the given directory - log show revision history of entire repository or files - merge merge another revision into working directory - pull pull changes from the specified source - push push changes to the specified destination - remove remove the specified files on the next commit - serve start stand-alone webserver - status show changed files in the working directory - summary summarize working directory state - update update working directory (or switch revisions) - - (use "hg help" for the full list of commands or "hg -v" for details) - [255] - $ hg fold - hg: unknown command 'fold' - Mercurial Distributed SCM - - basic commands: + o 0 : ROOT - test - add add the specified files on the next commit - annotate show changeset information by line for each file - clone make a copy of an existing repository - commit commit the specified files or all outstanding changes - diff diff repository (or selected files) - export dump the header and diffs for one or more changesets - forget forget the specified files on the next commit - init create a new repository in the given directory - log show revision history of entire repository or files - merge merge another revision into working directory - pull pull changes from the specified source - push push changes to the specified destination - remove remove the specified files on the next commit - serve start stand-alone webserver - status show changed files in the working directory - summary summarize working directory state - update update working directory (or switch revisions) - - (use "hg help" for the full list of commands or "hg -v" for details) - [255] -Enabling commands selectively, only fold enabled, next is still unknown - $ cat >> $HGRCPATH < [experimental] - > evolution=createmarkers - > evolutioncommands=fold - > EOF - $ hg fold - abort: no revisions specified - [255] - $ hg next - hg: unknown command 'next' - Mercurial Distributed SCM - - basic commands: - - add add the specified files on the next commit - annotate show changeset information by line for each file - clone make a copy of an existing repository - commit commit the specified files or all outstanding changes - diff diff repository (or selected files) - export dump the header and diffs for one or more changesets - fold fold multiple revisions into a single one - forget forget the specified files on the next commit - init create a new repository in the given directory - log show revision history of entire repository or files - merge merge another revision into working directory - pull pull changes from the specified source - push push changes to the specified destination - remove remove the specified files on the next commit - serve start stand-alone webserver - status show changed files in the working directory - summary summarize working directory state - update update working directory (or switch revisions) - - (use "hg help" for the full list of commands or "hg -v" for details) - [255] - -Restore all of the evolution features - - $ cat >> $HGRCPATH < [experimental] - > evolution=all - > EOF - -Check hg evolve --rev on singled out commit - $ hg up 19 -C - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ mkcommit j1 - $ mkcommit j2 - $ mkcommit j3 - $ hg up .^^ - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ echo "hello" > j4 - $ hg add j4 - $ hg amend - 2 new unstable changesets - $ glog -r "18::" - @ 25:8dc373be86d9@default(draft) add j1 - | - | o 23:d7eadcf6eccd@default(draft) add j3 - | | - | o 22:2223ea564144@default(draft) add j2 - | | - | x 21:48490698b269@default(draft) add j1 - |/ - | o 20:e02107f98737@default(draft) add gh - | | - o | 19:24e63b319adf@default(draft) add gg - |/ - o 18:edc3c9de504e@default(draft) a3 - | - ~ - - $ hg evolve --rev 23 --any - abort: cannot specify both "--rev" and "--any" - [255] - $ hg evolve --rev 23 - cannot solve instability of d7eadcf6eccd, skipping - -Check that uncommit respects the allowunstable option -With only createmarkers we can only uncommit on a head - $ cat >> $HGRCPATH < [experimental] - > evolution=createmarkers, allnewcommands - > EOF - $ hg up 8dc373be86d9^ - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg uncommit --all - abort: cannot uncommit in the middle of a stack - [255] - $ hg up 8dc373be86d9 - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg uncommit --all - new changeset is empty - (use 'hg prune .' to remove it) - $ glog -r "18::" - @ 26:044804d0c10d@default(draft) add j1 - | - | o 23:d7eadcf6eccd@default(draft) add j3 - | | - | o 22:2223ea564144@default(draft) add j2 - | | - | x 21:48490698b269@default(draft) add j1 - |/ - | o 20:e02107f98737@default(draft) add gh - | | - o | 19:24e63b319adf@default(draft) add gg - |/ - o 18:edc3c9de504e@default(draft) a3 - | - ~ - -Check that prune respects the allowunstable option - $ hg up -C . + $ hg update --clean . 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg up 20 - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg evolve --all - nothing to evolve on current working copy parent - (2 other unstable in the repository, do you want --any or --rev) - [2] - $ hg evolve --all --any - move:[22] add j2 - atop:[26] add j1 - move:[23] add j3 - atop:[27] add j2 - working directory is now at c9a20e2d74aa - $ glog -r "18::" - @ 28:c9a20e2d74aa@default(draft) add j3 - | - o 27:b0e3066231e2@default(draft) add j2 - | - o 26:044804d0c10d@default(draft) add j1 - | - | o 20:e02107f98737@default(draft) add gh - | | - o | 19:24e63b319adf@default(draft) add gg - |/ - o 18:edc3c9de504e@default(draft) a3 - | - ~ - $ hg up 19 - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ mkcommit c5_ - created new head - $ hg prune '26 + 27' - abort: cannot prune in the middle of a stack - (new unstable changesets are not allowed) - [255] - $ hg prune '19::28' - abort: cannot prune in the middle of a stack - (new unstable changesets are not allowed) - [255] - $ hg prune '26::' - 3 changesets pruned - $ glog -r "18::" - @ 29:2251801b6c91@default(draft) add c5_ - | - | o 20:e02107f98737@default(draft) add gh - | | - o | 19:24e63b319adf@default(draft) add gg - |/ - o 18:edc3c9de504e@default(draft) a3 - | - ~ - -Check that fold respects the allowunstable option - $ hg up edc3c9de504e - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ mkcommit unstableifparentisfolded - created new head - $ glog -r "18::" - @ 30:68330ac625b8@default(draft) add unstableifparentisfolded - | - | o 29:2251801b6c91@default(draft) add c5_ - | | - +---o 20:e02107f98737@default(draft) add gh - | | - | o 19:24e63b319adf@default(draft) add gg - |/ - o 18:edc3c9de504e@default(draft) a3 - | - ~ - - $ hg fold --exact "19 + 18" - abort: cannot fold chain not ending with a head or with branching - (new unstable changesets are not allowed) - [255] - $ hg fold --exact "18::29" - abort: cannot fold chain not ending with a head or with branching - (new unstable changesets are not allowed) - [255] - $ hg fold --exact "19::" - 2 changesets folded - -Check that evolve shows error while handling split commits --------------------------------------- - - $ cat >> $HGRCPATH < [experimental] - > evolution=all - > EOF - - $ glog -r "18::" - o 31:580886d07058@default(draft) add gg - | - | @ 30:68330ac625b8@default(draft) add unstableifparentisfolded - |/ - | o 20:e02107f98737@default(draft) add gh - |/ - o 18:edc3c9de504e@default(draft) a3 - | - ~ - -Create a split commit - $ printf "oo" > oo; - $ printf "pp" > pp; - $ hg add oo pp - $ hg commit -m "oo+pp" - $ mkcommit uu - $ hg up 30 - 0 files updated, 0 files merged, 3 files removed, 0 files unresolved - $ printf "oo" > oo; - $ hg add oo - $ hg commit -m "_oo" - created new head - $ printf "pp" > pp; - $ hg add pp - $ hg commit -m "_pp" - $ hg prune --succ "desc(_oo) + desc(_pp)" -r "desc('oo+pp')" --split - 1 changesets pruned - 1 new unstable changesets - $ glog -r "18::" - @ 35:7a555adf2b4a@default(draft) _pp - | - o 34:2be4d2d5bf34@default(draft) _oo - | - | o 33:53f0c003e03e@default(draft) add uu - | | - | x 32:1bf2152f4f82@default(draft) oo+pp - |/ - | o 31:580886d07058@default(draft) add gg - | | - o | 30:68330ac625b8@default(draft) add unstableifparentisfolded - |/ - | o 20:e02107f98737@default(draft) add gh - |/ - o 18:edc3c9de504e@default(draft) a3 - | - ~ - $ hg evolve --rev "18::" - move:[33] add uu - atop:[35] _pp - working directory is now at 43c3f5ef149f - - -Check that dirstate changes are kept at failure for conflicts (issue4966) ----------------------------------------- - - $ echo "will be amended" > newfile - $ hg commit -m "will be amended" - $ hg parents - 37 : will be amended - test - - $ echo "will be evolved safely" >> a - $ hg commit -m "will be evolved safely" - - $ echo "will cause conflict at evolve" > newfile - $ echo "newly added" > newlyadded - $ hg add newlyadded - $ hg commit -m "will cause conflict at evolve" - - $ hg update -q 37 - $ echo "amended" > newfile - $ hg amend -m "amended" - 2 new unstable changesets - - $ hg evolve --rev "37::" - move:[38] will be evolved safely - atop:[41] amended - move:[39] will cause conflict at evolve - atop:[42] will be evolved safely - merging newfile - warning: conflicts while merging newfile! (edit, then use 'hg resolve --mark') - evolve failed! - fix conflict and run 'hg evolve --continue' or use 'hg update -C .' to abort - abort: unresolved merge conflicts (see hg help resolve) - [255] - - $ glog -r "36::" --hidden - @ 42:c904da5245b0@default(draft) will be evolved safely - | - o 41:34ae045ec400@default(draft) amended - | - | x 40:e88bee38ffc2@default(draft) temporary amend commit for 36030b147271 - | | - | | o 39:02e943732647@default(draft) will cause conflict at evolve - | | | - | | x 38:f8e30e9317aa@default(draft) will be evolved safely - | |/ - | x 37:36030b147271@default(draft) will be amended - |/ - o 36:43c3f5ef149f@default(draft) add uu - | - ~ - - $ hg status newlyadded - A newlyadded - -hg metaedit ------------ - - $ hg update --clean . - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ rm newlyadded $ hg metaedit -r 0 abort: cannot edit commit information for public revisions [255] @@ -1493,28 +106,31 @@ $ hg metaedit -r 0 --fold abort: cannot fold public revisions [255] - $ hg metaedit '36 + 42' --fold + $ hg metaedit 'desc(C) + desc(F)' --fold abort: cannot fold non-linear revisions (multiple roots given) [255] - $ hg metaedit '36::39 + 41' --fold + $ hg metaedit "desc(C)::desc(D2) + desc(E)" --fold abort: cannot fold non-linear revisions (multiple heads given) [255] check that metaedit respects allowunstable $ hg metaedit '.^' --config 'experimental.evolution=createmarkers, allnewcommands' abort: cannot edit commit information in the middle of a stack - (c904da5245b0 will become unstable and new unstable changes are not allowed) + (587528abfffe will become unstable and new unstable changes are not allowed) [255] - $ hg metaedit '18::20' --fold --config 'experimental.evolution=createmarkers, allnewcommands' + $ hg metaedit 'desc(A)::desc(B)' --fold --config 'experimental.evolution=createmarkers, allnewcommands' abort: cannot fold chain not ending with a head or with branching (new unstable changesets are not allowed) [255] $ hg metaedit --user foobar 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg log --template '{rev}: {author}\n' -r '42:' --hidden - 42: test - 43: foobar + $ hg log --template '{rev}: {author}\n' -r 'desc(F):' --hidden + 5: test + 6: test + 7: test + 8: test + 9: foobar $ hg log --template '{rev}: {author}\n' -r . - 43: foobar + 9: foobar TODO: support this $ hg metaedit '.^::.' @@ -1523,13 +139,13 @@ $ HGEDITOR=cat hg metaedit '.^::.' --fold HG: This is a fold of 2 changesets. - HG: Commit message of changeset 41. + HG: Commit message of changeset 7. - amended + E - HG: Commit message of changeset 43. + HG: Commit message of changeset 9. - will be evolved safely + F @@ -1538,22 +154,22 @@ HG: -- HG: user: test HG: branch 'default' - HG: changed a - HG: changed newfile + HG: added E + HG: added F 2 changesets folded 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ glog -r . - @ 44:41bf1183869c@default(draft) amended + @ 10:a08d35fd7d9d@default(draft) E | ~ no new commit is created here because the date is the same $ HGEDITOR=cat hg metaedit - amended + E - will be evolved safely + F HG: Enter commit message. Lines beginning with 'HG:' are removed. @@ -1561,37 +177,39 @@ HG: -- HG: user: test HG: branch 'default' - HG: changed a - HG: changed newfile + HG: added E + HG: added F nothing changed $ glog -r '.^::.' - @ 44:41bf1183869c@default(draft) amended + @ 10:a08d35fd7d9d@default(draft) E | - o 36:43c3f5ef149f@default(draft) add uu + o 3:3260958f1169@default(draft) C | ~ -TODO: don't create a new commit in this case - $ hg metaedit --config defaults.metaedit= +TODO: don't create a new commit in this case, we should take the date of the +old commit (we add a default date with a value to show that metaedit is taking +the current date to generate the hash, this way we still have a stable hash +but highlight the bug) + $ hg metaedit --config defaults.metaedit= --config devel.default-date="42 0" 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n' - 36: add uu - 45: amended + 3: C + 11: E $ hg up .^ - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg metaedit --user foobar2 45 - $ hg log --template '{rev}: {author}\n' -r '42:' --hidden - 42: test - 43: foobar - 44: test - 45: test - 46: foobar2 - $ hg diff -r 45 -r 46 --hidden + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg metaedit --user foobar2 tip + $ hg log --template '{rev}: {author}\n' -r "user(foobar):" --hidden + 9: foobar + 10: test + 11: test + 12: foobar2 + $ hg diff -r "10" -r "11" --hidden 'fold' one commit - $ hg metaedit 39 --fold --user foobar3 + $ hg metaedit "desc(D2)" --fold --user foobar3 1 changesets folded - $ hg log -r 47 --template '{rev}: {author}\n' - 47: foobar3 + $ hg log -r "tip" --template '{rev}: {author}\n' + 13: foobar3 diff --git a/tests/test-obsolete-push.t b/tests/test-obsolete-push.t --- a/tests/test-obsolete-push.t +++ b/tests/test-obsolete-push.t @@ -56,7 +56,7 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files - 1 new obsolescence markers + 2 new obsolescence markers updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd strict-publish-client diff --git a/tests/test-prune.t b/tests/test-prune.t --- a/tests/test-prune.t +++ b/tests/test-prune.t @@ -355,6 +355,17 @@ more than 2 successors: 0 available keys: user: 7 + marker size: + format v1: + smallest length: 69 + longer length: 69 + median length: 69 + mean length: 69 + format v0: + smallest length: * (glob) + longer length: * (glob) + median length: * (glob) + mean length: * (glob) disconnected clusters: 7 any known node: 7 smallest length: 1 diff --git a/tests/test-stablesort.t b/tests/test-stablesort-criss-cross.t copy from tests/test-stablesort.t copy to tests/test-stablesort-criss-cross.t --- a/tests/test-stablesort.t +++ b/tests/test-stablesort-criss-cross.t @@ -12,8 +12,6 @@ > showsort = debugstablesort --template="{node|short}\n" > EOF - - $ checktopo () { > seen='null'; > for node in `hg showsort --rev "$1"`; do @@ -23,451 +21,6 @@ > done; > } -Basic tests -=========== -(no criss cross merge) - -Smoke tests ------------ - -Starts with a "simple case" - - $ hg init repo_A - $ cd repo_A - $ hg debugbuilddag ' - > ..:g # 2 nodes, tagged "g" - > <2.:h # another node base one -2 -> 0, tagged "h" - > *1/2:m # merge -1 and -2 (1, 2), tagged "m" - > <2+2:i # 2 nodes based on -2, tag head as "i" - > .:c # 1 node tagged "c" - > <2.:b # 1 node based on -2; tagged "b" - > <2.:e # 1 node based on -2, tagged "e" - > ' - $ hg log -G - o 15 1d8d22637c2d r15 tip - |\ - | o 14 43227190fef8 r14 f - | | - | | o 13 b4594d867745 r13 e - | | | - | | | o 12 e46a4836065c r12 d - | | |/ - | | o 11 bab5d5bf48bd r11 - | |/ - | | o 10 ff43616e5d0f r10 b - | | | - | | | o 9 dcbb326fdec2 r9 a - | | |/ - | | o 8 d62d843c9a01 r8 - | | | - | | o 7 e7d9710d9fc6 r7 - | |/ - +---o 6 2702dd0c91e7 r6 c - | | - o | 5 f0f3ef9a6cd5 r5 i - | | - o | 4 4c748ffd1a46 r4 - | | - | o 3 2b6d669947cd r3 m - |/| - o | 2 fa942426a6fd r2 h - | | - | o 1 66f7d451a68b r1 g - |/ - o 0 1ea73414a91b r0 - - $ hg showsort --rev 'all()' --traceback - 1ea73414a91b - 66f7d451a68b - fa942426a6fd - 2b6d669947cd - 43227190fef8 - bab5d5bf48bd - b4594d867745 - e46a4836065c - e7d9710d9fc6 - d62d843c9a01 - dcbb326fdec2 - ff43616e5d0f - 4c748ffd1a46 - f0f3ef9a6cd5 - 1d8d22637c2d - 2702dd0c91e7 - -Verify the topological order ----------------------------- - -Check we we did not issued a node before on ancestor - -output of log should be empty - - $ checktopo 'all()' - === checking 1ea73414a91b === - === checking 66f7d451a68b === - === checking fa942426a6fd === - === checking 2b6d669947cd === - === checking 43227190fef8 === - === checking bab5d5bf48bd === - === checking b4594d867745 === - === checking e46a4836065c === - === checking e7d9710d9fc6 === - === checking d62d843c9a01 === - === checking dcbb326fdec2 === - === checking ff43616e5d0f === - === checking 4c748ffd1a46 === - === checking f0f3ef9a6cd5 === - === checking 1d8d22637c2d === - === checking 2702dd0c91e7 === - -Check stability -=============== - -have repo with changesets in orders - - $ cd .. - $ hg -R repo_A log -G > A.log - $ hg clone repo_A repo_B --rev 5 - adding changesets - adding manifests - adding file changes - added 4 changesets with 0 changes to 0 files - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R repo_B pull --rev 13 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 4 changesets with 0 changes to 0 files (+1 heads) - (run 'hg heads' to see heads, 'hg merge' to merge) - $ hg -R repo_B pull --rev 14 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 0 changes to 0 files (+1 heads) - (run 'hg heads .' to see heads, 'hg merge' to merge) - $ hg -R repo_B pull - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 7 changesets with 0 changes to 0 files (+3 heads) - (run 'hg heads .' to see heads, 'hg merge' to merge) - $ hg -R repo_B log -G - o 15 1d8d22637c2d r15 tip - |\ - | | o 14 e46a4836065c r12 - | | | - | | | o 13 ff43616e5d0f r10 - | | | | - | | | | o 12 dcbb326fdec2 r9 - | | | |/ - | | | o 11 d62d843c9a01 r8 - | | | | - | | | o 10 e7d9710d9fc6 r7 - | | | | - +-------o 9 2702dd0c91e7 r6 - | | | | - | o---+ 8 43227190fef8 r14 - | / / - | +---o 7 b4594d867745 r13 - | | | - | o | 6 bab5d5bf48bd r11 - | |/ - | o 5 2b6d669947cd r3 - | |\ - | | o 4 66f7d451a68b r1 - | | | - @ | | 3 f0f3ef9a6cd5 r5 - | | | - o | | 2 4c748ffd1a46 r4 - |/ / - o / 1 fa942426a6fd r2 - |/ - o 0 1ea73414a91b r0 - - $ hg -R repo_B log -G > B.log - - $ hg clone repo_A repo_C --rev 10 - adding changesets - adding manifests - adding file changes - added 7 changesets with 0 changes to 0 files - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R repo_C pull --rev 12 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 0 changes to 0 files (+1 heads) - (run 'hg heads' to see heads, 'hg merge' to merge) - $ hg -R repo_C pull --rev 15 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 4 changesets with 0 changes to 0 files (+1 heads) - (run 'hg heads .' to see heads, 'hg merge' to merge) - $ hg -R repo_C pull - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 3 changesets with 0 changes to 0 files (+3 heads) - (run 'hg heads .' to see heads, 'hg merge' to merge) - $ hg -R repo_C log -G - o 15 b4594d867745 r13 tip - | - | o 14 dcbb326fdec2 r9 - | | - | | o 13 2702dd0c91e7 r6 - | | | - | | | o 12 1d8d22637c2d r15 - | | |/| - | | | o 11 43227190fef8 r14 - | | | | - | | o | 10 f0f3ef9a6cd5 r5 - | | | | - | | o | 9 4c748ffd1a46 r4 - | | | | - +-------o 8 e46a4836065c r12 - | | | | - o-----+ 7 bab5d5bf48bd r11 - / / / - +-----@ 6 ff43616e5d0f r10 - | | | - o | | 5 d62d843c9a01 r8 - | | | - o---+ 4 e7d9710d9fc6 r7 - / / - | o 3 2b6d669947cd r3 - |/| - o | 2 fa942426a6fd r2 - | | - | o 1 66f7d451a68b r1 - |/ - o 0 1ea73414a91b r0 - - $ hg -R repo_C log -G > C.log - - $ hg clone repo_A repo_D --rev 2 - adding changesets - adding manifests - adding file changes - added 2 changesets with 0 changes to 0 files - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R repo_D pull --rev 10 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 5 changesets with 0 changes to 0 files - (run 'hg update' to get a working copy) - $ hg -R repo_D pull --rev 15 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 4 changesets with 0 changes to 0 files (+1 heads) - (run 'hg heads' to see heads, 'hg merge' to merge) - $ hg -R repo_D pull - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 5 changesets with 0 changes to 0 files (+4 heads) - (run 'hg heads .' to see heads, 'hg merge' to merge) - $ hg -R repo_D log -G - o 15 b4594d867745 r13 tip - | - | o 14 e46a4836065c r12 - |/ - o 13 bab5d5bf48bd r11 - | - | o 12 dcbb326fdec2 r9 - | | - | | o 11 2702dd0c91e7 r6 - | | | - | | | o 10 1d8d22637c2d r15 - | | |/| - +-----o 9 43227190fef8 r14 - | | | - | | o 8 f0f3ef9a6cd5 r5 - | | | - | | o 7 4c748ffd1a46 r4 - | | | - | +---o 6 ff43616e5d0f r10 - | | | - | o | 5 d62d843c9a01 r8 - | | | - | o | 4 e7d9710d9fc6 r7 - |/ / - o | 3 2b6d669947cd r3 - |\| - o | 2 66f7d451a68b r1 - | | - | @ 1 fa942426a6fd r2 - |/ - o 0 1ea73414a91b r0 - - $ hg -R repo_D log -G > D.log - -check the log output are different - - $ python "$RUNTESTDIR/md5sum.py" *.log - 55919ebc9c02f28070cf3255b1690f8c A.log - c6244b76a60d0707767dc71780e544f3 B.log - 4d8b08b8c50ecbdd2460a62e5852d84d C.log - 0f327003593b50b9591bea8ee28acb81 D.log - -bug stable ordering should be identical ---------------------------------------- - - $ repos="A B C D " - -for 'all()' - - $ for x in $repos; do - > echo $x - > hg -R repo_$x showsort --rev 'all()' > ${x}.all.order; - > done - A - B - C - D - - $ python "$RUNTESTDIR/md5sum.py" *.all.order - 0c6b2e6f15249c0359b0f93e28c5bd1c A.all.order - 0c6b2e6f15249c0359b0f93e28c5bd1c B.all.order - 0c6b2e6f15249c0359b0f93e28c5bd1c C.all.order - 0c6b2e6f15249c0359b0f93e28c5bd1c D.all.order - -one specific head - - $ for x in $repos; do - > hg -R repo_$x showsort --rev 'b4594d867745' > ${x}.b4594d867745.order; - > done - - $ python "$RUNTESTDIR/md5sum.py" *.b4594d867745.order - 5c40900a22008f24eab8dfe2f30ad79f A.b4594d867745.order - 5c40900a22008f24eab8dfe2f30ad79f B.b4594d867745.order - 5c40900a22008f24eab8dfe2f30ad79f C.b4594d867745.order - 5c40900a22008f24eab8dfe2f30ad79f D.b4594d867745.order - -one secific heads, that is a merge - - $ for x in $repos; do - > hg -R repo_$x showsort --rev '1d8d22637c2d' > ${x}.1d8d22637c2d.order; - > done - - $ python "$RUNTESTDIR/md5sum.py" *.1d8d22637c2d.order - 77dc20a6f86db9103df8edaae9ad2754 A.1d8d22637c2d.order - 77dc20a6f86db9103df8edaae9ad2754 B.1d8d22637c2d.order - 77dc20a6f86db9103df8edaae9ad2754 C.1d8d22637c2d.order - 77dc20a6f86db9103df8edaae9ad2754 D.1d8d22637c2d.order - -changeset that are not heads - - $ for x in $repos; do - > hg -R repo_$x showsort --rev 'e7d9710d9fc6+43227190fef8' > ${x}.non-heads.order; - > done - - $ python "$RUNTESTDIR/md5sum.py" *.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 A.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 B.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 C.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 D.non-heads.order - -Check with different subset - - $ hg clone repo_A repo_E --rev "43227190fef8" - adding changesets - adding manifests - adding file changes - added 5 changesets with 0 changes to 0 files - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R repo_E pull --rev e7d9710d9fc6 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 0 changes to 0 files (+1 heads) - (run 'hg heads' to see heads, 'hg merge' to merge) - - $ hg clone repo_A repo_F --rev "1d8d22637c2d" - adding changesets - adding manifests - adding file changes - added 8 changesets with 0 changes to 0 files - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R repo_F pull --rev d62d843c9a01 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 0 changes to 0 files (+1 heads) - (run 'hg heads' to see heads, 'hg merge' to merge) - - $ hg clone repo_A repo_G --rev "e7d9710d9fc6" - adding changesets - adding manifests - adding file changes - added 5 changesets with 0 changes to 0 files - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R repo_G pull --rev 43227190fef8 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 0 changes to 0 files (+1 heads) - (run 'hg heads' to see heads, 'hg merge' to merge) - $ hg -R repo_G pull --rev 2702dd0c91e7 - pulling from $TESTTMP/repo_A (glob) - searching for changes - adding changesets - adding manifests - adding file changes - added 3 changesets with 0 changes to 0 files (+1 heads) - (run 'hg heads .' to see heads, 'hg merge' to merge) - - $ for x in E F G; do - > hg -R repo_$x showsort --rev 'e7d9710d9fc6+43227190fef8' > ${x}.non-heads.order; - > done - - $ python "$RUNTESTDIR/md5sum.py" *.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 A.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 B.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 C.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 D.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 E.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 F.non-heads.order - 94e0ea8cdade135dabde4ec5e9954329 G.non-heads.order - -Multiple recursions -=================== - $ cat << EOF >> random_rev.py > import random > import sys @@ -478,152 +31,6 @@ > print(x + random.randint(0, var)) > EOF - $ hg init recursion_A - $ cd recursion_A - $ hg debugbuilddag ' - > .:base - > +3:A - > +2/A:C - > +3:F - > +2 - > ' - $ hg log -G - o 20 160a7a0adbf4 r20 tip - | - o 19 1c645e73dbc6 r19 - | - o 18 0496f0a6a143 r18 - |\ - | o 17 d64d500024d1 r17 - | | - | o 16 4dbf739dd63f r16 - | | - | o 15 9fff0871d230 r15 - | | - | | o 14 4bbfc6078919 r14 F - | | | - | | o 13 013b27f11536 r13 - | | | - +---o 12 a66b68853635 r12 - | | - o | 11 001194dd78d5 r11 E - |\ \ - | o | 10 6ee532b68cfa r10 - | | | - o | | 9 529dfc5bb875 r9 D - | | | - o | | 8 abf57d94268b r8 - | | | - +---o 7 5f18015f9110 r7 C - | | | - | | o 6 a2f58e9c1e56 r6 - | | | - | | o 5 3a367db1fabc r5 - | |/ - | o 4 e7bd5218ca15 r4 B - | | - o | 3 2dc09a01254d r3 A - | | - o | 2 01241442b3c2 r2 - | | - o | 1 66f7d451a68b r1 - |/ - o 0 1ea73414a91b r0 base - - $ hg showsort --rev 'all()' - 1ea73414a91b - 66f7d451a68b - 01241442b3c2 - 2dc09a01254d - abf57d94268b - 529dfc5bb875 - e7bd5218ca15 - 3a367db1fabc - a2f58e9c1e56 - 5f18015f9110 - 9fff0871d230 - 4dbf739dd63f - d64d500024d1 - 6ee532b68cfa - 001194dd78d5 - 0496f0a6a143 - 1c645e73dbc6 - 160a7a0adbf4 - a66b68853635 - 013b27f11536 - 4bbfc6078919 - $ checktopo 'all()' - === checking 1ea73414a91b === - === checking 66f7d451a68b === - === checking 01241442b3c2 === - === checking 2dc09a01254d === - === checking abf57d94268b === - === checking 529dfc5bb875 === - === checking e7bd5218ca15 === - === checking 3a367db1fabc === - === checking a2f58e9c1e56 === - === checking 5f18015f9110 === - === checking 9fff0871d230 === - === checking 4dbf739dd63f === - === checking d64d500024d1 === - === checking 6ee532b68cfa === - === checking 001194dd78d5 === - === checking 0496f0a6a143 === - === checking 1c645e73dbc6 === - === checking 160a7a0adbf4 === - === checking a66b68853635 === - === checking 013b27f11536 === - === checking 4bbfc6078919 === - $ hg showsort --rev 'all()' > ../multiple.source.order - $ hg log -r tip - 20 160a7a0adbf4 r20 tip - $ cd .. - - $ hg clone recursion_A recursion_random --rev 0 - adding changesets - adding manifests - adding file changes - added 1 changesets with 0 changes to 0 files - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cd recursion_random - $ for x in `python ../random_rev.py 15 5`; do - > # using python to benefit from the random seed - > hg pull -r $x --quiet - > done; - $ hg pull --quiet - $ hg showsort --rev 'all()' > ../multiple.random.order - $ python "$RUNTESTDIR/md5sum.py" ../multiple.*.order - 6ff802a0a5f0a3ddd82b25f860238fbd ../multiple.random.order - 6ff802a0a5f0a3ddd82b25f860238fbd ../multiple.source.order - $ hg showsort --rev 'all()' - 1ea73414a91b - 66f7d451a68b - 01241442b3c2 - 2dc09a01254d - abf57d94268b - 529dfc5bb875 - e7bd5218ca15 - 3a367db1fabc - a2f58e9c1e56 - 5f18015f9110 - 9fff0871d230 - 4dbf739dd63f - d64d500024d1 - 6ee532b68cfa - 001194dd78d5 - 0496f0a6a143 - 1c645e73dbc6 - 160a7a0adbf4 - a66b68853635 - 013b27f11536 - 4bbfc6078919 - $ cd .. - Check criss cross merge ======================= @@ -1311,48 +718,3 @@ 8ae32c3ed670 84d6ec6a8e21 01f771406cab - - -Test behavior with oedipus merges -================================= - - $ hg init recursion_oedipus - $ cd recursion_oedipus - $ echo base > base - $ hg add base - $ hg ci -m base - $ hg branch foo - marked working directory as branch foo - (branches are permanent and global, did you want a bookmark?) - $ echo foo1 > foo1 - $ hg add foo1 - $ hg ci -m foo1 - $ echo foo2 > foo2 - $ hg add foo2 - $ hg ci -m foo2 - $ hg up default - 0 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg merge foo - 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg ci -m oedipus_merge - $ echo default1 > default1 - $ hg add default1 - $ hg ci -m default1 - $ hg log -G - @ 4 7f2454f6b04f default1 tip - | - o 3 ed776db7ed63 oedipus_merge - |\ - | o 2 0dedbcd995b6 foo2 - | | - | o 1 47da0f2c25e2 foo1 - |/ - o 0 d20a80d4def3 base - - $ hg showsort --rev '.' - d20a80d4def3 - 47da0f2c25e2 - 0dedbcd995b6 - ed776db7ed63 - 7f2454f6b04f diff --git a/tests/test-stablesort.t b/tests/test-stablesort.t --- a/tests/test-stablesort.t +++ b/tests/test-stablesort.t @@ -23,6 +23,16 @@ > done; > } + $ cat << EOF >> random_rev.py + > import random + > import sys + > + > loop = int(sys.argv[1]) + > var = int(sys.argv[2]) + > for x in range(loop): + > print(x + random.randint(0, var)) + > EOF + Basic tests =========== (no criss cross merge) @@ -468,16 +478,6 @@ Multiple recursions =================== - $ cat << EOF >> random_rev.py - > import random - > import sys - > - > loop = int(sys.argv[1]) - > var = int(sys.argv[2]) - > for x in range(loop): - > print(x + random.randint(0, var)) - > EOF - $ hg init recursion_A $ cd recursion_A $ hg debugbuilddag ' @@ -624,694 +624,6 @@ 4bbfc6078919 $ cd .. -Check criss cross merge -======================= - - $ hg init crisscross_A - $ cd crisscross_A - $ hg debugbuilddag ' - > ...:base # create some base - > # criss cross #1: simple - > +3:AbaseA # "A" branch for CC "A" - > # criss cross #2:multiple closes ones - > .:BbaseA - > # criss cross #2:many branches - > ' - $ hg log -G - o 94 01f771406cab r94 Cfinal tip - |\ - | o 93 84d6ec6a8e21 r93 CmergeZB - | |\ - o | | 92 721ba7c5f4ff r92 CmergeZA - |\| | - | | o 91 8ae32c3ed670 r91 CmergeYC - | | |\ - | o \ \ 90 8b79544bb56d r90 CmergeYB - | |\ \ \ - o \ \ \ \ 89 041e1188f5f1 r89 CmergeYA - |\ \ \ \ \ - | o \ \ \ \ 88 2472d042ec95 r88 CmergeXF - | |\ \ \ \ \ - | | | | o \ \ 87 c7d3029bf731 r87 CmergeXE - | | | | |\ \ \ - | | | | | | | o 86 469c700e9ed8 r86 CmergeXD - | | | | | | | |\ - | | | | | | o \ \ 85 28be96b80dc1 r85 CmergeXC - | | | | | | |\ \ \ - | | | o \ \ \ \ \ \ 84 dbde319d43a3 r84 CmergeXB - | | | |\ \ \ \ \ \ \ - o | | | | | | | | | | 83 b3cf98c3d587 r83 CmergeXA - |\| | | | | | | | | | - | | | | | | o | | | | 82 1da228afcf06 r82 CmergeWK - | | | | | | |\ \ \ \ \ - | | | | | | +-+-------o 81 0bab31f71a21 r81 CmergeWJ - | | | | | | | | | | | - | | | | | | | | | o | 80 cd345198cf12 r80 CmergeWI - | | | | | | | | | |\ \ - | | | | o \ \ \ \ \ \ \ 79 82238c0bc950 r79 CmergeWH - | | | | |\ \ \ \ \ \ \ \ - o \ \ \ \ \ \ \ \ \ \ \ \ 78 89a0fe204177 r78 CmergeWG - |\ \ \ \ \ \ \ \ \ \ \ \ \ - | | | o \ \ \ \ \ \ \ \ \ \ 77 97d19fc5236f r77 CmergeWF - | | | |\ \ \ \ \ \ \ \ \ \ \ - | | | | | | | | o \ \ \ \ \ \ 76 37ad3ab0cddf r76 CmergeWE - | | | | | | | | |\ \ \ \ \ \ \ - | | | | | | | | | | | | | | | o 75 790cdfecd168 r75 CmergeWD - | | | | | | | | | | | | | | | |\ - | | | | | | | | | | | | o \ \ \ \ 74 698970a2480b r74 CmergeWC - | | | | | | | | | | | | |\ \ \ \ \ - | | | | | o \ \ \ \ \ \ \ \ \ \ \ \ 73 31d7b43cc321 r73 CmergeWB - | | | | | |\ \ \ \ \ \ \ \ \ \ \ \ \ - | | o \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 72 eed373b0090d r72 CmergeWA - | | |\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ - | | | | | | | | | | | o \ \ \ \ \ \ \ \ 71 4f3b41956174 r71 CmergeT - | | | | | | | | | | | |\ \ \ \ \ \ \ \ \ - | | | | | o | | | | | | | | | | | | | | | 70 c3c7fa726f88 r70 CmergeS - | | | | | | | | | | | | | | | | | | | | | - | | | | | o-------------+ | | | | | | | | 69 d917f77a6439 r69 - | | | | | | | | | | | | | | | | | | | | | - | o | | | | | | | | | | | | | | | | | | | 68 fac9e582edd1 r68 CmergeR - | | | | | | | | | | | | | | | | | | | | | - | o | | | | | | | | | | | | | | | | | | | 67 e4cfd6264623 r67 - | | | | | | | | | | | | | | | | | | | | | - | o---------------------+ | | | | | | | | 66 d99e0f7dad5b r66 - | | | | | | | | | | | | | | | | | | | | | - | | | | | | | | | o-----+ | | | | | | | | 65 c713eae2d31f r65 CmergeQ - | | | | | | | | | | | | | | | | | | | | | - | | | | | | | | | | | +-+-----------o | | 64 b33fd5ad4c0c r64 CmergeP - | | | | | | | | | | | | | | | | | | / / - | | | | | +-----------+-----o | | | / / 63 bf6593f7e073 r63 CmergeO - | | | | | | | | | | | | | | / / / / / - | | | | | | | | | | | | | o | | | | | 62 3871506da61e r62 CmergeN - | | | | | | | | | | | | | | | | | | | - | | | | | | | | | | | | | o | | | | | 61 c84da74cf586 r61 - | | | | | | | | | | | | | | | | | | | - | | | | | | | | | | | | | o | | | | | 60 5eec91b12a58 r60 - | | | | | | | | | | | | | | | | | | | - | +-------------------+---o | | | | | 59 0484d39906c8 r59 - | | | | | | | | | | | | | / / / / / - | | | | | | | | | +---+-------o / / 58 29141354a762 r58 CmergeM - | | | | | | | | | | | | | | | / / - | | | | | | | | o | | | | | | | | 57 e7135b665740 r57 CmergeL - | | | | | | | | | | | | | | | | | - | | | | | | | | o | | | | | | | | 56 c7c1497fc270 r56 - | | | | | | | | | | | | | | | | | - | | | | | +-----o-------+ | | | | 55 76151e8066e1 r55 - | | | | | | | | / / / / / / / / - o | | | | | | | | | | | | | | | 54 9a67238ad1c4 r54 CmergeK - | | | | | | | | | | | | | | | | - o | | | | | | | | | | | | | | | 53 c37e7cd9f2bd r53 - | | | | | | | | | | | | | | | | - o | | | | | | | | | | | | | | | 52 0d153e3ad632 r52 - | | | | | | | | | | | | | | | | - o | | | | | | | | | | | | | | | 51 97ac964e34b7 r51 - | | | | | | | | | | | | | | | | - o | | | | | | | | | | | | | | | 50 900dd066a072 r50 - | | | | | | | | | | | | | | | | - o---------+---------+ | | | | | 49 673f5499c8c2 r49 - / / / / / / / / / / / / / / / - +-----o / / / / / / / / / / / 48 8ecb28746ec4 r48 CmergeJ - | | | |/ / / / / / / / / / / - | | | | | | | o | | | | | | 47 d6c9e2d27f14 r47 CmergeI - | | | | | | | | | | | | | | - | | | +-------o | | | | | | 46 bfcfd9a61e84 r46 - | | | | | | |/ / / / / / / - +---------------+-------o 45 40553f55397e r45 CmergeH - | | | | | | | | | | | | - | | o | | | | | | | | | 44 d94da36be176 r44 CmergeG - | | | | | | | | | | | | - +---o---------+ | | | | 43 4b39f229a0ce r43 - | | / / / / / / / / / - +---+---o / / / / / / 42 43fc0b77ff07 r42 CmergeF - | | | | / / / / / / - | | | | | | | | o | 41 88eace5ce682 r41 CmergeE - | | | | | | | | | | - | | | | | | | | o | 40 d928b4e8a515 r40 - | | | | | | | | | | - +-------+-------o | 39 88714f4125cb r39 - | | | | | | | | / - | | | | +---+---o 38 e3e6738c56ce r38 CmergeD - | | | | | | | | - | | | | | | | o 37 32b41ca704e1 r37 CmergeC - | | | | | | | | - | | | | +-+---o 36 01e29e20ea3f r36 - | | | | | | | - | | | o | | | 35 1f4a19f83a29 r35 CmergeB - | | |/|/ / / - | o | | | | 34 722d1b8b8942 r34 CmergeA - | | | | | | - | o | | | | 33 47c836a1f13e r33 - | | | | | | - | o | | | | 32 2ea3fbf151b5 r32 - | | | | | | - | o | | | | 31 0c3f2ba59eb7 r31 - | | | | | | - | o | | | | 30 f3441cd3e664 r30 - | | | | | | - | o | | | | 29 b9c3aa92fba5 r29 - | | | | | | - | o | | | | 28 3bdb00d5c818 r28 - | | | | | | - | o---+ | | 27 2bd677d0f13a r27 - |/ / / / / - | | | | o 26 de05b9c29ec7 r26 CbaseE - | | | | | - | | | o | 25 ad46a4a0fc10 r25 CbaseD - | | | | | - | | | o | 24 a457569c5306 r24 - | | | | | - | | | o | 23 f2bdd828a3aa r23 - | | | | | - | | | o | 22 5ce588c2b7c5 r22 - | | | | | - | | | o | 21 17b6e6bac221 r21 - | | | |/ - | o---+ 20 b115c694654e r20 CbaseC - | / / - o | | 19 884936b34999 r19 CbaseB - | | | - o---+ 18 9729470d9329 r18 - / / - o / 17 4f5078f7da8a r17 CbaseA - |/ - o 16 3e1560705803 r16 Bfinal - |\ - | o 15 55bf3fdb634f r15 BmergeD - | |\ - o---+ 14 39bab1cb1cbe r14 BmergeC - |/ / - | o 13 f7c6e7bfbcd0 r13 BmergeB - | |\ - o---+ 12 26f59ee8b1d7 r12 BmergeA - |/ / - | o 11 3e2da24aee59 r11 BbaseA - | | - | o 10 5ba9a53052ed r10 Afinal - |/| - o | 9 07c648efceeb r9 AmergeB BbaseB - |\ \ - +---o 8 c81423bf5a24 r8 AmergeA - | |/ - | o 7 65eb34ffc3a8 r7 AbaseB - | | - | o 6 0c1445abb33d r6 - | | - o | 5 c8d03c1b5e94 r5 AbaseA - | | - o | 4 bebd167eb94d r4 - | | - o | 3 2dc09a01254d r3 - |/ - o 2 01241442b3c2 r2 base - | - o 1 66f7d451a68b r1 - | - o 0 1ea73414a91b r0 - - -Basic check ------------ - - $ hg showsort --rev 'Afinal' - 1ea73414a91b - 66f7d451a68b - 01241442b3c2 - 0c1445abb33d - 65eb34ffc3a8 - 2dc09a01254d - bebd167eb94d - c8d03c1b5e94 - 07c648efceeb - c81423bf5a24 - 5ba9a53052ed - $ checktopo Afinal - === checking 1ea73414a91b === - === checking 66f7d451a68b === - === checking 01241442b3c2 === - === checking 0c1445abb33d === - === checking 65eb34ffc3a8 === - === checking 2dc09a01254d === - === checking bebd167eb94d === - === checking c8d03c1b5e94 === - === checking 07c648efceeb === - === checking c81423bf5a24 === - === checking 5ba9a53052ed === - $ hg showsort --rev 'AmergeA' - 1ea73414a91b - 66f7d451a68b - 01241442b3c2 - 0c1445abb33d - 65eb34ffc3a8 - 2dc09a01254d - bebd167eb94d - c8d03c1b5e94 - c81423bf5a24 - $ checktopo AmergeA - === checking 1ea73414a91b === - === checking 66f7d451a68b === - === checking 01241442b3c2 === - === checking 0c1445abb33d === - === checking 65eb34ffc3a8 === - === checking 2dc09a01254d === - === checking bebd167eb94d === - === checking c8d03c1b5e94 === - === checking c81423bf5a24 === - $ hg showsort --rev 'AmergeB' - 1ea73414a91b - 66f7d451a68b - 01241442b3c2 - 0c1445abb33d - 65eb34ffc3a8 - 2dc09a01254d - bebd167eb94d - c8d03c1b5e94 - 07c648efceeb - $ checktopo AmergeB - === checking 1ea73414a91b === - === checking 66f7d451a68b === - === checking 01241442b3c2 === - === checking 0c1445abb33d === - === checking 65eb34ffc3a8 === - === checking 2dc09a01254d === - === checking bebd167eb94d === - === checking c8d03c1b5e94 === - === checking 07c648efceeb === - -close criss cross - $ hg showsort --rev 'Bfinal' - 1ea73414a91b - 66f7d451a68b - 01241442b3c2 - 0c1445abb33d - 65eb34ffc3a8 - 2dc09a01254d - bebd167eb94d - c8d03c1b5e94 - 07c648efceeb - c81423bf5a24 - 5ba9a53052ed - 3e2da24aee59 - 26f59ee8b1d7 - f7c6e7bfbcd0 - 39bab1cb1cbe - 55bf3fdb634f - 3e1560705803 - $ checktopo Bfinal - === checking 1ea73414a91b === - === checking 66f7d451a68b === - === checking 01241442b3c2 === - === checking 0c1445abb33d === - === checking 65eb34ffc3a8 === - === checking 2dc09a01254d === - === checking bebd167eb94d === - === checking c8d03c1b5e94 === - === checking 07c648efceeb === - === checking c81423bf5a24 === - === checking 5ba9a53052ed === - === checking 3e2da24aee59 === - === checking 26f59ee8b1d7 === - === checking f7c6e7bfbcd0 === - === checking 39bab1cb1cbe === - === checking 55bf3fdb634f === - === checking 3e1560705803 === - -many branches criss cross - - $ hg showsort --rev 'Cfinal' - 1ea73414a91b - 66f7d451a68b - 01241442b3c2 - 0c1445abb33d - 65eb34ffc3a8 - 2dc09a01254d - bebd167eb94d - c8d03c1b5e94 - 07c648efceeb - c81423bf5a24 - 5ba9a53052ed - 3e2da24aee59 - 26f59ee8b1d7 - f7c6e7bfbcd0 - 39bab1cb1cbe - 55bf3fdb634f - 3e1560705803 - 17b6e6bac221 - 5ce588c2b7c5 - f2bdd828a3aa - a457569c5306 - ad46a4a0fc10 - 4f5078f7da8a - 01e29e20ea3f - 32b41ca704e1 - 29141354a762 - 9729470d9329 - 884936b34999 - 0484d39906c8 - 5eec91b12a58 - c84da74cf586 - 3871506da61e - 2bd677d0f13a - 3bdb00d5c818 - b9c3aa92fba5 - f3441cd3e664 - 0c3f2ba59eb7 - 2ea3fbf151b5 - 47c836a1f13e - 722d1b8b8942 - 4b39f229a0ce - d94da36be176 - eed373b0090d - 88714f4125cb - d928b4e8a515 - 88eace5ce682 - 698970a2480b - b115c694654e - 1f4a19f83a29 - 43fc0b77ff07 - 31d7b43cc321 - 673f5499c8c2 - 900dd066a072 - 97ac964e34b7 - 0d153e3ad632 - c37e7cd9f2bd - 9a67238ad1c4 - 8ecb28746ec4 - bf6593f7e073 - 0bab31f71a21 - 1da228afcf06 - bfcfd9a61e84 - d6c9e2d27f14 - de05b9c29ec7 - 40553f55397e - 4f3b41956174 - 37ad3ab0cddf - c7d3029bf731 - 76151e8066e1 - c7c1497fc270 - e7135b665740 - b33fd5ad4c0c - cd345198cf12 - 28be96b80dc1 - c713eae2d31f - 82238c0bc950 - dbde319d43a3 - 8b79544bb56d - d917f77a6439 - c3c7fa726f88 - 97d19fc5236f - 2472d042ec95 - d99e0f7dad5b - e4cfd6264623 - fac9e582edd1 - 89a0fe204177 - b3cf98c3d587 - 041e1188f5f1 - 721ba7c5f4ff - e3e6738c56ce - 790cdfecd168 - 469c700e9ed8 - 8ae32c3ed670 - 84d6ec6a8e21 - 01f771406cab - $ checktopo Cfinal - === checking 1ea73414a91b === - === checking 66f7d451a68b === - === checking 01241442b3c2 === - === checking 0c1445abb33d === - === checking 65eb34ffc3a8 === - === checking 2dc09a01254d === - === checking bebd167eb94d === - === checking c8d03c1b5e94 === - === checking 07c648efceeb === - === checking c81423bf5a24 === - === checking 5ba9a53052ed === - === checking 3e2da24aee59 === - === checking 26f59ee8b1d7 === - === checking f7c6e7bfbcd0 === - === checking 39bab1cb1cbe === - === checking 55bf3fdb634f === - === checking 3e1560705803 === - === checking 17b6e6bac221 === - === checking 5ce588c2b7c5 === - === checking f2bdd828a3aa === - === checking a457569c5306 === - === checking ad46a4a0fc10 === - === checking 4f5078f7da8a === - === checking 01e29e20ea3f === - === checking 32b41ca704e1 === - === checking 29141354a762 === - === checking 9729470d9329 === - === checking 884936b34999 === - === checking 0484d39906c8 === - === checking 5eec91b12a58 === - === checking c84da74cf586 === - === checking 3871506da61e === - === checking 2bd677d0f13a === - === checking 3bdb00d5c818 === - === checking b9c3aa92fba5 === - === checking f3441cd3e664 === - === checking 0c3f2ba59eb7 === - === checking 2ea3fbf151b5 === - === checking 47c836a1f13e === - === checking 722d1b8b8942 === - === checking 4b39f229a0ce === - === checking d94da36be176 === - === checking eed373b0090d === - === checking 88714f4125cb === - === checking d928b4e8a515 === - === checking 88eace5ce682 === - === checking 698970a2480b === - === checking b115c694654e === - === checking 1f4a19f83a29 === - === checking 43fc0b77ff07 === - === checking 31d7b43cc321 === - === checking 673f5499c8c2 === - === checking 900dd066a072 === - === checking 97ac964e34b7 === - === checking 0d153e3ad632 === - === checking c37e7cd9f2bd === - === checking 9a67238ad1c4 === - === checking 8ecb28746ec4 === - === checking bf6593f7e073 === - === checking 0bab31f71a21 === - === checking 1da228afcf06 === - === checking bfcfd9a61e84 === - === checking d6c9e2d27f14 === - === checking de05b9c29ec7 === - === checking 40553f55397e === - === checking 4f3b41956174 === - === checking 37ad3ab0cddf === - === checking c7d3029bf731 === - === checking 76151e8066e1 === - === checking c7c1497fc270 === - === checking e7135b665740 === - === checking b33fd5ad4c0c === - === checking cd345198cf12 === - === checking 28be96b80dc1 === - === checking c713eae2d31f === - === checking 82238c0bc950 === - === checking dbde319d43a3 === - === checking 8b79544bb56d === - === checking d917f77a6439 === - === checking c3c7fa726f88 === - === checking 97d19fc5236f === - === checking 2472d042ec95 === - === checking d99e0f7dad5b === - === checking e4cfd6264623 === - === checking fac9e582edd1 === - === checking 89a0fe204177 === - === checking b3cf98c3d587 === - === checking 041e1188f5f1 === - === checking 721ba7c5f4ff === - === checking e3e6738c56ce === - === checking 790cdfecd168 === - === checking 469c700e9ed8 === - === checking 8ae32c3ed670 === - === checking 84d6ec6a8e21 === - === checking 01f771406cab === - -Test stability of this mess ---------------------------- - - $ hg log -r tip - 94 01f771406cab r94 Cfinal tip - $ hg showsort --rev 'all()' > ../crisscross.source.order - $ cd .. - - $ hg clone crisscross_A crisscross_random --rev 0 - adding changesets - adding manifests - adding file changes - added 1 changesets with 0 changes to 0 files - updating to branch default - 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cd crisscross_random - $ for x in `python ../random_rev.py 50 44`; do - > # using python to benefit from the random seed - > hg pull -r $x --quiet - > done; - $ hg pull --quiet - - $ hg showsort --rev 'all()' > ../crisscross.random.order - $ python "$RUNTESTDIR/md5sum.py" ../crisscross.*.order - d9aab0d1907d5cf64d205a8b9036e959 ../crisscross.random.order - d9aab0d1907d5cf64d205a8b9036e959 ../crisscross.source.order - $ diff -u ../crisscross.*.order - $ hg showsort --rev 'all()' - 1ea73414a91b - 66f7d451a68b - 01241442b3c2 - 0c1445abb33d - 65eb34ffc3a8 - 2dc09a01254d - bebd167eb94d - c8d03c1b5e94 - 07c648efceeb - c81423bf5a24 - 5ba9a53052ed - 3e2da24aee59 - 26f59ee8b1d7 - f7c6e7bfbcd0 - 39bab1cb1cbe - 55bf3fdb634f - 3e1560705803 - 17b6e6bac221 - 5ce588c2b7c5 - f2bdd828a3aa - a457569c5306 - ad46a4a0fc10 - 4f5078f7da8a - 01e29e20ea3f - 32b41ca704e1 - 29141354a762 - 9729470d9329 - 884936b34999 - 0484d39906c8 - 5eec91b12a58 - c84da74cf586 - 3871506da61e - 2bd677d0f13a - 3bdb00d5c818 - b9c3aa92fba5 - f3441cd3e664 - 0c3f2ba59eb7 - 2ea3fbf151b5 - 47c836a1f13e - 722d1b8b8942 - 4b39f229a0ce - d94da36be176 - eed373b0090d - 88714f4125cb - d928b4e8a515 - 88eace5ce682 - 698970a2480b - b115c694654e - 1f4a19f83a29 - 43fc0b77ff07 - 31d7b43cc321 - 673f5499c8c2 - 900dd066a072 - 97ac964e34b7 - 0d153e3ad632 - c37e7cd9f2bd - 9a67238ad1c4 - 8ecb28746ec4 - bf6593f7e073 - 0bab31f71a21 - 1da228afcf06 - bfcfd9a61e84 - d6c9e2d27f14 - de05b9c29ec7 - 40553f55397e - 4f3b41956174 - 37ad3ab0cddf - c7d3029bf731 - 76151e8066e1 - c7c1497fc270 - e7135b665740 - b33fd5ad4c0c - cd345198cf12 - 28be96b80dc1 - c713eae2d31f - 82238c0bc950 - dbde319d43a3 - 8b79544bb56d - d917f77a6439 - c3c7fa726f88 - 97d19fc5236f - 2472d042ec95 - d99e0f7dad5b - e4cfd6264623 - fac9e582edd1 - 89a0fe204177 - b3cf98c3d587 - 041e1188f5f1 - 721ba7c5f4ff - e3e6738c56ce - 790cdfecd168 - 469c700e9ed8 - 8ae32c3ed670 - 84d6ec6a8e21 - 01f771406cab - Test behavior with oedipus merges ================================= diff --git a/tests/test-wireproto.t b/tests/test-wireproto.t --- a/tests/test-wireproto.t +++ b/tests/test-wireproto.t @@ -149,4 +149,34 @@ 1 new obsolescence markers (run 'hg heads' to see heads, 'hg merge' to merge) +test discovery avoid exchanging known markers + + $ hg push + pushing to ssh://user@dummy/server + searching for changes + no changes found + [1] + $ hg -R ../other pull + pulling from ssh://user@dummy/server + searching for changes + no changes found + +test discovery can be disabled + + $ hg push --config experimental.evolution.obsdiscovery=no + pushing to ssh://user@dummy/server + searching for changes + (skipping discovery of obsolescence markers, will exchange everything) + (controled by 'experimental.evolution.obsdiscovery' configuration) + no changes found + remote: obsmarker-exchange: 346 bytes received + [1] + $ hg -R ../other pull --config experimental.evolution.obsdiscovery=no + pulling from ssh://user@dummy/server + searching for changes + no changes found + (skipping discovery of obsolescence markers, will exchange everything) + (controled by 'experimental.evolution.obsdiscovery' configuration) + obsmarker-exchange: 346 bytes received + $ cd ..