Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/filelog.py @ 37497:1541e1a8e87d
filelog: wrap revlog instead of inheriting it (API)
The revlog base class exposes a ton of methods. Inheriting the
revlog class for filelog will make it difficult to expose a
clean interface. There will be abstraction violations.
This commit breaks the inheritance of revlog by the filelog
class. Filelog instances now contain a reference to a revlog
instance. Various properties and methods are now proxied to
that instance.
There is precedence for doing this: manifestlog does something
similar. Although, manifestlog has a cleaner interface than
filelog. We'll get there with filelog...
The new filelog class exposes a handful of extra properties and
methods that aren't part of the declared filelog interface.
Every extra item was added in order to get a test to pass. The
set of tests that failed without these extra proxies has
significant overlap with the set of tests that don't work with
the simple store repo. There should be no surprise there.
Hopefully the hardest part about this commit to review are the
changes to bundlerepo and unionrepo. Both repository types
define a custom revlog or revlog-like class and then have a
custom filelog that inherits from both filelog and their custom
revlog. This code has been changed so the filelog types don't
inherit from revlog. Instead, they replace the revlog instance
on the created filelog. This is super hacky. I plan to fix this
in a future commit by parameterizing filelog.__init__.
Because Python function call overhead is a thing, this change
could impact performance by introducing a nearly empty proxy
function for various methods and properties. I would gladly
measure the performance impact of it, but I'm not sure what
operations have tight loops over filelog attribute lookups
or function calls. I know some of the DAG traversal code can
be sensitive about the performance of e.g. parentrevs(). However,
many of these functions are implemented on the revlog class and
therefore have direct access to self.parentrevs() and aren't
going through a proxy.
.. api::
filelog.filelog is now a standalone class and doesn't inherit
from revlog. Instead, it wraps a revlog instance at self._revlog.
This change was made in an attempt to formalize storage APIs and
prevent revlog implementation details leaking through to callers.
Differential Revision: https://phab.mercurial-scm.org/D3154
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Fri, 06 Apr 2018 22:39:58 -0700 |
parents | 65250a66b55c |
children | 856f381ad74b |
comparison
equal
deleted
inserted
replaced
37496:1765ed63db40 | 37497:1541e1a8e87d |
---|---|
9 | 9 |
10 from .thirdparty.zope import ( | 10 from .thirdparty.zope import ( |
11 interface as zi, | 11 interface as zi, |
12 ) | 12 ) |
13 from . import ( | 13 from . import ( |
14 error, | |
14 repository, | 15 repository, |
15 revlog, | 16 revlog, |
16 ) | 17 ) |
17 | 18 |
18 @zi.implementer(repository.ifilestorage) | 19 @zi.implementer(repository.ifilestorage) |
19 class filelog(revlog.revlog): | 20 class filelog(object): |
20 def __init__(self, opener, path): | 21 def __init__(self, opener, path): |
21 super(filelog, self).__init__(opener, | 22 self._revlog = revlog.revlog(opener, |
22 "/".join(("data", path + ".i")), | 23 '/'.join(('data', path + '.i')), |
23 censorable=True) | 24 censorable=True) |
24 # full name of the user visible file, relative to the repository root | 25 # full name of the user visible file, relative to the repository root |
25 self.filename = path | 26 self.filename = path |
27 self.index = self._revlog.index | |
28 self.version = self._revlog.version | |
29 self.storedeltachains = self._revlog.storedeltachains | |
30 self._generaldelta = self._revlog._generaldelta | |
31 | |
32 def __len__(self): | |
33 return len(self._revlog) | |
34 | |
35 def __iter__(self): | |
36 return self._revlog.__iter__() | |
37 | |
38 def revs(self, start=0, stop=None): | |
39 return self._revlog.revs(start=start, stop=stop) | |
40 | |
41 def parents(self, node): | |
42 return self._revlog.parents(node) | |
43 | |
44 def parentrevs(self, rev): | |
45 return self._revlog.parentrevs(rev) | |
46 | |
47 def rev(self, node): | |
48 return self._revlog.rev(node) | |
49 | |
50 def node(self, rev): | |
51 return self._revlog.node(rev) | |
52 | |
53 def lookup(self, node): | |
54 return self._revlog.lookup(node) | |
55 | |
56 def linkrev(self, rev): | |
57 return self._revlog.linkrev(rev) | |
58 | |
59 def flags(self, rev): | |
60 return self._revlog.flags(rev) | |
61 | |
62 def commonancestorsheads(self, node1, node2): | |
63 return self._revlog.commonancestorsheads(node1, node2) | |
64 | |
65 def descendants(self, revs): | |
66 return self._revlog.descendants(revs) | |
67 | |
68 def headrevs(self): | |
69 return self._revlog.headrevs() | |
70 | |
71 def heads(self, start=None, stop=None): | |
72 return self._revlog.heads(start, stop) | |
73 | |
74 def children(self, node): | |
75 return self._revlog.children(node) | |
76 | |
77 def deltaparent(self, rev): | |
78 return self._revlog.deltaparent(rev) | |
79 | |
80 def candelta(self, baserev, rev): | |
81 return self._revlog.candelta(baserev, rev) | |
82 | |
83 def iscensored(self, rev): | |
84 return self._revlog.iscensored(rev) | |
85 | |
86 def rawsize(self, rev): | |
87 return self._revlog.rawsize(rev) | |
88 | |
89 def checkhash(self, text, node, p1=None, p2=None, rev=None): | |
90 return self._revlog.checkhash(text, node, p1=p1, p2=p2, rev=rev) | |
91 | |
92 def revision(self, node, _df=None, raw=False): | |
93 return self._revlog.revision(node, _df=_df, raw=raw) | |
94 | |
95 def revdiff(self, rev1, rev2): | |
96 return self._revlog.revdiff(rev1, rev2) | |
97 | |
98 def addrevision(self, revisiondata, transaction, linkrev, p1, p2, | |
99 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS, | |
100 cachedelta=None): | |
101 return self._revlog.addrevision(revisiondata, transaction, linkrev, | |
102 p1, p2, node=node, flags=flags, | |
103 cachedelta=cachedelta) | |
104 | |
105 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None): | |
106 return self._revlog.addgroup(deltas, linkmapper, transaction, | |
107 addrevisioncb=addrevisioncb) | |
108 | |
109 def getstrippoint(self, minlink): | |
110 return self._revlog.getstrippoint(minlink) | |
111 | |
112 def strip(self, minlink, transaction): | |
113 return self._revlog.strip(minlink, transaction) | |
114 | |
115 def files(self): | |
116 return self._revlog.files() | |
117 | |
118 def checksize(self): | |
119 return self._revlog.checksize() | |
26 | 120 |
27 def read(self, node): | 121 def read(self, node): |
28 t = self.revision(node) | 122 t = self.revision(node) |
29 if not t.startswith('\1\n'): | 123 if not t.startswith('\1\n'): |
30 return t | 124 return t |
54 return len(self.read(node)) | 148 return len(self.read(node)) |
55 if self.iscensored(rev): | 149 if self.iscensored(rev): |
56 return 0 | 150 return 0 |
57 | 151 |
58 # XXX if self.read(node).startswith("\1\n"), this returns (size+4) | 152 # XXX if self.read(node).startswith("\1\n"), this returns (size+4) |
59 return super(filelog, self).size(rev) | 153 return self._revlog.size(rev) |
60 | 154 |
61 def cmp(self, node, text): | 155 def cmp(self, node, text): |
62 """compare text with a given file revision | 156 """compare text with a given file revision |
63 | 157 |
64 returns True if text is different than what is stored. | 158 returns True if text is different than what is stored. |
66 | 160 |
67 t = text | 161 t = text |
68 if text.startswith('\1\n'): | 162 if text.startswith('\1\n'): |
69 t = '\1\n\1\n' + text | 163 t = '\1\n\1\n' + text |
70 | 164 |
71 samehashes = not super(filelog, self).cmp(node, t) | 165 samehashes = not self._revlog.cmp(node, t) |
72 if samehashes: | 166 if samehashes: |
73 return False | 167 return False |
74 | 168 |
75 # censored files compare against the empty file | 169 # censored files compare against the empty file |
76 if self.iscensored(self.rev(node)): | 170 if self.iscensored(self.rev(node)): |
81 if self.renamed(node): | 175 if self.renamed(node): |
82 t2 = self.read(node) | 176 t2 = self.read(node) |
83 return t2 != text | 177 return t2 != text |
84 | 178 |
85 return True | 179 return True |
180 | |
181 @property | |
182 def filename(self): | |
183 return self._revlog.filename | |
184 | |
185 @filename.setter | |
186 def filename(self, value): | |
187 self._revlog.filename = value | |
188 | |
189 # TODO these aren't part of the interface and aren't internal methods. | |
190 # Callers should be fixed to not use them. | |
191 @property | |
192 def indexfile(self): | |
193 return self._revlog.indexfile | |
194 | |
195 @indexfile.setter | |
196 def indexfile(self, value): | |
197 self._revlog.indexfile = value | |
198 | |
199 @property | |
200 def datafile(self): | |
201 return self._revlog.datafile | |
202 | |
203 @property | |
204 def opener(self): | |
205 return self._revlog.opener | |
206 | |
207 @property | |
208 def _lazydeltabase(self): | |
209 return self._revlog._lazydeltabase | |
210 | |
211 @_lazydeltabase.setter | |
212 def _lazydeltabase(self, value): | |
213 self._revlog._lazydeltabase = value | |
214 | |
215 @property | |
216 def _aggressivemergedeltas(self): | |
217 return self._revlog._aggressivemergedeltas | |
218 | |
219 @_aggressivemergedeltas.setter | |
220 def _aggressivemergedeltas(self, value): | |
221 self._revlog._aggressivemergedeltas = value | |
222 | |
223 @property | |
224 def _inline(self): | |
225 return self._revlog._inline | |
226 | |
227 @property | |
228 def _withsparseread(self): | |
229 return getattr(self._revlog, '_withsparseread', False) | |
230 | |
231 @property | |
232 def _srmingapsize(self): | |
233 return self._revlog._srmingapsize | |
234 | |
235 @property | |
236 def _srdensitythreshold(self): | |
237 return self._revlog._srdensitythreshold | |
238 | |
239 def _deltachain(self, rev, stoprev=None): | |
240 return self._revlog._deltachain(rev, stoprev) | |
241 | |
242 def chainbase(self, rev): | |
243 return self._revlog.chainbase(rev) | |
244 | |
245 def chainlen(self, rev): | |
246 return self._revlog.chainlen(rev) | |
247 | |
248 def clone(self, tr, destrevlog, **kwargs): | |
249 if not isinstance(destrevlog, filelog): | |
250 raise error.ProgrammingError('expected filelog to clone()') | |
251 | |
252 return self._revlog.clone(tr, destrevlog._revlog, **kwargs) | |
253 | |
254 def start(self, rev): | |
255 return self._revlog.start(rev) | |
256 | |
257 def end(self, rev): | |
258 return self._revlog.end(rev) | |
259 | |
260 def length(self, rev): | |
261 return self._revlog.length(rev) | |
262 | |
263 def compress(self, data): | |
264 return self._revlog.compress(data) | |
265 | |
266 def _addrevision(self, *args, **kwargs): | |
267 return self._revlog._addrevision(*args, **kwargs) |