Updated API tests.
[redakcja.git] / lib / wlrepo / backend_mercurial.py
1 ## -*- encoding: utf-8 -*-
2 #
3 #__author__ = "Ɓukasz Rekucki"
4 #__date__ = "$2009-09-18 10:49:24$"
5 #
6 #__doc__ = """RAL implementation over Mercurial"""
7 #
8 #import mercurial
9 #from mercurial import localrepo as hglrepo
10 #from mercurial import ui as hgui
11 #from mercurial.node import nullid
12 #import re
13 #import wlrepo
14 #
15 #FILTER = re.compile(r"^pub_(.+)\.xml$", re.UNICODE)
16 #
17 #def default_filter(name):
18 #    m = FILTER.match(name)
19 #    if m is not None:
20 #        return name, m.group(1)
21 #    return None
22 #
23 #class MercurialLibrary(wlrepo.Library):
24 #
25 #    def __init__(self, path, maincabinet="default", ** kwargs):
26 #        super(wlrepo.Library, self).__init__( ** kwargs)
27 #
28 #        self._hgui = hgui.ui()
29 #        self._hgui.config('ui', 'quiet', 'true')
30 #        self._hgui.config('ui', 'interactive', 'false')
31 #
32 #        import os.path
33 #        self._ospath = self._sanitize_string(os.path.realpath(path))
34 #
35 #        maincabinet = self._sanitize_string(maincabinet)
36 #
37 #        if os.path.isdir(path):
38 #            try:
39 #                self._hgrepo = hglrepo.localrepository(self._hgui, path)
40 #            except mercurial.error.RepoError:
41 #                raise wlrepo.LibraryException("[HGLibrary] Not a valid repository at path '%s'." % path)
42 #        elif kwargs.get('create', False):
43 #            os.makedirs(path)
44 #            try:
45 #                self._hgrepo = hglrepo.localrepository(self._hgui, path, create=1)
46 #            except mercurial.error.RepoError:
47 #                raise wlrepo.LibraryException("[HGLibrary] Can't create a repository on path '%s'." % path)
48 #        else:
49 #            raise wlrepo.LibraryException("[HGLibrary] Can't open a library on path '%s'." % path)
50 #
51 #        # fetch the main cabinet
52 #        lock = self._hgrepo.lock()
53 #        try:
54 #            btags = self._hgrepo.branchtags()
55 #
56 #            if not self._has_branch(maincabinet):
57 #                raise wlrepo.LibraryException("[HGLibrary] No branch named '%s' to init main cabinet" % maincabinet)
58 #
59 #            self._maincab = MercurialCabinet(self, maincabinet)
60 #        finally:
61 #            lock.release()
62 #
63 #    @property
64 #    def ospath(self):
65 #        return self._ospath
66 #
67 #    @property
68 #    def main_cabinet(self):
69 #        return self._maincab
70 #
71 #    def document(self, docid, user, part=None, shelve=None):
72 #        return self.cabinet(docid, user, create=False).retrieve(part=part, shelve=shelve)
73 #
74 #    def cabinet(self, docid, user, create=False):
75 #        docid = self._sanitize_string(docid)
76 #        user = self._sanitize_string(user)
77 #
78 #        bname = self._bname(user, docid)
79 #
80 #        lock = self._lock(True)
81 #        try:
82 #            if self._has_branch(bname):
83 #                return MercurialCabinet(self, doc=docid, user=user)
84 #
85 #            if not create:
86 #                raise wlrepo.CabinetNotFound(bname)
87 #
88 #            # check if the docid exists in the main cabinet
89 #            needs_touch = not self._maincab.exists(docid)
90 #            cab = MercurialCabinet(self, doc=docid, user=user)
91 #
92 #            name, fileid = cab._filename(None)
93 #
94 #            def cleanup_action(l):
95 #                if needs_touch:
96 #                    l._fileopener()(fileid, "w").write('')
97 #                    l._fileadd(fileid)
98 #
99 #                garbage = [fid for (fid, did) in l._filelist() if not did.startswith(docid)]
100 #                l._filesrm(garbage)
101 #                print "removed: ", garbage
102 #
103 #            # create the branch
104 #            self._create_branch(bname, before_commit=cleanup_action)
105 #            return MercurialCabinet(self, doc=docid, user=user)
106 #        finally:
107 #            lock.release()
108 #
109 #    #
110 #    # Private methods
111 #    #
112 #
113 #    #
114 #    # Locking
115 #    #
116 #
117 #    def _lock(self, write_mode=False):
118 #        return self._hgrepo.wlock() # no support for read/write mode yet
119 #
120 #    def _transaction(self, write_mode, action):
121 #        lock = self._lock(write_mode)
122 #        try:
123 #            return action(self)
124 #        finally:
125 #            lock.release()
126 #
127 #    #
128 #    # Basic repo manipulation
129 #    #
130 #
131 #    def _checkout(self, rev, force=True):
132 #        return MergeStatus(mercurial.merge.update(self._hgrepo, rev, False, force, None))
133 #
134 #    def _merge(self, rev):
135 #        """ Merge the revision into current working directory """
136 #        return MergeStatus(mercurial.merge.update(self._hgrepo, rev, True, False, None))
137 #
138 #    def _common_ancestor(self, revA, revB):
139 #        return self._hgrepo[revA].ancestor(self.repo[revB])
140 #
141 #    def _commit(self, message, user=u"library"):
142 #        return self._hgrepo.commit(text=message, user=user)
143 #
144 #
145 #    def _fileexists(self, fileid):
146 #        return (fileid in self._hgrepo[None])
147 #
148 #    def _fileadd(self, fileid):
149 #        return self._hgrepo.add([fileid])
150 #
151 #    def _filesadd(self, fileid_list):
152 #        return self._hgrepo.add(fileid_list)
153 #
154 #    def _filerm(self, fileid):
155 #        return self._hgrepo.remove([fileid])
156 #
157 #    def _filesrm(self, fileid_list):
158 #        return self._hgrepo.remove(fileid_list)
159 #
160 #    def _filelist(self, filter=default_filter):
161 #        for name in  self._hgrepo[None]:
162 #            result = filter(name)
163 #            if result is None: continue
164 #
165 #            yield result
166 #
167 #    def _fileopener(self):
168 #        return self._hgrepo.wopener
169 #
170 #    def _filectx(self, fileid, branchid):
171 #        return self._hgrepo.filectx(fileid, changeid=branchid)
172 #
173 #    def _changectx(self, nodeid):
174 #        return self._hgrepo.changectx(nodeid)
175 #
176 #    #
177 #    # BASIC BRANCH routines
178 #    #
179 #
180 #    def _bname(self, user, docid):
181 #        """Returns a branch name for a given document and user."""
182 #        docid = self._sanitize_string(docid)
183 #        uname = self._sanitize_string(user)
184 #        return "personal_" + uname + "_file_" + docid;
185 #
186 #    def _has_branch(self, name):
187 #        return self._hgrepo.branchmap().has_key(self._sanitize_string(name))
188 #
189 #    def _branch_tip(self, name):
190 #        name = self._sanitize_string(name)
191 #        return self._hgrepo.branchtags()[name]
192 #
193 #    def _create_branch(self, name, parent=None, before_commit=None):
194 #        name = self._sanitize_string(name)
195 #
196 #        if self._has_branch(name): return # just exit
197 #
198 #        if parent is None:
199 #            parent = self._maincab
200 #
201 #        parentrev = parent._hgtip()
202 #
203 #        self._checkout(parentrev)
204 #        self._hgrepo.dirstate.setbranch(name)
205 #
206 #        if before_commit: before_commit(self)
207 #
208 #        self._commit("[AUTO] Initial commit for branch '%s'." % name, user='library')
209 #
210 #        # revert back to main
211 #        self._checkout(self._maincab._hgtip())
212 #        return self._branch_tip(name)
213 #
214 #    def _switch_to_branch(self, branchname):
215 #        current = self._hgrepo[None].branch()
216 #
217 #        if current == branchname:
218 #            return current # quick exit
219 #
220 #        self._checkout(self._branch_tip(branchname))
221 #        return branchname
222 #
223 #    def shelf(self, nodeid=None):
224 #        if nodeid is None:
225 #            nodeid = self._maincab._name
226 #        return MercurialShelf(self, self._changectx(nodeid))
227 #
228 #
229 #    #
230 #    # Utils
231 #    #
232 #
233 #    @staticmethod
234 #    def _sanitize_string(s):
235 #        if isinstance(s, unicode):
236 #            s = s.encode('utf-8')
237 #        return s
238 #
239 #class MercurialCabinet(wlrepo.Cabinet):
240 #
241 #    def __init__(self, library, branchname=None, doc=None, user=None):
242 #        if doc and user:
243 #            super(MercurialCabinet, self).__init__(library, doc=doc, user=user)
244 #            self._branchname = library._bname(user=user, docid=doc)
245 #        elif branchname:
246 #            super(MercurialCabinet, self).__init__(library, name=branchname)
247 #            self._branchname = branchname
248 #        else:
249 #            raise ValueError("Provide either doc/user or branchname")
250 #
251 #    def shelf(self):
252 #        return self._library.shelf(self._branchname)
253 #
254 #    def parts(self):
255 #        return self._execute_in_branch(action=lambda l, c: (e[1] for e in l._filelist()))
256 #
257 #    def retrieve(self, part=None, shelf=None):
258 #        name, fileid = self._filename(part)
259 #
260 #        print "Retrieving document %s from cab %s" % (name, self._name)
261 #
262 #        if fileid is None:
263 #            raise wlrepo.LibraryException("Can't retrieve main document from main cabinet.")
264 #
265 #        def retrieve_action(l,c):
266 #            if l._fileexists(fileid):
267 #                return MercurialDocument(c, name=name, fileid=fileid)
268 #            print "File %s not found " % fileid
269 #            return None
270 #
271 #        return self._execute_in_branch(retrieve_action)
272 #
273 #    def create(self, name, initial_data):
274 #        name, fileid = self._filename(name)
275 #
276 #        if name is None:
277 #            raise ValueError("Can't create main doc for maincabinet.")
278 #
279 #        def create_action(l, c):
280 #            if l._fileexists(fileid):
281 #                raise wlrepo.LibraryException("Can't create document '%s' in cabinet '%s' - it already exists" % (fileid, c.name))
282 #
283 #            fd = l._fileopener()(fileid, "w")
284 #            fd.write(initial_data)
285 #            fd.close()
286 #            l._fileadd(fileid)
287 #            l._commit("File '%s' created." % fileid)
288 #            return MercurialDocument(c, fileid=fileid, name=name)
289 #
290 #        return self._execute_in_branch(create_action)
291 #
292 #    def exists(self, part=None, shelf=None):
293 #        name, filepath = self._filename(part)
294 #
295 #        if filepath is None: return False
296 #        return self._execute_in_branch(lambda l, c: l._fileexists(filepath))
297 #
298 #    def _execute_in_branch(self, action, write=False):
299 #        def switch_action(library):
300 #            old = library._switch_to_branch(self._branchname)
301 #            try:
302 #                return action(library, self)
303 #            finally:
304 #                library._switch_to_branch(old)
305 #
306 #        return self._library._transaction(write_mode=write, action=switch_action)
307 #
308 #
309 #    def _filename(self, docid):
310 #        return self._partname(docid, 'xml')
311 #
312 #    def _partname(self, docid, part):
313 #        docid = self._library._sanitize_string(part)
314 #        part = self._library._sanitize_string(part)
315 #
316 #        if part is None:
317 #            part = 'xml'
318 #
319 #        if self._maindoc == '' and docid is None:
320 #            return None
321 #
322 #        return 'pub_' + docid + '.' + part
323 #
324 #    def _fileopener(self):
325 #        return self._library._fileopener()
326 #
327 #    def _hgtip(self):
328 #        return self._library._branch_tip(self._branchname)
329 #
330 #    def _filectx(self, fileid):
331 #        return self._library._filectx(fileid, self._branchname)
332 #
333 #    def ismain(self):
334 #        return (self._library.main_cabinet == self)
335 #
336 #class MercurialDocument(wlrepo.Document):
337 #
338 #    def __init__(self, cabinet, docid):
339 #        super(MercurialDocument, self).__init__(cabinet, name=docid)
340 #        self._opener = self._cabinet._fileopener()
341 #        self._docid = docid
342 #        self._ctxs = {}
343 #
344 #    def _ctx(self, part):
345 #        if not self._ctxs.has_key(part):
346 #            self._ctxs[part] = self._cabinet._filectx(self._fileid())
347 #        return self._ctxs[part]
348 #
349 #    def _fileid(self, part='xml'):
350 #        return self._cabinet._partname(self._docid, part)
351 #
352 #    def read(self, part='xml'):
353 #        return self._opener(self._ctx(part).path(), "r").read()
354 #
355 #    def write(self, data, part='xml'):
356 #        return self._opener(self._ctx(part).path(), "w").write(data)
357 #
358 #    def commit(self, message, user):
359 #        """Commit all parts of the document."""
360 #        self.library._fileadd(self._fileid)
361 #        self.library._commit(self._fileid, message, user)
362 #
363 #    def update(self):
364 #        """Update parts of the document."""
365 #        lock = self.library._lock()
366 #        try:
367 #            if self._cabinet.ismain():
368 #                return True # always up-to-date
369 #
370 #            user = self._cabinet.username or 'library'
371 #            mdoc = self.library.document(self._fileid)
372 #
373 #            mshelf = mdoc.shelf()
374 #            shelf = self.shelf()
375 #
376 #            if not mshelf.ancestorof(shelf) and not shelf.parentof(mshelf):
377 #                shelf.merge_with(mshelf, user=user)
378 #
379 #            return True
380 #        finally:
381 #            lock.release()
382 #
383 #    def share(self, message):
384 #        lock = self.library._lock()
385 #        try:
386 #            print "sharing from", self._cabinet, self._cabinet.username
387 #
388 #            if self._cabinet.ismain():
389 #                return True # always shared
390 #
391 #            if self._cabinet.username is None:
392 #                raise ValueError("Can only share documents from personal cabinets.")
393 #
394 #            user = self._cabinet.username
395 #
396 #            main = self.library.shelf()
397 #            local = self.shelf()
398 #
399 #            no_changes = True
400 #
401 #            # Case 1:
402 #            #         * local
403 #            #         |
404 #            #         * <- can also be here!
405 #            #        /|
406 #            #       / |
407 #            # main *  *
408 #            #      |  |
409 #            # The local branch has been recently updated,
410 #            # so we don't need to update yet again, but we need to
411 #            # merge down to default branch, even if there was
412 #            # no commit's since last update
413 #
414 #            if main.ancestorof(local):
415 #                print "case 1"
416 #                main.merge_with(local, user=user, message=message)
417 #                no_changes = False
418 #            # Case 2:
419 #            #
420 #            # main *  * local
421 #            #      |\ |
422 #            #      | \|
423 #            #      |  *
424 #            #      |  |
425 #            #
426 #            # Default has no changes, to update from this branch
427 #            # since the last merge of local to default.
428 #            elif local.has_common_ancestor(main):
429 #                print "case 2"
430 #                if not local.parentof(main):
431 #                    main.merge_with(local, user=user, message=message)
432 #                    no_changes = False
433 #
434 #            # Case 3:
435 #            # main *
436 #            #      |
437 #            #      * <- this case overlaps with previos one
438 #            #      |\
439 #            #      | \
440 #            #      |  * local
441 #            #      |  |
442 #            #
443 #            # There was a recent merge to the defaul branch and
444 #            # no changes to local branch recently.
445 #            #
446 #            # Use the fact, that user is prepared to see changes, to
447 #            # update his branch if there are any
448 #            elif local.ancestorof(main):
449 #                print "case 3"
450 #                if not local.parentof(main):
451 #                    local.merge_with(main, user=user, message='Local branch update.')
452 #                    no_changes = False
453 #            else:
454 #                print "case 4"
455 #                local.merge_with(main, user=user, message='Local branch update.')
456 #                local = self.shelf()
457 #                main.merge_with(local, user=user, message=message)
458 #
459 #            print "no_changes: ", no_changes
460 #            return no_changes
461 #        finally:
462 #            lock.release()
463 #
464 #    def shared(self):
465 #        return self.library.main_cabinet.retrieve(self._name)
466 #
467 #    def exists(self, part='xml'):
468 #        return self._cabinet.exists(self._fileid(part))
469 #
470 #    @property
471 #    def size(self):
472 #        return self._filectx.size()
473 #
474 #    def shelf(self):
475 #        return self._cabinet.shelf()
476 #
477 #    @property
478 #    def last_modified(self):
479 #        return self._filectx.date()
480 #
481 #    def __str__(self):
482 #        return u"Document(%s->%s)" % (self._cabinet.name, self._name)
483 #
484 #    def __eq__(self, other):
485 #        return self._filectx == other._filectx
486 #
487 #
488 #
489 #class MercurialShelf(wlrepo.Shelf):
490 #
491 #    def __init__(self, lib, changectx):
492 #        super(MercurialShelf, self).__init__(lib)
493 #
494 #        if isinstance(changectx, str):
495 #            self._changectx = lib._changectx(changectx)
496 #        else:
497 #            self._changectx = changectx
498 #
499 #    @property
500 #    def _rev(self):
501 #        return self._changectx.node()
502 #
503 #    def __str__(self):
504 #        return self._changectx.hex()
505 #
506 #    def __repr__(self):
507 #        return "MercurialShelf(%s)" % self._changectx.hex()
508 #
509 #    def ancestorof(self, other):
510 #        nodes = list(other._changectx._parents)
511 #        while nodes[0].node() != nullid:
512 #            v = nodes.pop(0)
513 #            if v == self._changectx:
514 #                return True
515 #            nodes.extend( v._parents )
516 #        return False
517 #
518 #    def parentof(self, other):
519 #        return self._changectx in other._changectx._parents
520 #
521 #    def has_common_ancestor(self, other):
522 #        a = self._changectx.ancestor(other._changectx)
523 #        # print a, self._changectx.branch(), a.branch()
524 #
525 #        return (a.branch() == self._changectx.branch())
526 #
527 #    def merge_with(self, other, user, message):
528 #        lock = self._library._lock(True)
529 #        try:
530 #            self._library._checkout(self._changectx.node())
531 #            self._library._merge(other._changectx.node())
532 #            self._library._commit(user=user, message=message)
533 #        finally:
534 #            lock.release()
535 #
536 #    def __eq__(self, other):
537 #        return self._changectx.node() == other._changectx.node()
538 #
539 #
540 #class MergeStatus(object):
541 #    def __init__(self, mstatus):
542 #        self.updated = mstatus[0]
543 #        self.merged = mstatus[1]
544 #        self.removed = mstatus[2]
545 #        self.unresolved = mstatus[3]
546 #
547 #    def isclean(self):
548 #        return self.unresolved == 0
549 #
550 #class UpdateStatus(object):
551 #
552 #    def __init__(self, mstatus):
553 #        self.modified = mstatus[0]
554 #        self.added = mstatus[1]
555 #        self.removed = mstatus[2]
556 #        self.deleted = mstatus[3]
557 #        self.untracked = mstatus[4]
558 #        self.ignored = mstatus[5]
559 #        self.clean = mstatus[6]
560 #
561 #    def has_changes(self):
562 #        return bool(len(self.modified) + len(self.added) + \
563 #                    len(self.removed) + len(self.deleted))
564 #
565 #__all__ = ["MercurialLibrary"]