Mercurial > evolve
view hgext3rd/serverminitopic.py @ 5919:1a8e86053adb stable
topic: don't lose any file changes when changing topic of a merge commit
This file-related logic is taken directly from cmdutil.amend().
author | Anton Shestakov <av6@dwimlabs.net> |
---|---|
date | Sun, 09 May 2021 00:10:55 +0800 |
parents | ee242f4a2d73 |
children | 1b57516a9a56 |
line wrap: on
line source
"""enable a minimal verison of topic for server ! This extensions is not actively maintained ! We recommand using the main topic extension instead Non publishing repository will see topic as "branch:topic" in the branch field. In addition to adding the extensions, the feature must be manually enabled in the config: [experimental] server-mini-topic = yes """ import hashlib import contextlib from mercurial import ( branchmap, context, encoding, extensions, node, registrar, util, ) from mercurial import wireprotov1server if util.safehasattr(registrar, 'configitem'): configtable = {} configitem = registrar.configitem(configtable) configitem(b'experimental', b'server-mini-topic', default=False, ) # hg <= 5.4 (e2d17974a869) def nonpublicphaseroots(repo): if util.safehasattr(repo._phasecache, 'nonpublicphaseroots'): return repo._phasecache.nonpublicphaseroots(repo) return set().union( *[roots for roots in repo._phasecache.phaseroots[1:] if roots] ) def hasminitopic(repo): """true if minitopic is enabled on the repository (The value is cached on the repository) """ enabled = getattr(repo, '_hasminitopic', None) if enabled is None: enabled = (repo.ui.configbool(b'experimental', b'server-mini-topic') and not repo.publishing()) repo._hasminitopic = enabled return enabled ### make topic visible though "ctx.branch()" def topicbranch(orig, self): branch = orig(self) if hasminitopic(self._repo) and self.phase(): topic = self._changeset.extra.get(b'topic') if topic is not None: topic = encoding.tolocal(topic) branch = b'%s:%s' % (branch, topic) return branch ### avoid caching topic data in rev-branch-cache class revbranchcacheoverlay(object): """revbranch mixin that don't use the cache for non public changeset""" def _init__(self, *args, **kwargs): super(revbranchcacheoverlay, self).__init__(*args, **kwargs) if r'branchinfo' in vars(self): del self.branchinfo def branchinfo(self, rev, changelog=None): """return branch name and close flag for rev, using and updating persistent cache.""" phase = self._repo._phasecache.phase(self._repo, rev) if phase: ctx = self._repo[rev] return ctx.branch(), ctx.closesbranch() return super(revbranchcacheoverlay, self).branchinfo(rev) def reposetup(ui, repo): """install a repo class with a special revbranchcache""" if hasminitopic(repo): repo = repo.unfiltered() class minitopicrepo(repo.__class__): """repository subclass that install the modified cache""" def revbranchcache(self): if self._revbranchcache is None: cache = super(minitopicrepo, self).revbranchcache() class topicawarerbc(revbranchcacheoverlay, cache.__class__): pass cache.__class__ = topicawarerbc if r'branchinfo' in vars(cache): del cache.branchinfo self._revbranchcache = cache return self._revbranchcache repo.__class__ = minitopicrepo ### topic aware branch head cache def _phaseshash(repo, maxrev): """uniq ID for a phase matching a set of rev""" revs = set() cl = repo.changelog fr = cl.filteredrevs nm = cl.nodemap for n in nonpublicphaseroots(repo): r = nm.get(n) if r not in fr and r < maxrev: revs.add(r) key = node.nullid revs = sorted(revs) if revs: s = hashlib.sha1() for rev in revs: s.update(b'%d;' % rev) key = s.digest() return key # 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 _publiconly = set([ b'base', b'immutable', ]) def mighttopic(repo): return hasminitopic(repo) and repo.filtername not in _publiconly class _topiccache(branchmap.branchcache): # combine me with branchmap.branchcache @classmethod def fromfile(cls, repo): orig = super(_topiccache, cls).fromfile return wrapread(orig, repo) 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 args = (_entries, self.tipnode, self.tiprev, self.filteredhash, self._closednodes) if util.safehasattr(self, '_repo'): # hg <= 5.7 (6266d19556ad) args = (self._repo,) + args new = self.__class__(*args) new.phaseshash = self.phaseshash return new 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 self.phaseshash is None: # phasehash at None means this is a branchmap # coming from a public only set return True else: try: valid = self.phaseshash == _phaseshash(repo, self.tiprev) return valid except IndexError: return False def write(self, repo): # we expect (hope) mutable set to be small enough to be that computing # it all the time will be fast enough if not mighttopic(repo): 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. """ super(_topiccache, self).update(repo, revgen) if mighttopic(repo): self.phaseshash = _phaseshash(repo, self.tiprev) def wrapread(orig, repo): # Avoiding to write cache for filter where topic applies is a good step, # but we need to also avoid reading it. Existing branchmap cache might # exists before the turned the feature on. if mighttopic(repo): return None return orig(repo) # advertise topic capabilities def wireprotocaps(orig, repo, proto): caps = orig(repo, proto) if hasminitopic(repo): caps.append(b'topics') return caps # wrap the necessary bit def wrapclass(container, oldname, new): old = getattr(container, oldname) if not issubclass(old, new): targetclass = new # check if someone else already wrapped the class and handle that if not issubclass(new, old): class targetclass(new, old): pass setattr(container, oldname, targetclass) current = getattr(container, oldname) assert issubclass(current, new), (current, new, targetclass) def uisetup(ui): wrapclass(branchmap, 'branchcache', _topiccache) try: # hg <= 4.9 (3461814417f3) extensions.wrapfunction(branchmap, 'read', wrapread) except AttributeError: # Mercurial 5.0; branchcache.fromfile now takes care of this # which is alredy defined on _topiccache pass extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps) extensions.wrapfunction(context.changectx, 'branch', topicbranch)