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