ebb38d8893c3e753d65f3c0468ad16eaf593422e
[redakcja.git] / lib / wlrepo / mercurial_backend / library.py
1 # -*- encoding: utf-8 -*-
2
3 __author__= "Ɓukasz Rekucki"
4 __date__ = "$2009-09-25 09:33:02$"
5 __doc__ = "Module documentation."
6
7 import mercurial
8 from mercurial import localrepo as hglrepo
9 from mercurial import ui as hgui
10 from mercurial import error
11 import wlrepo
12
13 from wlrepo.mercurial_backend import MercurialRevision
14 from wlrepo.mercurial_backend.document import MercurialDocument
15
16 class MergeStatus(object):
17     def __init__(self, mstatus):
18         self.updated = mstatus[0]
19         self.merged = mstatus[1]
20         self.removed = mstatus[2]
21         self.unresolved = mstatus[3]
22
23     def isclean(self):
24         return self.unresolved == 0
25
26 class UpdateStatus(object):
27
28     def __init__(self, mstatus):
29         self.modified = mstatus[0]
30         self.added = mstatus[1]
31         self.removed = mstatus[2]
32         self.deleted = mstatus[3]
33         self.untracked = mstatus[4]
34         self.ignored = mstatus[5]
35         self.clean = mstatus[6]
36
37     def has_changes(self):
38         return bool(len(self.modified) + len(self.added) + \
39                     len(self.removed) + len(self.deleted))
40
41 class MercurialLibrary(wlrepo.Library):
42     """Mercurial implementation of the Library API"""
43
44     def __init__(self, path, **kwargs):
45         super(wlrepo.Library, self).__init__( ** kwargs)
46
47         self._revcache = {}
48         self._doccache = {}
49
50         self._hgui = hgui.ui()
51         self._hgui.config('ui', 'quiet', 'true')
52         self._hgui.config('ui', 'interactive', 'false')
53
54         import os.path
55         self._ospath = self._sanitize_string(os.path.realpath(path))        
56
57         if os.path.isdir(path):
58             try:
59                 self._hgrepo = hglrepo.localrepository(self._hgui, path)
60             except mercurial.error.RepoError:
61                 raise wlrepo.LibraryException("[HGLibrary] Not a valid repository at path '%s'." % path)
62         elif kwargs.get('create', False):
63             os.makedirs(path)
64             try:
65                 self._hgrepo = hglrepo.localrepository(self._hgui, path, create=1)
66             except mercurial.error.RepoError:
67                 raise wlrepo.LibraryException("[HGLibrary] Can't create a repository on path '%s'." % path)
68         else:
69             raise wlrepo.LibraryException("[HGLibrary] Can't open a library on path '%s'." % path)
70
71
72     def documents(self):
73         return [ key[5:].decode('utf-8') for key in \
74             self._hgrepo.branchmap() if key.startswith("$doc:") ]
75
76     @property
77     def ospath(self):
78         return self._ospath.decode('utf-8')
79
80     def document_for_rev(self, revision):
81         if revision is None:
82             raise ValueError("Revision can't be None.")
83         
84         if not isinstance(revision, MercurialRevision):            
85             rev = self.get_revision(revision)
86         else:
87             rev = revision       
88
89         if not self._doccache.has_key(str(rev)):
90             self._doccache[str(rev)] = MercurialDocument(self, rev)
91
92         # every revision is a document
93         return self._doccache[str(rev)]
94
95     def document(self, docid, user=None):       
96         return self.document_for_rev(self.fulldocid(docid, user))
97
98     def get_revision(self, revid):
99         revid = self._sanitize_string(revid).decode('utf-8')
100
101         print "Looking up rev %r (%s)" %(revid, type(revid))
102
103         try:
104             # THIS IS THE MOST BRAIN-DEAD API I EVER SEEN
105             # WHY DO ALL THE OTHER METHODS SIMPLY
106             # FAIL WHEN GIVEN UNICODE, WHEN THIS WORKS ONLY!! WITH IT
107
108             ctx = self._changectx( revid )
109         except mercurial.error.RepoError, e:
110             raise wlrepo.RevisionNotFound(revid)
111
112         if ctx is None:
113             raise wlrepo.RevisionNotFound(revid)
114
115         if self._revcache.has_key(ctx):
116             return self._revcache[ctx]
117
118         return MercurialRevision(self, ctx)
119
120     def fulldocid(self, docid, user=None):                
121         fulldocid = u''
122         if user is not None:
123             fulldocid += u'$user:' + user
124         fulldocid += u'$doc:' + docid
125         return fulldocid
126
127     def has_revision(self, revid):
128         try:
129             self._hgrepo[revid]
130             return True
131         except mercurial.error.RepoError:
132             return False
133
134     def document_create(self, docid):
135         
136         
137         # check if it already exists
138         fullid = self.fulldocid(docid)
139
140         if self.has_revision(fullid):
141             raise wlrepo.DocumentAlreadyExists(u"Document %s already exists!" % docid);
142
143         # doesn't exist
144         self._create_branch(self._sanitize_string(fullid))
145         return self.document_for_rev(fullid)
146
147     #
148     # Private methods
149     #
150
151     #
152     # Locking
153     #
154
155     def lock(self, write_mode=False):
156         return self._hgrepo.wlock() # no support for read/write mode yet
157
158     def _transaction(self, write_mode, action):
159         lock = self.lock(write_mode)
160         try:
161             return action(self)
162         finally:
163             lock.release()
164
165     #
166     # Basic repo manipulation
167     #
168
169     def _checkout(self, rev, force=True):
170         return MergeStatus(mercurial.merge.update(self._hgrepo, rev, False, force, None))
171
172     def _merge(self, rev):
173         """ Merge the revision into current working directory """
174         return MergeStatus(mercurial.merge.update(self._hgrepo, rev, True, False, None))
175
176     def _common_ancestor(self, revA, revB):
177         return self._hgrepo[revA].ancestor(self.repo[revB])
178
179     def _commit(self, message, user=u"library"):
180         return self._hgrepo.commit(text=message, user=user)
181
182
183     def _fileexists(self, fileid):
184         return (fileid in self._hgrepo[None])
185
186     def _fileadd(self, fileid):
187         return self._hgrepo.add([fileid])
188
189     def _filesadd(self, fileid_list):
190         return self._hgrepo.add(fileid_list)
191
192     def _filerm(self, fileid):
193         return self._hgrepo.remove([fileid])
194
195     def _filesrm(self, fileid_list):
196         return self._hgrepo.remove(fileid_list)   
197
198     def _fileopen(self, path, mode):
199         return self._hgrepo.wopener(path, mode)
200
201     def _filectx(self, fileid, revid):
202         return self._hgrepo.filectx(fileid, changeid=revid)
203
204     def _changectx(self, nodeid):
205         return self._hgrepo.changectx(nodeid)
206
207     def _rollback(self):
208         return self._hgrepo.rollback()
209
210     #
211     # BASIC BRANCH routines
212     #
213
214     def _bname(self, user, docid):
215         """Returns a branch name for a given document and user."""
216         docid = self._sanitize_string(docid)
217         uname = self._sanitize_string(user)
218         return "personal_" + uname + "_file_" + docid;
219
220     def _has_branch(self, name):
221         return self._hgrepo.branchmap().has_key(self._sanitize_string(name))
222
223     def _branch_tip(self, name):
224         name = self._sanitize_string(name)
225         return self._hgrepo.branchtags()[name]    
226
227     def _create_branch(self, name, parent=None, before_commit=None):
228         name = self._sanitize_string(name)
229
230         if self._has_branch(name): return # just exit
231
232         if parent is None:
233             parentrev = self._hgrepo['$branchbase'].node()
234         else:
235             parentrev = parent.hgrev()
236
237         self._checkout(parentrev)
238         self._hgrepo.dirstate.setbranch(name)
239
240         if before_commit: before_commit(self)
241
242         self._commit("$ADMN$ Initial commit for branch '%s'." % name, user='$library')        
243
244     def _switch_to_branch(self, branchname):
245         current = self._hgrepo[None].branch()
246
247         if current == branchname:
248             return current # quick exit
249
250         self._checkout(self._branch_tip(branchname))
251         return branchname    
252
253     #
254     # Utils
255     #
256
257     @staticmethod
258     def _sanitize_string(s):
259         if s is None:
260             return None
261
262         if isinstance(s, unicode):
263             s = s.encode('utf-8')        
264
265         return s