diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -6,7 +6,7 @@ # GNU General Public License version 2 or any later version. from node import bin, hex, nullid, nullrev, short from i18n import _ -import peer, changegroup, subrepo, discovery, pushkey, obsolete +import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview import changelog, dirstate, filelog, manifest, context, bookmarks, phases import lock, transaction, store, encoding, base85 import scmutil, util, extensions, hook, error, revset @@ -303,6 +303,14 @@ class localrepository(object): 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(repoview.repoview, self.unfiltered().__class__): + pass + return proxycls(self, name) + @repofilecache('bookmarks') def _bookmarks(self): return bookmarks.bmstore(self) diff --git a/mercurial/repoview.py b/mercurial/repoview.py new file mode 100644 --- /dev/null +++ b/mercurial/repoview.py @@ -0,0 +1,94 @@ +# repoview.py - Filtered view of a localrepo object +# +# Copyright 2012 Pierre-Yves David +# Logilab SA +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +import copy + +# function to compute filtered set +filtertable = {} + +def filteredrevs(repo, filtername): + """returns set of filtered revision for this filter name""" + return filtertable[filtername](repo.unfiltered()) + +class repoview(object): + """Provide a read/write view of a repo through a filtered changelog + + This object is used to access a filtered version of a repository without + altering the original repository object itself. We can not alter the + original object for two main reasons: + - It prevents the use of a repo with multiple filters at the same time. In + particular when multiple threads are involved. + - It makes scope of the filtering harder to control. + + This object behaves very closely to the original repository. All attribute + operations are done on the original repository: + - An access to `repoview.someattr` actually returns `repo.someattr`, + - A write to `repoview.someattr` actually sets value of `repo.someattr`, + - A deletion of `repoview.someattr` actually drops `someattr` + from `repo.__dict__`. + + The only exception is the `changelog` property. It is overridden to return + a (surface) copy of `repo.changelog` with some revisions filtered. The + `filtername` attribute of the view control the revisions that need to be + filtered. (the fact the changelog is copied is an implementation detail). + + Unlike attributes, this object intercepts all method calls. This means that + all methods are run on the `repoview` object with the filtered `changelog` + property. For this purpose the simple `repoview` class must be mixed with + the actual class of the repository. This ensures that the resulting + `repoview` object have the very same methods than the repo object. This + leads to the property below. + + repoview.method() --> repo.__class__.method(repoview) + + The inheritance has to be done dynamically because `repo` can be of any + subclasses of `localrepo`. Eg: `bundlerepo` or `httprepo`. + """ + + 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 +