mercurial/repoview.py
changeset 43076 2372284d9457
parent 42231 d345627d104b
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    17     phases,
    17     phases,
    18     pycompat,
    18     pycompat,
    19     tags as tagsmod,
    19     tags as tagsmod,
    20     util,
    20     util,
    21 )
    21 )
    22 from .utils import (
    22 from .utils import repoviewutil
    23     repoviewutil,
    23 
    24 )
       
    25 
    24 
    26 def hideablerevs(repo):
    25 def hideablerevs(repo):
    27     """Revision candidates to be hidden
    26     """Revision candidates to be hidden
    28 
    27 
    29     This is a standalone function to allow extensions to wrap it.
    28     This is a standalone function to allow extensions to wrap it.
    34     assertions and lead to crashes."""
    33     assertions and lead to crashes."""
    35     obsoletes = obsolete.getrevs(repo, 'obsolete')
    34     obsoletes = obsolete.getrevs(repo, 'obsolete')
    36     internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
    35     internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
    37     internals = frozenset(internals)
    36     internals = frozenset(internals)
    38     return obsoletes | internals
    37     return obsoletes | internals
       
    38 
    39 
    39 
    40 def pinnedrevs(repo):
    40 def pinnedrevs(repo):
    41     """revisions blocking hidden changesets from being filtered
    41     """revisions blocking hidden changesets from being filtered
    42     """
    42     """
    43 
    43 
    70         for p in pfunc(stack.pop()):
    70         for p in pfunc(stack.pop()):
    71             if p != nullrev and p in hidden:
    71             if p != nullrev and p in hidden:
    72                 hidden.remove(p)
    72                 hidden.remove(p)
    73                 stack.append(p)
    73                 stack.append(p)
    74 
    74 
       
    75 
    75 def computehidden(repo, visibilityexceptions=None):
    76 def computehidden(repo, visibilityexceptions=None):
    76     """compute the set of hidden revision to filter
    77     """compute the set of hidden revision to filter
    77 
    78 
    78     During most operation hidden should be filtered."""
    79     During most operation hidden should be filtered."""
    79     assert not repo.changelog.filteredrevs
    80     assert not repo.changelog.filteredrevs
    88 
    89 
    89         visible = mutable - hidden
    90         visible = mutable - hidden
    90         _revealancestors(pfunc, hidden, visible)
    91         _revealancestors(pfunc, hidden, visible)
    91     return frozenset(hidden)
    92     return frozenset(hidden)
    92 
    93 
       
    94 
    93 def computesecret(repo, visibilityexceptions=None):
    95 def computesecret(repo, visibilityexceptions=None):
    94     """compute the set of revision that can never be exposed through hgweb
    96     """compute the set of revision that can never be exposed through hgweb
    95 
    97 
    96     Changeset in the secret phase (or above) should stay unaccessible."""
    98     Changeset in the secret phase (or above) should stay unaccessible."""
    97     assert not repo.changelog.filteredrevs
    99     assert not repo.changelog.filteredrevs
    98     secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
   100     secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
    99     return frozenset(secrets)
   101     return frozenset(secrets)
       
   102 
   100 
   103 
   101 def computeunserved(repo, visibilityexceptions=None):
   104 def computeunserved(repo, visibilityexceptions=None):
   102     """compute the set of revision that should be filtered when used a server
   105     """compute the set of revision that should be filtered when used a server
   103 
   106 
   104     Secret and hidden changeset should not pretend to be here."""
   107     Secret and hidden changeset should not pretend to be here."""
   109     if secrets:
   112     if secrets:
   110         return frozenset(hiddens | secrets)
   113         return frozenset(hiddens | secrets)
   111     else:
   114     else:
   112         return hiddens
   115         return hiddens
   113 
   116 
       
   117 
   114 def computemutable(repo, visibilityexceptions=None):
   118 def computemutable(repo, visibilityexceptions=None):
   115     assert not repo.changelog.filteredrevs
   119     assert not repo.changelog.filteredrevs
   116     # fast check to avoid revset call on huge repo
   120     # fast check to avoid revset call on huge repo
   117     if any(repo._phasecache.phaseroots[1:]):
   121     if any(repo._phasecache.phaseroots[1:]):
   118         getphase = repo._phasecache.phase
   122         getphase = repo._phasecache.phase
   119         maymutable = filterrevs(repo, 'base')
   123         maymutable = filterrevs(repo, 'base')
   120         return frozenset(r for r in maymutable if getphase(repo, r))
   124         return frozenset(r for r in maymutable if getphase(repo, r))
   121     return frozenset()
   125     return frozenset()
       
   126 
   122 
   127 
   123 def computeimpactable(repo, visibilityexceptions=None):
   128 def computeimpactable(repo, visibilityexceptions=None):
   124     """Everything impactable by mutable revision
   129     """Everything impactable by mutable revision
   125 
   130 
   126     The immutable filter still have some chance to get invalidated. This will
   131     The immutable filter still have some chance to get invalidated. This will
   143             firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
   148             firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
   144     # protect from nullrev root
   149     # protect from nullrev root
   145     firstmutable = max(0, firstmutable)
   150     firstmutable = max(0, firstmutable)
   146     return frozenset(pycompat.xrange(firstmutable, len(cl)))
   151     return frozenset(pycompat.xrange(firstmutable, len(cl)))
   147 
   152 
       
   153 
   148 # function to compute filtered set
   154 # function to compute filtered set
   149 #
   155 #
   150 # When adding a new filter you MUST update the table at:
   156 # When adding a new filter you MUST update the table at:
   151 #     mercurial.utils.repoviewutil.subsettable
   157 #     mercurial.utils.repoviewutil.subsettable
   152 # Otherwise your filter will have to recompute all its branches cache
   158 # Otherwise your filter will have to recompute all its branches cache
   153 # from scratch (very slow).
   159 # from scratch (very slow).
   154 filtertable = {'visible': computehidden,
   160 filtertable = {
   155                'visible-hidden': computehidden,
   161     'visible': computehidden,
   156                'served.hidden': computesecret,
   162     'visible-hidden': computehidden,
   157                'served': computeunserved,
   163     'served.hidden': computesecret,
   158                'immutable':  computemutable,
   164     'served': computeunserved,
   159                'base':  computeimpactable}
   165     'immutable': computemutable,
       
   166     'base': computeimpactable,
       
   167 }
   160 
   168 
   161 _basefiltername = list(filtertable)
   169 _basefiltername = list(filtertable)
       
   170 
   162 
   171 
   163 def extrafilter(ui):
   172 def extrafilter(ui):
   164     """initialize extra filter and return its id
   173     """initialize extra filter and return its id
   165 
   174 
   166     If extra filtering is configured, we make sure the associated filtered view
   175     If extra filtering is configured, we make sure the associated filtered view
   176 
   185 
   177     subsettable = repoviewutil.subsettable
   186     subsettable = repoviewutil.subsettable
   178 
   187 
   179     if combine('base') not in filtertable:
   188     if combine('base') not in filtertable:
   180         for name in _basefiltername:
   189         for name in _basefiltername:
       
   190 
   181             def extrafilteredrevs(repo, *args, **kwargs):
   191             def extrafilteredrevs(repo, *args, **kwargs):
   182                 baserevs = filtertable[name](repo, *args, **kwargs)
   192                 baserevs = filtertable[name](repo, *args, **kwargs)
   183                 extrarevs = frozenset(repo.revs(frevs))
   193                 extrarevs = frozenset(repo.revs(frevs))
   184                 return baserevs | extrarevs
   194                 return baserevs | extrarevs
       
   195 
   185             filtertable[combine(name)] = extrafilteredrevs
   196             filtertable[combine(name)] = extrafilteredrevs
   186             if name in subsettable:
   197             if name in subsettable:
   187                 subsettable[combine(name)] = combine(subsettable[name])
   198                 subsettable[combine(name)] = combine(subsettable[name])
   188     return fid
   199     return fid
       
   200 
   189 
   201 
   190 def filterrevs(repo, filtername, visibilityexceptions=None):
   202 def filterrevs(repo, filtername, visibilityexceptions=None):
   191     """returns set of filtered revision for this filter name
   203     """returns set of filtered revision for this filter name
   192 
   204 
   193     visibilityexceptions is a set of revs which must are exceptions for
   205     visibilityexceptions is a set of revs which must are exceptions for
   198         if visibilityexceptions:
   210         if visibilityexceptions:
   199             return func(repo.unfiltered, visibilityexceptions)
   211             return func(repo.unfiltered, visibilityexceptions)
   200         repo.filteredrevcache[filtername] = func(repo.unfiltered())
   212         repo.filteredrevcache[filtername] = func(repo.unfiltered())
   201     return repo.filteredrevcache[filtername]
   213     return repo.filteredrevcache[filtername]
   202 
   214 
       
   215 
   203 class repoview(object):
   216 class repoview(object):
   204     """Provide a read/write view of a repo through a filtered changelog
   217     """Provide a read/write view of a repo through a filtered changelog
   205 
   218 
   206     This object is used to access a filtered version of a repository without
   219     This object is used to access a filtered version of a repository without
   207     altering the original repository object itself. We can not alter the
   220     altering the original repository object itself. We can not alter the
   239         object.__setattr__(self, r'_unfilteredrepo', repo)
   252         object.__setattr__(self, r'_unfilteredrepo', repo)
   240         object.__setattr__(self, r'filtername', filtername)
   253         object.__setattr__(self, r'filtername', filtername)
   241         object.__setattr__(self, r'_clcachekey', None)
   254         object.__setattr__(self, r'_clcachekey', None)
   242         object.__setattr__(self, r'_clcache', None)
   255         object.__setattr__(self, r'_clcache', None)
   243         # revs which are exceptions and must not be hidden
   256         # revs which are exceptions and must not be hidden
   244         object.__setattr__(self, r'_visibilityexceptions',
   257         object.__setattr__(self, r'_visibilityexceptions', visibilityexceptions)
   245                            visibilityexceptions)
       
   246 
   258 
   247     # not a propertycache on purpose we shall implement a proper cache later
   259     # not a propertycache on purpose we shall implement a proper cache later
   248     @property
   260     @property
   249     def changelog(self):
   261     def changelog(self):
   250         """return a filtered version of the changeset
   262         """return a filtered version of the changeset
   261         revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
   273         revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
   262         cl = self._clcache
   274         cl = self._clcache
   263         newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
   275         newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
   264         # if cl.index is not unfiindex, unfi.changelog would be
   276         # if cl.index is not unfiindex, unfi.changelog would be
   265         # recreated, and our clcache refers to garbage object
   277         # recreated, and our clcache refers to garbage object
   266         if (cl is not None and
   278         if cl is not None and (
   267             (cl.index is not unfiindex or newkey != self._clcachekey)):
   279             cl.index is not unfiindex or newkey != self._clcachekey
       
   280         ):
   268             cl = None
   281             cl = None
   269         # could have been made None by the previous if
   282         # could have been made None by the previous if
   270         if cl is None:
   283         if cl is None:
   271             cl = copy.copy(unfichangelog)
   284             cl = copy.copy(unfichangelog)
   272             cl.filteredrevs = revs
   285             cl.filteredrevs = revs
   283         if name == self.filtername and not visibilityexceptions:
   296         if name == self.filtername and not visibilityexceptions:
   284             return self
   297             return self
   285         return self.unfiltered().filtered(name, visibilityexceptions)
   298         return self.unfiltered().filtered(name, visibilityexceptions)
   286 
   299 
   287     def __repr__(self):
   300     def __repr__(self):
   288         return r'<%s:%s %r>' % (self.__class__.__name__,
   301         return r'<%s:%s %r>' % (
   289                                 pycompat.sysstr(self.filtername),
   302             self.__class__.__name__,
   290                                 self.unfiltered())
   303             pycompat.sysstr(self.filtername),
       
   304             self.unfiltered(),
       
   305         )
   291 
   306 
   292     # everything access are forwarded to the proxied repo
   307     # everything access are forwarded to the proxied repo
   293     def __getattr__(self, attr):
   308     def __getattr__(self, attr):
   294         return getattr(self._unfilteredrepo, attr)
   309         return getattr(self._unfilteredrepo, attr)
   295 
   310 
   296     def __setattr__(self, attr, value):
   311     def __setattr__(self, attr, value):
   297         return setattr(self._unfilteredrepo, attr, value)
   312         return setattr(self._unfilteredrepo, attr, value)
   298 
   313 
   299     def __delattr__(self, attr):
   314     def __delattr__(self, attr):
   300         return delattr(self._unfilteredrepo, attr)
   315         return delattr(self._unfilteredrepo, attr)
       
   316 
   301 
   317 
   302 # Python <3.4 easily leaks types via __mro__. See
   318 # Python <3.4 easily leaks types via __mro__. See
   303 # https://bugs.python.org/issue17950. We cache dynamically created types
   319 # https://bugs.python.org/issue17950. We cache dynamically created types
   304 # so they won't be leaked on every invocation of repo.filtered().
   320 # so they won't be leaked on every invocation of repo.filtered().
   305 _filteredrepotypes = weakref.WeakKeyDictionary()
   321 _filteredrepotypes = weakref.WeakKeyDictionary()
   306 
   322 
       
   323 
   307 def newtype(base):
   324 def newtype(base):
   308     """Create a new type with the repoview mixin and the given base class"""
   325     """Create a new type with the repoview mixin and the given base class"""
   309     if base not in _filteredrepotypes:
   326     if base not in _filteredrepotypes:
       
   327 
   310         class filteredrepo(repoview, base):
   328         class filteredrepo(repoview, base):
   311             pass
   329             pass
       
   330 
   312         _filteredrepotypes[base] = filteredrepo
   331         _filteredrepotypes[base] = filteredrepo
   313     return _filteredrepotypes[base]
   332     return _filteredrepotypes[base]