##// END OF EJS Templates
simplestorerepo: avoid shadowing dict in list comprehension over dict...
Augie Fackler -
r37359:d62d2e34 default
parent child Browse files
Show More
@@ -1,594 +1,594 b''
1 1 # simplestorerepo.py - Extension that swaps in alternate repository storage.
2 2 #
3 3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # To use this with the test suite:
9 9 #
10 10 # $ HGREPOFEATURES="simplestore" ./run-tests.py \
11 11 # --extra-config-opt extensions.simplestore=`pwd`/simplestorerepo.py
12 12
13 13 from __future__ import absolute_import
14 14
15 15 from mercurial.i18n import _
16 16 from mercurial.node import (
17 17 bin,
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 )
22 22 from mercurial.thirdparty import (
23 23 cbor,
24 24 )
25 25 from mercurial import (
26 26 ancestor,
27 27 error,
28 28 filelog,
29 29 mdiff,
30 30 pycompat,
31 31 revlog,
32 32 )
33 33
34 34 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
35 35 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
36 36 # be specifying the version(s) of Mercurial they are tested with, or
37 37 # leave the attribute unspecified.
38 38 testedwith = 'ships-with-hg-core'
39 39
40 40 def validatenode(node):
41 41 if isinstance(node, int):
42 42 raise ValueError('expected node; got int')
43 43
44 44 if len(node) != 20:
45 45 raise ValueError('expected 20 byte node')
46 46
47 47 def validaterev(rev):
48 48 if not isinstance(rev, int):
49 49 raise ValueError('expected int')
50 50
51 51 class filestorage(object):
52 52 """Implements storage for a tracked path.
53 53
54 54 Data is stored in the VFS in a directory corresponding to the tracked
55 55 path.
56 56
57 57 Index data is stored in an ``index`` file using CBOR.
58 58
59 59 Fulltext data is stored in files having names of the node.
60 60 """
61 61
62 62 def __init__(self, svfs, path):
63 63 self._svfs = svfs
64 64 self._path = path
65 65
66 66 self._storepath = b'/'.join([b'data', path])
67 67 self._indexpath = b'/'.join([self._storepath, b'index'])
68 68
69 69 indexdata = self._svfs.tryread(self._indexpath)
70 70 if indexdata:
71 71 indexdata = cbor.loads(indexdata)
72 72
73 73 self._indexdata = indexdata or []
74 74 self._indexbynode = {}
75 75 self._indexbyrev = {}
76 76 self.index = []
77 77 self._refreshindex()
78 78
79 79 # This is used by changegroup code :/
80 80 self._generaldelta = True
81 81 self.storedeltachains = False
82 82
83 83 self.version = 1
84 84
85 85 def _refreshindex(self):
86 86 self._indexbynode.clear()
87 87 self._indexbyrev.clear()
88 88 self.index = []
89 89
90 90 for i, entry in enumerate(self._indexdata):
91 91 self._indexbynode[entry[b'node']] = entry
92 92 self._indexbyrev[i] = entry
93 93
94 94 self._indexbynode[nullid] = {
95 95 b'node': nullid,
96 96 b'p1': nullid,
97 97 b'p2': nullid,
98 98 b'linkrev': nullrev,
99 99 b'flags': 0,
100 100 }
101 101
102 102 self._indexbyrev[nullrev] = {
103 103 b'node': nullid,
104 104 b'p1': nullid,
105 105 b'p2': nullid,
106 106 b'linkrev': nullrev,
107 107 b'flags': 0,
108 108 }
109 109
110 110 for i, entry in enumerate(self._indexdata):
111 111 p1rev, p2rev = self.parentrevs(self.rev(entry[b'node']))
112 112
113 113 # start, length, rawsize, chainbase, linkrev, p1, p2, node
114 114 self.index.append((0, 0, 0, -1, entry[b'linkrev'], p1rev, p2rev,
115 115 entry[b'node']))
116 116
117 117 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
118 118
119 119 def __len__(self):
120 120 return len(self._indexdata)
121 121
122 122 def __iter__(self):
123 123 return iter(range(len(self)))
124 124
125 125 def revs(self, start=0, stop=None):
126 126 step = 1
127 127 if stop is not None:
128 128 if start > stop:
129 129 step = -1
130 130
131 131 stop += step
132 132 else:
133 133 stop = len(self)
134 134
135 135 return range(start, stop, step)
136 136
137 137 def parents(self, node):
138 138 validatenode(node)
139 139
140 140 if node not in self._indexbynode:
141 141 raise KeyError('unknown node')
142 142
143 143 entry = self._indexbynode[node]
144 144
145 145 return entry[b'p1'], entry[b'p2']
146 146
147 147 def parentrevs(self, rev):
148 148 p1, p2 = self.parents(self._indexbyrev[rev][b'node'])
149 149 return self.rev(p1), self.rev(p2)
150 150
151 151 def rev(self, node):
152 152 validatenode(node)
153 153
154 154 # Will raise KeyError.
155 155 self._indexbynode[node]
156 156
157 157 for rev, entry in self._indexbyrev.items():
158 158 if entry[b'node'] == node:
159 159 return rev
160 160
161 161 raise error.ProgrammingError('this should not occur')
162 162
163 163 def node(self, rev):
164 164 validaterev(rev)
165 165
166 166 return self._indexbyrev[rev][b'node']
167 167
168 168 def lookup(self, node):
169 169 if isinstance(node, int):
170 170 return self.node(node)
171 171
172 172 if len(node) == 20:
173 173 try:
174 174 self.rev(node)
175 175 return node
176 176 except LookupError:
177 177 pass
178 178
179 179 try:
180 180 rev = int(node)
181 181 if '%d' % rev != node:
182 182 raise ValueError
183 183
184 184 if rev < 0:
185 185 rev = len(self) + rev
186 186 if rev < 0 or rev >= len(self):
187 187 raise ValueError
188 188
189 189 return self.node(rev)
190 190 except (ValueError, OverflowError):
191 191 pass
192 192
193 193 if len(node) == 40:
194 194 try:
195 195 rawnode = bin(node)
196 196 self.rev(rawnode)
197 197 return rawnode
198 198 except (TypeError, LookupError):
199 199 pass
200 200
201 201 raise LookupError(node, self._path, _('invalid lookup input'))
202 202
203 203 def linkrev(self, rev):
204 204 validaterev(rev)
205 205
206 206 return self._indexbyrev[rev][b'linkrev']
207 207
208 208 def flags(self, rev):
209 209 validaterev(rev)
210 210
211 211 return self._indexbyrev[rev][b'flags']
212 212
213 213 def deltaparent(self, rev):
214 214 validaterev(rev)
215 215
216 216 p1node = self.parents(self.node(rev))[0]
217 217 return self.rev(p1node)
218 218
219 219 def candelta(self, baserev, rev):
220 220 validaterev(baserev)
221 221 validaterev(rev)
222 222
223 223 if ((self.flags(baserev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS)
224 224 or (self.flags(rev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS)):
225 225 return False
226 226
227 227 return True
228 228
229 229 def rawsize(self, rev):
230 230 validaterev(rev)
231 231 node = self.node(rev)
232 232 return len(self.revision(node, raw=True))
233 233
234 234 def _processflags(self, text, flags, operation, raw=False):
235 235 if flags == 0:
236 236 return text, True
237 237
238 238 validatehash = True
239 239 # Depending on the operation (read or write), the order might be
240 240 # reversed due to non-commutative transforms.
241 241 orderedflags = revlog.REVIDX_FLAGS_ORDER
242 242 if operation == 'write':
243 243 orderedflags = reversed(orderedflags)
244 244
245 245 for flag in orderedflags:
246 246 # If a flagprocessor has been registered for a known flag, apply the
247 247 # related operation transform and update result tuple.
248 248 if flag & flags:
249 249 vhash = True
250 250
251 251 if flag not in revlog._flagprocessors:
252 252 message = _("missing processor for flag '%#x'") % (flag)
253 253 raise revlog.RevlogError(message)
254 254
255 255 processor = revlog._flagprocessors[flag]
256 256 if processor is not None:
257 257 readtransform, writetransform, rawtransform = processor
258 258
259 259 if raw:
260 260 vhash = rawtransform(self, text)
261 261 elif operation == 'read':
262 262 text, vhash = readtransform(self, text)
263 263 else: # write operation
264 264 text, vhash = writetransform(self, text)
265 265 validatehash = validatehash and vhash
266 266
267 267 return text, validatehash
268 268
269 269 def checkhash(self, text, node, p1=None, p2=None, rev=None):
270 270 if p1 is None and p2 is None:
271 271 p1, p2 = self.parents(node)
272 272 if node != revlog.hash(text, p1, p2):
273 273 raise error.RevlogError(_("integrity check failed on %s") %
274 274 self._path)
275 275
276 276 def revision(self, node, raw=False):
277 277 validatenode(node)
278 278
279 279 if node == nullid:
280 280 return b''
281 281
282 282 self._indexbynode[node]
283 283
284 284 rev = self.rev(node)
285 285 flags = self.flags(rev)
286 286
287 287 path = b'/'.join([self._storepath, hex(node)])
288 288 rawtext = self._svfs.read(path)
289 289
290 290 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
291 291 if validatehash:
292 292 self.checkhash(text, node, rev=rev)
293 293
294 294 return text
295 295
296 296 def read(self, node):
297 297 validatenode(node)
298 298
299 299 revision = self.revision(node)
300 300
301 301 if not revision.startswith(b'\1\n'):
302 302 return revision
303 303
304 304 start = revision.index(b'\1\n', 2)
305 305 return revision[start + 2:]
306 306
307 307 def renamed(self, node):
308 308 validatenode(node)
309 309
310 310 if self.parents(node)[0] != nullid:
311 311 return False
312 312
313 313 fulltext = self.revision(node)
314 314 m = filelog.parsemeta(fulltext)[0]
315 315
316 316 if m and 'copy' in m:
317 317 return m['copy'], bin(m['copyrev'])
318 318
319 319 return False
320 320
321 321 def cmp(self, node, text):
322 322 validatenode(node)
323 323
324 324 t = text
325 325
326 326 if text.startswith(b'\1\n'):
327 327 t = b'\1\n\1\n' + text
328 328
329 329 p1, p2 = self.parents(node)
330 330
331 331 if revlog.hash(t, p1, p2) == node:
332 332 return False
333 333
334 334 if self.iscensored(self.rev(node)):
335 335 return text != b''
336 336
337 337 if self.renamed(node):
338 338 t2 = self.read(node)
339 339 return t2 != text
340 340
341 341 return True
342 342
343 343 def size(self, rev):
344 344 validaterev(rev)
345 345
346 346 node = self._indexbyrev[rev][b'node']
347 347
348 348 if self.renamed(node):
349 349 return len(self.read(node))
350 350
351 351 if self.iscensored(rev):
352 352 return 0
353 353
354 354 return len(self.revision(node))
355 355
356 356 def iscensored(self, rev):
357 357 validaterev(rev)
358 358
359 359 return self.flags(rev) & revlog.REVIDX_ISCENSORED
360 360
361 361 def commonancestorsheads(self, a, b):
362 362 validatenode(a)
363 363 validatenode(b)
364 364
365 365 a = self.rev(a)
366 366 b = self.rev(b)
367 367
368 368 ancestors = ancestor.commonancestorsheads(self.parentrevs, a, b)
369 369 return pycompat.maplist(self.node, ancestors)
370 370
371 371 def descendants(self, revs):
372 372 # This is a copy of revlog.descendants()
373 373 first = min(revs)
374 374 if first == nullrev:
375 375 for i in self:
376 376 yield i
377 377 return
378 378
379 379 seen = set(revs)
380 380 for i in self.revs(start=first + 1):
381 381 for x in self.parentrevs(i):
382 382 if x != nullrev and x in seen:
383 383 seen.add(i)
384 384 yield i
385 385 break
386 386
387 387 # Required by verify.
388 388 def files(self):
389 389 entries = self._svfs.listdir(self._storepath)
390 390
391 391 # Strip out undo.backup.* files created as part of transaction
392 392 # recording.
393 393 entries = [f for f in entries if not f.startswith('undo.backup.')]
394 394
395 395 return [b'/'.join((self._storepath, f)) for f in entries]
396 396
397 397 # Required by verify.
398 398 def checksize(self):
399 399 return 0, 0
400 400
401 401 def add(self, text, meta, transaction, linkrev, p1, p2):
402 402 if meta or text.startswith(b'\1\n'):
403 403 text = filelog.packmeta(meta, text)
404 404
405 405 return self.addrevision(text, transaction, linkrev, p1, p2)
406 406
407 407 def addrevision(self, text, transaction, linkrev, p1, p2, node=None,
408 408 flags=0):
409 409 validatenode(p1)
410 410 validatenode(p2)
411 411
412 412 if flags:
413 413 node = node or revlog.hash(text, p1, p2)
414 414
415 415 rawtext, validatehash = self._processflags(text, flags, 'write')
416 416
417 417 node = node or revlog.hash(text, p1, p2)
418 418
419 419 if node in self._indexbynode:
420 420 return node
421 421
422 422 if validatehash:
423 423 self.checkhash(rawtext, node, p1=p1, p2=p2)
424 424
425 425 path = b'/'.join([self._storepath, hex(node)])
426 426
427 427 self._svfs.write(path, text)
428 428
429 429 self._indexdata.append({
430 430 b'node': node,
431 431 b'p1': p1,
432 432 b'p2': p2,
433 433 b'linkrev': linkrev,
434 434 b'flags': flags,
435 435 })
436 436
437 437 self._reflectindexupdate()
438 438
439 439 return node
440 440
441 441 def _reflectindexupdate(self):
442 442 self._refreshindex()
443 443 self._svfs.write(self._indexpath, cbor.dumps(self._indexdata))
444 444
445 445 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
446 446 nodes = []
447 447
448 448 transaction.addbackup(self._indexpath)
449 449
450 450 for node, p1, p2, linknode, deltabase, delta, flags in deltas:
451 451 linkrev = linkmapper(linknode)
452 452
453 453 nodes.append(node)
454 454
455 455 if node in self._indexbynode:
456 456 continue
457 457
458 458 # Need to resolve the fulltext from the delta base.
459 459 if deltabase == nullid:
460 460 text = mdiff.patch(b'', delta)
461 461 else:
462 462 text = mdiff.patch(self.revision(deltabase), delta)
463 463
464 464 self.addrevision(text, transaction, linkrev, p1, p2, flags)
465 465
466 466 if addrevisioncb:
467 467 addrevisioncb(self, node)
468 468
469 469 return nodes
470 470
471 471 def revdiff(self, rev1, rev2):
472 472 validaterev(rev1)
473 473 validaterev(rev2)
474 474
475 475 node1 = self.node(rev1)
476 476 node2 = self.node(rev2)
477 477
478 478 return mdiff.textdiff(self.revision(node1, raw=True),
479 479 self.revision(node2, raw=True))
480 480
481 481 def headrevs(self):
482 482 # Assume all revisions are heads by default.
483 ishead = {rev: True for rev in self._indexbyrev}
483 revishead = {rev: True for rev in self._indexbyrev}
484 484
485 485 for rev, entry in self._indexbyrev.items():
486 486 # Unset head flag for all seen parents.
487 ishead[self.rev(entry[b'p1'])] = False
488 ishead[self.rev(entry[b'p2'])] = False
487 revishead[self.rev(entry[b'p1'])] = False
488 revishead[self.rev(entry[b'p2'])] = False
489 489
490 return [rev for rev, ishead in sorted(ishead.items())
490 return [rev for rev, ishead in sorted(revishead.items())
491 491 if ishead]
492 492
493 493 def heads(self, start=None, stop=None):
494 494 # This is copied from revlog.py.
495 495 if start is None and stop is None:
496 496 if not len(self):
497 497 return [nullid]
498 498 return [self.node(r) for r in self.headrevs()]
499 499
500 500 if start is None:
501 501 start = nullid
502 502 if stop is None:
503 503 stop = []
504 504 stoprevs = set([self.rev(n) for n in stop])
505 505 startrev = self.rev(start)
506 506 reachable = {startrev}
507 507 heads = {startrev}
508 508
509 509 parentrevs = self.parentrevs
510 510 for r in self.revs(start=startrev + 1):
511 511 for p in parentrevs(r):
512 512 if p in reachable:
513 513 if r not in stoprevs:
514 514 reachable.add(r)
515 515 heads.add(r)
516 516 if p in heads and p not in stoprevs:
517 517 heads.remove(p)
518 518
519 519 return [self.node(r) for r in heads]
520 520
521 521 def children(self, node):
522 522 validatenode(node)
523 523
524 524 # This is a copy of revlog.children().
525 525 c = []
526 526 p = self.rev(node)
527 527 for r in self.revs(start=p + 1):
528 528 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
529 529 if prevs:
530 530 for pr in prevs:
531 531 if pr == p:
532 532 c.append(self.node(r))
533 533 elif p == nullrev:
534 534 c.append(self.node(r))
535 535 return c
536 536
537 537 def getstrippoint(self, minlink):
538 538
539 539 # This is largely a copy of revlog.getstrippoint().
540 540 brokenrevs = set()
541 541 strippoint = len(self)
542 542
543 543 heads = {}
544 544 futurelargelinkrevs = set()
545 545 for head in self.headrevs():
546 546 headlinkrev = self.linkrev(head)
547 547 heads[head] = headlinkrev
548 548 if headlinkrev >= minlink:
549 549 futurelargelinkrevs.add(headlinkrev)
550 550
551 551 # This algorithm involves walking down the rev graph, starting at the
552 552 # heads. Since the revs are topologically sorted according to linkrev,
553 553 # once all head linkrevs are below the minlink, we know there are
554 554 # no more revs that could have a linkrev greater than minlink.
555 555 # So we can stop walking.
556 556 while futurelargelinkrevs:
557 557 strippoint -= 1
558 558 linkrev = heads.pop(strippoint)
559 559
560 560 if linkrev < minlink:
561 561 brokenrevs.add(strippoint)
562 562 else:
563 563 futurelargelinkrevs.remove(linkrev)
564 564
565 565 for p in self.parentrevs(strippoint):
566 566 if p != nullrev:
567 567 plinkrev = self.linkrev(p)
568 568 heads[p] = plinkrev
569 569 if plinkrev >= minlink:
570 570 futurelargelinkrevs.add(plinkrev)
571 571
572 572 return strippoint, brokenrevs
573 573
574 574 def strip(self, minlink, transaction):
575 575 if not len(self):
576 576 return
577 577
578 578 rev, _ignored = self.getstrippoint(minlink)
579 579 if rev == len(self):
580 580 return
581 581
582 582 # Purge index data starting at the requested revision.
583 583 self._indexdata[rev:] = []
584 584 self._reflectindexupdate()
585 585
586 586 def reposetup(ui, repo):
587 587 if not repo.local():
588 588 return
589 589
590 590 class simplestorerepo(repo.__class__):
591 591 def file(self, f):
592 592 return filestorage(self.svfs, f)
593 593
594 594 repo.__class__ = simplestorerepo
General Comments 0
You need to be logged in to leave comments. Login now