38f01da6aea827f3b877e28dec4518f091e51510
[redakcja.git] / lib / wlrepo / backend_mercurial.py
1 # -*- encoding: utf-8 -*-
2 __author__ = "Ɓukasz Rekucki"
3 __date__ = "$2009-09-18 10:49:24$"
4
5 __doc__ = """RAL implementation over Mercurial"""
6
7 import mercurial
8 from mercurial import localrepo as hglrepo
9 from mercurial import ui as hgui
10 import re
11 import wlrepo
12
13 FILTER = re.compile(r"^pub_(.+)\.xml$", re.UNICODE)
14
15 def default_filter(name):
16     m = FILTER.match(name)    
17     if m is not None:
18         return name, m.group(1)
19     return None
20
21 class MercurialLibrary(wlrepo.Library):
22
23     def __init__(self, path, maincabinet="default", ** kwargs):
24         super(wlrepo.Library, self).__init__( ** kwargs)
25
26         self._hgui = hgui.ui()
27         self._hgui.config('ui', 'quiet', 'true')
28         self._hgui.config('ui', 'interactive', 'false')
29
30         import os.path        
31         self._ospath = self._sanitize_string(os.path.realpath(path))
32         
33         maincabinet = self._sanitize_string(maincabinet)
34
35         if os.path.isdir(path):
36             try:
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']:
41             os.makedirs(path)
42             try:
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)
46         else:
47             raise wlrepo.LibraryException("[HGLibrary] Can't open a library on path '%s'." % path)
48
49         # fetch the main cabinet
50         lock = self._hgrepo.lock()
51         try:
52             btags = self._hgrepo.branchtags()
53             
54             if not self._has_branch(maincabinet):
55                 raise wlrepo.LibraryException("[HGLibrary] No branch named '%s' to init main cabinet" % maincabinet)
56         
57             self._maincab = MercurialCabinet(self, maincabinet)
58         finally:
59             lock.release()
60
61     @property
62     def main_cabinet(self):
63         return self._maincab
64
65     def cabinet(self, docid, user, create=False):
66         bname = self._bname(user, docid)
67
68         lock = self._lock(True)
69         try:
70             if self._has_branch(bname):
71                 return MercurialCabinet(self, bname, doc=docid, user=user)
72
73             if not create:
74                 raise wlrepo.CabinetNotFound(docid, user)
75
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)
80
81             fileid = cab._fileid(None)
82
83             def cleanup_action(l):
84                 if needs_touch:
85                     print "Touch for file", docid
86                     l._fileopener()(fileid, "w").write('')
87                     l._fileadd(fileid)
88                 
89                 garbage = [fid for (fid, did) in l._filelist() if not did.startswith(docid)]
90                 print "Garbage: ", garbage
91                 l._filesrm(garbage)
92
93             # create the branch
94             self._create_branch(bname, before_commit=cleanup_action)
95             return MercurialCabinet(self, bname, doc=docid, user=user)
96         finally:
97             lock.release()
98             
99     #
100     # Private methods
101     #
102
103     #
104     # Locking
105     #
106  
107     def _lock(self, write_mode=False):
108         return self._hgrepo.wlock() # no support for read/write mode yet
109
110     def _transaction(self, write_mode, action):
111         lock = self._lock(write_mode)
112         try:
113             return action(self)
114         finally:
115             lock.release()
116             
117     #
118     # Basic repo manipulation
119     #   
120
121     def _checkout(self, rev, force=True):
122         return MergeStatus(mercurial.merge.update(self._hgrepo, rev, False, force, None))
123
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))
127     
128     def _common_ancestor(self, revA, revB):
129         return self._hgrepo[revA].ancestor(self.repo[revB])
130
131     def _commit(self, message, user="library"):
132         return self._hgrepo.commit(\
133                                    text=self._sanitize_string(message), \
134                                    user=self._sanitize_string(user))
135
136
137     def _fileexists(self, fileid):
138         return (fileid in self._hgrepo[None])
139
140     def _fileadd(self, fileid):
141         return self._hgrepo.add([fileid])
142     
143     def _filesadd(self, fileid_list):
144         return self._hgrepo.add(fileid_list)
145
146     def _filerm(self, fileid):
147         return self._hgrepo.remove([fileid])
148
149     def _filesrm(self, fileid_list):
150         return self._hgrepo.remove(fileid_list)
151
152     def _filelist(self, filter=default_filter):
153         for name in  self._hgrepo[None]:
154             result = filter(name)
155             if result is None: continue
156             
157             yield result
158
159     def _fileopener(self):
160         return self._hgrepo.wopener
161     
162     #
163     # BASIC BRANCH routines
164     #
165
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;
171
172     def _has_branch(self, name):
173         return self._hgrepo.branchmap().has_key(self._sanitize_string(name))
174
175     def _branch_tip(self, name):
176         name = self._sanitize_string(name)
177         return self._hgrepo.branchtags()[name]
178
179     def _create_branch(self, name, parent=None, before_commit=None):        
180         name = self._sanitize_string(name)
181
182         if self._has_branch(name): return # just exit
183
184         if parent is None:
185             parent = self._maincab
186
187         parentrev = parent._hgtip()
188
189         self._checkout(parentrev)
190         self._hgrepo.dirstate.setbranch(name)
191
192         if before_commit: before_commit(self)
193
194         print "commiting"
195         self._commit("[AUTO] Initial commit for branch '%s'." % name, user='library')
196         
197         # revert back to main
198         self._checkout(self._maincab._hgtip())
199         return self._branch_tip(name)
200
201     def _switch_to_branch(self, branchname):
202         current = self._hgrepo[None].branch()
203
204         if current == branchname:
205             return current # quick exit
206         
207         self._checkout(self._branch_tip(branchname))
208         return branchname        
209
210     #
211     # Merges
212     #
213     
214
215
216     #
217     # Utils
218     #
219
220     @staticmethod
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
225             return s
226
227 class MercurialCabinet(wlrepo.Cabinet):
228     
229     def __init__(self, library, branchname, doc=None, user=None):
230         if doc and user:
231             super(MercurialCabinet, self).__init__(library, doc=doc, user=user)
232         else:
233             super(MercurialCabinet, self).__init__(library, name=branchname)
234             
235         self._branchname = branchname
236
237     def documents(self):        
238         return self._execute_in_branch(action=lambda l, c: ( e[1] for e in l._filelist()) )
239
240     def retrieve(self, part=None, shelve=None):
241         fileid = self._fileid(part)
242
243         if fileid is None:
244             raise wlrepo.LibraryException("Can't retrieve main document from main cabinet.")
245                 
246         return self._execute_in_branch(lambda l, c: MercurialDocument(c, fileid))
247
248     def create(self, name, initial_data=''):
249         fileid = self._fileid(name)
250
251         if name is None:
252             raise ValueError("Can't create main doc for maincabinet.")
253
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))
257
258             fd = l._fileopener()(fileid, "w")
259             fd.write(initial_data)
260             l._fileadd(fileid)
261             l._commit("File '%d' created.")
262
263             return MercurialDocument(c, fileid)
264
265         return self._execute_in_branch(create_action)
266
267     def exists(self, part=None, shelve=None):
268         fileid = self._fileid(part)
269
270         if fileid is None: return false
271         return self._execute_in_branch(lambda l, c: l._fileexists(fileid))
272     
273     def _execute_in_branch(self, action, write=False):
274         def switch_action(library):
275             old = library._switch_to_branch(self._branchname)
276             try:
277                 return action(library, self)
278             finally:
279                 library._switch_to_branch(old)
280
281         return self._library._transaction(write_mode=write, action=switch_action)
282
283     def _fileid(self, part):
284         fileid = None
285
286         if self._maindoc == '':
287             if part is None: return None              
288             fileid = part
289         else:
290             fileid = self._maindoc + (('$' + part) if part else '')
291
292         return 'pub_' + fileid + '.xml'        
293
294     def _fileopener(self):
295         return self._library._fileopener()
296
297     def _hgtip(self):
298         return self._library._branch_tip(self._branchname)
299
300 class MercurialDocument(wlrepo.Document):
301
302     def __init__(self, cabinet, fileid):
303         super(MercurialDocument, self).__init__(cabinet, fileid)
304         self._opener = self._cabinet._fileopener()        
305
306     def read(self):
307         return self._opener(self._name, "r").read()
308
309     def write(self, data):
310         return self._opener(self._name, "w").write(data)
311
312
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]
319
320     def isclean(self):
321         return self.unresolved == 0
322
323 class UpdateStatus(object):
324
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]
333
334     def has_changes(self):
335         return bool(len(self.modified) + len(self.added) + \
336                     len(self.removed) + len(self.deleted))
337
338
339 __all__ = ["MercurialLibrary", "MercurialCabinet", "MercurialDocument"]