1 # -*- encoding: utf-8 -*-
3 __author__= "Łukasz Rekucki"
4 __date__ = "$2009-09-25 09:33:02$"
5 __doc__ = "Module documentation."
8 from mercurial import localrepo as hglrepo
9 from mercurial import ui as hgui
10 from mercurial import error
13 from wlrepo.mercurial_backend import MercurialRevision
14 from wlrepo.mercurial_backend.document import MercurialDocument
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]
24 return self.unresolved == 0
26 class UpdateStatus(object):
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]
37 def has_changes(self):
38 return bool(len(self.modified) + len(self.added) + \
39 len(self.removed) + len(self.deleted))
41 class MercurialLibrary(wlrepo.Library):
42 """Mercurial implementation of the Library API"""
44 def __init__(self, path, **kwargs):
45 super(wlrepo.Library, self).__init__( ** kwargs)
50 self._hgui = hgui.ui()
51 self._hgui.config('ui', 'quiet', 'true')
52 self._hgui.config('ui', 'interactive', 'false')
55 self._ospath = self._sanitize_string(os.path.realpath(path))
57 if os.path.isdir(path):
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):
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)
69 raise wlrepo.LibraryException("[HGLibrary] Can't open a library on path '%s'." % path)
73 return [ key[5:].decode('utf-8') for key in \
74 self._hgrepo.branchmap() if key.startswith("$doc:") ]
78 return self._ospath.decode('utf-8')
80 def document_for_rev(self, revision):
82 raise ValueError("Revision can't be None.")
84 if not isinstance(revision, MercurialRevision):
85 rev = self.get_revision(revision)
89 if not self._doccache.has_key(str(rev)):
90 self._doccache[str(rev)] = MercurialDocument(self, rev)
92 # every revision is a document
93 return self._doccache[str(rev)]
95 def document(self, docid, user=None):
96 return self.document_for_rev(self.fulldocid(docid, user))
98 def get_revision(self, revid):
99 revid = self._sanitize_string(revid)
102 ctx = self._changectx(revid)
103 except mercurial.error.RepoError, e:
104 raise wlrepo.RevisionNotFound(revid)
107 raise wlrepo.RevisionNotFound(revid)
109 if self._revcache.has_key(ctx):
110 return self._revcache[ctx]
112 return MercurialRevision(self, ctx)
114 def fulldocid(self, docid, user=None):
117 fulldocid += u'$user:' + user
118 fulldocid += u'$doc:' + docid
122 def has_revision(self, revid):
126 except mercurial.error.RepoError:
129 def document_create(self, docid):
132 # check if it already exists
133 fullid = self.fulldocid(docid)
135 if self.has_revision(fullid):
136 raise wlrepo.DocumentAlreadyExists(u"Document %s already exists!" % docid);
139 self._create_branch(self._sanitize_string(fullid))
140 return self.document_for_rev(fullid)
150 def lock(self, write_mode=False):
151 return self._hgrepo.wlock() # no support for read/write mode yet
153 def _transaction(self, write_mode, action):
154 lock = self.lock(write_mode)
161 # Basic repo manipulation
164 def _checkout(self, rev, force=True):
165 return MergeStatus(mercurial.merge.update(self._hgrepo, rev, False, force, None))
167 def _merge(self, rev):
168 """ Merge the revision into current working directory """
169 return MergeStatus(mercurial.merge.update(self._hgrepo, rev, True, False, None))
171 def _common_ancestor(self, revA, revB):
172 return self._hgrepo[revA].ancestor(self.repo[revB])
174 def _commit(self, message, user=u"library"):
175 return self._hgrepo.commit(text=message, user=user)
178 def _fileexists(self, fileid):
179 return (fileid in self._hgrepo[None])
181 def _fileadd(self, fileid):
182 return self._hgrepo.add([fileid])
184 def _filesadd(self, fileid_list):
185 return self._hgrepo.add(fileid_list)
187 def _filerm(self, fileid):
188 return self._hgrepo.remove([fileid])
190 def _filesrm(self, fileid_list):
191 return self._hgrepo.remove(fileid_list)
193 def _fileopen(self, path, mode):
194 return self._hgrepo.wopener(path, mode)
196 def _filectx(self, fileid, revid):
197 return self._hgrepo.filectx(fileid, changeid=revid)
199 def _changectx(self, nodeid):
200 return self._hgrepo.changectx(nodeid)
203 return self._hgrepo.rollback()
206 # BASIC BRANCH routines
209 def _bname(self, user, docid):
210 """Returns a branch name for a given document and user."""
211 docid = self._sanitize_string(docid)
212 uname = self._sanitize_string(user)
213 return "personal_" + uname + "_file_" + docid;
215 def _has_branch(self, name):
216 return self._hgrepo.branchmap().has_key(self._sanitize_string(name))
218 def _branch_tip(self, name):
219 name = self._sanitize_string(name)
220 return self._hgrepo.branchtags()[name]
222 def _create_branch(self, name, parent=None, before_commit=None):
223 name = self._sanitize_string(name)
225 if self._has_branch(name): return # just exit
228 parentrev = self._hgrepo['$branchbase'].node()
230 parentrev = parent.hgrev()
232 self._checkout(parentrev)
233 self._hgrepo.dirstate.setbranch(name)
235 if before_commit: before_commit(self)
237 self._commit("$ADMN$ Initial commit for branch '%s'." % name, user='$library')
239 def _switch_to_branch(self, branchname):
240 current = self._hgrepo[None].branch()
242 if current == branchname:
243 return current # quick exit
245 self._checkout(self._branch_tip(branchname))
253 def _sanitize_string(s):
257 if isinstance(s, unicode):
258 s = s.encode('utf-8')