Mercurial > evolve
changeset 2313:65b4a8c40fad stable
merge with default for 6.1.0
author | Pierre-Yves David <pierre-yves.david@ens-lyon.org> |
---|---|
date | Wed, 03 May 2017 12:31:40 +0200 |
parents | 18b8dc058f75 (current diff) b9ce138eca63 (diff) |
children | 2e0a15985657 |
files | |
diffstat | 17 files changed, 1181 insertions(+), 51 deletions(-) [+] |
line wrap: on
line diff
--- a/README Thu Apr 27 20:52:09 2017 +0200 +++ b/README Wed May 03 12:31:40 2017 +0200 @@ -112,10 +112,13 @@ Changelog ========= - -6.0.2 - in progress +6.1.0 - in progress ------------------- + - improve message about obsolete working copy parent, + - improve message issued when accessing hidden nodes (4.2 only), + - introduce a new caches to reduce the impact of evolution on read-only commands, + - add a 'experimental.auto-publish' config. See `hg help -e evolve` for details. - fix the propagation of some some cache invalidation, 6.0.1 -- 2017-04-20
--- a/hgext3rd/evolve/__init__.py Thu Apr 27 20:52:09 2017 +0200 +++ b/hgext3rd/evolve/__init__.py Wed May 03 12:31:40 2017 +0200 @@ -7,15 +7,43 @@ # GNU General Public License version 2 or any later version. """extends Mercurial feature related to Changeset Evolution -This extension provides several commands to mutate history and deal with -resulting issues. - -It also: - - - enables the "Changeset Obsolescence" feature of Mercurial, - - alters core commands and extensions that rewrite history to use - this feature, - - improves some aspect of the early implementation in Mercurial core +This extension: + +- provides several commands to mutate history and deal with resulting issues, +- enable the changeset-evolution feature for Mercurial, +- improves some aspect of the early implementation in Mercurial core, + +Note that a version dedicated to server usage only (no local working copy) is +available as 'evolve.serveronly'. + +While many feature related to changeset evolution are directly handled by core +this extensions contains significant additions recommended to any user of +changeset evolution. + +With the extensions various evolution events will display warning (new unstable +changesets, obsolete working copy parent, improved error when accessing hidden +revision, etc). + +In addition, the extension contains better discovery protocol for obsolescence +markers. This means less obs-markers will have to be pushed and pulled around, +speeding up such operation. + +Some improvement and bug fixes available in newer version of Mercurial are also +backported to older version of Mercurial by this extension. Some older +experimental protocol are also supported for a longer time in the extensions to +help people transitioning. (The extensions is currently compatible down to +Mercurial version 3.8). + +New Config: + + [experimental] + # Set to control the behavior when pushing draft changesets to a publishing + # repository. Possible value: + # * ignore: current core behavior (default) + # * warn: proceed with the push, but issue a warning + # * abort: abort the push + auto-publish = ignore + """ @@ -104,6 +132,7 @@ revset, scmutil, templatekw, + obsolete ) from mercurial.commands import walkopts, commitopts, commitopts2, mergetoolopts @@ -113,9 +142,11 @@ from . import ( checkheads, debugcmd, - obsexchange, exthelper, metadata, + obscache, + obsexchange, + safeguard, utility, ) @@ -147,6 +178,8 @@ eh.merge(debugcmd.eh) eh.merge(obsexchange.eh) eh.merge(checkheads.eh) +eh.merge(safeguard.eh) +eh.merge(obscache.eh) uisetup = eh.final_uisetup extsetup = eh.final_extsetup reposetup = eh.final_reposetup @@ -449,11 +482,102 @@ # This section take care of issue warning to the user when troubles appear +def _getobsoletereason(repo, revnode): + """ Return a tuple containing: + - the reason a revision is obsolete (diverged, pruned or superseed) + - the list of successors short node if the revision is neither pruned + or has diverged + """ + successorssets = obsolete.successorssets(repo, revnode) + + if len(successorssets) == 0: + # The commit has been pruned + return ('pruned', []) + elif len(successorssets) > 1: + return ('diverged', []) + else: + # No divergence, only one set of successors + successors = [node.short(node_id) for node_id in successorssets[0]] + + if len(successors) == 1: + return ('superseed', successors) + else: + return ('superseed_split', successors) + def _warnobsoletewc(ui, repo): - if repo['.'].obsolete(): - ui.warn(_('working directory parent is obsolete!\n')) - if (not ui.quiet) and obsolete.isenabled(repo, commandopt): - ui.warn(_("(use 'hg evolve' to update to its successor)\n")) + rev = repo['.'] + + if not rev.obsolete(): + return + + msg = _("working directory parent is obsolete! (%s)\n") + shortnode = node.short(rev.node()) + + ui.warn(msg % shortnode) + + # Check that evolve is activated for performance reasons + if ui.quiet or not obsolete.isenabled(repo, commandopt): + return + + # Show a warning for helping the user to solve the issue + reason, successors = _getobsoletereason(repo, rev.node()) + + if reason == 'pruned': + solvemsg = _("use 'hg evolve' to update to its parent successor") + elif reason == 'diverged': + debugcommand = "hg evolve -list --divergent" + basemsg = _("%s has diverged, use '%s' to resolve the issue") + solvemsg = basemsg % (shortnode, debugcommand) + elif reason == 'superseed': + msg = _("use 'hg evolve' to update to its successor: %s") + solvemsg = msg % successors[0] + elif reason == 'superseed_split': + msg = _("use 'hg evolve' to update to its tipmost successor: %s") + + if len(successors) <= 2: + solvemsg = msg % ", ".join(successors) + else: + firstsuccessors = ", ".join(successors[:2]) + remainingnumber = len(successors) - 2 + successorsmsg = _("%s and %d more") % (firstsuccessors, remainingnumber) + solvemsg = msg % successorsmsg + else: + raise ValueError(reason) + + ui.warn("(%s)\n" % solvemsg) + +if util.safehasattr(context, '_filterederror'): + # if < hg-4.2 we do not update the message + @eh.wrapfunction(context, '_filterederror') + def evolve_filtererror(original, repo, changeid): + """build an exception to be raised about a filtered changeid + + This is extracted in a function to help extensions (eg: evolve) to + experiment with various message variants.""" + if repo.filtername.startswith('visible'): + + unfilteredrepo = repo.unfiltered() + rev = unfilteredrepo[changeid] + reason, successors = _getobsoletereason(unfilteredrepo, rev.node()) + + # Be more precise in cqse the revision is superseed + if reason == 'superseed': + reason = _("successor: %s") % successors[0] + elif reason == 'superseed_split': + if len(successors) <= 2: + reason = _("successors: %s") % ", ".join(successors) + else: + firstsuccessors = ", ".join(successors[:2]) + remainingnumber = len(successors) - 2 + successorsmsg = _("%s and %d more") % (firstsuccessors, remainingnumber) + reason = _("successors: %s") % successorsmsg + + msg = _("hidden revision '%s'") % changeid + hint = _('use --hidden to access hidden revisions; %s') % reason + return error.FilteredRepoLookupError(msg, hint=hint) + msg = _("filtered revision '%s' (not in '%s' subset)") + msg %= (changeid, repo.filtername) + return error.FilteredRepoLookupError(msg) @eh.wrapcommand("update") @eh.wrapcommand("pull")
--- a/hgext3rd/evolve/metadata.py Thu Apr 27 20:52:09 2017 +0200 +++ b/hgext3rd/evolve/metadata.py Wed May 03 12:31:40 2017 +0200 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -__version__ = '6.0.1' +__version__ = '6.1.0.dev' testedwith = '3.8.4 3.9.2 4.0.2 4.1.1' minimumhgversion = '3.8' buglink = 'https://bz.mercurial-scm.org/'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext3rd/evolve/obscache.py Wed May 03 12:31:40 2017 +0200 @@ -0,0 +1,399 @@ +# Code dedicated to an cache around obsolescence property +# +# This module content aims at being upstreamed. +# +# Copyright 2017 Pierre-Yves David <pierre-yves.david@ens-lyon.org> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +import hashlib +import struct +import weakref +import errno + +from mercurial import ( + localrepo, + obsolete, + phases, + node, +) + +from . import ( + exthelper, +) + +eh = exthelper.exthelper() + +try: + obsstorefilecache = localrepo.localrepository.obsstore +except AttributeError: + # XXX hg-3.8 compat + # + # mercurial 3.8 has issue with accessing file cache property from their + # cache. This is fix by 36fbd72c2f39fef8ad52d7c559906c2bc388760c in core + # and shipped in 3.9 + obsstorefilecache = localrepo.localrepository.__dict__['obsstore'] + +# obsstore is a filecache so we have do to some spacial dancing +@eh.wrapfunction(obsstorefilecache, 'func') +def obsstorewithcache(orig, repo): + obsstore = orig(repo) + obsstore.obscache = obscache(repo.unfiltered()) + + class cachekeyobsstore(obsstore.__class__): + + _obskeysize = 200 + + def cachekey(self, index=None): + """return (current-length, cachekey) + + 'current-length': is the current length of the obsstore storage file, + 'cachekey' is the hash of the last 200 bytes ending at 'index'. + + if 'index' is unspecified, current obsstore length is used. + Cacheckey will be set to null id if the obstore is empty. + + If the index specified is higher than the current obsstore file + length, cachekey will be set to None.""" + # default value + obsstoresize = 0 + keydata = '' + # try to get actual data from the obsstore + try: + with self.svfs('obsstore') as obsfile: + obsfile.seek(0, 2) + obsstoresize = obsfile.tell() + if index is None: + index = obsstoresize + elif obsstoresize < index: + return obsstoresize, None + actualsize = min(index, self._obskeysize) + if actualsize: + obsfile.seek(index - actualsize, 0) + keydata = obsfile.read(actualsize) + except (OSError, IOError) as e: + if e.errno != errno.ENOENT: + raise + key = hashlib.sha1(keydata).digest() + return obsstoresize, key + + obsstore.__class__ = cachekeyobsstore + + return obsstore + +emptykey = (node.nullrev, node.nullid, 0, 0, node.nullid) + +def getcachekey(repo): + """get a cache key covering the changesets and obsmarkers content + + IT contains the following data. Combined with 'upgradeneeded' it allows to + do iterative upgrade for cache depending of theses two data. + + The cache key parts are" + - tip-rev, + - tip-node, + - obsstore-length (nb markers), + - obsstore-file-size (in bytes), + - obsstore "cache key" + """ + assert repo.filtername is None + cl = repo.changelog + index, key = repo.obsstore.cachekey() + tiprev = len(cl) - 1 + return (tiprev, + cl.node(tiprev), + len(repo.obsstore), + index, + key) + +def upgradeneeded(repo, key): + """return (valid, start-rev, start-obs-idx) + + 'valid': is "False" if older cache value needs invalidation, + + 'start-rev': first revision not in the cache. None if cache is up to date, + + 'start-obs-idx': index of the first obs-markers not in the cache. None is + up to date. + """ + + # XXX ideally, this function would return a bounded amount of changeset and + # obsmarkers and the associated new cache key. Otherwise we are exposed to + # a race condition between the time the cache is updated and the new cache + # key is computed. (however, we do not want to compute the full new cache + # key in all case because we want to skip reading the obsstore content. We + # could have a smarter implementation here. + # + # In pratice the cache is only updated after each transaction within a + # lock. So we should be fine. We could enforce this with a new repository + # requirement (or fix the race, that is not too hard). + invalid = (False, 0, 0) + if key is None: + return invalid + + ### Is the cache valid ? + keytiprev, keytipnode, keyobslength, keyobssize, keyobskey = key + # check for changelog strip + cl = repo.changelog + tiprev = len(cl) - 1 + if (tiprev < keytiprev + or cl.node(keytiprev) != keytipnode): + return invalid + # check for obsstore strip + obssize, obskey = repo.obsstore.cachekey(index=keyobssize) + if obskey != keyobskey: + return invalid + + ### cache is valid, is there anything to update + + # any new changesets ? + startrev = None + if keytiprev < tiprev: + startrev = keytiprev + 1 + + # any new markers + startidx = None + if keyobssize < obssize: + startidx = keyobslength + + return True, startrev, startidx + +class obscache(object): + """cache the "does a rev" is the precursors of some obsmarkers data + + This is not directly holding the "is this revision obsolete" information, + because phases data gets into play here. However, it allow to compute the + "obsolescence" set without reading the obsstore content. + + Implementation note #1: + + The obsstore is implementing only half of the transaction logic it + should. It properly record the starting point of the obsstore to allow + clean rollback. However it still write to the obsstore file directly + during the transaction. Instead it should be keeping data in memory and + write to a '.pending' file to make the data vailable for hooks. + + This cache is not going futher than what the obstore is doing, so it does + not has any '.pending' logic. When the obsstore gains proper '.pending' + support, adding it to this cache should not be too hard. As the flag + always move from 0 to 1, we could have a second '.pending' cache file to + be read. If flag is set in any of them, the value is 1. For the same + reason, updating the file in place should be possible. + + Implementation note #2: + + Instead of having a large final update run, we could update this cache at + the level adding a new changeset or a new obsmarkers. More on this in the + 'update code'. + + Implementation note #3: + + Storage-wise, we could have a "start rev" to avoid storing useless + zero. That would be especially useful for the '.pending' overlay. + """ + + _filepath = 'cache/evoext-obscache-00' + _headerformat = '>q20sQQ20s' + + def __init__(self, repo): + self._vfs = repo.vfs + # The cache key parts are" + # - tip-rev, + # - tip-node, + # - obsstore-length (nb markers), + # - obsstore-file-size (in bytes), + # - obsstore "cache key" + self._cachekey = None + self._ondiskkey = None + self._data = bytearray() + + def get(self, rev): + """return True if "rev" is used as "precursors for any obsmarkers + + Make sure the cache has been updated to match the repository content before using it""" + return self._data[rev] + + def clear(self): + """invalidate the cache content""" + self._cachekey = None + self._data = bytearray() + + def uptodate(self, repo): + if self._cachekey is None: + self.load(repo) + valid, startrev, startidx = upgradeneeded(repo, self._cachekey) + return (valid and startrev is None and startidx is None) + + def update(self, repo): + """Iteratively update the cache with new repository data""" + # If we do not have any data, try loading from disk + if self._cachekey is None: + self.load(repo) + + valid, startrev, startidx = upgradeneeded(repo, self._cachekey) + if not valid: + self.clear() + + if startrev is None and startidx is None: + return + + # process the new changesets + cl = repo.changelog + if startrev is not None: + node = cl.node + # Note: + # + # Newly added changeset might be affected by obsolescence markers + # we already have locally. So we needs to have soem global + # knowledge about the markers to handle that question. Right this + # requires parsing all markers in the obsstore. However, we could + # imagine using various optimisation (eg: bloom filter, other on + # disk cache) to remove this full parsing. + # + # For now we stick to the simpler approach or paying the + # performance cost on new changesets. + succs = repo.obsstore.successors + for r in cl.revs(startrev): + if node(r) in succs: + val = 1 + else: + val = 0 + self._data.append(val) + assert len(self._data) == len(cl), (len(self._data), len(cl)) + + # process the new obsmarkers + if startidx is not None: + rev = cl.nodemap.get + markers = repo.obsstore._all + # Note: + # + # There are no actually needs to load the full obsstore here, + # since we only read the latest ones. We do it for simplicity in + # the first implementation. Loading the full obsstore has a + # performance cost and should go away in this case too. We have + # two simples options for that: + # + # 1) provide and API to start reading markers from a byte offset + # (we have that data in the cache key) + # + # 2) directly update the cache at a lower level, in the code + # responsible for adding a markers. + # + # Option 2 is probably a bit more invasive, but more solid on the long run + + for i in xrange(startidx, len(repo.obsstore)): + r = rev(markers[i][0]) + # If markers affect a newly added nodes, it would have been + # caught in the previous loop, (so we skip < startrev) + if r is not None and (startrev is None or r < startrev): + self._data[r] = 1 + + assert repo._currentlock(repo._lockref) is not None + # XXX note that there are a potential race condition here, since the + # repo "might" have changed side the cache update above. However, this + # code will only be running in a lock so we ignore the issue for now. + # + # To work around this, 'upgradeneeded' should return a bounded amount + # of changeset and markers to read with their associated cachekey. see + # 'upgradeneeded' for detail. + self._cachekey = getcachekey(repo) + + def save(self, repo): + """save the data to disk""" + + # XXX it happens that the obsstore is (buggilly) always up to date on disk + if self._cachekey is None or self._cachekey == self._ondiskkey: + return + + with repo.vfs(self._filepath, 'w', atomictemp=True) as cachefile: + headerdata = struct.pack(self._headerformat, *self._cachekey) + cachefile.write(headerdata) + cachefile.write(self._data) + + def load(self, repo): + """load data from disk""" + assert repo.filtername is None + + data = repo.vfs.tryread(self._filepath) + if not data: + self._cachekey = emptykey + self._data = bytearray() + else: + headersize = struct.calcsize(self._headerformat) + self._cachekey = struct.unpack(self._headerformat, data[:headersize]) + self._data = bytearray(data[headersize:]) + self._ondiskkey = self._cachekey + +def _computeobsoleteset(orig, repo): + """the set of obsolete revisions""" + obs = set() + repo = repo.unfiltered() + notpublic = repo._phasecache.getrevset(repo, (phases.draft, phases.secret)) + if notpublic: + obscache = repo.obsstore.obscache + # Since we warm the cache at the end of every transaction, the cache + # should be up to date. However a non-enabled client might have touced + # the repository. + # + # Updating the cache without a lock is sloppy, so we fallback to the + # old method and rely on the fact the next transaction will write the + # cache down anyway. + # + # With the current implementation updating the cache will requires to + # load the obsstore anyway. Once loaded, hitting the obsstore directly + # will be about as fast.. + if not obscache.uptodate(repo): + if repo.currenttransaction() is None: + repo.ui.log('evoext-obscache', + 'obscache is out of date, ' + 'falling back to slower obsstore version\n') + repo.ui.debug('obscache is out of date') + return orig(repo) + else: + # If a transaction is open, it is worthwhile to update and use the + # cache as it will be written on disk when the transaction close. + obscache.update(repo) + isobs = obscache.get + for r in notpublic: + if isobs(r): + obs.add(r) + return obs + +@eh.uisetup +def cachefuncs(ui): + orig = obsolete.cachefuncs['obsolete'] + wrapped = lambda repo: _computeobsoleteset(orig, repo) + obsolete.cachefuncs['obsolete'] = wrapped + +@eh.reposetup +def setupcache(ui, repo): + + class obscacherepo(repo.__class__): + + @localrepo.unfilteredmethod + def destroyed(self): + if 'obsstore' in vars(self): + self.obsstore.obscache.clear() + + def transaction(self, *args, **kwargs): + tr = super(obscacherepo, self).transaction(*args, **kwargs) + reporef = weakref.ref(self) + + def _warmcache(tr): + repo = reporef() + if repo is None: + return + repo = repo.unfiltered() + # As pointed in 'obscache.update', we could have the + # changelog and the obsstore in charge of updating the + # cache when new items goes it. The tranaction logic would + # then only be involved for the 'pending' and final saving + # logic. + self.obsstore.obscache.update(repo) + self.obsstore.obscache.save(repo) + + tr.addpostclose('warmcache-obscache', _warmcache) + return tr + + repo.__class__ = obscacherepo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext3rd/evolve/safeguard.py Wed May 03 12:31:40 2017 +0200 @@ -0,0 +1,42 @@ +# Code dedicated to adding various "safeguard" around evolution +# +# Some of these will be pollished and upstream when mature. Some other will be +# replaced by better alternative later. +# +# Copyright 2017 Pierre-Yves David <pierre-yves.david@ens-lyon.org> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from mercurial import error + +from mercurial.i18n import _ + +from . import exthelper + +eh = exthelper.exthelper() + +@eh.reposetup +def setuppublishprevention(ui, repo): + + class noautopublishrepo(repo.__class__): + + def checkpush(self, pushop): + super(noautopublishrepo, self).checkpush(pushop) + behavior = repo.ui.config('experimental', 'auto-publish', 'default') + remotephases = pushop.remote.listkeys('phases') + publishing = remotephases.get('publishing', False) + if behavior in ('warn', 'abort') and publishing: + if pushop.revs is None: + published = repo.filtered('served').revs("not public()") + else: + published = repo.revs("::%ln - public()", pushop.revs) + if published: + if behavior == 'warn': + repo.ui.warn(_('%i changesets about to be published\n') % len(published)) + elif behavior == 'abort': + msg = _('push would publish 1 changesets') + hint = _("behavior controlled by 'experimental.auto-publish' config") + raise error.Abort(msg, hint=hint) + + repo.__class__ = noautopublishrepo
--- a/hgext3rd/topic/__init__.py Thu Apr 27 20:52:09 2017 +0200 +++ b/hgext3rd/topic/__init__.py Wed May 03 12:31:40 2017 +0200 @@ -154,7 +154,6 @@ def reposetup(ui, repo): - orig = repo.__class__ if not isinstance(repo, localrepo.localrepository): return # this can be a peer in the ssh case (puzzling) @@ -171,7 +170,7 @@ if repo.currenttopic != repo['.'].topic(): # bypass the core "nothing changed" logic self.ui.setconfig('ui', 'allowemptycommit', True) - return orig.commit(self, *args, **kwargs) + return super(topicrepo, self).commit(*args, **kwargs) finally: self.ui.restoreconfig(backup) @@ -187,7 +186,7 @@ # we are amending and need to remove a topic del ctx.extra()[constants.extrakey] with topicmap.usetopicmap(self): - return orig.commitctx(self, ctx, error=error) + return super(topicrepo, self).commitctx(ctx, error=error) @property def topics(self):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-evolve-obshistory.t Wed May 03 12:31:40 2017 +0200 @@ -0,0 +1,516 @@ +This test file test the various messages when accessing obsolete +revisions. + +Global setup +============ + + $ . $TESTDIR/testlib/common.sh + $ cat >> $HGRCPATH <<EOF + > [ui] + > interactive = true + > [phases] + > publish=False + > [extensions] + > evolve = + > EOF + +Test output on amended commit +============================= + +Test setup +---------- + + $ hg init $TESTTMP/local-amend + $ cd $TESTTMP/local-amend + $ mkcommit ROOT + $ mkcommit A0 + $ echo 42 >> A0 + $ hg amend -m "A1" + $ hg log --hidden -G + @ changeset: 3:a468dc9b3633 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A1 + | + | x changeset: 2:f137d23bb3e1 + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: temporary amend commit for 471f378eab4c + | | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + +Actual test +----------- + + $ hg update 471f378eab4c + abort: hidden revision '471f378eab4c'! + (use --hidden to access hidden revisions; successor: a468dc9b3633) + [255] + $ hg update --hidden "desc(A0)" + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: a468dc9b3633) + +Test output with pruned commit +============================== + +Test setup +---------- + + $ hg init $TESTTMP/local-prune + $ cd $TESTTMP/local-prune + $ mkcommit ROOT + $ mkcommit A0 # 0 + $ mkcommit B0 # 1 + $ hg log --hidden -G + @ changeset: 2:0dec01379d3b + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: B0 + | + o changeset: 1:471f378eab4c + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg prune -r 'desc(B0)' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + working directory now at 471f378eab4c + 1 changesets pruned + +Actual test +----------- + + $ hg up 1 + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg up 0dec01379d3b + abort: hidden revision '0dec01379d3b'! + (use --hidden to access hidden revisions; pruned) + [255] + $ hg up --hidden -r 'desc(B0)' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (0dec01379d3b) + (use 'hg evolve' to update to its parent successor) + +Test output with splitted commit +================================ + +Test setup +---------- + + $ hg init $TESTTMP/local-split + $ cd $TESTTMP/local-split + $ mkcommit ROOT + $ echo 42 >> a + $ echo 43 >> b + $ hg commit -A -m "A0" + adding a + adding b + $ hg log --hidden -G + @ changeset: 1:471597cad322 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg split -r 'desc(A0)' -d "0 0" << EOF + > y + > y + > n + > n + > y + > y + > EOF + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + adding a + adding b + diff --git a/a b/a + new file mode 100644 + examine changes to 'a'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +42 + record change 1/2 to 'a'? [Ynesfdaq?] y + + diff --git a/b b/b + new file mode 100644 + examine changes to 'b'? [Ynesfdaq?] n + + created new head + Done splitting? [yN] n + diff --git a/b b/b + new file mode 100644 + examine changes to 'b'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +43 + record this change to 'b'? [Ynesfdaq?] y + + no more change to split + + $ hg log --hidden -G + @ changeset: 3:f257fde29c7a + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 2:337fec4d2edc + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + | x changeset: 1:471597cad322 + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + +Actual test +----------- + + $ hg update 471597cad322 + abort: hidden revision '471597cad322'! + (use --hidden to access hidden revisions; successors: 337fec4d2edc, f257fde29c7a) + [255] + $ hg update --hidden 'min(desc(A0))' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (471597cad322) + (use 'hg evolve' to update to its tipmost successor: 337fec4d2edc, f257fde29c7a) + +Test output with lots of splitted commit +======================================== + +Test setup +---------- + + $ hg init $TESTTMP/local-lots-split + $ cd $TESTTMP/local-lots-split + $ mkcommit ROOT + $ echo 42 >> a + $ echo 43 >> b + $ echo 44 >> c + $ echo 45 >> d + $ hg commit -A -m "A0" + adding a + adding b + adding c + adding d + $ hg log --hidden -G + @ changeset: 1:de7290d8b885 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + + $ hg split -r 'desc(A0)' -d "0 0" << EOF + > y + > y + > n + > n + > n + > n + > y + > y + > n + > n + > n + > y + > y + > n + > n + > y + > y + > EOF + 0 files updated, 0 files merged, 4 files removed, 0 files unresolved + adding a + adding b + adding c + adding d + diff --git a/a b/a + new file mode 100644 + examine changes to 'a'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +42 + record change 1/4 to 'a'? [Ynesfdaq?] y + + diff --git a/b b/b + new file mode 100644 + examine changes to 'b'? [Ynesfdaq?] n + + diff --git a/c b/c + new file mode 100644 + examine changes to 'c'? [Ynesfdaq?] n + + diff --git a/d b/d + new file mode 100644 + examine changes to 'd'? [Ynesfdaq?] n + + created new head + Done splitting? [yN] n + diff --git a/b b/b + new file mode 100644 + examine changes to 'b'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +43 + record change 1/3 to 'b'? [Ynesfdaq?] y + + diff --git a/c b/c + new file mode 100644 + examine changes to 'c'? [Ynesfdaq?] n + + diff --git a/d b/d + new file mode 100644 + examine changes to 'd'? [Ynesfdaq?] n + + Done splitting? [yN] n + diff --git a/c b/c + new file mode 100644 + examine changes to 'c'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +44 + record change 1/2 to 'c'? [Ynesfdaq?] y + + diff --git a/d b/d + new file mode 100644 + examine changes to 'd'? [Ynesfdaq?] n + + Done splitting? [yN] n + diff --git a/d b/d + new file mode 100644 + examine changes to 'd'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +45 + record this change to 'd'? [Ynesfdaq?] y + + no more change to split + + $ hg log --hidden -G + @ changeset: 5:c7f044602e9b + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 4:1ae8bc733a14 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 3:f257fde29c7a + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 2:337fec4d2edc + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + | x changeset: 1:de7290d8b885 + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + +Actual test +----------- + + $ hg update de7290d8b885 + abort: hidden revision 'de7290d8b885'! + (use --hidden to access hidden revisions; successors: 337fec4d2edc, f257fde29c7a and 2 more) + [255] + $ hg update --hidden 'min(desc(A0))' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (de7290d8b885) + (use 'hg evolve' to update to its tipmost successor: 337fec4d2edc, f257fde29c7a and 2 more) + +Test output with folded commit +============================== + +Test setup +---------- + + $ hg init $TESTTMP/local-fold + $ cd $TESTTMP/local-fold + $ mkcommit ROOT + $ mkcommit A0 + $ mkcommit B0 + $ hg log --hidden -G + @ changeset: 2:0dec01379d3b + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: B0 + | + o changeset: 1:471f378eab4c + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg fold --exact -r 'desc(A0) + desc(B0)' --date "0 0" -m "C0" + 2 changesets folded + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log --hidden -G + @ changeset: 3:eb5a0daa2192 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: C0 + | + | x changeset: 2:0dec01379d3b + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: B0 + | | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + Actual test + ----------- + + $ hg update 471f378eab4c + abort: hidden revision '471f378eab4c'! + (use --hidden to access hidden revisions; successor: eb5a0daa2192) + [255] + $ hg update --hidden 'desc(A0)' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + $ hg update 0dec01379d3b + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + abort: hidden revision '0dec01379d3b'! + (use --hidden to access hidden revisions; successor: eb5a0daa2192) + [255] + $ hg update --hidden 'desc(B0)' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (0dec01379d3b) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + +Test output with divergence +=========================== + +Test setup +---------- + + $ hg init $TESTTMP/local-divergence + $ cd $TESTTMP/local-divergence + $ mkcommit ROOT + $ mkcommit A0 + $ hg amend -m "A1" + $ hg log --hidden -G + @ changeset: 2:fdf9bde5129a + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A1 + | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg update --hidden 'desc(A0)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: fdf9bde5129a) + $ hg amend -m "A2" + 2 new divergent changesets + $ hg log --hidden -G + @ changeset: 3:65b757b745b9 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | trouble: divergent + | summary: A2 + | + | o changeset: 2:fdf9bde5129a + |/ parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | trouble: divergent + | summary: A1 + | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + +Actual test +----------- + + $ hg update 471f378eab4c + abort: hidden revision '471f378eab4c'! + (use --hidden to access hidden revisions; diverged) + [255] + $ hg update --hidden 'desc(A0)' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (471f378eab4c) + (471f378eab4c has diverged, use 'hg evolve -list --divergent' to resolve the issue)
--- a/tests/test-evolve.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-evolve.t Wed May 03 12:31:40 2017 +0200 @@ -722,7 +722,7 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg fold --from 6 # want to run hg fold 6 abort: hidden revision '6'! - (use --hidden to access hidden revisions) + (use --hidden to access hidden revisions; successor: af636757ce3b) [255] $ hg log -r 11 --template '{desc}\n' add 3
--- a/tests/test-inhibit.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-inhibit.t Wed May 03 12:31:40 2017 +0200 @@ -364,7 +364,7 @@ $ hg export 1 3 abort: hidden revision '1'! - (use --hidden to access hidden revisions) + (use --hidden to access hidden revisions; pruned) [255] @@ -432,7 +432,7 @@ $ hg rebase -s 10 -d 3 abort: hidden revision '3'! - (use --hidden to access hidden revisions) + (use --hidden to access hidden revisions; pruned) [255] $ hg rebase -r ad78ff7d621f -r 53a94305e133 -d 2db36d8066ff --config experimental.rebaseskipobsolete=0 Warning: accessing hidden changesets 2db36d8066ff for write operation @@ -699,7 +699,7 @@ $ hg up 15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - working directory parent is obsolete! + working directory parent is obsolete! (2d66e189f5b5) $ cat >> $HGRCPATH <<EOF > [experimental] > evolution=all @@ -803,7 +803,7 @@ $ hg push -r 003a4735afde $pwd/inhibit2 pushing to $TESTTMP/inhibit2 abort: hidden revision '003a4735afde'! - (use --hidden to access hidden revisions) + (use --hidden to access hidden revisions; successor: 71eb4f100663) [255] Visible commits can still be pushed
--- a/tests/test-obsolete-push.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-obsolete-push.t Wed May 03 12:31:40 2017 +0200 @@ -44,3 +44,50 @@ comparing with ../clone searching for changes 0:1994f17a630e@default(obsolete/draft) A + $ cd .. + +Test options to prevent implicite publishing of changesets +---------------------------------------------------------- + + + $ hg clone source strict-publish-client --pull + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + 1 new obsolescence markers + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd strict-publish-client + $ echo c > c + $ hg ci -qAm C c + +abort behavior + + $ cat >> .hg/hgrc <<eof + > [experimental] + > auto-publish = abort + > eof + $ hg push -r . + pushing to $TESTTMP/source + abort: push would publish 1 changesets + (behavior controlled by 'experimental.auto-publish' config) + [255] + $ hg push + pushing to $TESTTMP/source + abort: push would publish 1 changesets + (behavior controlled by 'experimental.auto-publish' config) + [255] + +warning behavior + + $ echo 'auto-publish = warn' >> .hg/hgrc + $ hg push + pushing to $TESTTMP/source + 1 changesets about to be published + searching for changes + adding changesets + adding manifests + adding file changes + added 0 changesets with 0 changes to 1 files
--- a/tests/test-obsolete.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-obsolete.t Wed May 03 12:31:40 2017 +0200 @@ -121,7 +121,7 @@ 4 - 725c380fe99b $ hg up --hidden 3 -q - working directory parent is obsolete! + working directory parent is obsolete! (0d3f46688ccc) (reported by parents too) $ hg parents changeset: 3:0d3f46688ccc @@ -130,8 +130,8 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: add obsol_c - working directory parent is obsolete! - (use 'hg evolve' to update to its successor) + working directory parent is obsolete! (0d3f46688ccc) + (use 'hg evolve' to update to its successor: 725c380fe99b) $ mkcommit d # 5 (on 3) 1 new unstable changesets $ qlog -r 'obsolete()' @@ -206,7 +206,7 @@ 0 - 1f0dee641bb7 $ hg up --hidden 3 -q - working directory parent is obsolete! + working directory parent is obsolete! (0d3f46688ccc) $ mkcommit obsol_d # 6 created new head 1 new unstable changesets @@ -263,7 +263,7 @@ [1] $ hg up --hidden -q .^ # 3 - working directory parent is obsolete! + working directory parent is obsolete! (0d3f46688ccc) $ mkcommit "obsol_d'" # 7 created new head 1 new unstable changesets @@ -351,7 +351,7 @@ Test rollback support $ hg up --hidden .^ -q # 3 - working directory parent is obsolete! + working directory parent is obsolete! (0d3f46688ccc) $ mkcommit "obsol_d''" created new head 1 new unstable changesets @@ -687,7 +687,7 @@ $ hg up --hidden 3 -q - working directory parent is obsolete! + working directory parent is obsolete! (0d3f46688ccc) $ hg evolve parent is obsolete with multiple successors: [4] add obsol_c' @@ -704,8 +704,8 @@ $ hg up --hidden 2 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - working directory parent is obsolete! - (use 'hg evolve' to update to its successor) + working directory parent is obsolete! (4538525df7e2) + (4538525df7e2 has diverged, use 'hg evolve -list --divergent' to resolve the issue) $ hg export 9468a5f5d8b2 | hg import - applying patch from stdin 1 new unstable changesets
--- a/tests/test-prune.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-prune.t Wed May 03 12:31:40 2017 +0200 @@ -305,7 +305,7 @@ 1 changesets pruned $ hg id -ir dcbb326fdec2 abort: hidden revision 'dcbb326fdec2'! - (use --hidden to access hidden revisions) + (use --hidden to access hidden revisions; pruned) [255] $ hg id -ir d62d843c9a01 d62d843c9a01 @@ -339,7 +339,7 @@ $ hg tag --remove --local c $ hg id -ir 6:2702dd0c91e7 abort: hidden revision '6'! - (use --hidden to access hidden revisions) + (use --hidden to access hidden revisions; pruned) [255] $ hg debugobsstorestat
--- a/tests/test-stabilize-result.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-stabilize-result.t Wed May 03 12:31:40 2017 +0200 @@ -222,8 +222,8 @@ $ hg amend $ hg up --hidden 15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - working directory parent is obsolete! - (use 'hg evolve' to update to its successor) + working directory parent is obsolete! (3932c176bbaa) + (use 'hg evolve' to update to its successor: d2f173e25686) $ mv a a.old $ echo 'jungle' > a $ cat a.old >> a @@ -335,8 +335,8 @@ $ hg up --hidden 15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - working directory parent is obsolete! - (use 'hg evolve' to update to its successor) + working directory parent is obsolete! (3932c176bbaa) + (use 'hg evolve' to update to its successor: f344982e63c4) $ echo 'gotta break' >> a $ hg amend 2 new divergent changesets
--- a/tests/test-touch.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-touch.t Wed May 03 12:31:40 2017 +0200 @@ -33,8 +33,8 @@ $ hg commit -m ab --amend $ hg up --hidden 1 0 files updated, 0 files merged, 1 files removed, 0 files unresolved - working directory parent is obsolete! - (use 'hg evolve' to update to its successor) + working directory parent is obsolete! (*) (glob) + (use 'hg evolve' to update to its successor: *) (glob) $ hg log -G o 3:[0-9a-f]{12} ab (re)
--- a/tests/test-tutorial.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-tutorial.t Wed May 03 12:31:40 2017 +0200 @@ -741,15 +741,15 @@ pulling from $TESTTMP/local (glob) searching for changes no changes found - working directory parent is obsolete! - (use 'hg evolve' to update to its successor) + working directory parent is obsolete! (bf1b0d202029) + (use 'hg evolve' to update to its successor: ee942144f952) now let's see where we are, and update to the successor $ hg parents bf1b0d202029 (draft): animals - working directory parent is obsolete! - (use 'hg evolve' to update to its successor) + working directory parent is obsolete! (bf1b0d202029) + (use 'hg evolve' to update to its successor: ee942144f952) $ hg evolve update:[8] animals 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-uncommit.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-uncommit.t Wed May 03 12:31:40 2017 +0200 @@ -239,8 +239,8 @@ $ hg up -C 3 --hidden 8 files updated, 0 files merged, 1 files removed, 0 files unresolved (leaving bookmark touncommit-bm) - working directory parent is obsolete! - (use 'hg evolve' to update to its successor) + working directory parent is obsolete! (5eb72dbe0cb4) + (use 'hg evolve' to update to its successor: e8db4aa611f6) $ hg --config extensions.purge= purge $ hg uncommit -I 'set:added() and e' 2 new divergent changesets @@ -285,8 +285,8 @@ $ hg up -C 3 --hidden 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - working directory parent is obsolete! - (use 'hg evolve' to update to its successor) + working directory parent is obsolete! (5eb72dbe0cb4) + (5eb72dbe0cb4 has diverged, use 'hg evolve -list --divergent' to resolve the issue) $ hg --config extensions.purge= purge $ hg uncommit --all -X e 1 new divergent changesets
--- a/tests/test-userguide.t Thu Apr 27 20:52:09 2017 +0200 +++ b/tests/test-userguide.t Wed May 03 12:31:40 2017 +0200 @@ -39,7 +39,7 @@ $ hg commit --amend -u alice -d '2 0' -m 'implement feature Y' $ hg shortlog -q -r fe0ecd3bd2a4 abort: hidden revision 'fe0ecd3bd2a4'! - (use --hidden to access hidden revisions) + (use --hidden to access hidden revisions; successor: 934359450037) [255] $ hg --hidden shortlog -G @ 3:934359450037 draft implement feature Y