# HG changeset patch # User Pierre-Yves David # Date 1450793469 0 # Node ID 0c8548df67feb1cd33b274b999e14a91864816de # Parent 52c276d2ddb2f076c84ef8258038ef7b5ee00fe4# Parent dcfe3afe548bca7c8195a3750440dc29e82d5ad1 merge with stable diff -r dcfe3afe548b -r 0c8548df67fe Makefile --- a/Makefile Sat Nov 07 13:39:59 2015 -0500 +++ b/Makefile Tue Dec 22 14:11:09 2015 +0000 @@ -1,5 +1,3 @@ -PYTHON=python -HG=`which hg` VERSION=$(shell python setup.py --version) @@ -11,21 +9,6 @@ all: help -tests: - cd tests && $(PYTHON) run-tests.py --with-hg=$(HG) $(TESTFLAGS) - -test-%: - cd tests && $(PYTHON) run-tests.py --with-hg=$(HG) $(TESTFLAGS) $@ - -tests-%: - @echo "Path to crew repo is $(CREW) - set this with CREW= if needed." - hg -R $(CREW) checkout $$(echo $@ | sed s/tests-//) && \ - (cd $(CREW) ; $(MAKE) clean ) && \ - cd tests && $(PYTHON) $(CREW)/tests/run-tests.py $(TESTFLAGS) - -all-version-tests: tests-1.3.1 tests-1.4.3 tests-1.5.4 \ - tests-1.6.4 tests-1.7.5 tests-1.8 tests-tip - deb-prepare: python setup.py sdist --dist-dir .. mv -f ../hg-evolve-$(VERSION).tar.gz ../mercurial-evolve_$(VERSION).orig.tar.gz @@ -34,5 +17,3 @@ mv hg-evolve-$(VERSION) ../mercurial-evolve_$(VERSION).orig cp -r debian/ ../mercurial-evolve_$(VERSION).orig/ @cd ../mercurial-evolve_$(VERSION).orig && echo 'debian build directory ready at' `pwd` - -.PHONY: tests all-version-tests diff -r dcfe3afe548b -r 0c8548df67fe README --- a/README Sat Nov 07 13:39:59 2015 -0500 +++ b/README Tue Dec 22 14:11:09 2015 +0000 @@ -58,7 +58,10 @@ 5.3.0 -- -- split: add a new command to split changesets +- split: add a new command to split changesets, +- tests: drop our copy of 'run-tests.py' use core one instead, +- bookmark: do all bookmark movement within a transaction. +- evolve: compatibility with Mercurial 3.7 5.2.2 -- diff -r dcfe3afe548b -r 0c8548df67fe debian/rules --- a/debian/rules Sat Nov 07 13:39:59 2015 -0500 +++ b/debian/rules Tue Dec 22 14:11:09 2015 +0000 @@ -8,9 +8,13 @@ dh_auto_build $(MAKE) -C docs +hgsrc_defined: + # Use "! -z" instead of "-n", because "-n" without arguments is true + test ! -z $(HGSRC) && test -d $(HGSRC) || (echo "$(HGSRC) is not a directory"; false) + ifeq (,$(filter nocheck, $(DEB_BUILD_OPTIONS))) -override_dh_auto_test: - cd tests && python run-tests.py --with-hg=`which hg` --blacklist=$(CURDIR)/debian/test-blacklist +override_dh_auto_test: hgsrc_defined + cd tests && python $(HGSRC)/tests/run-tests.py --with-hg=$(HGSRC)/hg --blacklist=$(CURDIR)/debian/test-blacklist endif override_dh_python2: diff -r dcfe3afe548b -r 0c8548df67fe hgext/directaccess.py --- a/hgext/directaccess.py Sat Nov 07 13:39:59 2015 -0500 +++ b/hgext/directaccess.py Tue Dec 22 14:11:09 2015 +0000 @@ -1,5 +1,5 @@ """ This extension provides direct access -It is the ability to refer and access hidden sha in commands provided that you +It is the ability to refer and access hidden sha in commands provided that you know their value. For example hg log -r xxx where xxx is a commit has should work whether xxx is hidden or not as we assume that the user knows what he is doing when referring @@ -72,7 +72,8 @@ def setupdirectaccess(): """ Add two new filtername that behave like visible to provide direct access - and direct access with warning. Wraps the commands to setup direct access """ + and direct access with warning. Wraps the commands to setup direct access + """ repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden}) repoview.filtertable.update({'visible-directaccess-warn': _computehidden}) branchmap.subsettable['visible-directaccess-nowarn'] = 'visible' @@ -131,7 +132,7 @@ _listtuple = ('symbol', '_list') -def gethashsymbols(tree): +def gethashsymbols(tree, maxrev): # Returns the list of symbols of the tree that look like hashes # for example for the revset 3::abe3ff it will return ('abe3ff') if not tree: @@ -139,8 +140,12 @@ if len(tree) == 2 and tree[0] == "symbol": try: - int(tree[1]) - return [] + n = int(tree[1]) + # This isn't necessarily a rev number, could be a hash prefix + if n > maxrev: + return [tree[1]] + else: + return [] except ValueError as e: if hashre.match(tree[1]): return [tree[1]] @@ -155,7 +160,7 @@ elif len(tree) >= 3: results = [] for subtree in tree[1:]: - results += gethashsymbols(subtree) + results += gethashsymbols(subtree, maxrev) return results else: return [] @@ -171,8 +176,8 @@ if filternm is not None and filternm.startswith('visible-directaccess'): prelength = len(repo._explicitaccess) accessbefore = set(repo._explicitaccess) - repo.symbols = gethashsymbols(tree) cl = repo.unfiltered().changelog + repo.symbols = gethashsymbols(tree, len(cl)) for node in repo.symbols: try: node = cl._partialmatch(node) @@ -185,8 +190,8 @@ if prelength != len(repo._explicitaccess): if repo.filtername != 'visible-directaccess-nowarn': unhiddencommits = repo._explicitaccess - accessbefore - repo.ui.warn( _("Warning: accessing hidden changesets %s " - "for write operation\n") % - (",".join([str(repo.unfiltered()[l]) + repo.ui.warn(_("Warning: accessing hidden changesets %s " + "for write operation\n") % + (",".join([str(repo.unfiltered()[l]) for l in unhiddencommits]))) repo.invalidatevolatilesets() diff -r dcfe3afe548b -r 0c8548df67fe hgext/drophack.py --- a/hgext/drophack.py Sat Nov 07 13:39:59 2015 -0500 +++ b/hgext/drophack.py Tue Dec 22 14:11:09 2015 +0000 @@ -150,7 +150,8 @@ stripmarker(ui, repo, markers) # strip the changeset with timed(ui, 'strip nodes'): - repair.strip(ui, repo, list(allnodes), backup="all", topic='drophack') + repair.strip(ui, repo, list(allnodes), backup="all", + topic='drophack') finally: lockmod.release(lock, wlock) diff -r dcfe3afe548b -r 0c8548df67fe hgext/evolve.py --- a/hgext/evolve.py Sat Nov 07 13:39:59 2015 -0500 +++ b/hgext/evolve.py Tue Dec 22 14:11:09 2015 +0000 @@ -86,7 +86,7 @@ # Flags for enabling optional parts of evolve commandopt = 'allnewcommands' -from mercurial import bookmarks +from mercurial import bookmarks as bookmarksmod from mercurial import cmdutil from mercurial import commands from mercurial import context @@ -124,7 +124,8 @@ def memfilectx(repo, *args, **kwargs): return oldmemfilectx(*args, **kwargs) else: - raise ImportError('evolve needs version %s or above' % min(testedwith.split())) + raise ImportError('evolve needs version %s or above' % + min(testedwith.split())) aliases, entry = cmdutil.findcmd('commit', commands.table) hasinteractivemode = any(['interactive' in e for e in entry[1]]) @@ -415,8 +416,8 @@ if not matchingevolvecommands: raise error.Abort(_('unknown command: %s') % cmd) elif len(matchingevolvecommands) > 1: - raise error.Abort(_('ambiguous command specification: "%s" matches %r') - % (cmd, matchingevolvecommands)) + msg = _('ambiguous command specification: "%s" matches %r') + raise error.Abort(msg % (cmd, matchingevolvecommands)) else: whitelist.add(matchingevolvecommands[0]) for disabledcmd in set(cmdtable) - whitelist: @@ -747,7 +748,7 @@ """ try: return orig(repo, *args, **opts) - except util.Abort, ex: + except error.Abort as ex: hint = _("use 'hg evolve' to get a stable history " "or --force to ignore warnings") if (len(ex.args) >= 1 @@ -789,13 +790,13 @@ if rebase: extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors) except KeyError: - pass # rebase not found + pass # rebase not found try: histedit = extensions.find('histedit') if histedit: extensions.wrapcommand(histedit.cmdtable, 'histedit', warnobserrors) except KeyError: - pass # histedit not found + pass # histedit not found ##################################################################### ### Old Evolve extension content ### @@ -890,24 +891,25 @@ tr.close() return newid, created finally: - lockmod.release(lock, wlock, tr) - -class MergeFailure(util.Abort): + lockmod.release(tr, lock, wlock) + +class MergeFailure(error.Abort): pass def relocate(repo, orig, dest, keepbranch=False): """rewrite on dest""" if orig.rev() == dest.rev(): - raise util.Abort(_('tried to relocate a node on top of itself'), + 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 not orig.p2().rev() == node.nullrev: - raise util.Abort( + raise error.Abort( 'no support for evolving merge changesets yet', - hint="Redo the merge and use `hg prune --succ ` to obsolete the old one") + hint="Redo the merge and use `hg prune --succ ` " + "to obsolete the old one") destbookmarks = repo.nodebookmarks(dest.node()) nodesrc = orig.node() destphase = repo[nodesrc].phase() @@ -949,25 +951,10 @@ repo.dirstate.setbranch(orig.branch()) r = merge.graft(repo, orig, orig.p1(), ['local', 'graft']) if r[-1]: #some conflict - raise util.Abort( + raise error.Abort( 'unresolved merge conflicts (see hg help resolve)') - 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, 'rebase') - # 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) - except util.Abort, exc: + nodenew = _relocatecommit(repo, orig, commitmsg) + except error.Abort as exc: repo.dirstate.beginparentchange() repo.setparents(repo['.'].node(), nullid) writedirstate(repo.dirstate, tr) @@ -979,20 +966,7 @@ exc.__class__ = LocalMergeFailure raise 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) + _finalizerelocate(repo, orig, dest, nodenew, tr) tr.close() finally: tr.release() @@ -1016,14 +990,14 @@ ### bookmarks api compatibility layer ### def bmdeactivate(repo): try: - return bookmarks.deactivate(repo) + return bookmarksmod.deactivate(repo) except AttributeError: - return bookmarks.unsetcurrent(repo) + return bookmarksmod.unsetcurrent(repo) def bmactivate(repo, book): try: - return bookmarks.activate(repo, book) + return bookmarksmod.activate(repo, book) except AttributeError: - return bookmarks.setcurrent(repo, book) + return bookmarksmod.setcurrent(repo, book) def bmactive(repo): try: @@ -1156,7 +1130,7 @@ store.create(tr, mark[0], mark[1], mark[2], marks[3], parents=parents) if len(store._all) - before: - ui.write('created new markers for %i\n' % rev) + ui.write(_('created new markers for %i\n') % rev) ui.progress(pgop, idx, total=pgtotal) tr.close() ui.progress(pgop, None) @@ -1186,7 +1160,7 @@ store = repo.obsstore unfi = repo.unfiltered() nm = unfi.changelog.nodemap - ui.write('markers total: %9i\n' % len(store._all)) + ui.write(_('markers total: %9i\n') % len(store._all)) sucscount = [0, 0 , 0, 0] known = 0 parentsdata = 0 @@ -1195,7 +1169,7 @@ # a cluster is a (set(nodes), set(markers)) tuple clustersmap = {} # same data using parent information - pclustersmap= {} + pclustersmap = {} for mark in store: if mark[0] in nm: known += 1 @@ -1228,47 +1202,50 @@ fc = (frozenset(c[0]), frozenset(c[1])) for n in fc[0]: pclustersmap[n] = fc - ui.write(' for known precursors: %9i\n' % known) - ui.write(' with parents data: %9i\n' % parentsdata) + ui.write((' for known precursors: %9i\n' % known)) + ui.write((' with parents data: %9i\n' % parentsdata)) # successors data - ui.write('markers with no successors: %9i\n' % sucscount[0]) - ui.write(' 1 successors: %9i\n' % sucscount[1]) - ui.write(' 2 successors: %9i\n' % sucscount[2]) - ui.write(' more than 2 successors: %9i\n' % sucscount[3]) + ui.write(('markers with no successors: %9i\n' % sucscount[0])) + ui.write((' 1 successors: %9i\n' % sucscount[1])) + ui.write((' 2 successors: %9i\n' % sucscount[2])) + ui.write((' more than 2 successors: %9i\n' % sucscount[3])) # meta data info - ui.write(' available keys:\n') + ui.write((' available keys:\n')) for key in sorted(metakeys): - ui.write(' %15s: %9i\n' % (key, metakeys[key])) + ui.write((' %15s: %9i\n' % (key, metakeys[key]))) allclusters = list(set(clustersmap.values())) allclusters.sort(key=lambda x: len(x[1])) - ui.write('disconnected clusters: %9i\n' % len(allclusters)) + ui.write(('disconnected clusters: %9i\n' % len(allclusters))) ui.write(' any known node: %9i\n' % len([c for c in allclusters if [n for n in c[0] if nm.get(n) is not None]])) if allclusters: nbcluster = len(allclusters) - ui.write(' smallest length: %9i\n' % len(allclusters[0][1])) - ui.write(' longer length: %9i\n' % len(allclusters[-1][1])) + ui.write((' smallest length: %9i\n' % len(allclusters[0][1]))) + ui.write((' longer length: %9i\n' + % len(allclusters[-1][1]))) median = len(allclusters[nbcluster//2][1]) - ui.write(' median length: %9i\n' % median) + ui.write((' median length: %9i\n' % median)) mean = sum(len(x[1]) for x in allclusters) // nbcluster - ui.write(' mean length: %9i\n' % mean) + ui.write((' mean length: %9i\n' % mean)) allpclusters = list(set(pclustersmap.values())) allpclusters.sort(key=lambda x: len(x[1])) - ui.write(' using parents data: %9i\n' % len(allpclusters)) + ui.write((' using parents data: %9i\n' % len(allpclusters))) ui.write(' any known node: %9i\n' % len([c for c in allclusters if [n for n in c[0] if nm.get(n) is not None]])) if allpclusters: nbcluster = len(allpclusters) - ui.write(' smallest length: %9i\n' % len(allpclusters[0][1])) - ui.write(' longer length: %9i\n' % len(allpclusters[-1][1])) + ui.write((' smallest length: %9i\n' + % len(allpclusters[0][1]))) + ui.write((' longer length: %9i\n' + % len(allpclusters[-1][1]))) median = len(allpclusters[nbcluster//2][1]) - ui.write(' median length: %9i\n' % median) + ui.write((' median length: %9i\n' % median)) mean = sum(len(x[1]) for x in allpclusters) // nbcluster - ui.write(' mean length: %9i\n' % mean) + ui.write((' mean length: %9i\n' % mean)) def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category): """Resolve the troubles affecting one revision""" @@ -1367,8 +1344,8 @@ else: l = len(troubled[targetcat]) if l: - hint = (_("%d other %s in the repository, do you want --any or --rev") - % (l, targetcat)) + hint = _("%d other %s in the repository, do you want --any " + "or --rev") % (l, targetcat) else: othertroubles = [] for cat in unselectedcategories: @@ -1389,7 +1366,7 @@ def _cleanup(ui, repo, startnode, showprogress): if showprogress: - ui.progress('evolve', None) + ui.progress(_('evolve'), None) if repo['.'] != startnode: ui.status(_('working directory is now at %s\n') % repo['.']) @@ -1442,7 +1419,7 @@ for p in repo[r].parents(): try: succ = _singlesuccessor(repo, p) - except MultipleSuccessorsError, exc: + except MultipleSuccessorsError as exc: dependencies[r] = exc.successorssets continue if succ in revs: @@ -1476,7 +1453,8 @@ if revopt: revs = scmutil.revrange(repo, revopt) & revs elif not anyopt and targetcat == 'unstable': - revs = set(_aspiringdescendant(repo, repo.revs('(.::) - obsolete()::'))) + revs = set(_aspiringdescendant(repo, + repo.revs('(.::) - obsolete()::'))) if targetcat == 'divergent': # Pick one divergent per group of divergents revs = _dedupedivergents(repo, revs) @@ -1534,22 +1512,24 @@ _('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')), + ('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')), + ('a', 'all', False, _('evolve all troubled changesets related to the ' + 'current working directory and its descendants')), ('c', 'continue', False, _('continue an interrupted evolution')), ] + mergetoolopts, _('[OPTIONS]...')) def evolve(ui, repo, **opts): """solve troubled changesets in your repository - 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: + 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 @@ -1557,53 +1537,57 @@ - 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. + 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. 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.) + 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). + 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. + 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. - - 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. + ambiguous cases (e.g. unstable changesets with multiple children) by + evolving all branches. + + 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. - You can supply multiple revisions to evolve multiple troubled changesets in a - single invocation. In revset terms, ``--any`` is equivalent to ``--rev + 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. - 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``. + 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``. """ @@ -1620,15 +1604,16 @@ targetcat = 'unstable' if 1 < len(specifiedcategories): msg = _('cannot specify more than one trouble category to solve (yet)') - raise util.Abort(msg) + raise error.Abort(msg) elif len(specifiedcategories) == 1: targetcat = specifiedcategories[0] elif repo['.'].obsolete(): - displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate}) + 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, exc: + except MultipleSuccessorsError as exc: repo.ui.write_err('parent is obsolete with multiple successors:\n') for ln in exc.successorssets: for n in ln: @@ -1657,23 +1642,23 @@ def progresscb(): if revopt or allopt: - ui.progress('evolve', seen, unit='changesets', total=count) + ui.progress(_('evolve'), seen, unit='changesets', total=count) # Continuation handling if contopt: if anyopt: - raise util.Abort('cannot specify both "--any" and "--continue"') + raise error.Abort('cannot specify both "--any" and "--continue"') if allopt: - raise util.Abort('cannot specify both "--all" and "--continue"') + raise error.Abort('cannot specify both "--all" and "--continue"') graftcmd = commands.table['graft'][0] return graftcmd(ui, repo, old_obsolete=True, **{'continue': True}) cmdutil.bailifchanged(repo) if revopt and allopt: - raise util.Abort('cannot specify both "--rev" and "--all"') + raise error.Abort('cannot specify both "--rev" and "--all"') if revopt and anyopt: - raise util.Abort('cannot specify both "--rev" and "--any"') + raise error.Abort('cannot specify both "--rev" and "--any"') revs = _selectrevs(repo, allopt, revopt, anyopt, targetcat) @@ -1754,7 +1739,7 @@ obs = orig.parents()[1] # second parent is obsolete ? if not obs.obsolete(): - ui.warn("cannot solve instability of %s, skipping\n" % orig) + ui.warn(_("cannot solve instability of %s, skipping\n") % orig) return False newer = obsolete.successorssets(repo, obs.node()) # search of a parent which is not killed @@ -1765,7 +1750,8 @@ 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 + msg = _("skipping %s: divergent rewriting. can't choose " + "destination\n") % obs ui.write_err(msg) return 2 targets = newer[0] @@ -1790,7 +1776,7 @@ repo.ui.write(_('atop:')) displayer.show(target) if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y': - raise util.Abort(_('evolve aborted by user')) + raise error.Abort(_('evolve aborted by user')) if progresscb: progresscb() todo = 'hg rebase -r %s -d %s\n' % (orig, target) if dryrun: @@ -1822,7 +1808,8 @@ 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 not handled yet\n') % prec + msg = _('skipping: %s: public version is a merge, ' + 'this is not handled yet\n') % prec ui.write_err(msg) return 2 @@ -1833,13 +1820,13 @@ repo.ui.write(_('atop:')) displayer.show(prec) if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y': - raise util.Abort(_('evolve aborted by user')) + 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"') + 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 @@ -1927,7 +1914,8 @@ 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" + 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 " @@ -1936,12 +1924,13 @@ "| - hg prune\n" "| \n" "| You should contact your local evolution Guru for help.\n" - % (divergent, othersstr)) + ) % (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) + 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" @@ -1949,7 +1938,8 @@ 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) + 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" @@ -1957,7 +1947,7 @@ "| 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}) + ) % {'d': divergent, 'o': other} ui.write_err(msg) ui.write_err(hint) return 2 @@ -1971,35 +1961,45 @@ ui.write(_('base: ')) displayer.show(base) if confirm and ui.prompt(_('perform evolve? [Ny]'), 'n') != 'y': - raise util.Abort(_('evolve aborted by user')) + 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) + 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, - partial=None, - ancestor=base.node(), - mergeancestor=True) + if 'partial' in merge.update.__doc__: + # Mercurial < 43c00ca887d1 (3.7) + stats = merge.update(repo, + other.node(), + branchmerge=True, + force=False, + partial=None, + ancestor=base.node(), + mergeancestor=True) + else: + 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 abandon\n")) if stats[3] > 0: - raise util.Abort('merge conflict between several amendments ' + raise error.Abort('merge conflict between several amendments ' '(this is not automated yet)', hint="""/!\ You can try: /!\ * manual merge + resolve => new cset X @@ -2009,8 +2009,10 @@ /!\ * hg kill -n Y W Z """) if progresscb: progresscb() + emtpycommitallowed = repo.ui.backupconfig('ui', 'allowemptycommit') tr = repo.transaction('stabilize-divergent') try: + repo.ui.setconfig('ui', 'allowemptycommit', True) repo.dirstate.beginparentchange() repo.dirstate.setparents(divergent.node(), node.nullid) repo.dirstate.endparentchange() @@ -2025,6 +2027,7 @@ phases.retractboundary(repo, tr, other.phase(), [new.node()]) tr.close() finally: + repo.ui.restoreconfig(emtpycommitallowed) tr.release() def divergentdata(ctx): @@ -2041,7 +2044,7 @@ 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 util.Abort("base of divergent changeset %s not found" % ctx, + raise error.Abort("base of divergent changeset %s not found" % ctx, hint='this case is not yet handled') @@ -2052,7 +2055,8 @@ [('B', 'move-bookmark', False, _('move active bookmark after update')), ('', 'merge', False, _('bring uncommitted change along')), - ('n', 'dry-run', False, _('do not perform actions, just print what would be done'))], + ('n', 'dry-run', False, + _('do not perform actions, just print what would be done'))], '[OPTION]...') def cmdprevious(ui, repo, **opts): """update to parent revision @@ -2062,11 +2066,11 @@ wparents = wkctx.parents() dryrunopt = opts['dry_run'] if len(wparents) != 1: - raise util.Abort('merge in progress') + raise error.Abort('merge in progress') if not opts['merge']: try: cmdutil.bailifchanged(repo) - except error.Abort, exc: + except error.Abort as exc: exc.hint = _('do you want --merge?') raise @@ -2077,21 +2081,25 @@ bm = bmactive(repo) shouldmove = opts.get('move_bookmark') and bm is not None if dryrunopt: - ui.write('hg update %s;\n' % p.rev()) + ui.write(('hg update %s;\n' % p.rev())) if shouldmove: - ui.write('hg bookmark %s -r %s;\n' % (bm, p.rev())) + ui.write(('hg bookmark %s -r %s;\n' % (bm, p.rev()))) else: ret = hg.update(repo, p.rev()) if not ret: + tr = lock = None wlock = repo.wlock() try: + lock = repo.lock() + tr = repo.transaction('previous') if shouldmove: repo._bookmarks[bm] = p.node() - repo._bookmarks.write() + repo._bookmarks.recordchange(tr) else: bmdeactivate(repo) + tr.close() finally: - wlock.release() + lockmod.release(tr, lock, wlock) displayer.show(p) return 0 else: @@ -2105,8 +2113,9 @@ _('move active bookmark after update')), ('', 'merge', False, _('bring uncommitted change along')), ('', 'evolve', False, _('evolve the next changeset if necessary')), - ('n', 'dry-run', False, _('do not perform actions, just print what would be done'))], - '[OPTION]...') + ('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 @@ -2118,11 +2127,11 @@ wparents = wkctx.parents() dryrunopt = opts['dry_run'] if len(wparents) != 1: - raise util.Abort('merge in progress') + raise error.Abort('merge in progress') if not opts['merge']: try: cmdutil.bailifchanged(repo) - except error.Abort, exc: + except error.Abort as exc: exc.hint = _('do you want --merge?') raise @@ -2133,25 +2142,29 @@ bm = bmactive(repo) shouldmove = opts.get('move_bookmark') and bm is not None if dryrunopt: - ui.write('hg update %s;\n' % c.rev()) + ui.write(('hg update %s;\n' % c.rev())) if shouldmove: - ui.write('hg bookmark %s -r %s;\n' % (bm, c.rev())) + ui.write(('hg bookmark %s -r %s;\n' % (bm, c.rev()))) else: ret = hg.update(repo, c.rev()) if not ret: + lock = tr = None wlock = repo.wlock() try: + lock = repo.lock() + tr = repo.transaction('next') if shouldmove: repo._bookmarks[bm] = c.node() - repo._bookmarks.write() + repo._bookmarks.recordchange(tr) else: bmdeactivate(repo) + tr.close() finally: - wlock.release() + lockmod.release(tr, lock, wlock) displayer.show(c) result = 0 elif children: - ui.warn("ambigious next changeset:\n") + ui.warn(_("ambigious next changeset:\n")) for c in children: displayer.show(c) ui.warn(_('explicitly update to one of them\n')) @@ -2181,46 +2194,48 @@ return 1 return result -def _reachablefrombookmark(repo, revs, mark): +def _reachablefrombookmark(repo, revs, bookmarks): """filter revisions and bookmarks reachable from the given bookmark yoinked from mq.py """ - marks = repo._bookmarks - if mark not in marks: - raise util.Abort(_("bookmark '%s' not found") % mark) + 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. - uniquebm = True - for m, n in marks.iteritems(): - if m != mark and n == repo[mark].node(): - uniquebm = False - break - if uniquebm: - if util.safehasattr(repair, 'stripbmrevset'): - rsrevs = repair.stripbmrevset(repo, mark) - else: - rsrevs = repo.revs("ancestors(bookmark(%s)) - " - "ancestors(head() and not bookmark(%s)) - " - "ancestors(bookmark() and not bookmark(%s)) - " - "obsolete()", - mark, mark, mark) - revs = set(revs) - revs.update(set(rsrevs)) - revs = sorted(revs) - return marks, revs - -def _deletebookmark(repo, marks, mark): + nodetobookmarks = {} + for mark, node in repomarks.iteritems(): + nodetobookmarks.setdefault(node, []).append(mark) + for marks in nodetobookmarks.values(): + if bookmarks.issuperset(marks): + if util.safehasattr(repair, 'stripbmrevset'): + rsrevs = repair.stripbmrevset(repo, marks[0]) + else: + rsrevs = repo.revs("ancestors(bookmark(%s)) - " + "ancestors(head() and not bookmark(%s)) - " + "ancestors(bookmark() and not bookmark(%s)) - " + "obsolete()", + marks[0], marks[0], 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') - del marks[mark] - marks.recordchange(tr) + for bookmark in bookmarks: + del repomarks[bookmark] + repomarks.recordchange(tr) tr.close() - repo.ui.write(_("bookmark '%s' deleted\n") % mark) + for bookmark in sorted(bookmarks): + repo.ui.write(_("bookmark '%s' deleted\n") % bookmark) finally: lockmod.release(tr, lock, wlock) @@ -2243,9 +2258,11 @@ ('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" + ('', '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...')) # -U --noupdate option to prevent wc update and or bookmarks update ? @@ -2275,7 +2292,7 @@ """ revs = scmutil.revrange(repo, list(revs) + opts.get('rev')) succs = opts['new'] + opts['succ'] - bookmark = opts.get('bookmark') + bookmarks = set(opts.get('bookmark')) metadata = _getmetadata(**opts) biject = opts.get('biject') fold = opts.get('fold') @@ -2283,16 +2300,16 @@ options = [o for o in ('biject', 'fold', 'split') if opts.get(o)] if 1 < len(options): - raise util.Abort(_("can only specify one of %s") % ', '.join(options)) - - if bookmark: - marks,revs = _reachablefrombookmark(repo, revs, bookmark) + 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, marks, bookmark) + _deletebookmark(repo, repomarks, bookmarks) if not revs: - raise util.Abort(_('nothing to prune')) + raise error.Abort(_('nothing to prune')) wlock = lock = tr = None try: @@ -2306,15 +2323,15 @@ cp = repo[p] if not cp.mutable(): # note: createmarkers() would have raised something anyway - raise util.Abort('cannot prune immutable changeset: %s' % cp, + raise error.Abort('cannot prune immutable changeset: %s' % cp, hint='see "hg help phases" for details') precs.append(cp) if not precs: - raise util.Abort('nothing to prune') + raise error.Abort('nothing to prune') if not obsolete.isenabled(repo, obsolete.allowunstableopt): if repo.revs("(%ld::) - %ld", revs, revs): - raise util.Abort(_("cannot prune in the middle of a stack")) + raise error.Abort(_("cannot prune in the middle of a stack")) # defines successors changesets sucs = scmutil.revrange(repo, succs) @@ -2322,17 +2339,17 @@ 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" - raise util.Abort(msg) + raise error.Abort(msg) elif biject and len(sucs) != len(precs): msg = "Can't use %d successors for %d precursors" \ % (len(sucs), len(precs)) - raise util.Abort(msg) + 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 util.Abort(msg) + 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 util.Abort(msg) + raise error.Abort(msg) elif biject: relations = [(p, (s,)) for p, s in zip(precs, sucs)] else: @@ -2362,33 +2379,35 @@ descendantrevs = repo.revs("%d::." % newnode.rev()) changedfiles = [] for rev in descendantrevs: - # blindly reset the files, regardless of what actually changed + # 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) + repo.dirstate.rebuild(newnode.node(), newnode.manifest(), + changedfiles) writedirstate(dirstate, tr) else: bookactive = bmactive(repo) # 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 bookmark + movebookmark = bookactive and not bookmarks if movebookmark: bmdeactivate(repo) repo._bookmarks[bookactive] = newnode.node() - repo._bookmarks.write() + repo._bookmarks.recordchange(tr) commands.update(ui, repo, newnode.rev()) ui.status(_('working directory now at %s\n') % newnode) if movebookmark: bmactivate(repo, bookactive) # update bookmarks - if bookmark: - _deletebookmark(repo, marks, bookmark) + if bookmarks: + _deletebookmark(repo, repomarks, bookmarks) # create markers obsolete.createmarkers(repo, relations, metadata=metadata) @@ -2583,14 +2602,14 @@ lock = repo.lock() wctx = repo[None] if len(wctx.parents()) <= 0: - raise util.Abort(_("cannot uncommit null changeset")) + raise error.Abort(_("cannot uncommit null changeset")) if len(wctx.parents()) > 1: - raise util.Abort(_("cannot uncommit while merging")) + raise error.Abort(_("cannot uncommit while merging")) old = repo['.'] if old.phase() == phases.public: - raise util.Abort(_("cannot rewrite immutable changeset")) + raise error.Abort(_("cannot rewrite immutable changeset")) if len(old.parents()) > 1: - raise util.Abort(_("cannot uncommit merge changeset")) + raise error.Abort(_("cannot uncommit merge changeset")) oldphase = old.phase() @@ -2599,12 +2618,13 @@ rev = scmutil.revsingle(repo, opts.get('rev')) ctx = repo[None] if ctx.p1() == rev or ctx.p2() == rev: - raise util.Abort(_("cannot uncommit to parent changeset")) + raise error.Abort(_("cannot uncommit to parent changeset")) onahead = old.rev() in repo.changelog.headrevs() - disallowunstable = not obsolete.isenabled(repo, obsolete.allowunstableopt) + disallowunstable = not obsolete.isenabled(repo, + obsolete.allowunstableopt) if disallowunstable and not onahead: - raise util.Abort(_("cannot uncommit in the middle of a stack")) + raise error.Abort(_("cannot uncommit in the middle of a stack")) # Recommit the filtered changeset tr = repo.transaction('uncommit') @@ -2615,7 +2635,7 @@ match = scmutil.match(old, pats, opts) newid = _commitfiltered(repo, old, match, target=rev) if newid is None: - raise util.Abort(_('nothing to uncommit'), + 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],))]) @@ -2634,6 +2654,7 @@ @eh.wrapcommand('commit') def commitwrapper(orig, ui, repo, *arg, **kwargs): + tr = None if kwargs.get('amend', False): wlock = lock = None else: @@ -2656,17 +2677,23 @@ for book in oldbookmarks: repo._bookmarks[book] = new.node() if oldbookmarks: - repo._bookmarks.write() + 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(lock, wlock) + lockmod.release(tr, lock, wlock) @command('^split', [('r', 'rev', [], _("revision to fold")), ] + commitopts + commitopts2, _('hg split [OPTION]... [-r] REV')) def cmdsplit(ui, repo, *revs, **opts): - """split a changeset into smaller changesets (EXPERIMENTAL) + """split a changeset into smaller changesets By default, split the current revision by prompting for all its hunks to be redistributed into new changesets. @@ -2680,7 +2707,7 @@ if revopt: revs = scmutil.revrange(repo, revopt) if len(revs) != 1: - raise util.Abort(_("you can only specify one revision to split")) + raise error.Abort(_("you can only specify one revision to split")) else: rev = list(revs)[0] else: @@ -2698,10 +2725,10 @@ if disallowunstable: # XXX We should check head revs if repo.revs("(%d::) - %d", rev, rev): - raise util.Abort(_("cannot split commit: %s not a head") % ctx) + raise error.Abort(_("cannot split commit: %s not a head") % ctx) if len(ctx.parents()) > 1: - raise util.Abort(_("cannot split merge commits")) + raise error.Abort(_("cannot split merge commits")) prev = ctx.p1() bmupdate = _bookmarksupdater(repo, ctx.node(), tr) bookactive = bmactive(repo) @@ -2714,6 +2741,10 @@ def haschanges(): modified, added, removed, deleted = repo.status()[:4] return modified or added or removed or deleted + msg = 'HG: Please, edit the original changeset description.\n\n' + msg += ctx.description() + opts['message'] = msg + opts['edit'] = True while haschanges(): pats = () cmdutil.dorecord(ui, repo, commands.commit, 'commit', False, @@ -2727,7 +2758,7 @@ newcommits.append(repo['.']) break else: - ui.status("no more change to split\n") + ui.status(_("no more change to split\n")) tip = repo[newcommits[-1]] bmupdate(tip.node()) @@ -2771,7 +2802,8 @@ # 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 + """create successors that are identical to their predecessors except + for the changeset ID This is used to "resurrect" changesets """ @@ -2785,7 +2817,7 @@ ui.write_err('no revision to touch\n') return 1 if not duplicate and repo.revs('public() and %ld', revs): - raise util.Abort("can't touch public revision") + raise error.Abort("can't touch public revision") wlock = lock = tr = None try: wlock = repo.wlock() @@ -2863,7 +2895,7 @@ revs = list(revs) revs.extend(opts['rev']) if not revs: - raise util.Abort(_('no revisions specified')) + raise error.Abort(_('no revisions specified')) revs = scmutil.revrange(repo, revs) @@ -2872,7 +2904,7 @@ extrevs = repo.revs('(%ld::.) or (.::%ld)', revs, revs) discardedrevs = [r for r in revs if r not in extrevs] if discardedrevs: - raise util.Abort(_("cannot fold non-linear revisions"), + raise error.Abort(_("cannot fold non-linear revisions"), hint=_("given revisions are unrelated to parent " "of working directory")) revs = extrevs @@ -2883,20 +2915,20 @@ roots = repo.revs('roots(%ld)', revs) if len(roots) > 1: - raise util.Abort(_("cannot fold non-linear revisions " + raise error.Abort(_("cannot fold non-linear revisions " "(multiple roots given)")) root = repo[roots.first()] if root.phase() <= phases.public: - raise util.Abort(_("cannot fold public revisions")) + raise error.Abort(_("cannot fold public revisions")) heads = repo.revs('heads(%ld)', revs) if len(heads) > 1: - raise util.Abort(_("cannot fold non-linear revisions " + raise error.Abort(_("cannot fold non-linear revisions " "(multiple heads given)")) head = repo[heads.first()] disallowunstable = not obsolete.isenabled(repo, obsolete.allowunstableopt) if disallowunstable: if repo.revs("(%ld::) - %ld", revs, revs): - raise util.Abort(_("cannot fold chain not ending with a head "\ + raise error.Abort(_("cannot fold chain not ending with a head "\ "or with branching")) wlock = lock = None try: @@ -2918,7 +2950,8 @@ commitopts['edit'] = True newid, unusedvariable = rewrite(repo, root, allctx, head, - [root.p1().node(), root.p2().node()], + [root.p1().node(), + root.p2().node()], commitopts=commitopts) phases.retractboundary(repo, tr, targetphase, [newid]) obsolete.createmarkers(repo, [(ctx, (repo[newid],)) @@ -3026,7 +3059,8 @@ obsexcmsg(repo.ui, "looking for common markers in %i nodes\n" % len(revs)) commonrevs = list(unfi.revs('::%ln', pushop.outgoing.commonheads)) - common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, commonrevs) + common = findcommonobsmarkers(pushop.ui, unfi, pushop.remote, + commonrevs) revs = list(unfi.revs('%ld - (::%ln)', revs, common)) nodes = [cl.node(r) for r in revs] @@ -3058,8 +3092,9 @@ wireproto.commands['evoext_obshash'] = (srv_obshash, 'nodes') wireproto.commands['evoext_obshash1'] = (srv_obshash1, 'nodes') if getattr(exchange, '_pushdiscoveryobsmarkers', None) is None: - ui.warn('evolve: your mercurial version is too old\n' - 'evolve: (running in degraded mode, push will includes all markers)\n') + ui.warn(_('evolve: your mercurial version is too old\n' + 'evolve: (running in degraded mode, push will ' + 'includes all markers)\n')) else: olddisco = exchange.pushdiscoverymapping['obsmarker'] def newdisco(pushop): @@ -3086,7 +3121,8 @@ return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes))) def srv_obshash1(repo, proto, nodes): - return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes), version=1)) + return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes), + version=1)) @eh.addattr(localrepo.localpeer, 'evoext_obshash') def local_obshash(peer, nodes): @@ -3123,7 +3159,7 @@ common = set() undecided = set(probeset) totalnb = len(undecided) - ui.progress("comparing with other", 0, total=totalnb) + ui.progress(_("comparing with other"), 0, total=totalnb) _takefullsample = setdiscovery._takefullsample if remote.capable('_evoext_obshash_1'): getremotehash = remote.evoext_obshash1 @@ -3141,7 +3177,7 @@ sample = _takefullsample(dag, undecided, size=fullsamplesize) roundtrips += 1 - ui.progress("comparing with other", totalnb - len(undecided), + ui.progress(_("comparing with other"), totalnb - len(undecided), total=totalnb) ui.debug("query %i; still undecided: %i, sample size is: %i\n" % (roundtrips, len(undecided), len(sample))) @@ -3162,7 +3198,7 @@ undecided.difference_update(common) - ui.progress("comparing with other", None, total=totalnb) + ui.progress(_("comparing with other"), None, total=totalnb) result = dag.headsetofconnecteds(common) ui.debug("%d total queries\n" % roundtrips) @@ -3226,10 +3262,11 @@ else: rslts = [] remotedata = _pushkeyescape(markers).items() - totalbytes = sum(len(d) for k,d in remotedata) + totalbytes = sum(len(d) for k, d in remotedata) sentbytes = 0 - obsexcmsg(repo.ui, "pushing %i obsolescence markers in %i pushkey payload (%i bytes)\n" - % (len(markers), len(remotedata), totalbytes), + obsexcmsg(repo.ui, "pushing %i obsolescence markers in %i " + "pushkey payload (%i bytes)\n" + % (len(markers), len(remotedata), totalbytes), True) for key, data in remotedata: obsexcprg(repo.ui, sentbytes, item=key, unit="bytes", @@ -3271,10 +3308,10 @@ if l.strip(): self.ui.status(_('remote: '), l) return vals[0] - except socket.error, err: + except socket.error as err: if err.args[0] in (errno.ECONNRESET, errno.EPIPE): - raise util.Abort(_('push failed: %s') % err.args[1]) - raise util.Abort(err.args[1]) + raise error.Abort(_('push failed: %s') % err.args[1]) + raise error.Abort(err.args[1]) @eh.wrapfunction(localrepo.localrepository, '_restrictcapabilities') def local_pushobsmarker_capabilities(orig, repo, caps): @@ -3333,7 +3370,8 @@ @eh.wrapfunction(exchange, '_pullbundle2extraprepare') def _addobscommontob2pull(orig, pullop, kwargs): ret = orig(pullop, kwargs) - if 'obsmarkers' in kwargs and pullop.remote.capable('_evoext_getbundle_obscommon'): + if ('obsmarkers' in kwargs and + pullop.remote.capable('_evoext_getbundle_obscommon')): boundaries = _buildpullobsmarkersboundaries(pullop) common = boundaries['common'] if common != [nullid]: @@ -3446,7 +3484,7 @@ ui = self.ui obsexcprg(ui, current, unit="bytes", total=length) while current < length: - readsize = min(length-current, chunk) + readsize = min(length - current, chunk) data.write(f.read(readsize)) current += readsize obsexcprg(ui, current, unit="bytes", total=length) @@ -3481,7 +3519,7 @@ cache = [] unfi = repo.unfiltered() markercache = {} - repo.ui.progress("preparing locally", 0, total=len(unfi)) + repo.ui.progress(_("preparing locally"), 0, total=len(unfi)) for i in unfi: ctx = unfi[i] entry = 0 @@ -3511,14 +3549,13 @@ cache.append((ctx.node(), sha.digest())) else: cache.append((ctx.node(), nullid)) - repo.ui.progress("preparing locally", i, total=len(unfi)) - repo.ui.progress("preparing locally", None) + repo.ui.progress(_("preparing locally"), i, total=len(unfi)) + repo.ui.progress(_("preparing locally"), None) return cache @command('debugobsrelsethashtree', [('', 'v0', None, 'hash on marker format "0"'), - ('', 'v1', None, 'hash on marker format "1" (default)') - ,] , _('')) + ('', 'v1', None, 'hash on marker format "1" (default)')] , _('')) def debugobsrelsethashtree(ui, repo, v0=False, v1=False): """display Obsolete markers, Relevant Set, Hash Tree changeset-node obsrelsethashtree-node @@ -3526,7 +3563,7 @@ It computed form the "orsht" of its parent and markers relevant to the changeset itself.""" if v0 and v1: - raise util.Abort('cannot only specify one format') + raise error.Abort('cannot only specify one format') elif v0: treefunc = _obsrelsethashtreefm0 else: @@ -3549,7 +3586,7 @@ return for mark in markers: if node.nullid in mark[1]: - raise util.Abort(_('bad obsolescence marker detected: ' + raise error.Abort(_('bad obsolescence marker detected: ' 'invalid successors nullid'), hint=_('You should run `hg debugobsconvert`')) @@ -3561,7 +3598,7 @@ 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 util.Abort(msg) + raise error.Abort(msg) f = repo.svfs('obsstore', 'wb', atomictemp=True) known = set() markers = [] @@ -3621,3 +3658,42 @@ 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, 'rebase') + # 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) diff -r dcfe3afe548b -r 0c8548df67fe hgext/inhibit.py --- a/hgext/inhibit.py Sat Nov 07 13:39:59 2015 -0500 +++ b/hgext/inhibit.py Tue Dec 22 14:11:09 2015 +0000 @@ -9,9 +9,9 @@ should probably stick on using evolution in its current state, understand its concept and provide feedback -This extension provides the ability to "inhibit" obsolescence markers. obsolete -revision can be cheaply brought back to life that way. -However as the inhibitor are not fitting in an append only model, this is +This extension provides the ability to "inhibit" obsolescence markers. obsolete +revision can be cheaply brought back to life that way. +However as the inhibitor are not fitting in an append only model, this is incompatible with sharing mutable history. """ from mercurial import localrepo @@ -42,7 +42,7 @@ obsinhibit = set() raw = self.svfs.tryread('obsinhibit') for i in xrange(0, len(raw), 20): - obsinhibit.add(raw[i:i+20]) + obsinhibit.add(raw[i:i + 20]) return obsinhibit def commit(self, *args, **kwargs): @@ -61,7 +61,7 @@ """ wlock = None try: - # Evolve is running a hook on lock release to display a warning message + # Evolve is running a hook on lock release to display a warning message # if the workind dir's parent is obsolete. # We take the lock here to make sure that we inhibit the parent before # that hook get a chance to run. @@ -85,6 +85,11 @@ haspruneopt = opts.get('prune', False) if not haspruneopt: return orig(ui, repo, *bookmarks, **opts) + elif opts.get('rename'): + raise error.Abort('Cannot use both -m and -D') + elif len(bookmarks) == 0: + hint = _('make sure to put a space between -D and your bookmark name') + raise error.Abort(_('Error, please check your command'), hint=hint) # Call prune -B evolve = extensions.find('evolve') @@ -92,7 +97,7 @@ 'new': [], 'succ': [], 'rev': [], - 'bookmark': bookmarks[0], + 'bookmark': [bookmarks[0]], 'keep': None, 'biject': False, } @@ -129,13 +134,22 @@ if not _inhibitenabled(repo): return - newinhibit = repo.set('::%ln and obsolete()', nodes) + # we add (non public()) as a lower boundary to + # - use the C code in 3.6 (no ancestors in C as this is written) + # - restrict the search space. Otherwise, the ancestors can spend a lot of + # time iterating if you have a check very low in the repo. We do not need + # to iterate over tens of thousand of public revisions with higher + # revision number + # + # In addition, the revset logic could be made significantly smarter here. + newinhibit = repo.revs('(not public())::%ln and obsolete()', nodes) if newinhibit: + node = repo.changelog.node lock = tr = None try: lock = repo.lock() tr = repo.transaction('obsinhibit') - repo._obsinhibit.update(c.node() for c in newinhibit) + repo._obsinhibit.update(node(r) for r in newinhibit) _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit)) repo.invalidatevolatilesets() tr.close() @@ -176,6 +190,16 @@ finally: lockmod.release(tr, lock) +def _computeobsoletenotrebasedwrap(orig, repo, rebasesetrevs, dest): + repo._notinhibited = rebasesetrevs + try: + repo.invalidatevolatilesets() + r = orig(repo, rebasesetrevs, dest) + finally: + del repo._notinhibited + repo.invalidatevolatilesets() + return r + def transactioncallback(orig, repo, desc, *args, **kwargs): """ Wrap localrepo.transaction to inhibit new obsolete changes """ def inhibitposttransaction(transaction): @@ -188,7 +212,8 @@ _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete]) transaction = orig(repo, desc, *args, **kwargs) if desc != 'strip' and _inhibitenabled(repo): - transaction.addpostclose('inhibitposttransaction', inhibitposttransaction) + transaction.addpostclose('inhibitposttransaction', + inhibitposttransaction) return transaction def extsetup(ui): @@ -202,8 +227,10 @@ obs = obsfunc(repo) if _inhibitenabled(repo): getrev = repo.changelog.nodemap.get + blacklist = getattr(repo, '_notinhibited', set()) for n in repo._obsinhibit: - obs.discard(getrev(n)) + if getrev(n) not in blacklist: + obs.discard(getrev(n)) return obs try: extensions.find('directaccess') @@ -228,6 +255,14 @@ # wrap update to make sure that no obsolete commit is visible after an # update extensions.wrapcommand(commands.table, 'update', _update) + try: + rebase = extensions.find('rebase') + if rebase: + extensions.wrapfunction(rebase, + '_computeobsoletenotrebased', + _computeobsoletenotrebasedwrap) + except KeyError: + pass # There are two ways to save bookmark changes during a transation, we # wrap both to add inhibition markers. extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged) diff -r dcfe3afe548b -r 0c8548df67fe hgext/obsolete.py --- a/hgext/obsolete.py Sat Nov 07 13:39:59 2015 -0500 +++ b/hgext/obsolete.py Tue Dec 22 14:11:09 2015 +0000 @@ -5,9 +5,12 @@ # GNU General Public License version 2 or any later version. """Deprecated extension that formely introduces "Changeset Obsolescence". -This concept is now partially in Mercurial core (starting with mercurial 2.3). The remaining logic have been grouped with the evolve extension. +This concept is now partially in Mercurial core (starting with mercurial 2.3). +The remaining logic have been grouped with the evolve extension. -Some code cemains in this extensions to detect and convert prehistoric format of obsolete marker than early user may have create. Keep it enabled if you were such user. +Some code cemains in this extensions to detect and convert prehistoric format +of obsolete marker than early user may have create. Keep it enabled if you +were such user. """ from mercurial import util @@ -15,13 +18,14 @@ try: from mercurial import obsolete except ImportError: - raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)') + raise error.Abort('Obsolete extension requires Mercurial 2.3 (or later)') import sys import json from mercurial import cmdutil from mercurial import error +from mercurial.i18n import _ from mercurial.node import bin, nullid @@ -50,8 +54,8 @@ if not data: data = repo.svfs.tryread('obsoletemarkers') if data: - raise util.Abort('old format of obsolete marker detected!\n' - 'run `hg debugconvertobsolete` once.') + raise error.Abort('old format of obsolete marker detected!\n' + 'run `hg debugconvertobsolete` once.') def _obsdeserialise(flike): """read a file like object serialised with _obsserialise @@ -65,7 +69,7 @@ subnode = bin(subhex) if subnode == nullid: subnode = None - rels.setdefault( subnode, set()).add(bin(objhex)) + rels.setdefault(subnode, set()).add(bin(objhex)) return rels cmdtable = {} @@ -153,7 +157,7 @@ del repo._importoldobsolete l.release() if not some: - ui.warn('nothing to do\n') + ui.warn(_('nothing to do\n')) ui.status('%i obsolete marker converted\n' % cnt) if err: ui.write_err('%i conversion failed. check you graph!\n' % err) diff -r dcfe3afe548b -r 0c8548df67fe hgext/pushexperiment.py --- a/hgext/pushexperiment.py Sat Nov 07 13:39:59 2015 -0500 +++ b/hgext/pushexperiment.py Tue Dec 22 14:11:09 2015 +0000 @@ -3,7 +3,8 @@ - Add a new wire protocol command to exchange obsolescence markers. Sending the raw file as a binary instead of using pushkey hack. - Add a "push done" notification -- Push obsolescence marker before anything else (This works around the lack of global transaction) +- Push obsolescence marker before anything else (This works around the lack +of global transaction) """ @@ -61,7 +62,8 @@ def client_notifypushend(self): """wire peer command to notify a push is done""" - self.requirecap('_push_experiment_notifypushend_0', _('hook once push is all done')) + self.requirecap('_push_experiment_notifypushend_0', + _('hook once push is all done')) return self._call('push_experiment_notifypushend_0') @@ -75,7 +77,7 @@ def augmented_push(orig, repo, remote, *args, **kwargs): """push wrapped that call the wire protocol command""" if not remote.canpush(): - raise util.Abort(_("destination does not support push")) + raise error.Abort(_("destination does not support push")) if (obsolete.isenabled(repo, obsolete.exchangeopt) and repo.obsstore and remote.capable('_push_experiment_pushobsmarkers_0')): # push marker early to limit damage of pushing too early. @@ -104,8 +106,10 @@ def extsetup(ui): wireproto.wirepeer.push_experiment_pushobsmarkers_0 = client_pushobsmarkers wireproto.wirepeer.push_experiment_notifypushend_0 = client_notifypushend - wireproto.commands['push_experiment_pushobsmarkers_0'] = (srv_pushobsmarkers, '') - wireproto.commands['push_experiment_notifypushend_0'] = (srv_notifypushend, '') + wireproto.commands['push_experiment_pushobsmarkers_0'] = \ + (srv_pushobsmarkers, '') + wireproto.commands['push_experiment_notifypushend_0'] = \ + (srv_notifypushend, '') extensions.wrapfunction(wireproto, 'capabilities', capabilities) extensions.wrapfunction(obsolete, 'syncpush', syncpush) extensions.wrapfunction(localrepo.localrepository, 'push', augmented_push) diff -r dcfe3afe548b -r 0c8548df67fe hgext/simple4server.py --- a/hgext/simple4server.py Sat Nov 07 13:39:59 2015 -0500 +++ b/hgext/simple4server.py Tue Dec 22 14:11:09 2015 +0000 @@ -34,8 +34,8 @@ gboptslist = getattr(wireproto, 'gboptslist', None) gboptsmap = getattr(wireproto, 'gboptsmap', None) except (ImportError, AttributeError): - raise util.Abort('Your Mercurial is too old for this version of Evolve\n' - 'requires version 3.0.1 or above') + raise error.Abort('Your Mercurial is too old for this version of Evolve\n' + 'requires version 3.0.1 or above') # Start of simple4server specific content @@ -101,10 +101,11 @@ if not util.safehasattr(obsolete.obsstore, 'relevantmarkers'): # from evolve extension: 1a23c7c52a43 class pruneobsstore(obsolete.obsstore): - """And extended obsstore class that read parent information from v1 format + """And extended obsstore class that read parent information from v1 + format - Evolve extension adds parent information in prune marker. We use it to make - markers relevant to pushed changeset.""" + Evolve extension adds parent information in prune marker. + We use it to make markers relevant to pushed changeset.""" def __init__(self, *args, **kwargs): self.prunedchildren = {} @@ -237,7 +238,8 @@ # from evolve extension: 3249814dabd1 def srv_obshash1(repo, proto, nodes): - return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes), version=1)) + return wireproto.encodelist(_obshash(repo, wireproto.decodelist(nodes), + version=1)) # from evolve extension: 3249814dabd1 def capabilities(orig, repo, proto): diff -r dcfe3afe548b -r 0c8548df67fe tests/dummyssh --- a/tests/dummyssh Sat Nov 07 13:39:59 2015 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -import sys -import os - -os.chdir(os.getenv('TESTTMP')) - -if sys.argv[1] != "user@dummy": - sys.exit(-1) - -os.environ["SSH_CLIENT"] = "127.0.0.1 1 2" - -log = open("dummylog", "ab") -log.write("Got arguments") -for i, arg in enumerate(sys.argv[1:]): - log.write(" %d:%s" % (i + 1, arg)) -log.write("\n") -log.close() -hgcmd = sys.argv[2] -if os.name == 'nt': - # hack to make simple unix single quote quoting work on windows - hgcmd = hgcmd.replace("'", '"') -r = os.system(hgcmd) -sys.exit(bool(r)) diff -r dcfe3afe548b -r 0c8548df67fe tests/killdaemons.py --- a/tests/killdaemons.py Sat Nov 07 13:39:59 2015 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ -#!/usr/bin/env python - -import os, sys, time, errno, signal - -if os.name =='nt': - import ctypes - - def _check(ret, expectederr=None): - if ret == 0: - winerrno = ctypes.GetLastError() - if winerrno == expectederr: - return True - raise ctypes.WinError(winerrno) - - def kill(pid, logfn, tryhard=True): - logfn('# Killing daemon process %d' % pid) - PROCESS_TERMINATE = 1 - PROCESS_QUERY_INFORMATION = 0x400 - SYNCHRONIZE = 0x00100000 - WAIT_OBJECT_0 = 0 - WAIT_TIMEOUT = 258 - handle = ctypes.windll.kernel32.OpenProcess( - PROCESS_TERMINATE|SYNCHRONIZE|PROCESS_QUERY_INFORMATION, - False, pid) - if handle == 0: - _check(0, 87) # err 87 when process not found - return # process not found, already finished - try: - r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100) - if r == WAIT_OBJECT_0: - pass # terminated, but process handle still available - elif r == WAIT_TIMEOUT: - _check(ctypes.windll.kernel32.TerminateProcess(handle, -1)) - else: - _check(r) - - # TODO?: forcefully kill when timeout - # and ?shorter waiting time? when tryhard==True - r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100) - # timeout = 100 ms - if r == WAIT_OBJECT_0: - pass # process is terminated - elif r == WAIT_TIMEOUT: - logfn('# Daemon process %d is stuck') - else: - _check(r) # any error - except: #re-raises - ctypes.windll.kernel32.CloseHandle(handle) # no _check, keep error - raise - _check(ctypes.windll.kernel32.CloseHandle(handle)) - -else: - def kill(pid, logfn, tryhard=True): - try: - os.kill(pid, 0) - logfn('# Killing daemon process %d' % pid) - os.kill(pid, signal.SIGTERM) - if tryhard: - for i in range(10): - time.sleep(0.05) - os.kill(pid, 0) - else: - time.sleep(0.1) - os.kill(pid, 0) - logfn('# Daemon process %d is stuck - really killing it' % pid) - os.kill(pid, signal.SIGKILL) - except OSError, err: - if err.errno != errno.ESRCH: - raise - -def killdaemons(pidfile, tryhard=True, remove=False, logfn=None): - if not logfn: - logfn = lambda s: s - # Kill off any leftover daemon processes - try: - fp = open(pidfile) - for line in fp: - try: - pid = int(line) - except ValueError: - continue - kill(pid, logfn, tryhard) - fp.close() - if remove: - os.unlink(pidfile) - except IOError: - pass - -if __name__ == '__main__': - path, = sys.argv[1:] - killdaemons(path) diff -r dcfe3afe548b -r 0c8548df67fe tests/run-tests.py --- a/tests/run-tests.py Sat Nov 07 13:39:59 2015 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1128 +0,0 @@ -#!/usr/bin/env python -# -# run-tests.py - Run a set of tests on Mercurial -# -# Copyright 2006 Matt Mackall -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -# Modifying this script is tricky because it has many modes: -# - serial (default) vs parallel (-jN, N > 1) -# - no coverage (default) vs coverage (-c, -C, -s) -# - temp install (default) vs specific hg script (--with-hg, --local) -# - tests are a mix of shell scripts and Python scripts -# -# If you change this script, it is recommended that you ensure you -# haven't broken it by running it in various modes with a representative -# sample of test scripts. For example: -# -# 1) serial, no coverage, temp install: -# ./run-tests.py test-s* -# 2) serial, no coverage, local hg: -# ./run-tests.py --local test-s* -# 3) serial, coverage, temp install: -# ./run-tests.py -c test-s* -# 4) serial, coverage, local hg: -# ./run-tests.py -c --local test-s* # unsupported -# 5) parallel, no coverage, temp install: -# ./run-tests.py -j2 test-s* -# 6) parallel, no coverage, local hg: -# ./run-tests.py -j2 --local test-s* -# 7) parallel, coverage, temp install: -# ./run-tests.py -j2 -c test-s* # currently broken -# 8) parallel, coverage, local install: -# ./run-tests.py -j2 -c --local test-s* # unsupported (and broken) -# 9) parallel, custom tmp dir: -# ./run-tests.py -j2 --tmpdir /tmp/myhgtests -# -# (You could use any subset of the tests: test-s* happens to match -# enough that it's worth doing parallel runs, few enough that it -# completes fairly quickly, includes both shell and Python scripts, and -# includes some scripts that run daemon processes.) - -from distutils import version -import difflib -import errno -import optparse -import os -import shutil -import subprocess -import signal -import sys -import tempfile -import time -import re - -closefds = os.name == 'posix' -def Popen4(cmd, bufsize=-1): - p = subprocess.Popen(cmd, shell=True, bufsize=bufsize, - close_fds=closefds, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - p.fromchild = p.stdout - p.tochild = p.stdin - p.childerr = p.stderr - return p - -# reserved exit code to skip test (used by hghave) -SKIPPED_STATUS = 80 -SKIPPED_PREFIX = 'skipped: ' -FAILED_PREFIX = 'hghave check failed: ' -PYTHON = sys.executable -IMPL_PATH = 'PYTHONPATH' -if 'java' in sys.platform: - IMPL_PATH = 'JYTHONPATH' - -requiredtools = ["python", "diff", "grep", "sed"] - -defaults = { - 'jobs': ('HGTEST_JOBS', 1), - 'timeout': ('HGTEST_TIMEOUT', 180), - 'port': ('HGTEST_PORT', 20059), -} - -def parseargs(): - parser = optparse.OptionParser("%prog [options] [tests]") - - # keep these sorted - parser.add_option("--blacklist", action="append", - help="skip tests listed in the specified blacklist file") - parser.add_option("-C", "--annotate", action="store_true", - help="output files annotated with coverage") - parser.add_option("--child", type="int", - help="run as child process, summary to given fd") - parser.add_option("-c", "--cover", action="store_true", - help="print a test coverage report") - parser.add_option("-d", "--debug", action="store_true", - help="debug mode: write output of test scripts to console" - " rather than capturing and diff'ing it (disables timeout)") - parser.add_option("-f", "--first", action="store_true", - help="exit on the first test failure") - parser.add_option("--inotify", action="store_true", - help="enable inotify extension when running tests") - parser.add_option("-i", "--interactive", action="store_true", - help="prompt to accept changed output") - parser.add_option("-j", "--jobs", type="int", - help="number of jobs to run in parallel" - " (default: $%s or %d)" % defaults['jobs']) - parser.add_option("--keep-tmpdir", action="store_true", - help="keep temporary directory after running tests") - parser.add_option("-k", "--keywords", - help="run tests matching keywords") - parser.add_option("-l", "--local", action="store_true", - help="shortcut for --with-hg=/../hg") - parser.add_option("-n", "--nodiff", action="store_true", - help="skip showing test changes") - parser.add_option("-p", "--port", type="int", - help="port on which servers should listen" - " (default: $%s or %d)" % defaults['port']) - parser.add_option("--pure", action="store_true", - help="use pure Python code instead of C extensions") - parser.add_option("-R", "--restart", action="store_true", - help="restart at last error") - parser.add_option("-r", "--retest", action="store_true", - help="retest failed tests") - parser.add_option("-S", "--noskips", action="store_true", - help="don't report skip tests verbosely") - parser.add_option("-t", "--timeout", type="int", - help="kill errant tests after TIMEOUT seconds" - " (default: $%s or %d)" % defaults['timeout']) - parser.add_option("--tmpdir", type="string", - help="run tests in the given temporary directory" - " (implies --keep-tmpdir)") - parser.add_option("-v", "--verbose", action="store_true", - help="output verbose messages") - parser.add_option("--view", type="string", - help="external diff viewer") - parser.add_option("--with-hg", type="string", - metavar="HG", - help="test using specified hg script rather than a " - "temporary installation") - parser.add_option("-3", "--py3k-warnings", action="store_true", - help="enable Py3k warnings on Python 2.6+") - - for option, default in defaults.items(): - defaults[option] = int(os.environ.get(*default)) - parser.set_defaults(**defaults) - (options, args) = parser.parse_args() - - # jython is always pure - if 'java' in sys.platform or '__pypy__' in sys.modules: - options.pure = True - - if options.with_hg: - if not (os.path.isfile(options.with_hg) and - os.access(options.with_hg, os.X_OK)): - parser.error('--with-hg must specify an executable hg script') - if not os.path.basename(options.with_hg) == 'hg': - sys.stderr.write('warning: --with-hg should specify an hg script') - if options.local: - testdir = os.path.dirname(os.path.realpath(sys.argv[0])) - hgbin = os.path.join(os.path.dirname(testdir), 'hg') - if not os.access(hgbin, os.X_OK): - parser.error('--local specified, but %r not found or not executable' - % hgbin) - options.with_hg = hgbin - - options.anycoverage = options.cover or options.annotate - if options.anycoverage: - try: - import coverage - covver = version.StrictVersion(coverage.__version__).version - if covver < (3, 3): - parser.error('coverage options require coverage 3.3 or later') - except ImportError: - parser.error('coverage options now require the coverage package') - - if options.anycoverage and options.local: - # this needs some path mangling somewhere, I guess - parser.error("sorry, coverage options do not work when --local " - "is specified") - - global vlog - if options.verbose: - if options.jobs > 1 or options.child is not None: - pid = "[%d]" % os.getpid() - else: - pid = None - def vlog(*msg): - if pid: - print pid, - for m in msg: - print m, - print - sys.stdout.flush() - else: - vlog = lambda *msg: None - - if options.tmpdir: - options.tmpdir = os.path.expanduser(options.tmpdir) - - if options.jobs < 1: - parser.error('--jobs must be positive') - if options.interactive and options.jobs > 1: - print '(--interactive overrides --jobs)' - options.jobs = 1 - if options.interactive and options.debug: - parser.error("-i/--interactive and -d/--debug are incompatible") - if options.debug: - if options.timeout != defaults['timeout']: - sys.stderr.write( - 'warning: --timeout option ignored with --debug\n') - options.timeout = 0 - if options.py3k_warnings: - if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0): - parser.error('--py3k-warnings can only be used on Python 2.6+') - if options.blacklist: - blacklist = dict() - for filename in options.blacklist: - try: - path = os.path.expanduser(os.path.expandvars(filename)) - f = open(path, "r") - except IOError, err: - if err.errno != errno.ENOENT: - raise - print "warning: no such blacklist file: %s" % filename - continue - - for line in f.readlines(): - line = line.strip() - if line and not line.startswith('#'): - blacklist[line] = filename - - f.close() - - options.blacklist = blacklist - - return (options, args) - -def rename(src, dst): - """Like os.rename(), trade atomicity and opened files friendliness - for existing destination support. - """ - shutil.copy(src, dst) - os.remove(src) - -def splitnewlines(text): - '''like str.splitlines, but only split on newlines. - keep line endings.''' - i = 0 - lines = [] - while True: - n = text.find('\n', i) - if n == -1: - last = text[i:] - if last: - lines.append(last) - return lines - lines.append(text[i:n + 1]) - i = n + 1 - -def parsehghaveoutput(lines): - '''Parse hghave log lines. - Return tuple of lists (missing, failed): - * the missing/unknown features - * the features for which existence check failed''' - missing = [] - failed = [] - for line in lines: - if line.startswith(SKIPPED_PREFIX): - line = line.splitlines()[0] - missing.append(line[len(SKIPPED_PREFIX):]) - elif line.startswith(FAILED_PREFIX): - line = line.splitlines()[0] - failed.append(line[len(FAILED_PREFIX):]) - - return missing, failed - -def showdiff(expected, output, ref, err): - try: - for line in difflib.unified_diff(expected, output, ref, err): - sys.stdout.write(line) - except IOError, ex: - print >>sys.stderr, 'BORKEN PIPE', ex.errno - pass - -def findprogram(program): - """Search PATH for a executable program""" - for p in os.environ.get('PATH', os.defpath).split(os.pathsep): - name = os.path.join(p, program) - if os.access(name, os.X_OK): - return name - return None - -def checktools(): - # Before we go any further, check for pre-requisite tools - # stuff from coreutils (cat, rm, etc) are not tested - for p in requiredtools: - if os.name == 'nt': - p += '.exe' - found = findprogram(p) - if found: - vlog("# Found prerequisite", p, "at", found) - else: - print "WARNING: Did not find prerequisite tool: "+p - -def killdaemons(): - # Kill off any leftover daemon processes - try: - fp = open(DAEMON_PIDS) - for line in fp: - try: - pid = int(line) - except ValueError: - continue - try: - os.kill(pid, 0) - vlog('# Killing daemon process %d' % pid) - os.kill(pid, signal.SIGTERM) - time.sleep(0.25) - os.kill(pid, 0) - vlog('# Daemon process %d is stuck - really killing it' % pid) - os.kill(pid, signal.SIGKILL) - except OSError, err: - if err.errno != errno.ESRCH: - raise - fp.close() - os.unlink(DAEMON_PIDS) - except IOError: - pass - -def cleanup(options): - if not options.keep_tmpdir: - vlog("# Cleaning up HGTMP", HGTMP) - shutil.rmtree(HGTMP, True) - -def usecorrectpython(): - # some tests run python interpreter. they must use same - # interpreter we use or bad things will happen. - exedir, exename = os.path.split(sys.executable) - if exename == 'python': - path = findprogram('python') - if os.path.dirname(path) == exedir: - return - vlog('# Making python executable in test path use correct Python') - mypython = os.path.join(BINDIR, 'python') - try: - os.symlink(sys.executable, mypython) - except AttributeError: - # windows fallback - shutil.copyfile(sys.executable, mypython) - shutil.copymode(sys.executable, mypython) - -def installhg(options): - vlog("# Performing temporary installation of HG") - installerrs = os.path.join("tests", "install.err") - pure = options.pure and "--pure" or "" - - # Run installer in hg root - script = os.path.realpath(sys.argv[0]) - hgroot = os.path.dirname(os.path.dirname(script)) - os.chdir(hgroot) - nohome = '--home=""' - if os.name == 'nt': - # The --home="" trick works only on OS where os.sep == '/' - # because of a distutils convert_path() fast-path. Avoid it at - # least on Windows for now, deal with .pydistutils.cfg bugs - # when they happen. - nohome = '' - cmd = ('%s setup.py %s clean --all' - ' build --build-base="%s"' - ' install --force --prefix="%s" --install-lib="%s"' - ' --install-scripts="%s" %s >%s 2>&1' - % (sys.executable, pure, os.path.join(HGTMP, "build"), - INST, PYTHONDIR, BINDIR, nohome, installerrs)) - vlog("# Running", cmd) - if os.system(cmd) == 0: - if not options.verbose: - os.remove(installerrs) - else: - f = open(installerrs) - for line in f: - print line, - f.close() - sys.exit(1) - os.chdir(TESTDIR) - - usecorrectpython() - - vlog("# Installing dummy diffstat") - f = open(os.path.join(BINDIR, 'diffstat'), 'w') - f.write('#!' + sys.executable + '\n' - 'import sys\n' - 'files = 0\n' - 'for line in sys.stdin:\n' - ' if line.startswith("diff "):\n' - ' files += 1\n' - 'sys.stdout.write("files patched: %d\\n" % files)\n') - f.close() - os.chmod(os.path.join(BINDIR, 'diffstat'), 0700) - - if options.py3k_warnings and not options.anycoverage: - vlog("# Updating hg command to enable Py3k Warnings switch") - f = open(os.path.join(BINDIR, 'hg'), 'r') - lines = [line.rstrip() for line in f] - lines[0] += ' -3' - f.close() - f = open(os.path.join(BINDIR, 'hg'), 'w') - for line in lines: - f.write(line + '\n') - f.close() - - if options.anycoverage: - custom = os.path.join(TESTDIR, 'sitecustomize.py') - target = os.path.join(PYTHONDIR, 'sitecustomize.py') - vlog('# Installing coverage trigger to %s' % target) - shutil.copyfile(custom, target) - rc = os.path.join(TESTDIR, '.coveragerc') - vlog('# Installing coverage rc to %s' % rc) - os.environ['COVERAGE_PROCESS_START'] = rc - fn = os.path.join(INST, '..', '.coverage') - os.environ['COVERAGE_FILE'] = fn - -def outputcoverage(options): - - vlog('# Producing coverage report') - os.chdir(PYTHONDIR) - - def covrun(*args): - cmd = 'coverage %s' % ' '.join(args) - vlog('# Running: %s' % cmd) - os.system(cmd) - - if options.child: - return - - covrun('-c') - omit = ','.join([BINDIR, TESTDIR]) - covrun('-i', '-r', '"--omit=%s"' % omit) # report - if options.annotate: - adir = os.path.join(TESTDIR, 'annotated') - if not os.path.isdir(adir): - os.mkdir(adir) - covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit) - -class Timeout(Exception): - pass - -def alarmed(signum, frame): - raise Timeout - -def pytest(test, options, replacements): - py3kswitch = options.py3k_warnings and ' -3' or '' - cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test) - vlog("# Running", cmd) - return run(cmd, options, replacements) - -def shtest(test, options, replacements): - cmd = '"%s"' % test - vlog("# Running", cmd) - return run(cmd, options, replacements) - -needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search -escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub -escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256)) -escapemap.update({'\\': '\\\\', '\r': r'\r'}) -def escapef(m): - return escapemap[m.group(0)] -def stringescape(s): - return escapesub(escapef, s) - -def tsttest(test, options, replacements): - t = open(test) - out = [] - script = [] - salt = "SALT" + str(time.time()) - - pos = prepos = -1 - after = {} - expected = {} - for n, l in enumerate(t): - if not l.endswith('\n'): - l += '\n' - if l.startswith(' $ '): # commands - after.setdefault(pos, []).append(l) - prepos = pos - pos = n - script.append('echo %s %s $?\n' % (salt, n)) - script.append(l[4:]) - elif l.startswith(' > '): # continuations - after.setdefault(prepos, []).append(l) - script.append(l[4:]) - elif l.startswith(' '): # results - # queue up a list of expected results - expected.setdefault(pos, []).append(l[2:]) - else: - # non-command/result - queue up for merged output - after.setdefault(pos, []).append(l) - - t.close() - - script.append('echo %s %s $?\n' % (salt, n + 1)) - - fd, name = tempfile.mkstemp(suffix='hg-tst') - - try: - for l in script: - os.write(fd, l) - os.close(fd) - - cmd = '/bin/sh "%s"' % name - vlog("# Running", cmd) - exitcode, output = run(cmd, options, replacements) - # do not merge output if skipped, return hghave message instead - # similarly, with --debug, output is None - if exitcode == SKIPPED_STATUS or output is None: - return exitcode, output - finally: - os.remove(name) - - def rematch(el, l): - try: - # ensure that the regex matches to the end of the string - return re.match(el + r'\Z', l) - except re.error: - # el is an invalid regex - return False - - def globmatch(el, l): - # The only supported special characters are * and ?. Escaping is - # supported. - i, n = 0, len(el) - res = '' - while i < n: - c = el[i] - i += 1 - if c == '\\' and el[i] in '*?\\': - res += el[i - 1:i + 1] - i += 1 - elif c == '*': - res += '.*' - elif c == '?': - res += '.' - else: - res += re.escape(c) - return rematch(res, l) - - pos = -1 - postout = [] - ret = 0 - for n, l in enumerate(output): - lout, lcmd = l, None - if salt in l: - lout, lcmd = l.split(salt, 1) - - if lout: - if lcmd: - lout += ' (no-eol)\n' - - el = None - if pos in expected and expected[pos]: - el = expected[pos].pop(0) - - if el == lout: # perfect match (fast) - postout.append(" " + lout) - elif (el and - (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or - el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout) - or el.endswith(" (esc)\n") and - el.decode('string-escape') == l)): - postout.append(" " + el) # fallback regex/glob/esc match - else: - if needescape(lout): - lout = stringescape(lout.rstrip('\n')) + " (esc)\n" - postout.append(" " + lout) # let diff deal with it - - if lcmd: - # add on last return code - ret = int(lcmd.split()[1]) - if ret != 0: - postout.append(" [%s]\n" % ret) - if pos in after: - postout += after.pop(pos) - pos = int(lcmd.split()[0]) - - if pos in after: - postout += after.pop(pos) - - return exitcode, postout - -wifexited = getattr(os, "WIFEXITED", lambda x: False) -def run(cmd, options, replacements): - """Run command in a sub-process, capturing the output (stdout and stderr). - Return a tuple (exitcode, output). output is None in debug mode.""" - # TODO: Use subprocess.Popen if we're running on Python 2.4 - if options.debug: - proc = subprocess.Popen(cmd, shell=True) - ret = proc.wait() - return (ret, None) - - if os.name == 'nt' or sys.platform.startswith('java'): - tochild, fromchild = os.popen4(cmd) - tochild.close() - output = fromchild.read() - ret = fromchild.close() - if ret is None: - ret = 0 - else: - proc = Popen4(cmd) - def cleanup(): - os.kill(proc.pid, signal.SIGTERM) - ret = proc.wait() - if ret == 0: - ret = signal.SIGTERM << 8 - killdaemons() - return ret - - try: - output = '' - proc.tochild.close() - output = proc.fromchild.read() - ret = proc.wait() - if wifexited(ret): - ret = os.WEXITSTATUS(ret) - except Timeout: - vlog('# Process %d timed out - killing it' % proc.pid) - ret = cleanup() - output += ("\n### Abort: timeout after %d seconds.\n" - % options.timeout) - except KeyboardInterrupt: - vlog('# Handling keyboard interrupt') - cleanup() - raise - - for s, r in replacements: - output = re.sub(s, r, output) - return ret, splitnewlines(output) - -def runone(options, test, skips, fails): - '''tristate output: - None -> skipped - True -> passed - False -> failed''' - - def skip(msg): - if not options.verbose: - skips.append((test, msg)) - else: - print "\nSkipping %s: %s" % (testpath, msg) - return None - - def fail(msg): - fails.append((test, msg)) - if not options.nodiff: - print "\nERROR: %s %s" % (testpath, msg) - return None - - vlog("# Test", test) - - # create a fresh hgrc - hgrc = open(HGRCPATH, 'w+') - hgrc.write('[ui]\n') - hgrc.write('slash = True\n') - hgrc.write('[defaults]\n') - hgrc.write('backout = -d "0 0"\n') - hgrc.write('commit = -d "0 0"\n') - hgrc.write('tag = -d "0 0"\n') - if options.inotify: - hgrc.write('[extensions]\n') - hgrc.write('inotify=\n') - hgrc.write('[inotify]\n') - hgrc.write('pidfile=%s\n' % DAEMON_PIDS) - hgrc.write('appendpid=True\n') - hgrc.close() - - testpath = os.path.join(TESTDIR, test) - ref = os.path.join(TESTDIR, test+".out") - err = os.path.join(TESTDIR, test+".err") - if os.path.exists(err): - os.remove(err) # Remove any previous output files - try: - tf = open(testpath) - firstline = tf.readline().rstrip() - tf.close() - except: - firstline = '' - lctest = test.lower() - - if lctest.endswith('.py') or firstline == '#!/usr/bin/env python': - runner = pytest - elif lctest.endswith('.t'): - runner = tsttest - ref = testpath - else: - # do not try to run non-executable programs - if not os.access(testpath, os.X_OK): - return skip("not executable") - runner = shtest - - # Make a tmp subdirectory to work in - testtmp = os.environ["TESTTMP"] = os.path.join(HGTMP, test) - os.mkdir(testtmp) - os.chdir(testtmp) - - if options.timeout > 0: - signal.alarm(options.timeout) - - ret, out = runner(testpath, options, [ - (re.escape(testtmp), '$TESTTMP'), - (r':%s\b' % options.port, ':$HGPORT'), - (r':%s\b' % (options.port + 1), ':$HGPORT1'), - (r':%s\b' % (options.port + 2), ':$HGPORT2'), - ]) - vlog("# Ret was:", ret) - - if options.timeout > 0: - signal.alarm(0) - - mark = '.' - - skipped = (ret == SKIPPED_STATUS) - - # If we're not in --debug mode and reference output file exists, - # check test output against it. - if options.debug: - refout = None # to match "out is None" - elif os.path.exists(ref): - f = open(ref, "r") - refout = splitnewlines(f.read()) - f.close() - else: - refout = [] - - if (ret != 0 or out != refout) and not skipped and not options.debug: - # Save errors to a file for diagnosis - f = open(err, "wb") - for line in out: - f.write(line) - f.close() - - if skipped: - mark = 's' - if out is None: # debug mode: nothing to parse - missing = ['unknown'] - failed = None - else: - missing, failed = parsehghaveoutput(out) - if not missing: - missing = ['irrelevant'] - if failed: - fail("hghave failed checking for %s" % failed[-1]) - skipped = False - else: - skip(missing[-1]) - elif out != refout: - mark = '!' - if ret: - fail("output changed and returned error code %d" % ret) - else: - fail("output changed") - if not options.nodiff: - if options.view: - os.system("%s %s %s" % (options.view, ref, err)) - else: - showdiff(refout, out, ref, err) - ret = 1 - elif ret: - mark = '!' - fail("returned error code %d" % ret) - - if not options.verbose: - try: - sys.stdout.write(mark) - sys.stdout.flush() - except IOError, ex: - print >>sys.stderr, 'BORKEN PIPE', ex.errno - pass - - killdaemons() - - os.chdir(TESTDIR) - if not options.keep_tmpdir: - shutil.rmtree(testtmp, True) - if skipped: - return None - return ret == 0 - -_hgpath = None - -def _gethgpath(): - """Return the path to the mercurial package that is actually found by - the current Python interpreter.""" - global _hgpath - if _hgpath is not None: - return _hgpath - - cmd = '%s -c "import mercurial; print mercurial.__path__[0]"' - pipe = os.popen(cmd % PYTHON) - try: - _hgpath = pipe.read().strip() - finally: - pipe.close() - return _hgpath - -def _checkhglib(verb): - """Ensure that the 'mercurial' package imported by python is - the one we expect it to be. If not, print a warning to stderr.""" - expecthg = os.path.join(PYTHONDIR, 'mercurial') - actualhg = _gethgpath() - if actualhg != expecthg: - sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' - ' (expected %s)\n' - % (verb, actualhg, expecthg)) - -def runchildren(options, tests): - if INST: - installhg(options) - _checkhglib("Testing") - - optcopy = dict(options.__dict__) - optcopy['jobs'] = 1 - del optcopy['blacklist'] - if optcopy['with_hg'] is None: - optcopy['with_hg'] = os.path.join(BINDIR, "hg") - optcopy.pop('anycoverage', None) - - opts = [] - for opt, value in optcopy.iteritems(): - name = '--' + opt.replace('_', '-') - if value is True: - opts.append(name) - elif value is not None: - opts.append(name + '=' + str(value)) - - tests.reverse() - jobs = [[] for j in xrange(options.jobs)] - while tests: - for job in jobs: - if not tests: - break - job.append(tests.pop()) - fps = {} - - for j, job in enumerate(jobs): - if not job: - continue - rfd, wfd = os.pipe() - childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)] - childtmp = os.path.join(HGTMP, 'child%d' % j) - childopts += ['--tmpdir', childtmp] - cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job - vlog(' '.join(cmdline)) - fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r') - os.close(wfd) - signal.signal(signal.SIGINT, signal.SIG_IGN) - failures = 0 - tested, skipped, failed = 0, 0, 0 - skips = [] - fails = [] - while fps: - pid, status = os.wait() - fp = fps.pop(pid) - l = fp.read().splitlines() - try: - test, skip, fail = map(int, l[:3]) - except ValueError: - test, skip, fail = 0, 0, 0 - split = -fail or len(l) - for s in l[3:split]: - skips.append(s.split(" ", 1)) - for s in l[split:]: - fails.append(s.split(" ", 1)) - tested += test - skipped += skip - failed += fail - vlog('pid %d exited, status %d' % (pid, status)) - failures |= status - print - if not options.noskips: - for s in skips: - print "Skipped %s: %s" % (s[0], s[1]) - for s in fails: - print "Failed %s: %s" % (s[0], s[1]) - - _checkhglib("Tested") - print "# Ran %d tests, %d skipped, %d failed." % ( - tested, skipped, failed) - - if options.anycoverage: - outputcoverage(options) - sys.exit(failures != 0) - -def runtests(options, tests): - global DAEMON_PIDS, HGRCPATH - DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') - HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') - - try: - if INST: - installhg(options) - _checkhglib("Testing") - - if options.timeout > 0: - try: - signal.signal(signal.SIGALRM, alarmed) - vlog('# Running each test with %d second timeout' % - options.timeout) - except AttributeError: - print 'WARNING: cannot run tests with timeouts' - options.timeout = 0 - - tested = 0 - failed = 0 - skipped = 0 - - if options.restart: - orig = list(tests) - while tests: - if os.path.exists(tests[0] + ".err"): - break - tests.pop(0) - if not tests: - print "running all tests" - tests = orig - - skips = [] - fails = [] - - for test in tests: - if options.blacklist: - filename = options.blacklist.get(test) - if filename is not None: - skips.append((test, "blacklisted (%s)" % filename)) - skipped += 1 - continue - - if options.retest and not os.path.exists(test + ".err"): - skipped += 1 - continue - - if options.keywords: - fp = open(test) - t = fp.read().lower() + test.lower() - fp.close() - for k in options.keywords.lower().split(): - if k in t: - break - else: - skipped += 1 - continue - - ret = runone(options, test, skips, fails) - if ret is None: - skipped += 1 - elif not ret: - if options.interactive: - print "Accept this change? [n] ", - answer = sys.stdin.readline().strip() - if answer.lower() in "y yes".split(): - if test.endswith(".t"): - rename(test + ".err", test) - else: - rename(test + ".err", test + ".out") - tested += 1 - fails.pop() - continue - failed += 1 - if options.first: - break - tested += 1 - - if options.child: - fp = os.fdopen(options.child, 'w') - fp.write('%d\n%d\n%d\n' % (tested, skipped, failed)) - for s in skips: - fp.write("%s %s\n" % s) - for s in fails: - fp.write("%s %s\n" % s) - fp.close() - else: - print - for s in skips: - print "Skipped %s: %s" % s - for s in fails: - print "Failed %s: %s" % s - _checkhglib("Tested") - print "# Ran %d tests, %d skipped, %d failed." % ( - tested, skipped, failed) - - if options.anycoverage: - outputcoverage(options) - except KeyboardInterrupt: - failed = True - print "\ninterrupted!" - - if failed: - sys.exit(1) - -def main(): - (options, args) = parseargs() - if not options.child: - os.umask(022) - - checktools() - - if len(args) == 0: - args = os.listdir(".") - args.sort() - - tests = [] - skipped = [] - for test in args: - if (test.startswith("test-") and '~' not in test and - ('.' not in test or test.endswith('.py') or - test.endswith('.bat') or test.endswith('.t'))): - if not os.path.exists(test): - skipped.append(test) - else: - tests.append(test) - if not tests: - for test in skipped: - print 'Skipped %s: does not exist' % test - print "# Ran 0 tests, %d skipped, 0 failed." % len(skipped) - return - tests = tests + skipped - - # Reset some environment variables to well-known values so that - # the tests produce repeatable output. - os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C' - os.environ['TZ'] = 'GMT' - os.environ["EMAIL"] = "Foo Bar " - os.environ['CDPATH'] = '' - os.environ['COLUMNS'] = '80' - os.environ['GREP_OPTIONS'] = '' - os.environ['http_proxy'] = '' - - # unset env related to hooks - for k in os.environ.keys(): - if k.startswith('HG_'): - # can't remove on solaris - os.environ[k] = '' - del os.environ[k] - - global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE - TESTDIR = os.environ["TESTDIR"] = os.getcwd() - if options.tmpdir: - options.keep_tmpdir = True - tmpdir = options.tmpdir - if os.path.exists(tmpdir): - # Meaning of tmpdir has changed since 1.3: we used to create - # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if - # tmpdir already exists. - sys.exit("error: temp dir %r already exists" % tmpdir) - - # Automatically removing tmpdir sounds convenient, but could - # really annoy anyone in the habit of using "--tmpdir=/tmp" - # or "--tmpdir=$HOME". - #vlog("# Removing temp dir", tmpdir) - #shutil.rmtree(tmpdir) - os.makedirs(tmpdir) - else: - tmpdir = tempfile.mkdtemp('', 'hgtests.') - HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir) - DAEMON_PIDS = None - HGRCPATH = None - - os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"' - os.environ["HGMERGE"] = "internal:merge" - os.environ["HGUSER"] = "test" - os.environ["HGENCODING"] = "ascii" - os.environ["HGENCODINGMODE"] = "strict" - os.environ["HGPORT"] = str(options.port) - os.environ["HGPORT1"] = str(options.port + 1) - os.environ["HGPORT2"] = str(options.port + 2) - - if options.with_hg: - INST = None - BINDIR = os.path.dirname(os.path.realpath(options.with_hg)) - - # This looks redundant with how Python initializes sys.path from - # the location of the script being executed. Needed because the - # "hg" specified by --with-hg is not the only Python script - # executed in the test suite that needs to import 'mercurial' - # ... which means it's not really redundant at all. - PYTHONDIR = BINDIR - else: - INST = os.path.join(HGTMP, "install") - BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin") - PYTHONDIR = os.path.join(INST, "lib", "python") - - os.environ["BINDIR"] = BINDIR - os.environ["PYTHON"] = PYTHON - - if not options.child: - path = [BINDIR] + os.environ["PATH"].split(os.pathsep) - os.environ["PATH"] = os.pathsep.join(path) - - # Include TESTDIR in PYTHONPATH so that out-of-tree extensions - # can run .../tests/run-tests.py test-foo where test-foo - # adds an extension to HGRC - pypath = [PYTHONDIR, TESTDIR] - # We have to augment PYTHONPATH, rather than simply replacing - # it, in case external libraries are only available via current - # PYTHONPATH. (In particular, the Subversion bindings on OS X - # are in /opt/subversion.) - oldpypath = os.environ.get(IMPL_PATH) - if oldpypath: - pypath.append(oldpypath) - os.environ[IMPL_PATH] = os.pathsep.join(pypath) - - COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") - - vlog("# Using TESTDIR", TESTDIR) - vlog("# Using HGTMP", HGTMP) - vlog("# Using PATH", os.environ["PATH"]) - vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH]) - - try: - if len(tests) > 1 and options.jobs > 1: - runchildren(options, tests) - else: - runtests(options, tests) - finally: - time.sleep(1) - cleanup(options) - -if __name__ == '__main__': - main() diff -r dcfe3afe548b -r 0c8548df67fe tests/test-divergent.t --- a/tests/test-divergent.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-divergent.t Tue Dec 22 14:11:09 2015 +0000 @@ -67,4 +67,45 @@ | o 0:135f39f4bd78@default(draft) add _a [] +Test divergence resolution when it yields to an empty commit (issue4950) +cdivergent2 contains the same content than cdivergent1 and they are divergent +versions of the revision _c + + $ hg up "desc(_a)" + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ mkcommit _c + created new head + $ hg up "desc(_a)" + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ mkcommit cdivergent1 + created new head + $ hg up "desc(_a)" + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo "cdivergent1" > cdivergent1 + $ hg add cdivergent1 + $ hg ci -m "cdivergent2" + created new head + $ hg prune -s "desc(cdivergent1)" "desc(_c)" + 1 changesets pruned + $ hg prune -s "desc(cdivergent2)" "desc(_c)" --hidden + 1 changesets pruned + 2 new divergent changesets + $ hg log -G + @ 8:0a768ef678d9@default(draft) cdivergent2 [divergent] + | + | o 7:26c7705fee96@default(draft) add cdivergent1 [divergent] + |/ + | o 5:c26f1d3baed2@default(draft) add bdivergent1 [] + |/ + o 0:135f39f4bd78@default(draft) add _a [] + + $ hg evolve --all --any --divergent + merge:[7] add cdivergent1 + with: [8] cdivergent2 + base: [6] add _c + updating to "local" conflict + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory is now at 6602ff5a79dc + $ cd .. diff -r dcfe3afe548b -r 0c8548df67fe tests/test-exchange-A3.t --- a/tests/test-exchange-A3.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-exchange-A3.t Tue Dec 22 14:11:09 2015 +0000 @@ -204,7 +204,7 @@ adding changesets adding manifests adding file changes - added 1 changesets with 1 changes to 2 files (+1 heads) + added 1 changesets with 1 changes to 1 files (+1 heads) 1 new obsolescence markers (run 'hg heads' to see heads, 'hg merge' to merge) 1 new unstable changesets diff -r dcfe3afe548b -r 0c8548df67fe tests/test-inhibit.t --- a/tests/test-inhibit.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-inhibit.t Tue Dec 22 14:11:09 2015 +0000 @@ -305,6 +305,16 @@ | o 0:54ccbc537fc2 add cA +Test edge cases of bookmark -D + $ hg book -D book2 -m hello + abort: Cannot use both -m and -D + [255] + + $ hg book -Draster-fix + abort: Error, please check your command + (make sure to put a space between -D and your bookmark name) + [255] + Test that direct access make changesets visible $ hg export 2db36d8066ff 02bcbc3f6e56 @@ -437,6 +447,7 @@ |/ o 0:54ccbc537fc2 add cA + Check that amending in the middle of a stack does not show obsolete revs Since we are doing operation in the middle of the stack we cannot just have createmarkers as we are creating instability @@ -525,8 +536,14 @@ |/ o 14:d66ccb8c5871 add cL | - $ hg strip -r 104eed5354c7 - 1 changesets pruned + $ hg strip -r 210589181b14 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + working directory now at d66ccb8c5871 + 2 changesets pruned + +Using a hash prefix solely made of digits should work + $ hg update 210589181 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg rebase -d 18 -r 16 --keep rebasing 16:a438c045eb37 "add cN" $ hg log -r 14:: -G @@ -651,7 +668,7 @@ adding manifests adding file changes added 2 changesets with 1 changes to 2 files (+1 heads) - (run 'hg heads' to see heads, 'hg merge' to merge) + (run 'hg heads .' to see heads, 'hg merge' to merge) Only allow direct access and check that evolve works like before (also disable evolve commands to avoid hint about using evolve) @@ -700,6 +717,17 @@ nothing changed [1] +Check that the behavior of rebase with obsolescence markers is maintained +despite inhibit + + $ hg up a438c045eb37 + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg rebase -r 15:: -d 21 --config experimental.rebaseskipobsolete=True + note: not rebasing 15:2d66e189f5b5 "add cM", already in destination as 21:721c3c279519 "add cM" + rebasing 16:a438c045eb37 "add cN" + $ hg up 21 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + Directaccess should load after some extensions precised in the conf With no extension specified: @@ -714,7 +742,7 @@ > EOF $ hg id ['rebase', 'strip', 'evolve', 'directaccess', 'inhibit', 'testextension'] - 721c3c279519 tip + 721c3c279519 With test_extension specified: $ cat >> $HGRCPATH << EOF @@ -723,7 +751,7 @@ > EOF $ hg id ['rebase', 'strip', 'evolve', 'inhibit', 'testextension', 'directaccess'] - 721c3c279519 tip + 721c3c279519 Inhibit should not work without directaccess $ cat >> $HGRCPATH <> $HGRCPATH $ cd .. - hg push should not allow directaccess unless forced with --hidden We copy the inhibhit repo to inhibit2 and make some changes to push to inhibit @@ -746,6 +773,7 @@ $ pwd=$(pwd) $ cd inhibit $ mkcommit pk + created new head $ hg id 003a4735afde tip $ echo "OO" > pk @@ -767,7 +795,7 @@ adding changesets adding manifests adding file changes - added 1 changesets with 1 changes to 1 files + added 1 changesets with 1 changes to 1 files (+1 heads) 2 new obsolescence markers Pulling from a inhibit repo to a non-inhibit repo should work diff -r dcfe3afe548b -r 0c8548df67fe tests/test-prune.t --- a/tests/test-prune.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-prune.t Tue Dec 22 14:11:09 2015 +0000 @@ -279,11 +279,15 @@ $ cd .. $ hg init bookmarks $ cd bookmarks - $ hg debugbuilddag '..<2.*1/2:m<2+3:c> file1 $ hg commit -u bob -m 'fix bug 24 (v1)' $ hg shortlog -r . - 4:2fe6c4bd32d0 draft fix bug 24 (v1) + 4:6d407e4bc7f4 draft fix bug 24 (v1) Since Alice and Bob are now in cowboy mode, Alice pulls Bob's draft changeset and amends it herself. :: @@ -454,13 +454,13 @@ $ echo 'better fix (bob)' >> file1 $ hg amend -u bob -m 'fix bug 24 (v2 by bob)' $ hg --hidden shortlog -G -r 3:: - @ 6:a360947f6faf draft fix bug 24 (v2 by bob) + @ 6:059a6be5b63a draft fix bug 24 (v2 by bob) | - | x 5:3466c7f5a149 draft temporary amend commit for 2fe6c4bd32d0 + | x 5:a2493482aafe draft temporary amend commit for 6d407e4bc7f4 | | - | x 4:2fe6c4bd32d0 draft fix bug 24 (v1) + | x 4:6d407e4bc7f4 draft fix bug 24 (v1) |/ - o 3:a06ec1bf97bd public fix bug 15 (v2) + o 3:754cbfea9b13 public fix bug 15 (v2) | Bob discovers the divergence. @@ -477,19 +477,19 @@ Figure SG09: multiple heads! divergence! oh my! $ hg --hidden shortlog -G -r 3:: - o 7:e3f99ce9d9cd draft fix bug 24 (v2 by alice) + o 7:cdcc922d8d15 draft fix bug 24 (v2 by alice) | - | @ 6:a360947f6faf draft fix bug 24 (v2 by bob) + | @ 6:059a6be5b63a draft fix bug 24 (v2 by bob) |/ - | x 5:3466c7f5a149 draft temporary amend commit for 2fe6c4bd32d0 + | x 5:a2493482aafe draft temporary amend commit for 6d407e4bc7f4 | | - | x 4:2fe6c4bd32d0 draft fix bug 24 (v1) + | x 4:6d407e4bc7f4 draft fix bug 24 (v1) |/ - o 3:a06ec1bf97bd public fix bug 15 (v2) + o 3:754cbfea9b13 public fix bug 15 (v2) | - $ hg --hidden shortlog -r 'successors(2fe6)' - 6:a360947f6faf draft fix bug 24 (v2 by bob) - 7:e3f99ce9d9cd draft fix bug 24 (v2 by alice) + $ hg --hidden shortlog -r 'successors(6d407)' + 6:059a6be5b63a draft fix bug 24 (v2 by bob) + 7:cdcc922d8d15 draft fix bug 24 (v2 by alice) Use evolve to fix the divergence. $ HGMERGE=internal:other hg evolve --divergent @@ -497,28 +497,28 @@ with: [7] fix bug 24 (v2 by alice) base: [4] fix bug 24 (v1) 0 files updated, 1 files merged, 0 files removed, 0 files unresolved - working directory is now at 5ad6037c046c + working directory is now at 70e807c6e67f $ hg log -q -r 'divergent()' Figure SG10: Bob's repository after fixing divergence. $ hg --hidden shortlog -G -r 3:: - @ 9:5ad6037c046c draft fix bug 24 (v2 by bob) + @ 9:70e807c6e67f draft fix bug 24 (v2 by bob) | - | x 8:bcfc9a755ac3 draft temporary amend commit for a360947f6faf + | x 8:d8d2e15ab73d draft temporary amend commit for 059a6be5b63a | | - +---x 7:e3f99ce9d9cd draft fix bug 24 (v2 by alice) + +---x 7:cdcc922d8d15 draft fix bug 24 (v2 by alice) | | - | x 6:a360947f6faf draft fix bug 24 (v2 by bob) + | x 6:059a6be5b63a draft fix bug 24 (v2 by bob) |/ - | x 5:3466c7f5a149 draft temporary amend commit for 2fe6c4bd32d0 + | x 5:a2493482aafe draft temporary amend commit for 6d407e4bc7f4 | | - | x 4:2fe6c4bd32d0 draft fix bug 24 (v1) + | x 4:6d407e4bc7f4 draft fix bug 24 (v1) |/ - o 3:a06ec1bf97bd public fix bug 15 (v2) + o 3:754cbfea9b13 public fix bug 15 (v2) | $ hg --hidden shortlog -r 'precursors(9)' - 6:a360947f6faf draft fix bug 24 (v2 by bob) - 7:e3f99ce9d9cd draft fix bug 24 (v2 by alice) + 6:059a6be5b63a draft fix bug 24 (v2 by bob) + 7:cdcc922d8d15 draft fix bug 24 (v2 by alice) $ cat file1 Do stuff. pretty good fix diff -r dcfe3afe548b -r 0c8548df67fe tests/test-simple4server-bundle2.t --- a/tests/test-simple4server-bundle2.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-simple4server-bundle2.t Tue Dec 22 14:11:09 2015 +0000 @@ -140,7 +140,7 @@ $ echo '[__temporary__]' >> server/.hg/hgrc $ echo 'advertiseobsolete=False' >> server/.hg/hgrc - $ $TESTDIR/killdaemons.py $DAEMON_PIDS + $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS @@ -150,7 +150,7 @@ phases $ echo 'advertiseobsolete=True' >> server/.hg/hgrc - $ $TESTDIR/killdaemons.py $DAEMON_PIDS + $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS diff -r dcfe3afe548b -r 0c8548df67fe tests/test-simple4server.t --- a/tests/test-simple4server.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-simple4server.t Tue Dec 22 14:11:09 2015 +0000 @@ -143,7 +143,7 @@ $ echo '[__temporary__]' >> server/.hg/hgrc $ echo 'advertiseobsolete=False' >> server/.hg/hgrc - $ $TESTDIR/killdaemons.py $DAEMON_PIDS + $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS @@ -157,7 +157,7 @@ [1] $ echo 'advertiseobsolete=True' >> server/.hg/hgrc - $ $TESTDIR/killdaemons.py $DAEMON_PIDS + $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log $ cat hg.pid >> $DAEMON_PIDS diff -r dcfe3afe548b -r 0c8548df67fe tests/test-stabilize-conflict.t --- a/tests/test-stabilize-conflict.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-stabilize-conflict.t Tue Dec 22 14:11:09 2015 +0000 @@ -169,7 +169,7 @@ grafting 5:71c18f70c34f "babar count up to fifteen" $ hg resolve -l $ hg log -G - @ changeset: 8:1836b91c6c1d + @ changeset: 8:7f3d9db50b5d | tag: tip | user: test | date: Thu Jan 01 00:00:00 1970 +0000 @@ -232,7 +232,7 @@ | date: Thu Jan 01 00:00:00 1970 +0000 | summary: babar count up to ten | - | o changeset: 8:1836b91c6c1d + | o changeset: 8:7f3d9db50b5d | | user: test | | date: Thu Jan 01 00:00:00 1970 +0000 | | summary: babar count up to fifteen diff -r dcfe3afe548b -r 0c8548df67fe tests/test-tutorial.t --- a/tests/test-tutorial.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-tutorial.t Tue Dec 22 14:11:09 2015 +0000 @@ -251,7 +251,7 @@ My local work is now rebased on the remote one. $ hg log -G - @ 41aff6a42b75 (draft): adding fruit + @ f49d36e87e8a (draft): adding fruit | o dfd3a2d7691e (draft): adding condiment | @@ -273,9 +273,9 @@ > EOF $ hg ci -m 'transport' $ hg log -G - @ 1125e39fbf21 (draft): transport + @ 207804be803b (draft): transport | - o 41aff6a42b75 (draft): adding fruit + o f49d36e87e8a (draft): adding fruit | o dfd3a2d7691e (draft): adding condiment | @@ -289,13 +289,13 @@ $ hg prune . # "." is for working directory parent 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - working directory now at 41aff6a42b75 + working directory now at f49d36e87e8a 1 changesets pruned The silly changeset is gone. $ hg log -G - @ 41aff6a42b75 (draft): adding fruit + @ f49d36e87e8a (draft): adding fruit | o dfd3a2d7691e (draft): adding condiment | @@ -323,11 +323,11 @@ $ sed -i'' -e 's/Spam/Spam Spam Spam/g' shopping $ hg ci -m 'SPAM SPAM' $ hg log -G - @ fac207dec9f5 (draft): SPAM SPAM + @ 0a0104b5de2f (draft): SPAM SPAM | - o 10b8aeaa8cc8 (draft): bathroom stuff + o 0dbf8779b56f (draft): bathroom stuff | - o 41aff6a42b75 (draft): adding fruit + o f49d36e87e8a (draft): adding fruit | o dfd3a2d7691e (draft): adding condiment | @@ -346,18 +346,18 @@ .. note: grab is an alias for `hg rebase --dest . --rev ; hg up ` - $ hg up 'p1(10b8aeaa8cc8)' # going on "bathroom stuff" parent + $ hg up 'p1(0dbf8779b56f)' # going on "bathroom stuff" parent 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg grab fac207dec9f5 # moving "SPAM SPAM" to the working directory parent - rebasing 10:fac207dec9f5 "SPAM SPAM" (tip) + $ hg grab 0a0104b5de2f # moving "SPAM SPAM" to the working directory parent + rebasing 10:0a0104b5de2f "SPAM SPAM" (tip) merging shopping ? files updated, 0 files merged, 0 files removed, 0 files unresolved (glob) $ hg log -G - @ a224f2a4fb9f (draft): SPAM SPAM + @ 21ddb8bb7ace (draft): SPAM SPAM | - | o 10b8aeaa8cc8 (draft): bathroom stuff + | o 0dbf8779b56f (draft): bathroom stuff |/ - o 41aff6a42b75 (draft): adding fruit + o f49d36e87e8a (draft): adding fruit | o dfd3a2d7691e (draft): adding condiment | @@ -377,8 +377,8 @@ # User test # Date 0 0 # Thu Jan 01 00:00:00 1970 +0000 - # Node ID a224f2a4fb9f9f828f608959912229d7b38b26de - # Parent 41aff6a42b7578ec7ec3cb2041633f1ca43cca96 + # Node ID 21ddb8bb7acea9d1c46bf8a3965590c595aecb99 + # Parent f49d36e87e8ac78cba19b4aa94421cf13c18bcb5 SPAM SPAM diff --git a/shopping b/shopping @@ -394,7 +394,7 @@ To make sure I do not push unready changeset by mistake I set the "bathroom stuff" changeset in the secret phase. - $ hg phase --force --secret 10b8aeaa8cc8 + $ hg phase --force --secret 0dbf8779b56f we can now push our change: @@ -409,17 +409,17 @@ for simplicity sake we get the bathroom change in line again - $ hg grab 10b8aeaa8cc8 - rebasing 9:10b8aeaa8cc8 "bathroom stuff" + $ hg grab 0dbf8779b56f + rebasing 9:0dbf8779b56f "bathroom stuff" merging shopping ? files updated, 0 files merged, 0 files removed, 0 files unresolved (glob) $ hg phase --draft . $ hg log -G - @ 75954b8cd933 (draft): bathroom stuff + @ 1820dfc355a0 (draft): bathroom stuff | - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | @@ -528,11 +528,11 @@ 1 new obsolescence markers (run 'hg update' to get a working copy) $ hg log -G - o 75954b8cd933 (public): bathroom stuff + o 1820dfc355a0 (public): bathroom stuff | - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | @@ -550,9 +550,9 @@ $ hg rollback repository tip rolled back to revision 4 (undo pull) $ hg log -G - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | @@ -585,11 +585,11 @@ 1 new obsolescence markers (run 'hg update' to get a working copy) $ hg log -G - o 75954b8cd933 (draft): bathroom stuff + o 1820dfc355a0 (draft): bathroom stuff | - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | @@ -603,7 +603,7 @@ Remotely someone add a new changeset on top of the mutable "bathroom" on. - $ hg up 75954b8cd933 -q + $ hg up 1820dfc355a0 -q $ cat >> shopping << EOF > Giraffe > Rhino @@ -615,15 +615,15 @@ But at the same time, locally, this same "bathroom changeset" was updated. $ cd ../local - $ hg up 75954b8cd933 -q + $ hg up 1820dfc355a0 -q $ sed -i'' -e 's/... More bathroom stuff to come/Bath Robe/' shopping $ hg commit --amend $ hg log -G - @ a44c85f957d3 (draft): bathroom stuff + @ adef71cd3160 (draft): bathroom stuff | - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | @@ -650,15 +650,15 @@ see both version showing up in the log. $ hg log -G - o bf1b0d202029 (draft): animals + o f9d416ff3ea5 (draft): animals | - | @ a44c85f957d3 (draft): bathroom stuff + | @ adef71cd3160 (draft): bathroom stuff | | - x | 75954b8cd933 (draft): bathroom stuff + x | 1820dfc355a0 (draft): bathroom stuff |/ - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | @@ -678,7 +678,7 @@ $ hg push other pushing to $TESTTMP/other (glob) searching for changes - abort: push includes unstable changeset: bf1b0d202029! + abort: push includes unstable changeset: f9d416ff3ea5! (use 'hg evolve' to get a stable history or --force to ignore warnings) [255] @@ -693,7 +693,7 @@ $ hg evolve --dry-run move:[15] animals atop:[14] bathroom stuff - hg rebase -r bf1b0d202029 -d a44c85f957d3 + hg rebase -r f9d416ff3ea5 -d adef71cd3160 Let's do it @@ -701,18 +701,18 @@ move:[15] animals atop:[14] bathroom stuff merging shopping - working directory is now at ee942144f952 + working directory is now at 0325a6c93ec2 The old version of bathroom is hidden again. $ hg log -G - @ ee942144f952 (draft): animals + @ 0325a6c93ec2 (draft): animals | - o a44c85f957d3 (draft): bathroom stuff + o adef71cd3160 (draft): bathroom stuff | - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | @@ -746,13 +746,13 @@ now let's see where we are, and update to the successor $ hg parents - bf1b0d202029 (draft): animals + f9d416ff3ea5 (draft): animals working directory parent is obsolete! (use "hg evolve" to update to its successor) $ hg evolve update:[8] animals 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - working directory is now at ee942144f952 + working directory is now at 0325a6c93ec2 Relocating unstable change after prune ---------------------------------------------- @@ -774,15 +774,15 @@ added 1 changesets with 1 changes to 1 files (run 'hg update' to get a working copy) $ hg log -G - o 99f039c5ec9e (draft): SPAM SPAM SPAM + o e8373c1af7dc (draft): SPAM SPAM SPAM | - @ ee942144f952 (draft): animals + @ 0325a6c93ec2 (draft): animals | - o a44c85f957d3 (draft): bathroom stuff + o adef71cd3160 (draft): bathroom stuff | - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | @@ -793,9 +793,9 @@ In the mean time I noticed you can't buy animals in a super market and I prune the animal changeset: - $ hg prune ee942144f952 + $ hg prune 0325a6c93ec2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - working directory now at a44c85f957d3 + working directory now at adef71cd3160 1 changesets pruned 1 new unstable changesets @@ -804,15 +804,15 @@ is neither dead or obsolete. My repository is in an unstable state again. $ hg log -G - o 99f039c5ec9e (draft): SPAM SPAM SPAM + o e8373c1af7dc (draft): SPAM SPAM SPAM | - x ee942144f952 (draft): animals + x 0325a6c93ec2 (draft): animals | - @ a44c85f957d3 (draft): bathroom stuff + @ adef71cd3160 (draft): bathroom stuff | - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | @@ -822,22 +822,22 @@ $ hg log -r 'unstable()' - 99f039c5ec9e (draft): SPAM SPAM SPAM + e8373c1af7dc (draft): SPAM SPAM SPAM $ hg evolve move:[17] SPAM SPAM SPAM atop:[14] bathroom stuff merging shopping - working directory is now at 40aa40daeefb + working directory is now at 5b9223df71d2 $ hg log -G - @ 40aa40daeefb (draft): SPAM SPAM SPAM + @ 5b9223df71d2 (draft): SPAM SPAM SPAM | - o a44c85f957d3 (draft): bathroom stuff + o adef71cd3160 (draft): bathroom stuff | - o a224f2a4fb9f (public): SPAM SPAM + o 21ddb8bb7ace (public): SPAM SPAM | - o 41aff6a42b75 (public): adding fruit + o f49d36e87e8a (public): adding fruit | o dfd3a2d7691e (public): adding condiment | diff -r dcfe3afe548b -r 0c8548df67fe tests/test-wireproto-bundle1.t --- a/tests/test-wireproto-bundle1.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-wireproto-bundle1.t Tue Dec 22 14:11:09 2015 +0000 @@ -3,7 +3,7 @@ > [defaults] > amend=-d "0 0" > [ui] - > ssh=python "$TESTDIR/dummyssh" + > ssh=python "$RUNTESTDIR/dummyssh" > [phases] > publish = False > [extensions] diff -r dcfe3afe548b -r 0c8548df67fe tests/test-wireproto.t --- a/tests/test-wireproto.t Sat Nov 07 13:39:59 2015 -0500 +++ b/tests/test-wireproto.t Tue Dec 22 14:11:09 2015 +0000 @@ -6,7 +6,7 @@ > obsmarkers-exchange-debug=true > bundle2-exp=true > [ui] - > ssh=python "$TESTDIR/dummyssh" + > ssh=python "$RUNTESTDIR/dummyssh" > [phases] > publish = False > [extensions] @@ -144,7 +144,7 @@ adding changesets adding manifests adding file changes - added 1 changesets with 0 changes to 3 files (+1 heads) + added 1 changesets with 0 changes to 1 files (+1 heads) obsmarker-exchange: 208 bytes received 1 new obsolescence markers (run 'hg heads' to see heads, 'hg merge' to merge)