##// END OF EJS Templates
typing: make `unionrepository` subclass `localrepository` while type checking...
Matt Harbison -
r53152:1b17309c default
parent child Browse files
Show More
@@ -1,353 +1,360
1 1 # unionrepo.py - repository class for viewing union of repository changesets
2 2 #
3 3 # Derived from bundlerepo.py
4 4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
5 5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Repository class for "in-memory pull" of one local repository to another,
11 11 allowing operations like diff and log with revsets.
12 12 """
13 13
14 14 from __future__ import annotations
15 15
16 16 import contextlib
17
17 import typing
18 18
19 19 from .i18n import _
20 20
21 21 from . import (
22 22 changelog,
23 23 cmdutil,
24 24 encoding,
25 25 error,
26 26 filelog,
27 27 localrepo,
28 28 manifest,
29 29 mdiff,
30 30 pathutil,
31 31 revlog,
32 32 util,
33 33 vfs as vfsmod,
34 34 )
35 35
36 36 from .revlogutils import (
37 37 constants as revlog_constants,
38 38 )
39 39
40 40
41 41 class unionrevlog(revlog.revlog):
42 42 def __init__(self, opener, radix, revlog2, linkmapper):
43 43 # How it works:
44 44 # To retrieve a revision, we just need to know the node id so we can
45 45 # look it up in revlog2.
46 46 #
47 47 # To differentiate a rev in the second revlog from a rev in the revlog,
48 48 # we check revision against repotiprev.
49 49 opener = vfsmod.readonlyvfs(opener)
50 50 target = getattr(revlog2, 'target', None)
51 51 if target is None:
52 52 # a revlog wrapper, eg: the manifestlog that is not an actual revlog
53 53 target = revlog2._revlog.target
54 54 revlog.revlog.__init__(self, opener, target=target, radix=radix)
55 55 self.revlog2 = revlog2
56 56
57 57 n = len(self)
58 58 self.repotiprev = n - 1
59 59 self.bundlerevs = set() # used by 'bundle()' revset expression
60 60 for rev2 in self.revlog2:
61 61 rev = self.revlog2.index[rev2]
62 62 # rev numbers - in revlog2, very different from self.rev
63 63 (
64 64 _start,
65 65 _csize,
66 66 rsize,
67 67 base,
68 68 linkrev,
69 69 p1rev,
70 70 p2rev,
71 71 node,
72 72 _sdo,
73 73 _sds,
74 74 _dcm,
75 75 _sdcm,
76 76 rank,
77 77 ) = rev
78 78 flags = _start & 0xFFFF
79 79
80 80 if linkmapper is None: # link is to same revlog
81 81 assert linkrev == rev2 # we never link back
82 82 link = n
83 83 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
84 84 link = linkmapper(linkrev)
85 85
86 86 if linkmapper is not None: # link is to same revlog
87 87 base = linkmapper(base)
88 88
89 89 this_rev = self.index.get_rev(node)
90 90 if this_rev is not None:
91 91 # this happens for the common revlog revisions
92 92 self.bundlerevs.add(this_rev)
93 93 continue
94 94
95 95 p1node = self.revlog2.node(p1rev)
96 96 p2node = self.revlog2.node(p2rev)
97 97
98 98 # TODO: it's probably wrong to set compressed length to -1, but
99 99 # I have no idea if csize is valid in the base revlog context.
100 100 e = (
101 101 flags,
102 102 -1,
103 103 rsize,
104 104 base,
105 105 link,
106 106 self.rev(p1node),
107 107 self.rev(p2node),
108 108 node,
109 109 0, # sidedata offset
110 110 0, # sidedata size
111 111 revlog_constants.COMP_MODE_INLINE,
112 112 revlog_constants.COMP_MODE_INLINE,
113 113 rank,
114 114 )
115 115 self.index.append(e)
116 116 self.bundlerevs.add(n)
117 117 n += 1
118 118
119 119 @contextlib.contextmanager
120 120 def reading(self):
121 121 if 0 <= len(self.bundlerevs) < len(self.index):
122 122 read_1 = super().reading
123 123 else:
124 124 read_1 = util.nullcontextmanager
125 125 if 0 < len(self.bundlerevs):
126 126 read_2 = self.revlog2.reading
127 127 else:
128 128 read_2 = util.nullcontextmanager
129 129 with read_1(), read_2():
130 130 yield
131 131
132 132 def _chunk(self, rev):
133 133 if rev <= self.repotiprev:
134 134 return revlog.revlog._chunk(self, rev)
135 135 return self.revlog2._chunk(self.node(rev))
136 136
137 137 def revdiff(self, rev1, rev2):
138 138 """return or calculate a delta between two revisions"""
139 139 if rev1 > self.repotiprev and rev2 > self.repotiprev:
140 140 return self.revlog2.revdiff(
141 141 self.revlog2.rev(self.node(rev1)),
142 142 self.revlog2.rev(self.node(rev2)),
143 143 )
144 144 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
145 145 return super(unionrevlog, self).revdiff(rev1, rev2)
146 146
147 147 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
148 148
149 149 def _revisiondata(self, nodeorrev, raw=False):
150 150 if isinstance(nodeorrev, int):
151 151 rev = nodeorrev
152 152 node = self.node(rev)
153 153 else:
154 154 node = nodeorrev
155 155 rev = self.rev(node)
156 156
157 157 if rev > self.repotiprev:
158 158 # work around manifestrevlog NOT being a revlog
159 159 revlog2 = getattr(self.revlog2, '_revlog', self.revlog2)
160 160 func = revlog2._revisiondata
161 161 else:
162 162 func = super(unionrevlog, self)._revisiondata
163 163 return func(node, raw=raw)
164 164
165 165 def addrevision(
166 166 self,
167 167 text,
168 168 transaction,
169 169 link,
170 170 p1,
171 171 p2,
172 172 cachedelta=None,
173 173 node=None,
174 174 flags=revlog.REVIDX_DEFAULT_FLAGS,
175 175 deltacomputer=None,
176 176 sidedata=None,
177 177 ):
178 178 raise NotImplementedError
179 179
180 180 def addgroup(
181 181 self,
182 182 deltas,
183 183 linkmapper,
184 184 transaction,
185 185 alwayscache=False,
186 186 addrevisioncb=None,
187 187 duplicaterevisioncb=None,
188 188 debug_info=None,
189 189 delta_base_reuse_policy=None,
190 190 ):
191 191 raise NotImplementedError
192 192
193 193 def strip(self, minlink, transaction):
194 194 raise NotImplementedError
195 195
196 196 def checksize(self):
197 197 raise NotImplementedError
198 198
199 199
200 200 class unionchangelog(unionrevlog, changelog.changelog):
201 201 def __init__(self, opener, opener2):
202 202 changelog.changelog.__init__(self, opener)
203 203 linkmapper = None
204 204 changelog2 = changelog.changelog(opener2)
205 205 unionrevlog.__init__(self, opener, self.radix, changelog2, linkmapper)
206 206
207 207
208 208 class unionmanifest(unionrevlog, manifest.manifestrevlog):
209 209 repotiprev: int
210 210 revlog2: manifest.ManifestRevlog
211 211
212 212 def __init__(self, nodeconstants, opener, opener2, linkmapper):
213 213 # XXX manifestrevlog is not actually a revlog , so mixing it with
214 214 # bundlerevlog is not a good idea.
215 215 manifest.manifestrevlog.__init__(self, nodeconstants, opener)
216 216 manifest2 = manifest.manifestrevlog(nodeconstants, opener2)
217 217 unionrevlog.__init__(
218 218 self, opener, self._revlog.radix, manifest2, linkmapper
219 219 )
220 220
221 221
222 222 class unionfilelog(filelog.filelog):
223 223 _revlog: unionrevlog
224 224 repotiprev: int
225 225 revlog2: revlog.revlog
226 226
227 227 def __init__(self, opener, path, opener2, linkmapper, repo):
228 228 filelog.filelog.__init__(self, opener, path)
229 229 filelog2 = filelog.filelog(opener2, path)
230 230 self._revlog = unionrevlog(
231 231 opener, self._revlog.radix, filelog2._revlog, linkmapper
232 232 )
233 233 self._repo = repo
234 234 self.repotiprev = self._revlog.repotiprev
235 235 self.revlog2 = self._revlog.revlog2
236 236
237 237 def iscensored(self, rev):
238 238 """Check if a revision is censored."""
239 239 if rev <= self.repotiprev:
240 240 return filelog.filelog.iscensored(self, rev)
241 241 node = self.node(rev)
242 242 return self.revlog2.iscensored(self.revlog2.rev(node))
243 243
244 244
245 245 class unionpeer(localrepo.localpeer):
246 246 def canpush(self):
247 247 return False
248 248
249 249
250 class unionrepository:
250 _union_repo_baseclass = object
251
252 if typing.TYPE_CHECKING:
253 _union_repo_baseclass = localrepo.localrepository
254
255
256 class unionrepository(_union_repo_baseclass):
251 257 """Represents the union of data in 2 repositories.
252 258
253 259 Instances are not usable if constructed directly. Use ``instance()``
254 260 or ``makeunionrepository()`` to create a usable instance.
255 261 """
256 262
263 # noinspection PyMissingConstructor
257 264 def __init__(self, repo2, url):
258 265 self.repo2 = repo2
259 266 self._url = url
260 267
261 268 self.ui.setconfig(b'phases', b'publish', False, b'unionrepo')
262 269
263 270 @localrepo.unfilteredpropertycache
264 271 def changelog(self):
265 272 return unionchangelog(self.svfs, self.repo2.svfs)
266 273
267 274 @localrepo.unfilteredpropertycache
268 275 def manifestlog(self):
269 276 rootstore = unionmanifest(
270 277 self.nodeconstants,
271 278 self.svfs,
272 279 self.repo2.svfs,
273 280 self.unfiltered()._clrev,
274 281 )
275 282 return manifest.manifestlog(
276 283 self.svfs, self, rootstore, self.narrowmatch()
277 284 )
278 285
279 286 def _clrev(self, rev2):
280 287 """map from repo2 changelog rev to temporary rev in self.changelog"""
281 288 node = self.repo2.changelog.node(rev2)
282 289 return self.changelog.rev(node)
283 290
284 291 def url(self):
285 292 return self._url
286 293
287 294 def file(self, f):
288 295 return unionfilelog(
289 296 self.svfs, f, self.repo2.svfs, self.unfiltered()._clrev, self
290 297 )
291 298
292 299 def close(self):
293 300 self.repo2.close()
294 301
295 302 def cancopy(self):
296 303 return False
297 304
298 305 def peer(self, path=None, remotehidden=False):
299 306 return unionpeer(self, path=None, remotehidden=remotehidden)
300 307
301 308 def getcwd(self):
302 309 return encoding.getcwd() # always outside the repo
303 310
304 311
305 312 def instance(ui, path, create, intents=None, createopts=None):
306 313 if create:
307 314 raise error.Abort(_(b'cannot create new union repository'))
308 315 parentpath = ui.config(b"bundle", b"mainreporoot")
309 316 if not parentpath:
310 317 # try to find the correct path to the working directory repo
311 318 parentpath = cmdutil.findrepo(encoding.getcwd())
312 319 if parentpath is None:
313 320 parentpath = b''
314 321 if parentpath:
315 322 # Try to make the full path relative so we get a nice, short URL.
316 323 # In particular, we don't want temp dir names in test outputs.
317 324 cwd = encoding.getcwd()
318 325 if parentpath == cwd:
319 326 parentpath = b''
320 327 else:
321 328 cwd = pathutil.normasprefix(cwd)
322 329 if parentpath.startswith(cwd):
323 330 parentpath = parentpath[len(cwd) :]
324 331 if path.startswith(b'union:'):
325 332 s = path.split(b":", 1)[1].split(b"+", 1)
326 333 if len(s) == 1:
327 334 repopath, repopath2 = parentpath, s[0]
328 335 else:
329 336 repopath, repopath2 = s
330 337 else:
331 338 repopath, repopath2 = parentpath, path
332 339
333 340 return makeunionrepository(ui, repopath, repopath2)
334 341
335 342
336 343 def makeunionrepository(ui, repopath1, repopath2):
337 344 """Make a union repository object from 2 local repo paths."""
338 345 repo1 = localrepo.instance(ui, repopath1, create=False)
339 346 repo2 = localrepo.instance(ui, repopath2, create=False)
340 347
341 348 url = b'union:%s+%s' % (
342 349 util.expandpath(repopath1),
343 350 util.expandpath(repopath2),
344 351 )
345 352
346 353 class derivedunionrepository(unionrepository, repo1.__class__):
347 354 pass
348 355
349 356 repo = repo1
350 357 repo.__class__ = derivedunionrepository
351 358 unionrepository.__init__(repo1, repo2, url)
352 359
353 360 return repo
General Comments 0
You need to be logged in to leave comments. Login now