Mercurial > evolve
changeset 2372:a0099d568ef8
merge with stable
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Wed, 17 May 2017 09:52:06 +0200 |
parents | 48e879b3f5b6 (diff) 3be45918c7b5 (current diff) |
children | 2a1aad0fd8bf |
files | README hgext3rd/evolve/obscache.py |
diffstat | 8 files changed, 1331 insertions(+), 199 deletions(-) [+] |
line wrap: on
line diff
--- a/README Tue May 16 23:37:10 2017 -0700 +++ b/README Wed May 17 09:52:06 2017 +0200 @@ -112,6 +112,17 @@ Changelog ========= +6.2.0 - in progress +------------------- + + - add a debugobshistory command to inspect the obs-history of a changeset + - topic: have thg display topic name if possible, + - obscache: more efficient update in the (rare) case of a transaction adding + markers without changesets + - obshashrange-cache: update incrementally in the (common) case of a + transaction not affecting existing range, + - obshashrange-cache: keep the cache mostly warm after each transaction. + 6.1.1 - in progress -------------------
--- a/hgext3rd/evolve/__init__.py Tue May 16 23:37:10 2017 -0700 +++ b/hgext3rd/evolve/__init__.py Wed May 17 09:52:06 2017 +0200 @@ -162,6 +162,15 @@ obsexcmsg = utility.obsexcmsg +colortable = {'evolve.short_node': 'yellow', + 'evolve.user': 'green', + 'evolve.rev': 'blue', + 'evolve.short_description': '', + 'evolve.date': 'cyan', + 'evolve.current_rev': 'bold', + 'evolve.verb': '', + } + _pack = struct.pack _unpack = struct.unpack @@ -3249,3 +3258,109 @@ f.write(orig.topic()) return merge.graft(repo, orig, pctx, ['local', 'graft'], True) + +@eh.command( + '^debugobshistory', + [] + commands.formatteropts, + _('hg debugobshistory [OPTION]... [REV]')) +def debugobshistory(ui, repo, *revs, **opts): + revs = scmutil.revrange(repo, revs) + fm = ui.formatter('debugobshistory', opts) + + revs.reverse() + _debugobshistorysingle(fm, repo, revs) + + fm.end() + +def _debugobshistorysingle(fm, repo, revs): + """ Display the obsolescence history for a single revision + """ + precursors = repo.obsstore.precursors + successors = repo.obsstore.successors + nodec = repo.changelog.node + nodes = [nodec(r) for r in revs] + + seen = set(nodes) + + while nodes: + ctxnode = nodes.pop() + + _debugobshistorydisplaynode(fm, repo, ctxnode) + + succs = successors.get(ctxnode, ()) + + markerfm = fm.nested("debugobshistory.markers") + for successor in sorted(succs): + _debugobshistorydisplaymarker(markerfm, repo, successor) + markerfm.end() + + precs = precursors.get(ctxnode, ()) + for p in sorted(precs): + # Only show nodes once + if p[0] not in seen: + seen.add(p[0]) + nodes.append(p[0]) + +def _debugobshistorydisplaynode(fm, repo, node): + if node in repo.unfiltered(): + _debugobshistorydisplayctx(fm, repo.unfiltered()[node]) + else: + _debugobshistorydisplaymissingctx(fm, node) + +def _debugobshistorydisplayctx(fm, ctx): + shortdescription = ctx.description().splitlines()[0] + + fm.startitem() + fm.write('debugobshistory.node', '%s', str(ctx), + label="evolve.short_node") + fm.plain(' ') + + fm.write('debugobshistory.rev', '(%d)', int(ctx), + label="evolve.rev") + fm.plain(' ') + + fm.write('debugobshistory.shortdescription', '%s', shortdescription, + label="evolve.short_description") + fm.plain('\n') + +def _debugobshistorydisplaymissingctx(fm, nodewithoutctx): + hexnode = node.short(nodewithoutctx) + fm.startitem() + fm.write('debugobshistory.node', '%s', hexnode, + label="evolve.short_node evolve.missing_change_ctx") + fm.plain('\n') + +def _debugobshistorydisplaymarker(fm, repo, marker): + succnodes = marker[1] + date = marker[4] + metadata = dict(marker[3]) + + fm.startitem() + fm.plain(' ') + + # Detect pruned revisions + if len(succnodes) == 0: + verb = 'pruned' + else: + verb = 'rewritten' + + fm.write('debugobshistory.verb', '%s', verb, + label="evolve.verb") + fm.plain(' by ') + + fm.write('debugobshistory.marker_user', '%s', metadata['user'], + label="evolve.user") + fm.plain(' ') + + fm.write('debugobshistory.marker_date', '(%s)', fm.formatdate(date), + label="evolve.date") + + if len(succnodes) > 0: + fm.plain(' as ') + + shortsnodes = (node.short(succnode) for succnode in sorted(succnodes)) + nodes = fm.formatlist(shortsnodes, 'debugobshistory.succnodes', sep=', ') + fm.write('debugobshistory.succnodes', '%s', nodes, + label="evolve.short_node") + + fm.plain("\n")
--- a/hgext3rd/evolve/obscache.py Tue May 16 23:37:10 2017 -0700 +++ b/hgext3rd/evolve/obscache.py Wed May 17 09:52:06 2017 +0200 @@ -13,6 +13,7 @@ import errno from mercurial import ( + error, localrepo, obsolete, phases, @@ -20,6 +21,8 @@ util, ) +from mercurial.i18n import _ + from . import ( exthelper, ) @@ -76,91 +79,226 @@ except (OSError, IOError) as e: if e.errno != errno.ENOENT: raise - key = hashlib.sha1(keydata).digest() + if keydata: + key = hashlib.sha1(keydata).digest() + else: + # reusing an existing "empty" value make it easier to define a + # default cachekey for 'no data'. + key = node.nullid 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. +# XXX copied as is from Mercurial 4.2 and added the "offset" parameters +@util.nogc +def _readmarkers(data, offset=None): + """Read and enumerate markers from raw data""" + off = 0 + diskversion = struct.unpack('>B', data[off:off + 1])[0] + if offset is None: + off += 1 + else: + assert 1 <= offset + off = offset + if diskversion not in obsolete.formats: + raise error.Abort(_('parsing obsolete marker: unknown version %r') + % diskversion) + return diskversion, obsolete.formats[diskversion][0](data, off) - 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 markersfrom(obsstore, byteoffset, firstmarker): + if not firstmarker: + return list(obsstore) + elif '_all' in vars(obsstore): + # if the data are in memory, just use that + return obsstore._all[firstmarker:] + else: + obsdata = obsstore.svfs.tryread('obsstore') + return _readmarkers(obsdata, byteoffset)[1] -def upgradeneeded(repo, key): - """return (valid, start-rev, start-obs-idx) - 'valid': is "False" if older cache value needs invalidation, +class dualsourcecache(object): + """An abstract class for cache that needs both changelog and obsstore - '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. + This class handle the tracking of changelog and obsstore update. It provide + data to performs incremental update (see the 'updatefrom' function for + details). This class can also detect stripping of the changelog or the + obsstore and can reset the cache in this cache (see the 'clear' function + for details). """ - # 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. + # default key used for an empty cache + # + # The cache key covering the changesets and obsmarkers content # - # 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 + # The cache key parts are: + # - tip-rev, + # - tip-node, + # - obsstore-length (nb markers), + # - obsstore-file-size (in bytes), + # - obsstore "cache key" + emptykey = (node.nullrev, node.nullid, 0, 0, node.nullid) + + def __init__(self): + super(dualsourcecache, self).__init__() + self._cachekey = None + + def _updatefrom(self, repo, revs, obsmarkers): + """override this method to update your cache data incrementally + + revs: list of new revision in the changelog + obsmarker: list of new obsmarkers in the obsstore + """ + raise NotImplementedError + + def clear(self, reset=False): + """invalidate the cache content + + if 'reset' is passed, we detected a strip and the cache will have to be + recomputed. + """ + # /!\ IMPORTANT /!\ + # You must overide this method to actually + self._cachekey = self.emptykey if reset else None + s = super(dualsourcecache, self) + if util.safehasattr(s, 'clear') and callable(s.clear): + s.clear() + + def load(self, repo): + """Load data from disk + + Do not forget to restore the "cachekey" attribute while doing so. + """ + raise NotImplementedError + + # Useful public function (no need to override them) + + def uptodate(self, repo): + """return True if the cache content is up to date False otherwise + + This method can be used to detect of the cache is lagging behind new + data in either changelog or obsstore. + """ + if self._cachekey is None: + self.load(repo) + status = self._checkkey(repo.changelog, repo.obsstore) + return (status is not None + and status[0] == self._cachekey[0] # tiprev + and status[1] == self._cachekey[3]) # obssize + + def update(self, repo): + """update the cache with new repository data + + The update will be incremental when possible""" + repo = repo.unfiltered() + # If we do not have any data, try loading from disk + if self._cachekey is None: + self.load(repo) + + assert repo.filtername is None + cl = repo.changelog + + upgrade = self._upgradeneeded(repo) + if upgrade is None: + return + + reset, revs, obsmarkers, obskeypair = upgrade + if reset or self._cachekey is None: + self.clear(reset=True) + + self._updatefrom(repo, revs, obsmarkers) - ### 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 + # update the key from the new data + key = list(self._cachekey) + if revs: + key[0] = len(cl) - 1 + key[1] = cl.node(key[0]) + if obsmarkers: + key[2] += len(obsmarkers) + key[3], key[4] = obskeypair + self._cachekey = tuple(key) + + # from here, there are internal function only + + def _checkkey(self, changelog, obsstore): + """internal function""" + key = self._cachekey + if key is None: + return None + + ### Is the cache valid ? + keytiprev, keytipnode, keyobslength, keyobssize, keyobskey = key + # check for changelog strip + tiprev = len(changelog) - 1 + if (tiprev < keytiprev + or changelog.node(keytiprev) != keytipnode): + return None + # check for obsstore strip + obssize, obskey = obsstore.cachekey(index=keyobssize) + if obskey != keyobskey: + return None + return tiprev, obssize, obskey + + def _upgradeneeded(self, repo): + """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. + """ - ### cache is valid, is there anything to update + # We need to ensure we use the same changelog and obsstore through the + # processing. Otherwise some invalidation could update the object and their + # content after we computed the cache key. + cl = repo.changelog + obsstore = repo.obsstore + key = self._cachekey + + reset = False - # any new changesets ? - startrev = None - if keytiprev < tiprev: - startrev = keytiprev + 1 + status = self._checkkey(cl, obsstore) + if status is None: + reset = True + key = self.emptykey + obssize, obskey = obsstore.cachekey() + tiprev = len(cl) - 1 + else: + tiprev, obssize, obskey = status + + keytiprev, keytipnode, keyobslength, keyobssize, keyobskey = key + + if not reset and keytiprev == tiprev and keyobssize == obssize: + return None # nothing to upgrade - # any new markers - startidx = None - if keyobssize < obssize: - startidx = keyobslength + ### cache is valid, is there anything to update + + # any new changesets ? + revs = () + if keytiprev < tiprev: + revs = list(cl.revs(start=keytiprev + 1, stop=tiprev)) - return True, startrev, startidx + # any new markers + markers = () + if keyobssize < obssize: + # XXX Three are a small race change here. Since the obsstore might have + # move forward between the time we computed the cache key and we access + # the data. To fix this we need so "up to" argument when fetching the + # markers here. Otherwise we might return more markers than covered by + # the cache key. + # + # 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). + markers = markersfrom(obsstore, keyobssize, keyobslength) -class obscache(object): + return reset, revs, markers, (obssize, obskey) + + +class obscache(dualsourcecache): """cache the "does a rev" is the precursors of some obsmarkers data This is not directly holding the "is this revision obsolete" information, @@ -197,16 +335,12 @@ _filepath = 'cache/evoext-obscache-00' _headerformat = '>q20sQQ20s' + emptykey = (node.nullrev, node.nullid, 0, 0, node.nullid) + def __init__(self, repo): + super(obscache, self).__init__() + self._ondiskkey = None 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): @@ -215,90 +349,56 @@ Make sure the cache has been updated to match the repository content before using it""" return self._data[rev] - def clear(self): + def clear(self, reset=False): """invalidate the cache content""" - self._cachekey = None + super(obscache, self).clear(reset=reset) 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 _updatefrom(self, repo, revs, obsmarkers): + if revs: + self._updaterevs(repo, revs) + if obsmarkers: + self._updatemarkers(repo, obsmarkers) + + def _updaterevs(self, repo, revs): + """update the cache with new revisions + + Newly added changeset might be affected by obsolescence markers + we already have locally. So we needs to have some global + knowledge about the markers to handle that question. + + Right now this requires parsing all markers in the obsstore. We could + imagine using various optimisation (eg: another cache, network + exchange, etc). - 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) + A possible approach to this is to build a set of all node used as + precursors in `obsstore._obscandidate`. If markers are not loaded yet, + we could initialize it by doing a quick scan through the obsstore data + and filling a (pre-sized) set. Doing so would be much faster than + parsing all the obsmarkers since we would access less data, not create + any object beside the nodes and not have to decode any complex data. - 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 + For now we stick to the simpler approach of paying the + performance cost on new changesets. + """ + node = repo.changelog.node + succs = repo.obsstore.successors + for r in revs: + if node(r) in succs: + val = 1 + else: + val = 0 + self._data.append(val) 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 _updatemarkers(self, repo, obsmarkers): + """update the cache with new markers""" + rev = repo.changelog.nodemap.get + for m in obsmarkers: + r = rev(m[0]) + if r is not None: + self._data[r] = 1 def save(self, repo): """save the data to disk""" @@ -319,7 +419,7 @@ data = repo.vfs.tryread(self._filepath) if not data: - self._cachekey = emptykey + self._cachekey = self.emptykey self._data = bytearray() else: headersize = struct.calcsize(self._headerformat) @@ -339,7 +439,7 @@ 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 + # should be up to date. However a non-enabled client might have touched # the repository. # # Updating the cache without a lock is sloppy, so we fallback to the @@ -348,7 +448,7 @@ # # 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.. + # will be about as fast... if not obscache.uptodate(repo): if repo.currenttransaction() is None: repo.ui.log('evoext-obscache', @@ -357,8 +457,9 @@ 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. + # If a transaction is open, it is worthwhile to update and use + # the cache, the lock prevent race and it will be written on + # disk when the transaction close. obscache.update(repo) isobs = obscache.get for r in notpublic: @@ -392,11 +493,10 @@ 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. + # 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 writing on disk. self.obsstore.obscache.update(repo) self.obsstore.obscache.save(repo)
--- a/hgext3rd/evolve/obsdiscovery.py Tue May 16 23:37:10 2017 -0700 +++ b/hgext3rd/evolve/obsdiscovery.py Wed May 17 09:52:06 2017 +0200 @@ -46,6 +46,7 @@ from . import ( exthelper, + obscache, utility, stablerange, ) @@ -241,6 +242,7 @@ entry = (h, 0) addentry(entry) + local.obsstore.rangeobshashcache.update(local) querycount = 0 ui.progress(_("comparing obsmarker with other"), querycount) overflow = [] @@ -342,6 +344,7 @@ linetemplate = '%12d %12s %12d %12d %12d %12s\n' headertemplate = linetemplate.replace('d', 's') ui.status(headertemplate % headers) + repo.obsstore.rangeobshashcache.update(repo) for r in ranges: d = (r[0], s(cl.node(r[0])), @@ -392,10 +395,11 @@ _sqliteschema = [ """CREATE TABLE meta(schemaversion INTEGER NOT NULL, + tiprev INTEGER NOT NULL, + tipnode BLOB NOT NULL, nbobsmarker INTEGER NOT NULL, - obstipdata BLOB NOT NULL, - tiprev INTEGER NOT NULL, - tipnode BLOB NOT NULL + obssize BLOB NOT NULL, + obskey BLOB NOT NULL );""", """CREATE TABLE obshashrange(rev INTEGER NOT NULL, idx INTEGER NOT NULL, @@ -404,31 +408,46 @@ "CREATE INDEX range_index ON obshashrange(rev, idx);", ] _queryexist = "SELECT name FROM sqlite_master WHERE type='table' AND name='meta';" -_newmeta = """INSERT INTO meta (schemaversion, nbobsmarker, obstipdata, tiprev, tipnode) - VALUES (?,?,?,?,?);""" +_newmeta = """INSERT INTO meta (schemaversion, tiprev, tipnode, nbobsmarker, obssize, obskey) + VALUES (?,?,?,?,?, ?);""" _updateobshash = "INSERT INTO obshashrange(rev, idx, obshash) VALUES (?,?,?);" -_querymeta = "SELECT schemaversion, nbobsmarker, obstipdata, tiprev, tipnode FROM meta;" +_querymeta = "SELECT schemaversion, tiprev, tipnode, nbobsmarker, obssize, obskey FROM meta;" _queryobshash = "SELECT obshash FROM obshashrange WHERE (rev = ? AND idx = ?);" -class _obshashcache(dict): +_reset = "DELETE FROM obshashrange;" - _schemaversion = 0 +class _obshashcache(obscache.dualsourcecache, dict): + + _schemaversion = 1 def __init__(self, repo): super(_obshashcache, self).__init__() - self._path = repo.vfs.join('cache/evoext_obshashrange_v0.sqlite') + self._path = repo.vfs.join('cache/evoext_obshashrange_v1.sqlite') self._new = set() self._valid = True self._repo = weakref.ref(repo.unfiltered()) # cache status self._ondiskcachekey = None - def clear(self): + def clear(self, reset=False): self._valid = False - super(_obshashcache, self).clear() + if reset: + con = self._con + if con is not None: + con.execute(_reset) + super(_obshashcache, self).clear(reset=reset) self._new.clear() + if not reset and '_con' in vars(self): + del self._con def get(self, rangeid): + # revision should be covered by out tiprev + # XXX should be a programming error + # + # XXX there are issue with cache warming, we hack around it for now + if not getattr(self, '_updating', False): + assert rangeid[0] <= self._cachekey[0] + value = super(_obshashcache, self).get(rangeid) if value is None and self._con is not None: nrange = (rangeid[0], rangeid[1]) @@ -441,16 +460,57 @@ self._new.add(rangeid) super(_obshashcache, self).__setitem__(rangeid, obshash) - def _cachekey(self, repo): - # XXX for now the cache is very volatile, but this is still a win - nbobsmarker = len(repo.obsstore._all) - if nbobsmarker: - tipdata = obsolete._fm1encodeonemarker(repo.obsstore._all[-1]) - else: - tipdata = node.nullid - tiprev = len(repo.changelog) - 1 - tipnode = repo.changelog.node(tiprev) - return (self._schemaversion, nbobsmarker, tipdata, tiprev, tipnode) + def _updatefrom(self, repo, revs, obsmarkers): + """override this method to update your cache data incrementally + + revs: list of new revision in the changelog + obsmarker: list of new obsmarkers in the obsstore + """ + # XXX for now, we'll not actually update the cache, but we'll be + # smarter at invalidating it. + # + # 1) new revisions does not get their entry updated (not update) + # 2) if we detect markers affecting non-new revision we reset the cache + + self._updating = True + + setrevs = set(revs) + rev = repo.changelog.nodemap.get + # if we have a new markers affecting a node already covered by the + # cache, we must abort. + for m in obsmarkers: + # check successors and parent + for l in (m[1], m[5]): + if l is None: + continue + for p in l: + r = rev(p) + if r is not None and r not in setrevs: + self.clear(reset=True) + break + else: + continue + break + + # XXX the current reset is too strong we could just drop the affected range + + # XXX if we reset, we should warm the cache for existing heads (draft and public) + + # warm the cache for the new revs + for r in revs: + _obshashrange(repo, (r, 0)) + + del self._updating + + @property + def _fullcachekey(self): + return (self._schemaversion, ) + self._cachekey + + def load(self, repo): + if self._con is None: + self._cachekey = self.emptykey + self._ondiskcachekey = self.emptykey + assert self._cachekey is not None @util.propertycache def _con(self): @@ -459,7 +519,6 @@ repo = self._repo() if repo is None: return None - cachekey = self._cachekey(repo) con = sqlite3.connect(self._path) con.text_factory = str cur = con.execute(_queryexist) @@ -467,17 +526,19 @@ self._valid = False return None meta = con.execute(_querymeta).fetchone() - if meta != cachekey: + if meta is None or meta[0] != self._schemaversion: self._valid = False return None - self._ondiskcachekey = meta + self._cachekey = self._ondiskcachekey = meta[1:] return con def save(self, repo): + if self._cachekey is None: + return + if self._cachekey == self._ondiskcachekey and not self._new: + return repo = repo.unfiltered() try: - if not self._new: - return with repo.lock(): self._save(repo) except error.LockError: @@ -499,26 +560,27 @@ for req in _sqliteschema: con.execute(req) - con.execute(_newmeta, self._cachekey(repo)) + con.execute(_newmeta, self._fullcachekey) else: con = self._con if self._ondiskcachekey is not None: meta = con.execute(_querymeta).fetchone() - if meta != self._ondiskcachekey: + if meta[1:] != self._ondiskcachekey: # drifting is currently an issue because this means another # process might have already added the cache line we are about # to add. This will confuse sqlite msg = _('obshashrange cache: skipping write, ' 'database drifted under my feet\n') - data = (meta[2], meta[1], self._ondisktiprev, self._ondisktipnode) + data = (meta[2], meta[1], self._ondiskcachekey[0], self._ondiskcachekey[1]) repo.ui.warn(msg) + return data = ((rangeid[0], rangeid[1], self[rangeid]) for rangeid in self._new) con.executemany(_updateobshash, data) - cachekey = self._cachekey(repo) + cachekey = self._fullcachekey con.execute(_newmeta, cachekey) con.commit() self._new.clear() - self._ondiskcachekey = cachekey + self._ondiskcachekey = self._cachekey @eh.wrapfunction(obsolete.obsstore, '_addmarkers') def _addmarkers(orig, obsstore, *args, **kwargs): @@ -549,10 +611,31 @@ class obshashrepo(repo.__class__): @localrepo.unfilteredmethod def destroyed(self): - if 'stablerange' in vars(self): - del self.stablerange + if 'obsstore' in vars(self): + self.obsstore.rangeobshashcache.clear() super(obshashrepo, self).destroyed() + def transaction(self, *args, **kwargs): + tr = super(obshashrepo, self).transaction(*args, **kwargs) + reporef = weakref.ref(self) + + def _warmcache(tr): + repo = reporef() + if repo is None: + return + if not repo.ui.configbool('experimental', 'obshashrange', False): + 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 writing on disk. + self.obsstore.rangeobshashcache.update(repo) + self.obsstore.rangeobshashcache.save(repo) + + tr.addpostclose('warmcache-20-obscacherange', _warmcache) + return tr + repo.__class__ = obshashrepo ### wire protocol commands @@ -570,6 +653,7 @@ if maxrev is not None: repo.stablerange.warmup(repo, upto=maxrev) result = [] + repo.obsstore.rangeobshashcache.update(repo) for r in ranges: if r[0] is None: result.append(node.wdirid)
--- a/hgext3rd/evolve/stablerange.py Tue May 16 23:37:10 2017 -0700 +++ b/hgext3rd/evolve/stablerange.py Wed May 17 09:52:06 2017 +0200 @@ -902,7 +902,7 @@ # new nodes ! repo.stablerange.warmup(repo) - tr.addpostclose('warmcache-stablerange', _warmcache) + tr.addpostclose('warmcache-10-stablerange', _warmcache) return tr repo.__class__ = stablerangerepo
--- a/hgext3rd/topic/__init__.py Tue May 16 23:37:10 2017 -0700 +++ b/hgext3rd/topic/__init__.py Wed May 17 09:52:06 2017 +0200 @@ -100,6 +100,10 @@ 'topic.stack.summary.behindcount': 'cyan', 'topic.stack.summary.behinderror': 'red', 'topic.stack.summary.headcount.multiple': 'yellow', + # default color to help log output and thg + # (first pick I could think off, update as needed + 'log.topic': 'green_background', + 'topic.active': 'green', } testedwith = '3.9' @@ -157,6 +161,10 @@ if not isinstance(repo, localrepo.localrepository): return # this can be a peer in the ssh case (puzzling) + if repo.ui.config('experimental', 'thg.displaynames', None) is None: + repo.ui.setconfig('experimental', 'thg.displaynames', 'topics', + source='topic-extension') + class topicrepo(repo.__class__): def _restrictcapabilities(self, caps):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-check-commit.t Wed May 17 09:52:06 2017 +0200 @@ -0,0 +1,22 @@ +#require test-repo + +Enable obsolescence to avoid the warning issue when obsmarker are found + + $ cat << EOF >> $HGRCPATH + > [experimental] + > evolution=all + > EOF + +Go back in the hg repo + + $ cd $TESTDIR/.. + + $ for node in `hg log --rev 'not public() and ::. and not desc("# no-check-commit")' --template '{node|short}\n'`; do + > hg export $node | ${RUNTESTDIR}/../contrib/check-commit > ${TESTTMP}/check-commit.out + > if [ $? -ne 0 ]; then + > echo "Revision $node does not comply with rules" + > echo '------------------------------------------------------' + > cat ${TESTTMP}/check-commit.out + > echo + > fi + > done
--- a/tests/test-evolve-obshistory.t Tue May 16 23:37:10 2017 -0700 +++ b/tests/test-evolve-obshistory.t Wed May 17 09:52:06 2017 +0200 @@ -25,9 +25,11 @@ $ mkcommit ROOT $ mkcommit A0 $ echo 42 >> A0 - $ hg amend -m "A1" + $ hg amend -m "A1 + > + > Better commit message" $ hg log --hidden -G - @ changeset: 3:a468dc9b3633 + @ changeset: 3:4ae3a4151de9 | tag: tip | parent: 0:ea207398892e | user: test @@ -51,15 +53,69 @@ Actual test ----------- - + $ hg debugobshistory 4ae3a4151de9 + 4ae3a4151de9 (3) A1 + 471f378eab4c (1) A0 + rewritten by test (*20*) as 4ae3a4151de9 (glob) + $ hg debugobshistory 4ae3a4151de9 -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [], + "debugobshistory.node": "4ae3a4151de9", + "debugobshistory.rev": 3, + "debugobshistory.shortdescription": "A1" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "4ae3a4151de9" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ hg debugobshistory --hidden 471f378eab4c + 471f378eab4c (1) A0 + rewritten by test (*20*) as 4ae3a4151de9 (glob) + $ hg debugobshistory --hidden 471f378eab4c -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "4ae3a4151de9" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] $ hg update 471f378eab4c abort: hidden revision '471f378eab4c'! - (use --hidden to access hidden revisions; successor: a468dc9b3633) + (use --hidden to access hidden revisions; successor: 4ae3a4151de9) [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) + (use 'hg evolve' to update to its successor: 4ae3a4151de9) Test output with pruned commit ============================== @@ -93,10 +149,59 @@ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved working directory now at 471f378eab4c 1 changesets pruned + $ hg log --hidden -G + x changeset: 2:0dec01379d3b + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: B0 + | + @ 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 debugobshistory 'desc(B0)' --hidden + 0dec01379d3b (2) B0 + pruned by test (*20*) (glob) + $ hg debugobshistory 'desc(B0)' --hidden -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.verb": "pruned" + } + ], + "debugobshistory.node": "0dec01379d3b", + "debugobshistory.rev": 2, + "debugobshistory.shortdescription": "B0" + } + ] + $ hg debugobshistory 'desc(A0)' + 471f378eab4c (1) A0 + $ hg debugobshistory 'desc(A0)' -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] $ hg up 1 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg up 0dec01379d3b @@ -195,6 +300,95 @@ Actual test ----------- + $ hg debugobshistory 471597cad322 --hidden + 471597cad322 (1) A0 + rewritten by test (*20*) as 337fec4d2edc, f257fde29c7a (glob) + $ hg debugobshistory 471597cad322 --hidden -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "337fec4d2edc", + "f257fde29c7a" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471597cad322", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ hg debugobshistory 337fec4d2edc + 337fec4d2edc (2) A0 + 471597cad322 (1) A0 + rewritten by test (*20*) as 337fec4d2edc, f257fde29c7a (glob) + $ hg debugobshistory 337fec4d2edc -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [], + "debugobshistory.node": "337fec4d2edc", + "debugobshistory.rev": 2, + "debugobshistory.shortdescription": "A0" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "337fec4d2edc", + "f257fde29c7a" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471597cad322", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ hg debugobshistory f257fde29c7a + f257fde29c7a (3) A0 + 471597cad322 (1) A0 + rewritten by test (*20*) as 337fec4d2edc, f257fde29c7a (glob) + $ hg debugobshistory f257fde29c7a -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [], + "debugobshistory.node": "f257fde29c7a", + "debugobshistory.rev": 3, + "debugobshistory.shortdescription": "A0" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "337fec4d2edc", + "f257fde29c7a" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471597cad322", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] $ hg update 471597cad322 abort: hidden revision '471597cad322'! (use --hidden to access hidden revisions; successors: 337fec4d2edc, f257fde29c7a) @@ -357,6 +551,74 @@ Actual test ----------- + $ hg debugobshistory de7290d8b885 --hidden + de7290d8b885 (1) A0 + rewritten by test (*20*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob) + $ hg debugobshistory de7290d8b885 --hidden -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "1ae8bc733a14", + "337fec4d2edc", + "c7f044602e9b", + "f257fde29c7a" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "de7290d8b885", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ hg debugobshistory c7f044602e9b + c7f044602e9b (5) A0 + de7290d8b885 (1) A0 + rewritten by test (*20*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob) + $ hg debugobshistory c7f044602e9b -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [], + "debugobshistory.node": "c7f044602e9b", + "debugobshistory.rev": 5, + "debugobshistory.shortdescription": "A0" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "1ae8bc733a14", + "337fec4d2edc", + "c7f044602e9b", + "f257fde29c7a" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "de7290d8b885", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ hg debugobshistory 2:5 + 337fec4d2edc (2) A0 + de7290d8b885 (1) A0 + rewritten by test (*20*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob) + f257fde29c7a (3) A0 + 1ae8bc733a14 (4) A0 + c7f044602e9b (5) A0 $ hg update de7290d8b885 abort: hidden revision 'de7290d8b885'! (use --hidden to access hidden revisions; successors: 337fec4d2edc, f257fde29c7a and 2 more) @@ -423,6 +685,105 @@ Actual test ----------- + $ hg debugobshistory --hidden 471f378eab4c + 471f378eab4c (1) A0 + rewritten by test (*20*) as eb5a0daa2192 (glob) + $ hg debugobshistory --hidden 471f378eab4c -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "eb5a0daa2192" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ hg debugobshistory --hidden 0dec01379d3b + 0dec01379d3b (2) B0 + rewritten by test (*20*) as eb5a0daa2192 (glob) + $ hg debugobshistory --hidden 0dec01379d3b -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "eb5a0daa2192" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "0dec01379d3b", + "debugobshistory.rev": 2, + "debugobshistory.shortdescription": "B0" + } + ] + $ hg debugobshistory eb5a0daa2192 + eb5a0daa2192 (3) C0 + 471f378eab4c (1) A0 + rewritten by test (*20*) as eb5a0daa2192 (glob) + 0dec01379d3b (2) B0 + rewritten by test (*20*) as eb5a0daa2192 (glob) + $ hg debugobshistory eb5a0daa2192 -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [], + "debugobshistory.node": "eb5a0daa2192", + "debugobshistory.rev": 3, + "debugobshistory.shortdescription": "C0" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "eb5a0daa2192" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "eb5a0daa2192" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "0dec01379d3b", + "debugobshistory.rev": 2, + "debugobshistory.shortdescription": "B0" + } + ] $ hg update 471f378eab4c abort: hidden revision '471f378eab4c'! (use --hidden to access hidden revisions; successor: eb5a0daa2192) @@ -506,6 +867,128 @@ Actual test ----------- + $ hg debugobshistory --hidden 471f378eab4c + 471f378eab4c (1) A0 + rewritten by test (*20*) as 65b757b745b9 (glob) + rewritten by test (*20*) as fdf9bde5129a (glob) + $ hg debugobshistory --hidden 471f378eab4c -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "65b757b745b9" + ], + "debugobshistory.verb": "rewritten" + }, + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "fdf9bde5129a" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ hg debugobshistory fdf9bde5129a + fdf9bde5129a (2) A1 + 471f378eab4c (1) A0 + rewritten by test (*20*) as 65b757b745b9 (glob) + rewritten by test (*20*) as fdf9bde5129a (glob) + $ hg debugobshistory fdf9bde5129a -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [], + "debugobshistory.node": "fdf9bde5129a", + "debugobshistory.rev": 2, + "debugobshistory.shortdescription": "A1" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "65b757b745b9" + ], + "debugobshistory.verb": "rewritten" + }, + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "fdf9bde5129a" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ hg debugobshistory 65b757b745b9 + 65b757b745b9 (3) A2 + 471f378eab4c (1) A0 + rewritten by test (*20*) as 65b757b745b9 (glob) + rewritten by test (*20*) as fdf9bde5129a (glob) + $ hg debugobshistory 65b757b745b9 -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [], + "debugobshistory.node": "65b757b745b9", + "debugobshistory.rev": 3, + "debugobshistory.shortdescription": "A2" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "65b757b745b9" + ], + "debugobshistory.verb": "rewritten" + }, + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "fdf9bde5129a" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] $ hg update 471f378eab4c abort: hidden revision '471f378eab4c'! (use --hidden to access hidden revisions; diverged) @@ -514,3 +997,312 @@ 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) + +Test output with amended + folded commit +======================================== + +Test setup +---------- + + $ hg init $TESTTMP/local-amend-fold + $ cd $TESTTMP/local-amend-fold + $ mkcommit ROOT + $ mkcommit A0 + $ mkcommit B0 + $ hg amend -m "B1" + $ hg log --hidden -G + @ changeset: 3:b7ea6d14e664 + | tag: tip + | parent: 1:471f378eab4c + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: B1 + | + | x changeset: 2:0dec01379d3b + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: B0 + | + o changeset: 1:471f378eab4c + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg fold --exact -r 'desc(A0) + desc(B1)' --date "0 0" -m "C0" + 2 changesets folded + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log --hidden -G + @ changeset: 4:eb5a0daa2192 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: C0 + | + | x changeset: 3:b7ea6d14e664 + | | parent: 1:471f378eab4c + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: B1 + | | + | | x changeset: 2:0dec01379d3b + | |/ user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: B0 + | | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + Actual test + ----------- + + $ hg debugobshistory --hidden 471f378eab4c + 471f378eab4c (1) A0 + rewritten by test (*20*) as eb5a0daa2192 (glob) + $ hg debugobshistory --hidden 471f378eab4c -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "eb5a0daa2192" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ hg debugobshistory --hidden 0dec01379d3b + 0dec01379d3b (2) B0 + rewritten by test (*20*) as b7ea6d14e664 (glob) + $ hg debugobshistory --hidden 0dec01379d3b -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "b7ea6d14e664" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "0dec01379d3b", + "debugobshistory.rev": 2, + "debugobshistory.shortdescription": "B0" + } + ] + $ hg debugobshistory eb5a0daa2192 + eb5a0daa2192 (4) C0 + b7ea6d14e664 (3) B1 + rewritten by test (*20*) as eb5a0daa2192 (glob) + 0dec01379d3b (2) B0 + rewritten by test (*20*) as b7ea6d14e664 (glob) + 471f378eab4c (1) A0 + rewritten by test (*20*) as eb5a0daa2192 (glob) + $ hg debugobshistory eb5a0daa2192 -Tjson | python -m json.tool + [ + { + "debugobshistory.markers": [], + "debugobshistory.node": "eb5a0daa2192", + "debugobshistory.rev": 4, + "debugobshistory.shortdescription": "C0" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "eb5a0daa2192" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "b7ea6d14e664", + "debugobshistory.rev": 3, + "debugobshistory.shortdescription": "B1" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "b7ea6d14e664" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "0dec01379d3b", + "debugobshistory.rev": 2, + "debugobshistory.shortdescription": "B0" + }, + { + "debugobshistory.markers": [ + { + "debugobshistory.marker_date": [ + *, (glob) + 0 (glob) + ], + "debugobshistory.marker_user": "test", + "debugobshistory.succnodes": [ + "eb5a0daa2192" + ], + "debugobshistory.verb": "rewritten" + } + ], + "debugobshistory.node": "471f378eab4c", + "debugobshistory.rev": 1, + "debugobshistory.shortdescription": "A0" + } + ] + $ 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 --hidden 0dec01379d3b + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (0dec01379d3b) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + $ hg update 0dec01379d3b + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + working directory parent is obsolete! (0dec01379d3b) + (use 'hg evolve' to update to its successor: eb5a0daa2192) + $ hg update --hidden 'desc(B0)' + 0 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 pushed and pulled obs markers +============================================== + +Test setup +---------- + + $ hg init $TESTTMP/local-remote-markers-1 + $ cd $TESTTMP/local-remote-markers-1 + $ mkcommit ROOT + $ mkcommit A0 + $ hg log --hidden -G + @ changeset: 1:471f378eab4c + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ hg clone $TESTTMP/local-remote-markers-1 $TESTTMP/local-remote-markers-2 + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd $TESTTMP/local-remote-markers-2 + $ hg log --hidden -G + @ changeset: 1:471f378eab4c + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + $ cd $TESTTMP/local-remote-markers-1 + $ hg amend -m "A1" + $ hg amend -m "A2" + $ hg log --hidden -G + @ changeset: 3:7a230b46bf61 + | tag: tip + | parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A2 + | + | x changeset: 2:fdf9bde5129a + |/ parent: 0:ea207398892e + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A1 + | + | x changeset: 1:471f378eab4c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: A0 + | + o changeset: 0:ea207398892e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: ROOT + + Actual test + ----------- + + $ hg debugobshistory 7a230b46bf61 + 7a230b46bf61 (3) A2 + fdf9bde5129a (2) A1 + rewritten by test (*20*) as 7a230b46bf61 (glob) + 471f378eab4c (1) A0 + rewritten by test (*20*) as fdf9bde5129a (glob) + $ cd $TESTTMP/local-remote-markers-2 + $ hg pull + pulling from $TESTTMP/local-remote-markers-1 + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 0 changes to 1 files (+1 heads) + 2 new obsolescence markers + (run 'hg heads' to see heads, 'hg merge' to merge) + working directory parent is obsolete! (471f378eab4c) + (use 'hg evolve' to update to its successor: 7a230b46bf61) + $ hg debugobshistory 7a230b46bf61 --traceback + 7a230b46bf61 (2) A2 + fdf9bde5129a + rewritten by test (*20*) as 7a230b46bf61 (glob) + 471f378eab4c (1) A0 + rewritten by test (*20*) as fdf9bde5129a (glob) + $ hg debugobshistory 7a230b46bf61 --color=debug + [evolve.short_node|7a230b46bf61] [evolve.rev|(2)] [evolve.short_description|A2] + [evolve.short_node evolve.missing_change_ctx|fdf9bde5129a] + [evolve.verb|rewritten] by [evolve.user|test] [evolve.date|(*20*)] as [evolve.short_node|7a230b46bf61] (glob) + [evolve.short_node|471f378eab4c] [evolve.rev|(1)] [evolve.short_description|A0] + [evolve.verb|rewritten] by [evolve.user|test] [evolve.date|(*20*)] as [evolve.short_node|fdf9bde5129a] (glob)