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:] 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 revision = self._sanitize_string(unicode(revision))
86 rev = self.get_revision(revision)
90 if not self._doccache.has_key(str(rev)):
91 self._doccache[str(rev)] = MercurialDocument(self, rev)
93 # every revision is a document
94 return self._doccache[str(rev)]
96 def document(self, docid, user=None):
97 return self.document_for_rev(self.fulldocid(docid, user))
99 def get_revision(self, revid):
100 revid = self._sanitize_string(revid)
103 ctx = self._changectx(revid)
104 except mercurial.error.RepoError, e:
105 raise wlrepo.RevisionNotFound(revid)
108 raise wlrepo.RevisionNotFound(revid)
110 if self._revcache.has_key(ctx):
111 return self._revcache[ctx]
113 return MercurialRevision(self, ctx)
115 def fulldocid(self, docid, user=None):
118 fulldocid += u'$user:' + user
119 fulldocid += u'$doc:' + docid
123 def has_revision(self, revid):
127 except mercurial.error.RepoError:
130 def document_create(self, docid):
133 # check if it already exists
134 fullid = self.fulldocid(docid)
136 if self.has_revision(fullid):
137 raise wlrepo.DocumentAlreadyExists(u"Document %s already exists!" % docid);
140 self._create_branch(self._sanitize_string(fullid))
141 return self.document_for_rev(fullid)
151 def lock(self, write_mode=False):
152 return self._hgrepo.wlock() # no support for read/write mode yet
154 def _transaction(self, write_mode, action):
155 lock = self.lock(write_mode)
162 # Basic repo manipulation
165 def _checkout(self, rev, force=True):
166 return MergeStatus(mercurial.merge.update(self._hgrepo, rev, False, force, None))
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))
172 def _common_ancestor(self, revA, revB):
173 return self._hgrepo[revA].ancestor(self.repo[revB])
175 def _commit(self, message, user=u"library"):
176 return self._hgrepo.commit(text=message, user=user)
179 def _fileexists(self, fileid):
180 return (fileid in self._hgrepo[None])
182 def _fileadd(self, fileid):
183 return self._hgrepo.add([fileid])
185 def _filesadd(self, fileid_list):
186 return self._hgrepo.add(fileid_list)
188 def _filerm(self, fileid):
189 return self._hgrepo.remove([fileid])
191 def _filesrm(self, fileid_list):
192 return self._hgrepo.remove(fileid_list)
194 def _fileopen(self, path, mode):
195 return self._hgrepo.wopener(path, mode)
197 def _filectx(self, fileid, revid):
198 return self._hgrepo.filectx(fileid, changeid=revid)
200 def _changectx(self, nodeid):
201 return self._hgrepo.changectx(nodeid)
204 return self._hgrepo.rollback()
207 # BASIC BRANCH routines
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;
216 def _has_branch(self, name):
217 return self._hgrepo.branchmap().has_key(self._sanitize_string(name))
219 def _branch_tip(self, name):
220 name = self._sanitize_string(name)
221 return self._hgrepo.branchtags()[name]
223 def _create_branch(self, name, parent=None, before_commit=None):
224 name = self._sanitize_string(name)
226 if self._has_branch(name): return # just exit
229 parentrev = self._hgrepo['$branchbase'].node()
231 parentrev = parent.hgrev()
233 self._checkout(parentrev)
234 self._hgrepo.dirstate.setbranch(name)
236 if before_commit: before_commit(self)
238 self._commit("$ADMN$ Initial commit for branch '%s'." % name, user='$library')
240 def _switch_to_branch(self, branchname):
241 current = self._hgrepo[None].branch()
243 if current == branchname:
244 return current # quick exit
246 self._checkout(self._branch_tip(branchname))
254 def _sanitize_string(s):
258 if isinstance(s, unicode):
259 s = s.encode('utf-8')