X-Git-Url: https://git.mdrn.pl/redakcja.git/blobdiff_plain/b2d72160e2a68991c66ea6017b871d7f42c0b29d..62f4867f53b4397e820e0e59c240fbe27582f26e:/lib/wlrepo/backend_mercurial.py diff --git a/lib/wlrepo/backend_mercurial.py b/lib/wlrepo/backend_mercurial.py index 38f01da6..dfcef099 100644 --- a/lib/wlrepo/backend_mercurial.py +++ b/lib/wlrepo/backend_mercurial.py @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- + __author__ = "Łukasz Rekucki" __date__ = "$2009-09-18 10:49:24$" @@ -7,6 +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 nullid import re import wlrepo @@ -36,8 +38,8 @@ class MercurialLibrary(wlrepo.Library): try: self._hgrepo = hglrepo.localrepository(self._hgui, path) except mercurial.error.RepoError: - raise wlrepo.LibraryException("[HGLibrary]Not a valid repository at path '%s'." % path) - elif kwargs['create']: + raise wlrepo.LibraryException("[HGLibrary] Not a valid repository at path '%s'." % path) + elif kwargs.get('create', False): os.makedirs(path) try: self._hgrepo = hglrepo.localrepository(self._hgui, path, create=1) @@ -58,41 +60,49 @@ class MercurialLibrary(wlrepo.Library): finally: lock.release() + @property + def ospath(self): + return self._ospath + @property def main_cabinet(self): return self._maincab + def document(self, docid, user): + 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(docid, user) + raise wlrepo.CabinetNotFound(bname) # check if the docid exists in the main cabinet - needs_touch = not self._maincab.exists(docid) - print "Creating branch: ", needs_touch - cab = MercurialCabinet(self, bname, doc=docid, user=user) + needs_touch = not self._maincab.exists(docid) + cab = MercurialCabinet(self, doc=docid, user=user) - fileid = cab._fileid(None) + name, fileid = cab._filename(None) def cleanup_action(l): - if needs_touch: - print "Touch for file", docid + if needs_touch: l._fileopener()(fileid, "w").write('') l._fileadd(fileid) - garbage = [fid for (fid, did) in l._filelist() if not did.startswith(docid)] - print "Garbage: ", garbage + 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() @@ -128,10 +138,8 @@ class MercurialLibrary(wlrepo.Library): def _common_ancestor(self, revA, revB): return self._hgrepo[revA].ancestor(self.repo[revB]) - def _commit(self, message, user="library"): - return self._hgrepo.commit(\ - text=self._sanitize_string(message), \ - user=self._sanitize_string(user)) + def _commit(self, message, user=u"library"): + return self._hgrepo.commit(text=message, user=user) def _fileexists(self, fileid): @@ -158,6 +166,12 @@ class MercurialLibrary(wlrepo.Library): def _fileopener(self): return self._hgrepo.wopener + + def _filectx(self, fileid, branchid): + return self._hgrepo.filectx(fileid, changeid=branchid) + + def _changectx(self, nodeid): + return self._hgrepo.changectx(nodeid) # # BASIC BRANCH routines @@ -191,7 +205,6 @@ class MercurialLibrary(wlrepo.Library): if before_commit: before_commit(self) - print "commiting" self._commit("[AUTO] Initial commit for branch '%s'." % name, user='library') # revert back to main @@ -207,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)) # @@ -218,35 +231,47 @@ class MercurialLibrary(wlrepo.Library): # @staticmethod - def _sanitize_string(s): - if isinstance(s, unicode): # - return s.encode('utf-8') - else: # it's a string, so we have no idea what encoding it is - return s + def _sanitize_string(s): + if isinstance(s, unicode): + s = s.encode('utf-8') + return s 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): + 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()) ) + return self._execute_in_branch(action=lambda l, c: (e[1] for e in l._filelist())) - def retrieve(self, part=None, shelve=None): - fileid = self._fileid(part) + 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, fileid)) + return self._execute_in_branch(retrieve_action) - def create(self, name, initial_data=''): - fileid = self._fileid(name) + def create(self, name, initial_data): + name, fileid = self._filename(name) if name is None: raise ValueError("Can't create main doc for maincabinet.") @@ -257,18 +282,18 @@ class MercurialCabinet(wlrepo.Cabinet): fd = l._fileopener()(fileid, "w") fd.write(initial_data) - l._fileadd(fileid) - l._commit("File '%d' created.") - - return MercurialDocument(c, fileid) + fd.close() + l._fileadd(fileid) + l._commit("File '%s' created." % fileid) + return MercurialDocument(c, fileid=fileid, name=name) return self._execute_in_branch(create_action) - def exists(self, part=None, shelve=None): - fileid = self._fileid(part) + def exists(self, part=None, shelf=None): + name, filepath = self._filename(part) - if fileid is None: return false - return self._execute_in_branch(lambda l, c: l._fileexists(fileid)) + if filepath is None: return False + return self._execute_in_branch(lambda l, c: l._fileexists(filepath)) def _execute_in_branch(self, action, write=False): def switch_action(library): @@ -280,16 +305,17 @@ class MercurialCabinet(wlrepo.Cabinet): return self._library._transaction(write_mode=write, action=switch_action) - def _fileid(self, part): - fileid = None + def _filename(self, part): + part = self._library._sanitize_string(part) + docid = None if self._maindoc == '': - if part is None: return 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 'pub_' + fileid + '.xml' + return docid, 'pub_' + docid + '.xml' def _fileopener(self): return self._library._fileopener() @@ -297,18 +323,208 @@ class MercurialCabinet(wlrepo.Cabinet): def _hgtip(self): return self._library._branch_tip(self._branchname) + 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, fileid): - super(MercurialDocument, self).__init__(cabinet, fileid) - self._opener = self._cabinet._fileopener() + def __init__(self, cabinet, name, fileid): + super(MercurialDocument, self).__init__(cabinet, name=name) + self._opener = self._cabinet._fileopener() + self._fileid = fileid + self.refresh() + + def refresh(self): + self._filectx = self._cabinet._filectx(self._fileid) def read(self): - return self._opener(self._name, "r").read() + return self._opener(self._filectx.path(), "r").read() def write(self, data): - return self._opener(self._name, "w").write(data) + return self._opener(self._filectx.path(), "w").write(data) + + def commit(self, message, user): + self.library._fileadd(self._fileid) + self.library._commit(self._fileid, message, user) + + def update(self): + lock = self.library._lock() + try: + if self._cabinet.ismain(): + return True # always up-to-date + user = self._cabinet.username or 'library' + mdoc = self.library.document(self._fileid) + + mshelf = mdoc.shelf() + shelf = self.shelf() + + if not mshelf.ancestorof(shelf) and not shelf.parentof(mshelf): + shelf.merge_with(mshelf, user=user) + + return True + finally: + lock.release() + + 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 + + if self._cabinet.username is None: + raise ValueError("Can only share documents from personal cabinets.") + + user = self._cabinet.username + + main = self.library.shelf() + local = self.shelf() + + 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 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.') + local = self.shelf() + 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.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 MercurialShelf(self.library, self._filectx.node()) + + @property + def last_modified(self): + return self._filectx.date() + + 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, lib, changectx): + super(MercurialShelf, self).__init__(lib) + + if isinstance(changectx, str): + self._changectx = lib._changectx(changectx) + else: + self._changectx = changectx + + @property + def _rev(self): + return self._changectx.node() + + def __str__(self): + return self._changectx.hex() + + def __repr__(self): + return "MercurialShelf(%s)" % self._changectx.hex() + + def ancestorof(self, other): + 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): @@ -335,5 +551,4 @@ class UpdateStatus(object): return bool(len(self.modified) + len(self.added) + \ len(self.removed) + len(self.deleted)) - -__all__ = ["MercurialLibrary", "MercurialCabinet", "MercurialDocument"] \ No newline at end of file +__all__ = ["MercurialLibrary"] \ No newline at end of file