Patchwork [2,of,5,filtering,part,2] clfilter: add actual repo filtering mechanism

mail settings
Submitter Pierre-Yves David
Date Dec. 4, 2012, 12:26 a.m.
Message ID <e1e4bc0047f35958b6db.1354580815@yamac.lan>
Download mbox | patch
Permalink /patch/7/
State Superseded, archived
Commit 3a6ddacb7198c99be9f2c62d2bea09a8eda36758
Headers show


Pierre-Yves David - Dec. 4, 2012, 12:26 a.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david at>
# Date 1354576431 -3600
# Node ID e1e4bc0047f35958b6dbdb8dca10e81b60bbb223
# Parent  9ccccb02c2771df1b554c4424d144de5a4dd2e47
clfilter: add actual repo filtering mechanism

We add a `filtered` method on repo. This method return instance of `repoproxy`
that behave exactly as the original repository but with a filtered changelog
attribut. Filter are identified by a "name". Planed filter are "unserved",
"hidden" and mutable.  See the `repoproxier` docstring for details.

Mecanism to compute filtered revision are also installed. Some cache will be installed in later commit.


diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -13,10 +13,11 @@  import scmutil, util, extensions, hook, 
 import match as matchmod
 import merge as mergemod
 import tags as tagsmod
 from lock import release
 import weakref, errno, os, time, inspect
+import copy
 propertycache = util.propertycache
 filecache = scmutil.filecache
 class repofilecache(filecache):
     """All filecache usage on repo are done for logic that should be unfiltered
@@ -55,10 +56,73 @@  def unfilteredmethod(orig):
     """decorate method that always need to be run on unfiltered version"""
     def wrapper(repo, *args, **kwargs):
         return orig(repo.unfiltered(), *args, **kwargs)
     return wrapper
+# function to compute filtered set
+computefiltered = {}
+def _filteredrevs(repo, filtername):
+    """returns set of filtered revision for this filter name"""
+    return computefiltered[filtername](repo.unfiltered())
+class repoproxy(object):
+    """Changelog filterered localrepo proxy object
+    This object act is a proxy for attribute operation. setattr is done on the
+    initial repo, getattr is get from the parent (unless defined on proxy) and
+    delattr operate on proxied repo.
+    The changelog attribute is overridden to return a copy of the original
+    changelog but with some revision filtered.
+    You have to mix this class with the actual localrepo subclass to be able to
+    use the very same logic than the proxied repo. See `filtered` method of
+    local repo for details."""
+    def __init__(self, repo, filtername):
+        object.__setattr__(self, '_unfilteredrepo', repo)
+        object.__setattr__(self, 'filtername', filtername)
+    # not a cacheproperty on purpose we shall implement a proper cache later
+    @property
+    def changelog(self):
+        """return a filtered version of the changeset
+        this changelog must not be used for writing"""
+        # some cache may be implemented later
+        cl = copy.copy(self._unfilteredrepo.changelog)
+        cl.filteredrevs = _filteredrevs(self._unfilteredrepo, self.filtername)
+        return cl
+    def unfiltered(self):
+        """Return an unfiltered version of a repo"""
+        return self._unfilteredrepo
+    def filtered(self, name):
+        """Return a filtered version of a repository"""
+        if name == self.filtername:
+            return self
+        return self.unfiltered().filtered(name)
+    # everything access are forwarded to the proxied repo
+    def __getattr__(self, attr):
+        return getattr(self._unfilteredrepo, attr)
+    def __setattr__(self, attr, value):
+        return setattr(self._unfilteredrepo, attr, value)
+    def __delattr__(self, attr):
+        return delattr(self._unfilteredrepo, attr)
+    # The `requirement` attribut is initialiazed during __init__. But
+    # __getattr__ won't be called as it also exists on the class. We need
+    # explicit forwarding to main repo here
+    @property
+    def requirements(self):
+        return self._unfilteredrepo.requirements
 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
 class localpeer(peer.peerrepository):
     '''peer for a local repo; reflects only the most recent API'''
@@ -301,10 +365,18 @@  class localrepository(object):
         """Return unfiltered version of the repository
         Intended to be ovewritten by filtered repo."""
         return self
+    def filtered(self, name):
+        """Return a filtered version of a repository"""
+        # build a new class with the mixin and the current class
+        # (possibily subclass of the repo)
+        class proxycls(repoproxy, self.unfiltered().__class__):
+            pass
+        return proxycls(self, name)
     def _bookmarks(self):
         return bookmarks.bmstore(self)