1 # -*- encoding: utf-8 -*-
4 log = logging.getLogger('ral.mercurial')
6 __author__= "Ćukasz Rekucki"
7 __date__ = "$2009-09-25 09:33:02$"
8 __doc__ = "Module documentation."
11 from mercurial import localrepo as hglrepo
12 from mercurial import ui as hgui
13 from mercurial import error
16 from wlrepo.mercurial_backend.revision import MercurialRevision
17 from wlrepo.mercurial_backend.document import MercurialDocument
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]
27 return self.unresolved == 0
29 class UpdateStatus(object):
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]
40 def has_changes(self):
41 return bool(len(self.modified) + len(self.added) + \
42 len(self.removed) + len(self.deleted))
44 class MercurialLibrary(wlrepo.Library):
45 """Mercurial implementation of the Library API"""
47 def __init__(self, path, **kwargs):
48 super(wlrepo.Library, self).__init__( ** kwargs)
53 self._hgui = hgui.ui()
54 self._hgui.config('ui', 'quiet', 'true')
55 self._hgui.config('ui', 'interactive', 'false')
58 self._ospath = self._sanitize_string(os.path.realpath(path))
60 if os.path.isdir(path):
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):
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)
72 raise wlrepo.LibraryException("[HGLibrary] Can't open a library on path '%s'." % path)
76 return [ key[5:].decode('utf-8') for key in \
77 self._hgrepo.branchmap() if key.startswith("$doc:") ]
81 return self._ospath.decode('utf-8')
83 def document_for_revision(self, revision):
85 raise ValueError("Revision can't be None.")
87 if not isinstance(revision, MercurialRevision):
88 rev = self.get_revision(revision)
92 if not self._doccache.has_key(str(rev)):
93 self._doccache[str(rev)] = MercurialDocument(self, rev)
95 # every revision is a document
96 return self._doccache[str(rev)]
98 def document(self, docid, user=None, rev=u'latest'):
99 rev = self._sanitize_string(rev)
102 doc = self.document_for_revision(rev)
104 if doc.id != docid or (doc.owner != user):
105 raise wlrepo.RevisionMismatch(self.fulldocid(docid, user)+u'@'+unicode(rev))
109 return self.document_for_revision(self.fulldocid(docid, user))
111 def get_revision(self, revid):
112 revid = self._sanitize_string(revid)
114 log.info("Looking up rev %r (%s)" %(revid, type(revid)) )
117 ctx = self._changectx( revid )
118 except mercurial.error.RepoError, e:
119 raise wlrepo.RevisionNotFound(revid)
122 raise wlrepo.RevisionNotFound(revid)
124 return self._revision(ctx)
126 def _revision(self, ctx):
127 if self._revcache.has_key(ctx):
128 return self._revcache[ctx]
130 return MercurialRevision(self, ctx)
132 def fulldocid(self, docid, user=None):
135 fulldocid += u'$user:' + user
136 fulldocid += u'$doc:' + docid
139 def has_revision(self, revid):
140 revid = self._sanitize_string(revid)
145 except mercurial.error.RepoError:
148 def document_create(self, docid):
150 # check if it already exists
151 fullid = self.fulldocid(docid)
153 if self.has_revision(fullid):
154 raise wlrepo.DocumentAlreadyExists(u"Document %s already exists!" % docid);
157 self._create_branch(self._sanitize_string(fullid))
158 return self.document_for_revision(fullid)
168 def lock(self, write_mode=False):
169 return self._hgrepo.wlock() # no support for read/write mode yet
171 def _transaction(self, write_mode, action):
172 lock = self.lock(write_mode)
179 # Basic repo manipulation
182 def _checkout(self, rev, force=True):
183 return MergeStatus(mercurial.merge.update(self._hgrepo, rev, False, force, None))
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))
189 def _common_ancestor(self, revA, revB):
190 return self._hgrepo[revA].ancestor(self.repo[revB])
192 def _commit(self, message, user=u"library"):
193 return self._hgrepo.commit(text=message, user=user)
196 def _fileexists(self, fileid):
197 return (fileid in self._hgrepo[None])
199 def _fileadd(self, fileid):
200 return self._hgrepo.add([fileid])
202 def _filesadd(self, fileid_list):
203 return self._hgrepo.add(fileid_list)
205 def _filerm(self, fileid):
206 return self._hgrepo.remove([fileid])
208 def _filesrm(self, fileid_list):
209 return self._hgrepo.remove(fileid_list)
211 def _fileopen(self, path, mode):
212 return self._hgrepo.wopener(path, mode)
214 def _filectx(self, fileid, revid):
215 return self._hgrepo.filectx(fileid, changeid=revid)
217 def _changectx(self, nodeid):
218 return self._hgrepo.changectx(nodeid)
221 return self._hgrepo.rollback()
224 # BASIC BRANCH routines
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;
233 def _has_branch(self, name):
234 return self._hgrepo.branchmap().has_key(self._sanitize_string(name))
236 def _branch_tip(self, name):
237 name = self._sanitize_string(name)
238 return self._hgrepo.branchtags()[name]
240 def _create_branch(self, name, parent=None, before_commit=None, message=None):
241 name = self._sanitize_string(name)
243 if self._has_branch(name): return # just exit
246 parentrev = self._hgrepo['$branchbase'].node()
248 parentrev = parent.hgrev()
250 self._checkout(parentrev)
251 self._set_branchname(name)
253 if before_commit: before_commit(self)
255 message = message or "$ADMN$ Initial commit for branch '%s'." % name
256 self._commit(message, user='$library')
258 def _set_branchname(self, name):
259 name = self._sanitize_string(name)
260 self._hgrepo.dirstate.setbranch(name)
262 def _switch_to_branch(self, branchname):
263 current = self._hgrepo[None].branch()
265 if current == branchname:
266 return current # quick exit
268 self._checkout(self._branch_tip(branchname))
276 def _sanitize_string(s):
280 if isinstance(s, unicode):
281 s = s.encode('utf-8')