# -*- encoding: utf-8 -*-
+
__author__ = "Łukasz Rekucki"
__date__ = "$2009-09-18 10:49:24$"
import mercurial
from mercurial import localrepo as hglrepo
from mercurial import ui as hgui
+from mercurial.node import nullid
import re
import wlrepo
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)
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):
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)
# 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()
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):
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
if before_commit: before_commit(self)
- print "commiting"
self._commit("[AUTO] Initial commit for branch '%s'." % name, user='library')
# revert back to main
self._checkout(self._branch_tip(branchname))
return branchname
- #
- # Merges
- #
-
+ def shelf(self, nodeid):
+ return MercurialShelf(self, self._changectx(nodeid))
#
#
@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, shelf=None):
+ name, fileid = self._filename(part)
- def retrieve(self, part=None, shelve=None):
- fileid = self._fileid(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)
+ 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.")
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):
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()
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.shared().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):
+ 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):
+ 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):
+ if not local.parentof(main):
+ 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()
+
+ main.merge_with(local, user=user, message=message)
+ 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())
+ finally:
+ lock.release()
+
+ def __eq__(self, other):
+ return self._changectx.node() == other._changectx.node()
+
class MergeStatus(object):
def __init__(self, mstatus):
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