Oops #2
[redakcja.git] / lib / hg.py
1 # -*- coding: utf-8 -*-
2 import os
3 from mercurial import localrepo, ui, encoding, util
4 import mercurial.merge, mercurial.error
5
6 encoding.encoding = 'utf-8'
7
8 X = 'g\xc5\xbceg\xc5\xbc\xc3\xb3\xc5\x82ka'
9
10 def sanitize_string(path):
11     if isinstance(path, unicode): #
12         return path.encode('utf-8')
13     else: # it's a string, so we have no idea what encoding it is
14         return path
15
16 class Repository(object):
17     """Abstrakcja repozytorium Mercurial. DziaƂa z Mercurial w wersji 1.3.1."""
18     
19     def __init__(self, path, create=False):
20         self.ui = ui.ui()
21         self.ui.config('ui', 'quiet', 'true')
22         self.ui.config('ui', 'interactive', 'false')
23         
24         self.real_path = sanitize_string(os.path.realpath(path))
25         self.repo = self._open_repository(self.real_path, create)
26
27     def _open_repository(self, path, create=False):
28         if os.path.isdir(path):
29             try:
30                 return localrepo.localrepository(self.ui, path)
31             except mercurial.error.RepoError:
32                 # dir is not an hg repo, we must init it
33                 if create:
34                     return localrepo.localrepository(self.ui, path, create=1)
35         elif create:
36             os.makedirs(path)
37             return localrepo.localrepository(self.ui, path, create=1)
38         raise RepositoryDoesNotExist("Repository %s does not exist." % path)
39         
40     def file_list(self, branch):
41         return self.in_branch(lambda: self._file_list(), branch)
42
43     def _file_list(self):
44         return list(self.repo[None])
45     
46     def get_file(self, path, branch):
47         return self.in_branch(lambda: self._get_file(path), branch)
48
49     def _get_file(self, path):
50         path = sanitize_string(path)
51         if not self._file_exists(path):
52             raise RepositoryException("File not availble in this branch.")
53         
54         return self.repo.wread(path)
55
56     def file_exists(self, path, branch):
57         return self.in_branch(lambda: self._file_exists(path), branch)
58
59     def _file_exists(self, path):
60         path = sanitize_string(path)
61         return self.repo.dirstate[path] != "?"
62
63     def write_file(self, path, value, branch):
64         return self.in_branch(lambda: self._write_file(path, value), branch)
65
66     def _write_file(self, path, value):
67         path = sanitize_string(path)
68         return self.repo.wwrite(path, value, [])
69
70     def add_file(self, path, value, branch):
71         return self.in_branch(lambda: self._add_file(path, value), branch)
72
73     def _add_file(self, path, value):
74         path = sanitize_string(path)
75         self._write_file(path, value)
76         return self.repo.add( [path] )
77
78     def _commit(self, message, user=None):
79         return self.repo.commit(text=sanitize_string(message), user=sanitize_string(user))
80     
81     def commit(self, message, branch, user=None):
82         return self.in_branch(lambda: self._commit(message, key=key, user=user), branch)
83
84     def in_branch(self, action, bname):
85         wlock = self.repo.wlock()
86         try:
87             old = self._switch_to_branch(bname)
88             try:
89                 # do some stuff
90                 return action()
91             finally:
92                 self._switch_to_branch(old)
93         finally:
94             wlock.release()
95
96     def merge_branches(self, bnameA, bnameB, user, message):
97         wlock = self.repo.wlock()
98         try:
99             return self.merge_revisions(self.get_branch_tip(bnameA),
100                 self.get_branch_tip(bnameB), user, message)
101         finally:
102             wlock.release()
103
104     def diff(self, revA, revB):
105         return UpdateStatus(self.repo.status(revA, revB))
106
107     def merge_revisions(self, revA, revB, user, message):
108         wlock = self.repo.wlock()
109         try:
110             old = self.repo[None]
111             
112             self._checkout(revA)
113             mergestatus = self._merge(revB)
114             if not mergestatus.isclean():
115                 # revert the failed merge
116                 self.repo.recover()
117                 raise UncleanMerge(u'Failed to merge %d files.' % len(mergestatus.unresolved))
118
119             # commit the clean merge
120             self._commit(message, user)
121
122             # cleanup after yourself
123             self._checkout(old.rev())
124         except util.Abort, ae:
125             raise RepositoryException(u'Failed merge: ' + ae.message)
126         finally:
127             wlock.release()
128
129     def common_ancestor(self, revA, revB):
130         return self.repo[revA].ancestor(self.repo[revB])
131         
132     def _checkout(self, rev, force=True):
133         return MergeStatus(mercurial.merge.update(self.repo, rev, False, force, None))
134         
135     def _merge(self, rev):
136         """ Merge the revision into current working directory """
137         return MergeStatus(mercurial.merge.update(self.repo, rev, True, False, None))
138
139     def _switch_to_branch(self, bname):
140         bname = sanitize_string(bname)
141         wlock = self.repo.wlock()
142         try:
143             current = self.repo[None].branch()
144             if current == bname:
145                 return current
146             
147             tip = self.get_branch_tip(bname)
148             status = self._checkout(tip)
149
150             if not status.isclean():
151                 raise RepositoryException("Unclean branch switch. This IS REALLY bad.")
152             
153             return current 
154         except KeyError, ke:
155             raise RepositoryException((u"Can't switch to branch '%s': no such branch." % bname) , ke)
156         except util.Abort, ae:
157             raise RepositoryException(u"Can't switch to branch '%s': %s"  % (bname, ae.message), ae)
158         finally:
159             wlock.release()
160
161     def with_wlock(self, action):
162         wlock = self.repo.wlock()
163         try:
164             action()
165         finally:
166             wlock.release()
167
168     def _create_branch(self, name, parent_rev, msg=None, before_commit=None):
169         """WARNING: leaves the working directory in the new branch"""
170         name = sanitize_string(name)
171         
172         if self.has_branch(name): return # just exit
173
174         self._checkout(parent_rev)
175         self.repo.dirstate.setbranch(name)
176         
177         if msg is None:
178             msg = "Initial commit for branch '%s'." % name
179
180         if before_commit: before_commit()        
181         self._commit(msg, user='platform')
182         return self.get_branch_tip(name)
183
184     def write_lock(self):
185         """Returns w write lock to the repository."""
186         return self.repo.wlock()
187
188     def has_branch(self, name):
189         name = sanitize_string(name)
190         return (name in self.repo.branchmap().keys())
191     
192     def get_branch_tip(self, name):
193         name = sanitize_string(name)
194         return self.repo.branchtags()[name]
195
196     def getnode(self, rev):
197         return self.repo[rev]
198
199 class MergeStatus(object):
200
201     def __init__(self, mstatus):       
202         self.updated = mstatus[0]
203         self.merged = mstatus[1]
204         self.removed = mstatus[2]
205         self.unresolved = mstatus[3]
206
207     def isclean(self):
208         return self.unresolved == 0
209
210 class UpdateStatus(object):
211
212     def __init__(self, mstatus):
213         self.modified = mstatus[0]
214         self.added = mstatus[1]
215         self.removed = mstatus[2]
216         self.deleted = mstatus[3]
217         self.untracked = mstatus[4]
218         self.ignored = mstatus[5]
219         self.clean = mstatus[6]
220
221     def has_changes(self):
222         return bool( len(self.modified) + len(self.added) + \
223             len(self.removed) + len(self.deleted) )
224
225 class RepositoryException(Exception):
226     def __init__(self, msg, cause=None):
227         Exception.__init__(self, msg)
228         self.cause = cause
229
230 class UncleanMerge(RepositoryException):
231     pass
232
233 class RepositoryDoesNotExist(RepositoryException):
234     pass
235