1 # -*- encoding: utf-8 -*-
2 __author__ = "Ćukasz Rekucki"
3 __date__ = "$2009-09-18 10:49:24$"
5 __doc__ = """RAL implementation over Mercurial"""
8 from mercurial import localrepo as hglrepo
9 from mercurial import ui as hgui
13 FILTER = re.compile(r"^pub_(.+)\.xml$", re.UNICODE)
15 def default_filter(name):
16 m = FILTER.match(name)
18 return name, m.group(1)
21 class MercurialLibrary(wlrepo.Library):
23 def __init__(self, path, maincabinet="default", ** kwargs):
24 super(wlrepo.Library, self).__init__( ** kwargs)
26 self._hgui = hgui.ui()
27 self._hgui.config('ui', 'quiet', 'true')
28 self._hgui.config('ui', 'interactive', 'false')
31 self._ospath = self._sanitize_string(os.path.realpath(path))
33 maincabinet = self._sanitize_string(maincabinet)
35 if os.path.isdir(path):
37 self._hgrepo = hglrepo.localrepository(self._hgui, path)
38 except mercurial.error.RepoError:
39 raise wlrepo.LibraryException("[HGLibrary]Not a valid repository at path '%s'." % path)
40 elif kwargs['create']:
43 self._hgrepo = hglrepo.localrepository(self._hgui, path, create=1)
44 except mercurial.error.RepoError:
45 raise wlrepo.LibraryException("[HGLibrary] Can't create a repository on path '%s'." % path)
47 raise wlrepo.LibraryException("[HGLibrary] Can't open a library on path '%s'." % path)
49 # fetch the main cabinet
50 lock = self._hgrepo.lock()
52 btags = self._hgrepo.branchtags()
54 if not self._has_branch(maincabinet):
55 raise wlrepo.LibraryException("[HGLibrary] No branch named '%s' to init main cabinet" % maincabinet)
57 self._maincab = MercurialCabinet(self, maincabinet)
62 def main_cabinet(self):
65 def cabinet(self, docid, user, create=False):
66 bname = self._bname(user, docid)
68 lock = self._lock(True)
70 if self._has_branch(bname):
71 return MercurialCabinet(self, bname, doc=docid, user=user)
74 raise wlrepo.CabinetNotFound(docid, user)
76 # check if the docid exists in the main cabinet
77 needs_touch = not self._maincab.exists(docid)
78 print "Creating branch: ", needs_touch
79 cab = MercurialCabinet(self, bname, doc=docid, user=user)
81 fileid = cab._fileid(None)
83 def cleanup_action(l):
85 print "Touch for file", docid
86 l._fileopener()(fileid, "w").write('')
89 garbage = [fid for (fid, did) in l._filelist() if not did.startswith(docid)]
90 print "Garbage: ", garbage
94 self._create_branch(bname, before_commit=cleanup_action)
95 return MercurialCabinet(self, bname, doc=docid, user=user)
107 def _lock(self, write_mode=False):
108 return self._hgrepo.wlock() # no support for read/write mode yet
110 def _transaction(self, write_mode, action):
111 lock = self._lock(write_mode)
118 # Basic repo manipulation
121 def _checkout(self, rev, force=True):
122 return MergeStatus(mercurial.merge.update(self._hgrepo, rev, False, force, None))
124 def _merge(self, rev):
125 """ Merge the revision into current working directory """
126 return MergeStatus(mercurial.merge.update(self._hgrepo, rev, True, False, None))
128 def _common_ancestor(self, revA, revB):
129 return self._hgrepo[revA].ancestor(self.repo[revB])
131 def _commit(self, message, user="library"):
132 return self._hgrepo.commit(\
133 text=self._sanitize_string(message), \
134 user=self._sanitize_string(user))
137 def _fileexists(self, fileid):
138 return (fileid in self._hgrepo[None])
140 def _fileadd(self, fileid):
141 return self._hgrepo.add([fileid])
143 def _filesadd(self, fileid_list):
144 return self._hgrepo.add(fileid_list)
146 def _filerm(self, fileid):
147 return self._hgrepo.remove([fileid])
149 def _filesrm(self, fileid_list):
150 return self._hgrepo.remove(fileid_list)
152 def _filelist(self, filter=default_filter):
153 for name in self._hgrepo[None]:
154 result = filter(name)
155 if result is None: continue
159 def _fileopener(self):
160 return self._hgrepo.wopener
163 # BASIC BRANCH routines
166 def _bname(self, user, docid):
167 """Returns a branch name for a given document and user."""
168 docid = self._sanitize_string(docid)
169 uname = self._sanitize_string(user)
170 return "personal_" + uname + "_file_" + docid;
172 def _has_branch(self, name):
173 return self._hgrepo.branchmap().has_key(self._sanitize_string(name))
175 def _branch_tip(self, name):
176 name = self._sanitize_string(name)
177 return self._hgrepo.branchtags()[name]
179 def _create_branch(self, name, parent=None, before_commit=None):
180 name = self._sanitize_string(name)
182 if self._has_branch(name): return # just exit
185 parent = self._maincab
187 parentrev = parent._hgtip()
189 self._checkout(parentrev)
190 self._hgrepo.dirstate.setbranch(name)
192 if before_commit: before_commit(self)
195 self._commit("[AUTO] Initial commit for branch '%s'." % name, user='library')
197 # revert back to main
198 self._checkout(self._maincab._hgtip())
199 return self._branch_tip(name)
201 def _switch_to_branch(self, branchname):
202 current = self._hgrepo[None].branch()
204 if current == branchname:
205 return current # quick exit
207 self._checkout(self._branch_tip(branchname))
221 def _sanitize_string(s):
222 if isinstance(s, unicode): #
223 return s.encode('utf-8')
224 else: # it's a string, so we have no idea what encoding it is
227 class MercurialCabinet(wlrepo.Cabinet):
229 def __init__(self, library, branchname, doc=None, user=None):
231 super(MercurialCabinet, self).__init__(library, doc=doc, user=user)
233 super(MercurialCabinet, self).__init__(library, name=branchname)
235 self._branchname = branchname
238 return self._execute_in_branch(action=lambda l, c: ( e[1] for e in l._filelist()) )
240 def retrieve(self, part=None, shelve=None):
241 fileid = self._fileid(part)
244 raise wlrepo.LibraryException("Can't retrieve main document from main cabinet.")
246 return self._execute_in_branch(lambda l, c: MercurialDocument(c, fileid))
248 def create(self, name, initial_data=''):
249 fileid = self._fileid(name)
252 raise ValueError("Can't create main doc for maincabinet.")
254 def create_action(l, c):
255 if l._fileexists(fileid):
256 raise wlrepo.LibraryException("Can't create document '%s' in cabinet '%s' - it already exists" % (fileid, c.name))
258 fd = l._fileopener()(fileid, "w")
259 fd.write(initial_data)
261 l._commit("File '%d' created.")
263 return MercurialDocument(c, fileid)
265 return self._execute_in_branch(create_action)
267 def exists(self, part=None, shelve=None):
268 fileid = self._fileid(part)
270 if fileid is None: return false
271 return self._execute_in_branch(lambda l, c: l._fileexists(fileid))
273 def _execute_in_branch(self, action, write=False):
274 def switch_action(library):
275 old = library._switch_to_branch(self._branchname)
277 return action(library, self)
279 library._switch_to_branch(old)
281 return self._library._transaction(write_mode=write, action=switch_action)
283 def _fileid(self, part):
286 if self._maindoc == '':
287 if part is None: return None
290 fileid = self._maindoc + (('$' + part) if part else '')
292 return 'pub_' + fileid + '.xml'
294 def _fileopener(self):
295 return self._library._fileopener()
298 return self._library._branch_tip(self._branchname)
300 class MercurialDocument(wlrepo.Document):
302 def __init__(self, cabinet, fileid):
303 super(MercurialDocument, self).__init__(cabinet, fileid)
304 self._opener = self._cabinet._fileopener()
307 return self._opener(self._name, "r").read()
309 def write(self, data):
310 return self._opener(self._name, "w").write(data)
313 class MergeStatus(object):
314 def __init__(self, mstatus):
315 self.updated = mstatus[0]
316 self.merged = mstatus[1]
317 self.removed = mstatus[2]
318 self.unresolved = mstatus[3]
321 return self.unresolved == 0
323 class UpdateStatus(object):
325 def __init__(self, mstatus):
326 self.modified = mstatus[0]
327 self.added = mstatus[1]
328 self.removed = mstatus[2]
329 self.deleted = mstatus[3]
330 self.untracked = mstatus[4]
331 self.ignored = mstatus[5]
332 self.clean = mstatus[6]
334 def has_changes(self):
335 return bool(len(self.modified) + len(self.added) + \
336 len(self.removed) + len(self.deleted))
339 __all__ = ["MercurialLibrary", "MercurialCabinet", "MercurialDocument"]