Patchwork [2,of,6,V2,RFC] scmutil": add a unionvfs class

login
register
mail settings
Submitter Angel Ezquerra
Date Dec. 26, 2014, 11:46 a.m.
Message ID <3229dbe4251c8fed502c.1419594410@108.1.168.192.in-addr.arpa>
Download mbox | patch
Permalink /patch/7243/
State Changes Requested
Headers show

Comments

Angel Ezquerra - Dec. 26, 2014, 11:46 a.m.
# HG changeset patch
# User Angel Ezquerra <angel.ezquerra@gmail.com>
# Date 1419560658 -3600
#      Fri Dec 26 03:24:18 2014 +0100
# Node ID 3229dbe4251c8fed502c6c7914e03350dd1c5f5d
# Parent  b213f4439d5427cfff6c7fa28927c25e4422a948
scmutil": add a unionvfs class

The unionvfs class is derived from the abstractvfs class. It lets you combine
multiple vfs instances to create a single unionvfs. This unionvfs also takes a
"selection function". This selection function is used to execute methods that
take a path argument (positional or named). The selection function takes the
path and returs the index of the vfs that will be used to execute the chosen
method. Indexes correspond to the order in which vfss are passed to the unionvfs
__init__ method.

# Notes:

- The unionvfs code is quite tedious and repetitive. Most methods are very
similar: take the given path, call the selection function and forward the call
to the selected vfs. Perhaps there is a better, more generic way to do this?
One of the problems to find a better way is that the vfs methods are not
normalized in the way the receive their path argument (sometimes it is
positional, sometimes named, often the path is passed as the first positional
argument, but on occassion it is the second, and often it is a named parameter).

- We implement all abstractvfs methods in addition to __call__ and join. All
other methods will raise NotImplementedError.

- We always get properties from the first, base vfs (and we use a custom
__getattr__ method to do so), but we don't do the same for unknown methods
(because it seems that if a new method were added to the vfs classes we should
explicitly add it to the unionvfs as well, rather than forwarding it to the
main vfs).

Patch

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -331,6 +331,150 @@ 
     def utime(self, path=None, t=None):
         return os.utime(self.join(path), t)
 
+class unionvfs(abstractvfs):
+    """vfs-like class which is the union of several actual vcs object
+
+    This class is a sort of proxy for several vcs objects. It receives a
+    "selection function" followed by one or more vfs objects. The selection
+    function takes the path that is passed to most vfs methods and returns an
+    index to the actual vcs that will actually execute the method call.
+
+    The first vfs object is the "base vfs". It is special in that all
+    properties are read from it.
+
+    The class implements forwarding for all abstractvfs methods as well as the
+    __call__ and join methods. Trying to execute other vfs methods will result
+    in a NotImplementedError exception. Instead we could forward missing methods
+    to the basevfs, but that seems error prone.
+    """
+    def __init__(self, selectfn, basevfs, *vfss):
+        # the unionvfs class __init__ method receives a selection function and
+        # one or more vfs objects.
+        # The selection function must take a string (a path) and return an index
+        # corresponding to one of the vfs objects.
+        # The first vfs object is the base vfs object that will provide the
+        # values of all the union properties.
+        if not callable(selectfn):
+            raise ValueError(
+                _('unionvfs object must receive at valid selection function'))
+        self.selectfn = selectfn
+        self.basevfs = basevfs
+        self.vfss = tuple([basevfs] + list(vfss))
+
+    def _normpath(self, path):
+        if not path:
+            return path
+        return util.normpath(path)
+
+    def selectvcs(self, path):
+        selectedvcsidx = int(self.selectfn(self._normpath(path)))
+        return self.vfss[selectedvcsidx]
+
+    def __getattr__(self, attrib):
+        attr = getattr(self.basevcs, attrib)
+        if callable(attr):
+            raise NotImplementedError(
+                _('unionvfs does not implement method %s') % attrib)
+        return attr
+
+    def __call__(self, path, *args, **kwargs):
+        return self.selectvcs(path).__call__(path, *args, **kwargs)
+
+    def join(self, path):
+        return self.selectvcs(path).join(path)
+
+    def tryread(self, path):
+        return self.selectvcs(path).tryread(path)
+
+    def tryreadlines(self, path, *args, **kwargs):
+        return self.selectvcs(path).tryreadlines(path, *args, **kwargs)
+
+    def open(self, path, *args, **kwargs):
+        return self.selectvcs(path).open(path, *args, **kwargs)
+
+    def read(self, path, *args, **kwargs):
+        return self.selectvcs(path).read(path, *args, **kwargs)
+
+    def readlines(self, path, *args, **kwargs):
+        return self.selectvcs(path).readlines(path, *args, **kwargs)
+
+    def write(self, path, *args, **kwargs):
+        return self.selectvcs(path).write(path, *args, **kwargs)
+
+    def writelines(self, path, *args, **kwargs):
+        return self.selectvcs(path).writelines(path, *args, **kwargs)
+
+    def append(self, path, *args, **kwargs):
+        return self.selectvcs(path).append(path, *args, **kwargs)
+
+    def chmod(self, path, *args, **kwargs):
+        return self.selectvcs(path).chmod(path, *args, **kwargs)
+
+    def exists(self, path=None, **kwargs):
+        return self.selectvcs(path).exists(path=path, **kwargs)
+
+    def isdir(self, path=None, **kwargs):
+        return self.selectvcs(path).isdir(path=path, **kwargs)
+
+    def isfile(self, path=None, **kwargs):
+        return self.selectvcs(path).isfile(path=path, **kwargs)
+
+    def islink(self, path=None, **kwargs):
+        return self.selectvcs(path).islink(path=path, **kwargs)
+
+    def split(self, path, *args, **kwargs):
+        return self.selectvcs(path).split(path, *args, **kwargs)
+
+    def lexists(self, path=None, **kwargs):
+        return self.selectvcs(path).lexists(path=path, **kwargs)
+
+    def lstat(self, path=None, **kwargs):
+        return self.selectvcs(path).lstat(path=path, **kwargs)
+
+    def listdir(self, path=None, **kwargs):
+        return self.selectvcs(path).listdir(path=path, **kwargs)
+
+    def makedir(self, path=None, **kwargs):
+        return self.selectvcs(path).makedir(path=path, **kwargs)
+
+    def makedirs(self, path=None, **kwargs):
+        return self.selectvcs(path).makedirs(path=path, **kwargs)
+
+    def makelock(self, info, path, *args, **kwargs):
+        return self.selectvcs(path).makelock(info, path, *args, **kwargs)
+
+    def mkdir(self, path=None, **kwargs):
+        return self.selectvcs(path).mkdir(path=path, **kwargs)
+
+    def readdir(self, path=None, **kwargs):
+        return self.selectvcs(path).readdir(path=path, **kwargs)
+
+    def readlock(self, path, *args, **kwargs):
+        return self.selectvcs(path).readlock(path, *args, **kwargs)
+
+    def rename(self, src, dst):
+        srcvfs = self.selectvcs(src)
+        dstvfs = self.selectvcs(src)
+        return util.rename(srcvfs.join(src), dstvfs.join(dst))
+
+    def readlink(self, path, *args, **kwargs):
+        return self.selectvcs(path).readlink(path, *args, **kwargs)
+
+    def setflags(self, path, *args, **kwargs):
+        return self.selectvcs(path).setflags(path, *args, **kwargs)
+
+    def stat(self, path=None, **kwargs):
+        return self.selectvcs(path).stat(path=path, **kwargs)
+
+    def unlink(self, path=None, **kwargs):
+        return self.selectvcs(path).unlink(path=path, **kwargs)
+
+    def unlinkpath(self, path=None, **kwargs):
+        return self.selectvcs(path).unlinkpath(path=path, **kwargs)
+
+    def utime(self, path=None, **kwargs):
+        return self.selectvcs(path).utime(path=path, **kwargs)
+
 class vfs(abstractvfs):
     '''Operate files relative to a base directory