Mercurial > evolve
view hgext3rd/topic/topicmap.py @ 5150:e0c091b199bc stable
topic: extend topic gating to descendant
If a changeset with topic as descendant without topic, we need to exclude them
too when using: topic.server-gate-topic-changesets.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Wed, 04 Mar 2020 17:08:05 +0100 |
parents | bb2b4f6c99dc |
children | a4d081923c81 |
line wrap: on
line source
import contextlib import hashlib from mercurial.i18n import _ from mercurial.node import nullid from mercurial import ( branchmap, changegroup, cmdutil, extensions, repoview, util, ) from . import ( common, compat, ) basefilter = set([b'base', b'immutable']) def topicfilter(name): """return a "topic" version of a filter level""" if name in basefilter: return name elif name is None: return None elif name.endswith(b'-topic'): return name else: return name + b'-topic' def istopicfilter(filtername): if filtername is None: return False return filtername.endswith(b'-topic') def gettopicrepo(repo): if not common.hastopicext(repo): return repo filtername = topicfilter(repo.filtername) if filtername == repo.filtername: return repo return repo.filtered(filtername) def _setuptopicfilter(ui): """extend the filter related mapping with topic related one""" funcmap = repoview.filtertable partialmap = branchmap.subsettable # filter level not affected by topic that we should not override for plainname in list(funcmap): newfilter = topicfilter(plainname) if newfilter == plainname: continue def revsfunc(repo, name=plainname): return repoview.filterrevs(repo, name) base = topicfilter(partialmap[plainname]) if newfilter not in funcmap: funcmap[newfilter] = revsfunc partialmap[newfilter] = base funcmap[b'unfiltered-topic'] = lambda repo: frozenset() partialmap[b'unfiltered-topic'] = b'visible-topic' def _phaseshash(repo, maxrev): """uniq ID for a phase matching a set of rev""" revs = set() cl = repo.changelog fr = cl.filteredrevs getrev = compat.getgetrev(cl) for roots in repo._phasecache.phaseroots[1:]: for n in roots: r = getrev(n) if r not in fr and r < maxrev: revs.add(r) key = nullid revs = sorted(revs) if revs: s = hashlib.sha1() for rev in revs: s.update(b'%d;' % rev) key = s.digest() return key def modsetup(ui): """call at uisetup time to install various wrappings""" _setuptopicfilter(ui) _wrapbmcache(ui) extensions.wrapfunction(changegroup.cg1unpacker, 'apply', cgapply) extensions.wrapfunction(cmdutil, 'commitstatus', commitstatus) def cgapply(orig, self, repo, *args, **kwargs): """make sure a topicmap is used when applying a changegroup""" other = repo.filtered(topicfilter(repo.filtername)) return orig(self, other, *args, **kwargs) def commitstatus(orig, repo, node, branch, bheads=None, opts=None): # wrap commit status use the topic branch heads ctx = repo[node] if ctx.topic() and ctx.branch() == branch: bheads = repo.branchheads(b"%s:%s" % (branch, ctx.topic())) ret = orig(repo, node, branch, bheads=bheads, opts=opts) # logic copy-pasted from cmdutil.commitstatus() if opts is None: opts = {} if ctx.topic(): return ret parents = ctx.parents() if (not opts.get(b'amend') and bheads and node not in bheads and not [x for x in parents if x.node() in bheads and x.branch() == branch]): repo.ui.status(_(b"(consider using topic for lightweight branches." b" See 'hg help topic')\n")) return ret def _wrapbmcache(ui): class topiccache(_topiccache, branchmap.branchcache): pass branchmap.branchcache = topiccache try: # Mercurial 4.9 class remotetopiccache(_topiccache, branchmap.remotebranchcache): pass branchmap.remotebranchcache = remotetopiccache def _wrapupdatebmcachemethod(orig, self, repo): # pass in the bound method as the original return _wrapupdatebmcache(orig.__get__(self), repo) extensions.wrapfunction(branchmap.BranchMapCache, 'updatecache', _wrapupdatebmcachemethod) except AttributeError: # Mercurial 4.8 and before extensions.wrapfunction(branchmap, 'updatecache', _wrapupdatebmcache) def _wrapupdatebmcache(orig, repo): previous = getattr(repo, '_autobranchmaptopic', False) try: repo._autobranchmaptopic = False return orig(repo) finally: repo._autobranchmaptopic = previous # needed to prevent reference used for 'super()' call using in branchmap.py to # no go into cycle. (yes, URG) _oldbranchmap = branchmap.branchcache @contextlib.contextmanager def oldbranchmap(): previous = branchmap.branchcache try: branchmap.branchcache = _oldbranchmap yield finally: branchmap.branchcache = previous class _topiccache(object): # combine me with branchmap.branchcache def __init__(self, *args, **kwargs): # super() call may fail otherwise with oldbranchmap(): super(_topiccache, self).__init__(*args, **kwargs) self.phaseshash = None def copy(self): """return an deep copy of the branchcache object""" if util.safehasattr(self, '_entries'): _entries = self._entries else: # hg <= 4.9 (624d6683c705, b137a6793c51) _entries = self new = self.__class__(_entries, self.tipnode, self.tiprev, self.filteredhash, self._closednodes) new.phaseshash = self.phaseshash return new def branchtip(self, branch, topic=b''): '''Return the tipmost open head on branch head, otherwise return the tipmost closed head on branch. Raise KeyError for unknown branch.''' if topic: branch = b'%s:%s' % (branch, topic) return super(_topiccache, self).branchtip(branch) def branchheads(self, branch, closed=False, topic=b''): if topic: branch = b'%s:%s' % (branch, topic) return super(_topiccache, self).branchheads(branch, closed=closed) def validfor(self, repo): """Is the cache content valid regarding a repo - False when cached tipnode is unknown or if we detect a strip. - True when cache is up to date or a subset of current repo.""" valid = super(_topiccache, self).validfor(repo) if not valid: return False elif not istopicfilter(repo.filtername) or self.phaseshash is None: # phasehash at None means this is a branchmap # come from non topic thing return True else: try: valid = self.phaseshash == _phaseshash(repo, self.tiprev) return valid except IndexError: return False def write(self, repo): # we expect mutable set to be small enough to be that computing it all # the time will be fast enough if not istopicfilter(repo.filtername): super(_topiccache, self).write(repo) def update(self, repo, revgen): """Given a branchhead cache, self, that may have extra nodes or be missing heads, and a generator of nodes that are strictly a superset of heads missing, this function updates self to be correct. """ if not istopicfilter(repo.filtername): return super(_topiccache, self).update(repo, revgen) unfi = repo.unfiltered() oldgetbranchinfo = unfi.revbranchcache().branchinfo def branchinfo(r, changelog=None): info = oldgetbranchinfo(r) topic = b'' ctx = unfi[r] if ctx.mutable(): topic = ctx.topic() branch = info[0] if topic: branch = b'%s:%s' % (branch, topic) return (branch, info[1]) try: unfi.revbranchcache().branchinfo = branchinfo super(_topiccache, self).update(repo, revgen) self.phaseshash = _phaseshash(repo, self.tiprev) finally: unfi.revbranchcache().branchinfo = oldgetbranchinfo