042fda2991dea9980479ccf6210baad25dc6eb5c
[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:] 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             revision = self._sanitize_string(unicode(revision))
86             rev = self.get_revision(revision)
87         else:
88             rev = revision       
89
90         if not self._doccache.has_key(str(rev)):
91             self._doccache[str(rev)] = MercurialDocument(self, rev)
92
93         # every revision is a document
94         return self._doccache[str(rev)]
95
96     def document(self, docid, user=None):       
97         return self.document_for_rev(self.fulldocid(docid, user))
98
99     def get_revision(self, revid):
100         revid = self._sanitize_string(revid)
101
102         try:
103             ctx = self._changectx(revid)
104         except mercurial.error.RepoError, e:
105             raise wlrepo.RevisionNotFound(revid)
106
107         if ctx is None:
108             raise wlrepo.RevisionNotFound(revid)
109
110         if self._revcache.has_key(ctx):
111             return self._revcache[ctx]
112
113         return MercurialRevision(self, ctx)
114
115     def fulldocid(self, docid, user=None):                
116         fulldocid = u''
117         if user is not None:
118             fulldocid += u'$user:' + user
119         fulldocid += u'$doc:' + docid
120         return fulldocid
121
122
123     def has_revision(self, revid):
124         try:
125             self._hgrepo[revid]
126             return True
127         except mercurial.error.RepoError:
128             return False
129
130     def document_create(self, docid):
131         
132         
133         # check if it already exists
134         fullid = self.fulldocid(docid)
135
136         if self.has_revision(fullid):
137             raise wlrepo.DocumentAlreadyExists(u"Document %s already exists!" % docid);
138
139         # doesn't exist
140         self._create_branch(self._sanitize_string(fullid))
141         return self.document_for_rev(fullid)
142
143     #
144     # Private methods
145     #
146
147     #
148     # Locking
149     #
150
151     def lock(self, write_mode=False):
152         return self._hgrepo.wlock() # no support for read/write mode yet
153
154     def _transaction(self, write_mode, action):
155         lock = self.lock(write_mode)
156         try:
157             return action(self)
158         finally:
159             lock.release()
160
161     #
162     # Basic repo manipulation
163     #
164
165     def _checkout(self, rev, force=True):
166         return MergeStatus(mercurial.merge.update(self._hgrepo, rev, False, force, None))
167
168     def _merge(self, rev):
169         """ Merge the revision into current working directory """
170         return MergeStatus(mercurial.merge.update(self._hgrepo, rev, True, False, None))
171
172     def _common_ancestor(self, revA, revB):
173         return self._hgrepo[revA].ancestor(self.repo[revB])
174
175     def _commit(self, message, user=u"library"):
176         return self._hgrepo.commit(text=message, user=user)
177
178
179     def _fileexists(self, fileid):
180         return (fileid in self._hgrepo[None])
181
182     def _fileadd(self, fileid):
183         return self._hgrepo.add([fileid])
184
185     def _filesadd(self, fileid_list):
186         return self._hgrepo.add(fileid_list)
187
188     def _filerm(self, fileid):
189         return self._hgrepo.remove([fileid])
190
191     def _filesrm(self, fileid_list):
192         return self._hgrepo.remove(fileid_list)   
193
194     def _fileopen(self, path, mode):
195         return self._hgrepo.wopener(path, mode)
196
197     def _filectx(self, fileid, revid):
198         return self._hgrepo.filectx(fileid, changeid=revid)
199
200     def _changectx(self, nodeid):
201         return self._hgrepo.changectx(nodeid)
202
203     def _rollback(self):
204         return self._hgrepo.rollback()
205
206     #
207     # BASIC BRANCH routines
208     #
209
210     def _bname(self, user, docid):
211         """Returns a branch name for a given document and user."""
212         docid = self._sanitize_string(docid)
213         uname = self._sanitize_string(user)
214         return "personal_" + uname + "_file_" + docid;
215
216     def _has_branch(self, name):
217         return self._hgrepo.branchmap().has_key(self._sanitize_string(name))
218
219     def _branch_tip(self, name):
220         name = self._sanitize_string(name)
221         return self._hgrepo.branchtags()[name]    
222
223     def _create_branch(self, name, parent=None, before_commit=None):
224         name = self._sanitize_string(name)
225
226         if self._has_branch(name): return # just exit
227
228         if parent is None:
229             parentrev = self._hgrepo['$branchbase'].node()
230         else:
231             parentrev = parent.hgrev()
232
233         self._checkout(parentrev)
234         self._hgrepo.dirstate.setbranch(name)
235
236         if before_commit: before_commit(self)
237
238         self._commit("$ADMN$ Initial commit for branch '%s'." % name, user='$library')        
239
240     def _switch_to_branch(self, branchname):
241         current = self._hgrepo[None].branch()
242
243         if current == branchname:
244             return current # quick exit
245
246         self._checkout(self._branch_tip(branchname))
247         return branchname    
248
249     #
250     # Utils
251     #
252
253     @staticmethod
254     def _sanitize_string(s):
255         if s is None:
256             return None
257
258         if isinstance(s, unicode):
259             s = s.encode('utf-8')        
260
261         return s