##// END OF EJS Templates
repoview: discard filtered changelog if index isn't shared with unfiltered...
repoview: discard filtered changelog if index isn't shared with unfiltered Before this patch, revisions rollbacked at failure of previous transaction might be visible at subsequent operations unintentionally, if repoview object is reused even after failure of transaction: e.g. command server and HTTP server are typical cases. 'repoview' uses the tuple of values below of unfiltered changelog as "the key" to examine validity of filtered changelog cache. - length - tip node - filtered revisions (as hashed value) - '_delayed' field 'repoview' compares between "the key" of unfiltered changelog at previous caching and now, and reuses filtered changelog cache if no change is detected. But this comparison indicates only that there is no change between unfiltered 'repo.changelog' at last caching and now, but not that filtered changelog cache is valid for current unfiltered one. 'repoview' uses "shallow copy" of unfiltered changelog to create filtered changelog cache. In this case, 'index' buffer of unfiltered changelog is also referred by filtered changelog. At failure of transaction, unfiltered changelog itself is invalidated (= un-referred) on the 'repo' side (see 0a7610758c42 also). But 'index' of it still contains revisions to be rollbacked at this failure, and is referred by filtered changelog. Therefore, even if there is no change between unfiltered 'repo.changelog' at last caching and now, steps below makes rollbacked revisions visible via filtered changelog unintentionally. 1. instantiate unfiltered changelog as 'repo.changelog' (call it CL1) 2. make filtered (= shallow copy of) CL1 (call it FCL1) 3. cache FCL1 with "the key" of CL1 4. revisions are appended to 'index', which is shared by CL1 and FCL1 5. invalidate 'repo.changelog' (= CL1) at failure of transaction 6. instantiate 'repo.changelog' again at next operation (call it CL2) CL2 doesn't have revisions added at (4), because it is instantiated from '00changelog.i', which isn't changed while failed transaction. 7. compare between "the key" of CL1 and CL2 8. FCL1 cached at (3) is reused, because comparison at (7) doesn't detect change between CL1 at (1) and CL2 9. revisions rollbacked at (5) are visible via FCL1 unintentionally, because FCL1 still refers 'index' changed at (4) The root cause of this issue is that there is no examination about validity of filtered changelog cache against current unfiltered one. This patch discards filtered changelog cache, if its 'index' object isn't shared with unfiltered one. BTW, at the time of this patch, redundant truncation of '00changelog.i' at failure of transaction (see 0a7610758c42 for detail) often prevents "hg serve" from making already rollbacked revisions visible, because updating timestamps of '00changelog.i' by truncation makes "hg serve" discard old repoview object with invalid filtered changelog cache. This is reason why this issue is overlooked before this patch, even though test-bundle2-exchange.t has tests in similar situation: failure of "hg push" via HTTP by pretxnclose hook on server side doesn't prevent subsequent commands from looking up outgoing revisions correctly. But timestamp on the filesystem doesn't have enough resolution for recent computation power, and it can't be assumed that this avoidance always works as expected. Therefore, without this patch, this issue might appear occasionally.

File last commit:

r27585:60bf90eb default
r28265:33292621 default
Show More
registrar.py
128 lines | 3.7 KiB | text/x-python | PythonLexer
FUJIWARA Katsunori
registrar: add funcregistrar class to register function for specific purpose...
r27583 # registrar.py - utilities to register function for specific purpose
#
# Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
from . import (
util,
)
class funcregistrar(object):
"""Base of decorator to register a fuction for specific purpose
The least derived class can be defined by overriding 'table' and
'formatdoc', for example::
symbols = {}
class keyword(funcregistrar):
table = symbols
formatdoc = ":%s: %s"
@keyword('bar')
def barfunc(*args, **kwargs):
'''Explanation of bar keyword ....
'''
pass
In this case:
- 'barfunc' is registered as 'bar' in 'symbols'
- online help uses ":bar: Explanation of bar keyword"
"""
def __init__(self, decl):
"""'decl' is a name or more descriptive string of a function
Specification of 'decl' depends on registration purpose.
"""
self.decl = decl
table = None
def __call__(self, func):
"""Execute actual registration for specified function
"""
name = self.getname()
if func.__doc__ and not util.safehasattr(func, '_origdoc'):
doc = func.__doc__.strip()
func._origdoc = doc
if callable(self.formatdoc):
func.__doc__ = self.formatdoc(doc)
else:
# convenient shortcut for simple format
func.__doc__ = self.formatdoc % (self.decl, doc)
self.table[name] = func
self.extraaction(name, func)
return func
def getname(self):
"""Return the name of the registered function from self.decl
Derived class should override this, if it allows more
descriptive 'decl' string than just a name.
"""
return self.decl
FUJIWARA Katsunori
revset: use decorator to register a function as revset predicate...
r27584 def parsefuncdecl(self):
"""Parse function declaration and return the name of function in it
"""
i = self.decl.find('(')
if i > 0:
return self.decl[:i]
else:
return self.decl
FUJIWARA Katsunori
registrar: add funcregistrar class to register function for specific purpose...
r27583 def formatdoc(self, doc):
"""Return formatted document of the registered function for help
'doc' is '__doc__.strip()' of the registered function.
If this is overridden by non-callable object in derived class,
such value is treated as "format string" and used to format
document by 'self.formatdoc % (self.decl, doc)' for convenience.
"""
raise NotImplementedError()
def extraaction(self, name, func):
"""Execute exra action for registered function, if needed
"""
pass
FUJIWARA Katsunori
registrar: add delayregistrar class to register function in extensions...
r27585
class delayregistrar(object):
"""Decorator to delay actual registration until uisetup or so
For example, the decorator class to delay registration by
'keyword' funcregistrar can be defined as below::
class extkeyword(delayregistrar):
registrar = keyword
"""
def __init__(self):
self._list = []
registrar = None
def __call__(self, *args, **kwargs):
"""Return the decorator to delay actual registration until setup
"""
assert self.registrar is not None
def decorator(func):
# invocation of self.registrar() here can detect argument
# mismatching immediately
self._list.append((func, self.registrar(*args, **kwargs)))
return func
return decorator
def setup(self):
"""Execute actual registration
"""
while self._list:
func, decorator = self._list.pop(0)
decorator(func)