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)