DC resource. More merge tests.
[redakcja.git] / lib / wlrepo / backend_mercurial.py
index 11ecfc7..dfcef09 100644 (file)
@@ -8,7 +8,7 @@ __doc__ = """RAL implementation over Mercurial"""
 import mercurial
 from mercurial import localrepo as hglrepo
 from mercurial import ui as hgui
-from mercurial.node import hex as to_hex
+from mercurial.node import nullid
 import re
 import wlrepo
 
@@ -60,6 +60,10 @@ class MercurialLibrary(wlrepo.Library):
         finally:
             lock.release()
 
+    @property
+    def ospath(self):
+        return self._ospath
+
     @property
     def main_cabinet(self):
         return self._maincab
@@ -68,19 +72,22 @@ class MercurialLibrary(wlrepo.Library):
         return self.cabinet(docid, user, create=False).retrieve()
 
     def cabinet(self, docid, user, create=False):
+        docid = self._sanitize_string(docid)
+        user = self._sanitize_string(user)
+        
         bname = self._bname(user, docid)
 
         lock = self._lock(True)
         try:
             if self._has_branch(bname):
-                return MercurialCabinet(self, bname, doc=docid, user=user)
+                return MercurialCabinet(self, doc=docid, user=user)
 
             if not create:
                 raise wlrepo.CabinetNotFound(bname)
 
             # check if the docid exists in the main cabinet
             needs_touch = not self._maincab.exists(docid)            
-            cab = MercurialCabinet(self, bname, doc=docid, user=user)
+            cab = MercurialCabinet(self, doc=docid, user=user)
 
             name, fileid = cab._filename(None)
 
@@ -91,10 +98,11 @@ class MercurialLibrary(wlrepo.Library):
                 
                 garbage = [fid for (fid, did) in l._filelist() if not did.startswith(docid)]                
                 l._filesrm(garbage)
+                print "removed: ", garbage
 
             # create the branch
             self._create_branch(bname, before_commit=cleanup_action)
-            return MercurialCabinet(self, bname, doc=docid, user=user)
+            return MercurialCabinet(self, doc=docid, user=user)
         finally:
             lock.release()
             
@@ -212,10 +220,10 @@ class MercurialLibrary(wlrepo.Library):
         self._checkout(self._branch_tip(branchname))
         return branchname        
 
-    #
-    # Merges
-    #
-    
+    def shelf(self, nodeid=None):
+        if nodeid is None:
+            nodeid = self._maincab._name
+        return MercurialShelf(self, self._changectx(nodeid))   
 
 
     #
@@ -230,19 +238,18 @@ class MercurialLibrary(wlrepo.Library):
 
 class MercurialCabinet(wlrepo.Cabinet):
     
-    def __init__(self, library, branchname, doc=None, user=None):
+    def __init__(self, library, branchname=None, doc=None, user=None):
         if doc and user:
             super(MercurialCabinet, self).__init__(library, doc=doc, user=user)
-        else:
+            self._branchname = library._bname(user=user, docid=doc)
+        elif branchname:
             super(MercurialCabinet, self).__init__(library, name=branchname)
-            
-        self._branchname = branchname
+            self._branchname = branchname
+        else:
+            raise ValueError("Provide either doc/user or branchname")
 
     def shelf(self, selector=None):
-        if selector is not None:
-            raise NotImplementedException()
-
-        return MercurialShelf(self, self._library._changectx(self._branchname))
+        return self._library.shelf(self._branchname)
 
     def documents(self):        
         return self._execute_in_branch(action=lambda l, c: (e[1] for e in l._filelist()))
@@ -250,10 +257,18 @@ class MercurialCabinet(wlrepo.Cabinet):
     def retrieve(self, part=None, shelf=None):
         name, fileid = self._filename(part)
 
+        print "Retrieving document %s from cab %s" % (name, self._name)
+
         if fileid is None:
             raise wlrepo.LibraryException("Can't retrieve main document from main cabinet.")
+
+        def retrieve_action(l,c):
+            if l._fileexists(fileid):
+                return MercurialDocument(c, name=name, fileid=fileid)
+            print "File %s not found " % fileid
+            return None
                 
-        return self._execute_in_branch(lambda l, c: MercurialDocument(c, name, fileid))
+        return self._execute_in_branch(retrieve_action)        
 
     def create(self, name, initial_data):
         name, fileid = self._filename(name)
@@ -292,15 +307,15 @@ class MercurialCabinet(wlrepo.Cabinet):
 
     def _filename(self, part):
         part = self._library._sanitize_string(part)
-        fileid = None
+        docid = None
 
         if self._maindoc == '':
-            if part is None: return [None, None]
-            fileid = part
+            if part is None: rreeturn [None, None]
+            docid = part
         else:
-            fileid = self._maindoc + (('$' + part) if part else '')
+            docid = self._maindoc + (('$' + part) if part else '')
 
-        return fileid, 'pub_' + fileid + '.xml'
+        return docid, 'pub_' + docid + '.xml'
 
     def _fileopener(self):
         return self._library._fileopener()
@@ -311,17 +326,19 @@ class MercurialCabinet(wlrepo.Cabinet):
     def _filectx(self, fileid):
         return self._library._filectx(fileid, self._branchname)
 
+    def ismain(self):
+        return (self._library.main_cabinet == self)
+
 class MercurialDocument(wlrepo.Document):
 
     def __init__(self, cabinet, name, fileid):
         super(MercurialDocument, self).__init__(cabinet, name=name)
         self._opener = self._cabinet._fileopener()
         self._fileid = fileid
-        self._refresh()
+        self.refresh()
 
-    def _refresh(self):
-        self._filectx = self._cabinet._filectx(self._fileid)
-        self._shelf = MercurialShelf(self, self._filectx.node())
+    def refresh(self):
+        self._filectx = self._cabinet._filectx(self._fileid)        
 
     def read(self):
         return self._opener(self._filectx.path(), "r").read()
@@ -334,96 +351,117 @@ class MercurialDocument(wlrepo.Document):
         self.library._commit(self._fileid, message, user)
 
     def update(self):
-        if self._cabinet.is_main():
-            return True # always up-to-date
-
-        mdoc = self.library.document(self._fileid)
+        lock = self.library._lock()
+        try:
+            if self._cabinet.ismain():
+                return True # always up-to-date
 
-        mshelf = mdoc.shelf()
-        shelf = self.shelf()
+            user = self._cabinet.username or 'library'
+            mdoc = self.library.document(self._fileid)
 
-        if not mshelf.ancestorof(shelf) and not shelf.parentof(mshelf):
-            shelf.merge_with(mshelf)
+            mshelf = mdoc.shelf()
+            shelf = self.shelf()
 
-        return rc.ALL_OK
+            if not mshelf.ancestorof(shelf) and not shelf.parentof(mshelf):
+                shelf.merge_with(mshelf, user=user)
 
-    def share(self, message, user):
-        if self._cabinet.is_main():
-            return True # always shared
+            return True
+        finally:
+            lock.release()            
 
-        main = self.shared_version()
-        local = self.shelf()
+    def share(self, message):
+        lock = self.library._lock()
+        try:
+            print "sharing from", self._cabinet, self._cabinet.username
+            
+            if self._cabinet.ismain():
+                return True # always shared
 
-        no_changes = True
+            if self._cabinet.username is None:
+                raise ValueError("Can only share documents from personal cabinets.")
+            
+            user = self._cabinet.username
 
-        # Case 1:
-        #         * local
-        #         |
-        #         * <- can also be here!
-        #        /|
-        #       / |
-        # main *  *
-        #      |  |
-        # The local branch has been recently updated,
-        # so we don't need to update yet again, but we need to
-        # merge down to default branch, even if there was
-        # no commit's since last update
+            main = self.library.shelf()
+            local = self.shelf()
 
-        if main.ancestorof(local):
-            main.merge_with(local, user=user, message=message)
-            no_changes = False
-            
-        # Case 2:
-        #
-        # main *  * local
-        #      |\ |
-        #      | \|
-        #      |  *
-        #      |  |
-        #
-        # Default has no changes, to update from this branch
-        # since the last merge of local to default.
-        elif main.has_common_ancestor(local):
-            if not local.parentof(main):
+            no_changes = True
+
+            # Case 1:
+            #         * local
+            #         |
+            #         * <- can also be here!
+            #        /|
+            #       / |
+            # main *  *
+            #      |  |
+            # The local branch has been recently updated,
+            # so we don't need to update yet again, but we need to
+            # merge down to default branch, even if there was
+            # no commit's since last update
+
+            if main.ancestorof(local):
+                print "case 1"
                 main.merge_with(local, user=user, message=message)
                 no_changes = False
-
-        # Case 3:
-        # main *
-        #      |
-        #      * <- this case overlaps with previos one
-        #      |\
-        #      | \
-        #      |  * local
-        #      |  |
-        #
-        # There was a recent merge to the defaul branch and
-        # no changes to local branch recently.
-        #
-        # Use the fact, that user is prepared to see changes, to
-        # update his branch if there are any
-        elif local.ancestorof(main):
-            if not local.parentof(main):
+            # Case 2:
+            #
+            # main *  * local
+            #      |\ |
+            #      | \|
+            #      |  *
+            #      |  |
+            #
+            # Default has no changes, to update from this branch
+            # since the last merge of local to default.
+            elif local.has_common_ancestor(main):
+                print "case 2"
+                if not local.parentof(main):
+                    main.merge_with(local, user=user, message=message)
+                    no_changes = False
+
+            # Case 3:
+            # main *
+            #      |
+            #      * <- this case overlaps with previos one
+            #      |\
+            #      | \
+            #      |  * local
+            #      |  |
+            #
+            # There was a recent merge to the defaul branch and
+            # no changes to local branch recently.
+            #
+            # Use the fact, that user is prepared to see changes, to
+            # update his branch if there are any
+            elif local.ancestorof(main):
+                print "case 3"
+                if not local.parentof(main):
+                    local.merge_with(main, user=user, message='Local branch update.')
+                    no_changes = False
+            else:
+                print "case 4"
                 local.merge_with(main, user=user, message='Local branch update.')
-                no_changes = False
-        else:
-            local.merge_with(main, user=user, message='Local branch update.')
-
-            self._refresh()
-            local = self.shelf()
+                local = self.shelf()
+                main.merge_with(local, user=user, message=message)
 
-            main.merge_with(local, user=user, message=message)
+            print "no_changes: ", no_changes
+            return no_changes
+        finally:
+            lock.release()
                
     def shared(self):
-        return self.library.document(self._fileid)
+        return self.library.main_cabinet.retrieve(self._name)
+
+    def exists(self):
+        return self._cabinet.exists(self._fileid)
 
     @property
     def size(self):
         return self._filectx.size()
-
     
     def shelf(self):
-        return self._shelf
+        return MercurialShelf(self.library, self._filectx.node())
 
     @property
     def last_modified(self):
@@ -432,12 +470,20 @@ class MercurialDocument(wlrepo.Document):
     def __str__(self):
         return u"Document(%s->%s)" % (self._cabinet.name, self._name)
 
+    def __eq__(self, other):
+        return self._filectx == other._filectx
+
+
 
 class MercurialShelf(wlrepo.Shelf):
 
-    def __init__(self, cabinet, changectx):
-        super(MercurialShelf, self).__init__(cabinet)
-        self._changectx = changectx        
+    def __init__(self, lib, changectx):
+        super(MercurialShelf, self).__init__(lib)
+
+        if isinstance(changectx, str):
+            self._changectx = lib._changectx(changectx)
+        else:
+            self._changectx = changectx
 
     @property
     def _rev(self):
@@ -446,9 +492,39 @@ class MercurialShelf(wlrepo.Shelf):
     def __str__(self):
         return self._changectx.hex()
 
+    def __repr__(self):
+        return "MercurialShelf(%s)" % self._changectx.hex()
 
     def ancestorof(self, other):
-        pass
+        nodes = list(other._changectx._parents)
+        while nodes[0].node() != nullid:
+            v = nodes.pop(0)
+            if v == self._changectx:
+                return True
+            nodes.extend( v._parents )
+        return False
+
+    def parentof(self, other):
+        return self._changectx in other._changectx._parents
+
+    def has_common_ancestor(self, other):
+        a = self._changectx.ancestor(other._changectx)
+        # print a, self._changectx.branch(), a.branch()
+
+        return (a.branch() == self._changectx.branch())
+
+    def merge_with(self, other, user, message):
+        lock = self._library._lock(True)
+        try:
+            self._library._checkout(self._changectx.node())
+            self._library._merge(other._changectx.node())
+            self._library._commit(user=user, message=message)
+        finally:
+            lock.release()
+
+    def __eq__(self, other):
+        return self._changectx.node() == other._changectx.node()
+        
 
 class MergeStatus(object):
     def __init__(self, mstatus):