##// END OF EJS Templates
revlog: drop emitrevisiondeltas() and associated functionality (API)...
Gregory Szorc -
r39902:e23c03dc default
parent child Browse files
Show More
@@ -1,1381 +1,1363 b''
1 1 # changegroup.py - Mercurial changegroup manipulation functions
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.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 from __future__ import absolute_import
9 9
10 10 import os
11 11 import struct
12 12 import weakref
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 hex,
17 17 nullid,
18 18 nullrev,
19 19 short,
20 20 )
21 21
22 from .thirdparty import (
23 attr,
24 )
25
26 22 from . import (
27 23 error,
28 24 match as matchmod,
29 25 mdiff,
30 26 phases,
31 27 pycompat,
32 28 repository,
33 29 revlog,
34 30 util,
35 31 )
36 32
37 from .utils import (
38 interfaceutil,
39 )
40
41 33 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct("20s20s20s20s")
42 34 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct("20s20s20s20s20s")
43 35 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(">20s20s20s20s20sH")
44 36
45 37 LFS_REQUIREMENT = 'lfs'
46 38
47 39 readexactly = util.readexactly
48 40
49 41 def getchunk(stream):
50 42 """return the next chunk from stream as a string"""
51 43 d = readexactly(stream, 4)
52 44 l = struct.unpack(">l", d)[0]
53 45 if l <= 4:
54 46 if l:
55 47 raise error.Abort(_("invalid chunk length %d") % l)
56 48 return ""
57 49 return readexactly(stream, l - 4)
58 50
59 51 def chunkheader(length):
60 52 """return a changegroup chunk header (string)"""
61 53 return struct.pack(">l", length + 4)
62 54
63 55 def closechunk():
64 56 """return a changegroup chunk header (string) for a zero-length chunk"""
65 57 return struct.pack(">l", 0)
66 58
67 59 def _fileheader(path):
68 60 """Obtain a changegroup chunk header for a named path."""
69 61 return chunkheader(len(path)) + path
70 62
71 63 def writechunks(ui, chunks, filename, vfs=None):
72 64 """Write chunks to a file and return its filename.
73 65
74 66 The stream is assumed to be a bundle file.
75 67 Existing files will not be overwritten.
76 68 If no filename is specified, a temporary file is created.
77 69 """
78 70 fh = None
79 71 cleanup = None
80 72 try:
81 73 if filename:
82 74 if vfs:
83 75 fh = vfs.open(filename, "wb")
84 76 else:
85 77 # Increase default buffer size because default is usually
86 78 # small (4k is common on Linux).
87 79 fh = open(filename, "wb", 131072)
88 80 else:
89 81 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
90 82 fh = os.fdopen(fd, r"wb")
91 83 cleanup = filename
92 84 for c in chunks:
93 85 fh.write(c)
94 86 cleanup = None
95 87 return filename
96 88 finally:
97 89 if fh is not None:
98 90 fh.close()
99 91 if cleanup is not None:
100 92 if filename and vfs:
101 93 vfs.unlink(cleanup)
102 94 else:
103 95 os.unlink(cleanup)
104 96
105 97 class cg1unpacker(object):
106 98 """Unpacker for cg1 changegroup streams.
107 99
108 100 A changegroup unpacker handles the framing of the revision data in
109 101 the wire format. Most consumers will want to use the apply()
110 102 method to add the changes from the changegroup to a repository.
111 103
112 104 If you're forwarding a changegroup unmodified to another consumer,
113 105 use getchunks(), which returns an iterator of changegroup
114 106 chunks. This is mostly useful for cases where you need to know the
115 107 data stream has ended by observing the end of the changegroup.
116 108
117 109 deltachunk() is useful only if you're applying delta data. Most
118 110 consumers should prefer apply() instead.
119 111
120 112 A few other public methods exist. Those are used only for
121 113 bundlerepo and some debug commands - their use is discouraged.
122 114 """
123 115 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
124 116 deltaheadersize = deltaheader.size
125 117 version = '01'
126 118 _grouplistcount = 1 # One list of files after the manifests
127 119
128 120 def __init__(self, fh, alg, extras=None):
129 121 if alg is None:
130 122 alg = 'UN'
131 123 if alg not in util.compengines.supportedbundletypes:
132 124 raise error.Abort(_('unknown stream compression type: %s')
133 125 % alg)
134 126 if alg == 'BZ':
135 127 alg = '_truncatedBZ'
136 128
137 129 compengine = util.compengines.forbundletype(alg)
138 130 self._stream = compengine.decompressorreader(fh)
139 131 self._type = alg
140 132 self.extras = extras or {}
141 133 self.callback = None
142 134
143 135 # These methods (compressed, read, seek, tell) all appear to only
144 136 # be used by bundlerepo, but it's a little hard to tell.
145 137 def compressed(self):
146 138 return self._type is not None and self._type != 'UN'
147 139 def read(self, l):
148 140 return self._stream.read(l)
149 141 def seek(self, pos):
150 142 return self._stream.seek(pos)
151 143 def tell(self):
152 144 return self._stream.tell()
153 145 def close(self):
154 146 return self._stream.close()
155 147
156 148 def _chunklength(self):
157 149 d = readexactly(self._stream, 4)
158 150 l = struct.unpack(">l", d)[0]
159 151 if l <= 4:
160 152 if l:
161 153 raise error.Abort(_("invalid chunk length %d") % l)
162 154 return 0
163 155 if self.callback:
164 156 self.callback()
165 157 return l - 4
166 158
167 159 def changelogheader(self):
168 160 """v10 does not have a changelog header chunk"""
169 161 return {}
170 162
171 163 def manifestheader(self):
172 164 """v10 does not have a manifest header chunk"""
173 165 return {}
174 166
175 167 def filelogheader(self):
176 168 """return the header of the filelogs chunk, v10 only has the filename"""
177 169 l = self._chunklength()
178 170 if not l:
179 171 return {}
180 172 fname = readexactly(self._stream, l)
181 173 return {'filename': fname}
182 174
183 175 def _deltaheader(self, headertuple, prevnode):
184 176 node, p1, p2, cs = headertuple
185 177 if prevnode is None:
186 178 deltabase = p1
187 179 else:
188 180 deltabase = prevnode
189 181 flags = 0
190 182 return node, p1, p2, deltabase, cs, flags
191 183
192 184 def deltachunk(self, prevnode):
193 185 l = self._chunklength()
194 186 if not l:
195 187 return {}
196 188 headerdata = readexactly(self._stream, self.deltaheadersize)
197 189 header = self.deltaheader.unpack(headerdata)
198 190 delta = readexactly(self._stream, l - self.deltaheadersize)
199 191 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
200 192 return (node, p1, p2, cs, deltabase, delta, flags)
201 193
202 194 def getchunks(self):
203 195 """returns all the chunks contains in the bundle
204 196
205 197 Used when you need to forward the binary stream to a file or another
206 198 network API. To do so, it parse the changegroup data, otherwise it will
207 199 block in case of sshrepo because it don't know the end of the stream.
208 200 """
209 201 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
210 202 # and a list of filelogs. For changegroup 3, we expect 4 parts:
211 203 # changelog, manifestlog, a list of tree manifestlogs, and a list of
212 204 # filelogs.
213 205 #
214 206 # Changelog and manifestlog parts are terminated with empty chunks. The
215 207 # tree and file parts are a list of entry sections. Each entry section
216 208 # is a series of chunks terminating in an empty chunk. The list of these
217 209 # entry sections is terminated in yet another empty chunk, so we know
218 210 # we've reached the end of the tree/file list when we reach an empty
219 211 # chunk that was proceeded by no non-empty chunks.
220 212
221 213 parts = 0
222 214 while parts < 2 + self._grouplistcount:
223 215 noentries = True
224 216 while True:
225 217 chunk = getchunk(self)
226 218 if not chunk:
227 219 # The first two empty chunks represent the end of the
228 220 # changelog and the manifestlog portions. The remaining
229 221 # empty chunks represent either A) the end of individual
230 222 # tree or file entries in the file list, or B) the end of
231 223 # the entire list. It's the end of the entire list if there
232 224 # were no entries (i.e. noentries is True).
233 225 if parts < 2:
234 226 parts += 1
235 227 elif noentries:
236 228 parts += 1
237 229 break
238 230 noentries = False
239 231 yield chunkheader(len(chunk))
240 232 pos = 0
241 233 while pos < len(chunk):
242 234 next = pos + 2**20
243 235 yield chunk[pos:next]
244 236 pos = next
245 237 yield closechunk()
246 238
247 239 def _unpackmanifests(self, repo, revmap, trp, prog):
248 240 self.callback = prog.increment
249 241 # no need to check for empty manifest group here:
250 242 # if the result of the merge of 1 and 2 is the same in 3 and 4,
251 243 # no new manifest will be created and the manifest group will
252 244 # be empty during the pull
253 245 self.manifestheader()
254 246 deltas = self.deltaiter()
255 247 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
256 248 prog.complete()
257 249 self.callback = None
258 250
259 251 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
260 252 expectedtotal=None):
261 253 """Add the changegroup returned by source.read() to this repo.
262 254 srctype is a string like 'push', 'pull', or 'unbundle'. url is
263 255 the URL of the repo where this changegroup is coming from.
264 256
265 257 Return an integer summarizing the change to this repo:
266 258 - nothing changed or no source: 0
267 259 - more heads than before: 1+added heads (2..n)
268 260 - fewer heads than before: -1-removed heads (-2..-n)
269 261 - number of heads stays the same: 1
270 262 """
271 263 repo = repo.unfiltered()
272 264 def csmap(x):
273 265 repo.ui.debug("add changeset %s\n" % short(x))
274 266 return len(cl)
275 267
276 268 def revmap(x):
277 269 return cl.rev(x)
278 270
279 271 changesets = files = revisions = 0
280 272
281 273 try:
282 274 # The transaction may already carry source information. In this
283 275 # case we use the top level data. We overwrite the argument
284 276 # because we need to use the top level value (if they exist)
285 277 # in this function.
286 278 srctype = tr.hookargs.setdefault('source', srctype)
287 279 url = tr.hookargs.setdefault('url', url)
288 280 repo.hook('prechangegroup',
289 281 throw=True, **pycompat.strkwargs(tr.hookargs))
290 282
291 283 # write changelog data to temp files so concurrent readers
292 284 # will not see an inconsistent view
293 285 cl = repo.changelog
294 286 cl.delayupdate(tr)
295 287 oldheads = set(cl.heads())
296 288
297 289 trp = weakref.proxy(tr)
298 290 # pull off the changeset group
299 291 repo.ui.status(_("adding changesets\n"))
300 292 clstart = len(cl)
301 293 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
302 294 total=expectedtotal)
303 295 self.callback = progress.increment
304 296
305 297 efiles = set()
306 298 def onchangelog(cl, node):
307 299 efiles.update(cl.readfiles(node))
308 300
309 301 self.changelogheader()
310 302 deltas = self.deltaiter()
311 303 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
312 304 efiles = len(efiles)
313 305
314 306 if not cgnodes:
315 307 repo.ui.develwarn('applied empty changelog from changegroup',
316 308 config='warn-empty-changegroup')
317 309 clend = len(cl)
318 310 changesets = clend - clstart
319 311 progress.complete()
320 312 self.callback = None
321 313
322 314 # pull off the manifest group
323 315 repo.ui.status(_("adding manifests\n"))
324 316 # We know that we'll never have more manifests than we had
325 317 # changesets.
326 318 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
327 319 total=changesets)
328 320 self._unpackmanifests(repo, revmap, trp, progress)
329 321
330 322 needfiles = {}
331 323 if repo.ui.configbool('server', 'validate'):
332 324 cl = repo.changelog
333 325 ml = repo.manifestlog
334 326 # validate incoming csets have their manifests
335 327 for cset in pycompat.xrange(clstart, clend):
336 328 mfnode = cl.changelogrevision(cset).manifest
337 329 mfest = ml[mfnode].readdelta()
338 330 # store file cgnodes we must see
339 331 for f, n in mfest.iteritems():
340 332 needfiles.setdefault(f, set()).add(n)
341 333
342 334 # process the files
343 335 repo.ui.status(_("adding file changes\n"))
344 336 newrevs, newfiles = _addchangegroupfiles(
345 337 repo, self, revmap, trp, efiles, needfiles)
346 338 revisions += newrevs
347 339 files += newfiles
348 340
349 341 deltaheads = 0
350 342 if oldheads:
351 343 heads = cl.heads()
352 344 deltaheads = len(heads) - len(oldheads)
353 345 for h in heads:
354 346 if h not in oldheads and repo[h].closesbranch():
355 347 deltaheads -= 1
356 348 htext = ""
357 349 if deltaheads:
358 350 htext = _(" (%+d heads)") % deltaheads
359 351
360 352 repo.ui.status(_("added %d changesets"
361 353 " with %d changes to %d files%s\n")
362 354 % (changesets, revisions, files, htext))
363 355 repo.invalidatevolatilesets()
364 356
365 357 if changesets > 0:
366 358 if 'node' not in tr.hookargs:
367 359 tr.hookargs['node'] = hex(cl.node(clstart))
368 360 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
369 361 hookargs = dict(tr.hookargs)
370 362 else:
371 363 hookargs = dict(tr.hookargs)
372 364 hookargs['node'] = hex(cl.node(clstart))
373 365 hookargs['node_last'] = hex(cl.node(clend - 1))
374 366 repo.hook('pretxnchangegroup',
375 367 throw=True, **pycompat.strkwargs(hookargs))
376 368
377 369 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
378 370 phaseall = None
379 371 if srctype in ('push', 'serve'):
380 372 # Old servers can not push the boundary themselves.
381 373 # New servers won't push the boundary if changeset already
382 374 # exists locally as secret
383 375 #
384 376 # We should not use added here but the list of all change in
385 377 # the bundle
386 378 if repo.publishing():
387 379 targetphase = phaseall = phases.public
388 380 else:
389 381 # closer target phase computation
390 382
391 383 # Those changesets have been pushed from the
392 384 # outside, their phases are going to be pushed
393 385 # alongside. Therefor `targetphase` is
394 386 # ignored.
395 387 targetphase = phaseall = phases.draft
396 388 if added:
397 389 phases.registernew(repo, tr, targetphase, added)
398 390 if phaseall is not None:
399 391 phases.advanceboundary(repo, tr, phaseall, cgnodes)
400 392
401 393 if changesets > 0:
402 394
403 395 def runhooks():
404 396 # These hooks run when the lock releases, not when the
405 397 # transaction closes. So it's possible for the changelog
406 398 # to have changed since we last saw it.
407 399 if clstart >= len(repo):
408 400 return
409 401
410 402 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
411 403
412 404 for n in added:
413 405 args = hookargs.copy()
414 406 args['node'] = hex(n)
415 407 del args['node_last']
416 408 repo.hook("incoming", **pycompat.strkwargs(args))
417 409
418 410 newheads = [h for h in repo.heads()
419 411 if h not in oldheads]
420 412 repo.ui.log("incoming",
421 413 "%d incoming changes - new heads: %s\n",
422 414 len(added),
423 415 ', '.join([hex(c[:6]) for c in newheads]))
424 416
425 417 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
426 418 lambda tr: repo._afterlock(runhooks))
427 419 finally:
428 420 repo.ui.flush()
429 421 # never return 0 here:
430 422 if deltaheads < 0:
431 423 ret = deltaheads - 1
432 424 else:
433 425 ret = deltaheads + 1
434 426 return ret
435 427
436 428 def deltaiter(self):
437 429 """
438 430 returns an iterator of the deltas in this changegroup
439 431
440 432 Useful for passing to the underlying storage system to be stored.
441 433 """
442 434 chain = None
443 435 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
444 436 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
445 437 yield chunkdata
446 438 chain = chunkdata[0]
447 439
448 440 class cg2unpacker(cg1unpacker):
449 441 """Unpacker for cg2 streams.
450 442
451 443 cg2 streams add support for generaldelta, so the delta header
452 444 format is slightly different. All other features about the data
453 445 remain the same.
454 446 """
455 447 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
456 448 deltaheadersize = deltaheader.size
457 449 version = '02'
458 450
459 451 def _deltaheader(self, headertuple, prevnode):
460 452 node, p1, p2, deltabase, cs = headertuple
461 453 flags = 0
462 454 return node, p1, p2, deltabase, cs, flags
463 455
464 456 class cg3unpacker(cg2unpacker):
465 457 """Unpacker for cg3 streams.
466 458
467 459 cg3 streams add support for exchanging treemanifests and revlog
468 460 flags. It adds the revlog flags to the delta header and an empty chunk
469 461 separating manifests and files.
470 462 """
471 463 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
472 464 deltaheadersize = deltaheader.size
473 465 version = '03'
474 466 _grouplistcount = 2 # One list of manifests and one list of files
475 467
476 468 def _deltaheader(self, headertuple, prevnode):
477 469 node, p1, p2, deltabase, cs, flags = headertuple
478 470 return node, p1, p2, deltabase, cs, flags
479 471
480 472 def _unpackmanifests(self, repo, revmap, trp, prog):
481 473 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
482 474 for chunkdata in iter(self.filelogheader, {}):
483 475 # If we get here, there are directory manifests in the changegroup
484 476 d = chunkdata["filename"]
485 477 repo.ui.debug("adding %s revisions\n" % d)
486 478 deltas = self.deltaiter()
487 479 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
488 480 raise error.Abort(_("received dir revlog group is empty"))
489 481
490 482 class headerlessfixup(object):
491 483 def __init__(self, fh, h):
492 484 self._h = h
493 485 self._fh = fh
494 486 def read(self, n):
495 487 if self._h:
496 488 d, self._h = self._h[:n], self._h[n:]
497 489 if len(d) < n:
498 490 d += readexactly(self._fh, n - len(d))
499 491 return d
500 492 return readexactly(self._fh, n)
501 493
502 @interfaceutil.implementer(repository.irevisiondeltarequest)
503 @attr.s(slots=True, frozen=True)
504 class revisiondeltarequest(object):
505 node = attr.ib()
506 linknode = attr.ib()
507 p1node = attr.ib()
508 p2node = attr.ib()
509 basenode = attr.ib()
510 ellipsis = attr.ib(default=False)
511
512 494 def _revisiondeltatochunks(delta, headerfn):
513 495 """Serialize a revisiondelta to changegroup chunks."""
514 496
515 497 # The captured revision delta may be encoded as a delta against
516 498 # a base revision or as a full revision. The changegroup format
517 499 # requires that everything on the wire be deltas. So for full
518 500 # revisions, we need to invent a header that says to rewrite
519 501 # data.
520 502
521 503 if delta.delta is not None:
522 504 prefix, data = b'', delta.delta
523 505 elif delta.basenode == nullid:
524 506 data = delta.revision
525 507 prefix = mdiff.trivialdiffheader(len(data))
526 508 else:
527 509 data = delta.revision
528 510 prefix = mdiff.replacediffheader(delta.baserevisionsize,
529 511 len(data))
530 512
531 513 meta = headerfn(delta)
532 514
533 515 yield chunkheader(len(meta) + len(prefix) + len(data))
534 516 yield meta
535 517 if prefix:
536 518 yield prefix
537 519 yield data
538 520
539 521 def _sortnodesellipsis(store, nodes, cl, lookup):
540 522 """Sort nodes for changegroup generation."""
541 523 # Ellipses serving mode.
542 524 #
543 525 # In a perfect world, we'd generate better ellipsis-ified graphs
544 526 # for non-changelog revlogs. In practice, we haven't started doing
545 527 # that yet, so the resulting DAGs for the manifestlog and filelogs
546 528 # are actually full of bogus parentage on all the ellipsis
547 529 # nodes. This has the side effect that, while the contents are
548 530 # correct, the individual DAGs might be completely out of whack in
549 531 # a case like 882681bc3166 and its ancestors (back about 10
550 532 # revisions or so) in the main hg repo.
551 533 #
552 534 # The one invariant we *know* holds is that the new (potentially
553 535 # bogus) DAG shape will be valid if we order the nodes in the
554 536 # order that they're introduced in dramatis personae by the
555 537 # changelog, so what we do is we sort the non-changelog histories
556 538 # by the order in which they are used by the changelog.
557 539 key = lambda n: cl.rev(lookup(n))
558 540 return sorted(nodes, key=key)
559 541
560 542 def _resolvenarrowrevisioninfo(cl, store, ischangelog, rev, linkrev,
561 543 linknode, clrevtolocalrev, fullclnodes,
562 544 precomputedellipsis):
563 545 linkparents = precomputedellipsis[linkrev]
564 546 def local(clrev):
565 547 """Turn a changelog revnum into a local revnum.
566 548
567 549 The ellipsis dag is stored as revnums on the changelog,
568 550 but when we're producing ellipsis entries for
569 551 non-changelog revlogs, we need to turn those numbers into
570 552 something local. This does that for us, and during the
571 553 changelog sending phase will also expand the stored
572 554 mappings as needed.
573 555 """
574 556 if clrev == nullrev:
575 557 return nullrev
576 558
577 559 if ischangelog:
578 560 return clrev
579 561
580 562 # Walk the ellipsis-ized changelog breadth-first looking for a
581 563 # change that has been linked from the current revlog.
582 564 #
583 565 # For a flat manifest revlog only a single step should be necessary
584 566 # as all relevant changelog entries are relevant to the flat
585 567 # manifest.
586 568 #
587 569 # For a filelog or tree manifest dirlog however not every changelog
588 570 # entry will have been relevant, so we need to skip some changelog
589 571 # nodes even after ellipsis-izing.
590 572 walk = [clrev]
591 573 while walk:
592 574 p = walk[0]
593 575 walk = walk[1:]
594 576 if p in clrevtolocalrev:
595 577 return clrevtolocalrev[p]
596 578 elif p in fullclnodes:
597 579 walk.extend([pp for pp in cl.parentrevs(p)
598 580 if pp != nullrev])
599 581 elif p in precomputedellipsis:
600 582 walk.extend([pp for pp in precomputedellipsis[p]
601 583 if pp != nullrev])
602 584 else:
603 585 # In this case, we've got an ellipsis with parents
604 586 # outside the current bundle (likely an
605 587 # incremental pull). We "know" that we can use the
606 588 # value of this same revlog at whatever revision
607 589 # is pointed to by linknode. "Know" is in scare
608 590 # quotes because I haven't done enough examination
609 591 # of edge cases to convince myself this is really
610 592 # a fact - it works for all the (admittedly
611 593 # thorough) cases in our testsuite, but I would be
612 594 # somewhat unsurprised to find a case in the wild
613 595 # where this breaks down a bit. That said, I don't
614 596 # know if it would hurt anything.
615 597 for i in pycompat.xrange(rev, 0, -1):
616 598 if store.linkrev(i) == clrev:
617 599 return i
618 600 # We failed to resolve a parent for this node, so
619 601 # we crash the changegroup construction.
620 602 raise error.Abort(
621 603 'unable to resolve parent while packing %r %r'
622 604 ' for changeset %r' % (store.indexfile, rev, clrev))
623 605
624 606 return nullrev
625 607
626 608 if not linkparents or (
627 609 store.parentrevs(rev) == (nullrev, nullrev)):
628 610 p1, p2 = nullrev, nullrev
629 611 elif len(linkparents) == 1:
630 612 p1, = sorted(local(p) for p in linkparents)
631 613 p2 = nullrev
632 614 else:
633 615 p1, p2 = sorted(local(p) for p in linkparents)
634 616
635 617 p1node, p2node = store.node(p1), store.node(p2)
636 618
637 619 return p1node, p2node, linknode
638 620
639 621 def deltagroup(repo, store, nodes, ischangelog, lookup, forcedeltaparentprev,
640 622 topic=None,
641 623 ellipses=False, clrevtolocalrev=None, fullclnodes=None,
642 624 precomputedellipsis=None):
643 625 """Calculate deltas for a set of revisions.
644 626
645 627 Is a generator of ``revisiondelta`` instances.
646 628
647 629 If topic is not None, progress detail will be generated using this
648 630 topic name (e.g. changesets, manifests, etc).
649 631 """
650 632 if not nodes:
651 633 return
652 634
653 635 cl = repo.changelog
654 636
655 637 if ischangelog:
656 638 # `hg log` shows changesets in storage order. To preserve order
657 639 # across clones, send out changesets in storage order.
658 640 nodesorder = 'storage'
659 641 elif ellipses:
660 642 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
661 643 nodesorder = 'nodes'
662 644 else:
663 645 nodesorder = None
664 646
665 647 # Perform ellipses filtering and revision massaging. We do this before
666 648 # emitrevisions() because a) filtering out revisions creates less work
667 649 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
668 650 # assumptions about delta choices and we would possibly send a delta
669 651 # referencing a missing base revision.
670 652 #
671 653 # Also, calling lookup() has side-effects with regards to populating
672 654 # data structures. If we don't call lookup() for each node or if we call
673 655 # lookup() after the first pass through each node, things can break -
674 656 # possibly intermittently depending on the python hash seed! For that
675 657 # reason, we store a mapping of all linknodes during the initial node
676 658 # pass rather than use lookup() on the output side.
677 659 if ellipses:
678 660 filtered = []
679 661 adjustedparents = {}
680 662 linknodes = {}
681 663
682 664 for node in nodes:
683 665 rev = store.rev(node)
684 666 linknode = lookup(node)
685 667 linkrev = cl.rev(linknode)
686 668 clrevtolocalrev[linkrev] = rev
687 669
688 670 # If linknode is in fullclnodes, it means the corresponding
689 671 # changeset was a full changeset and is being sent unaltered.
690 672 if linknode in fullclnodes:
691 673 linknodes[node] = linknode
692 674
693 675 # If the corresponding changeset wasn't in the set computed
694 676 # as relevant to us, it should be dropped outright.
695 677 elif linkrev not in precomputedellipsis:
696 678 continue
697 679
698 680 else:
699 681 # We could probably do this later and avoid the dict
700 682 # holding state. But it likely doesn't matter.
701 683 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
702 684 cl, store, ischangelog, rev, linkrev, linknode,
703 685 clrevtolocalrev, fullclnodes, precomputedellipsis)
704 686
705 687 adjustedparents[node] = (p1node, p2node)
706 688 linknodes[node] = linknode
707 689
708 690 filtered.append(node)
709 691
710 692 nodes = filtered
711 693
712 694 # We expect the first pass to be fast, so we only engage the progress
713 695 # meter for constructing the revision deltas.
714 696 progress = None
715 697 if topic is not None:
716 698 progress = repo.ui.makeprogress(topic, unit=_('chunks'),
717 699 total=len(nodes))
718 700
719 701 revisions = store.emitrevisions(
720 702 nodes,
721 703 nodesorder=nodesorder,
722 704 revisiondata=True,
723 705 assumehaveparentrevisions=not ellipses,
724 706 deltaprevious=forcedeltaparentprev)
725 707
726 708 for i, revision in enumerate(revisions):
727 709 if progress:
728 710 progress.update(i + 1)
729 711
730 712 if ellipses:
731 713 linknode = linknodes[revision.node]
732 714
733 715 if revision.node in adjustedparents:
734 716 p1node, p2node = adjustedparents[revision.node]
735 717 revision.p1node = p1node
736 718 revision.p2node = p2node
737 719 revision.flags |= revlog.REVIDX_ELLIPSIS
738 720
739 721 else:
740 722 linknode = lookup(revision.node)
741 723
742 724 revision.linknode = linknode
743 725 yield revision
744 726
745 727 if progress:
746 728 progress.complete()
747 729
748 730 class cgpacker(object):
749 731 def __init__(self, repo, filematcher, version,
750 732 builddeltaheader, manifestsend,
751 733 forcedeltaparentprev=False,
752 734 bundlecaps=None, ellipses=False,
753 735 shallow=False, ellipsisroots=None, fullnodes=None):
754 736 """Given a source repo, construct a bundler.
755 737
756 738 filematcher is a matcher that matches on files to include in the
757 739 changegroup. Used to facilitate sparse changegroups.
758 740
759 741 forcedeltaparentprev indicates whether delta parents must be against
760 742 the previous revision in a delta group. This should only be used for
761 743 compatibility with changegroup version 1.
762 744
763 745 builddeltaheader is a callable that constructs the header for a group
764 746 delta.
765 747
766 748 manifestsend is a chunk to send after manifests have been fully emitted.
767 749
768 750 ellipses indicates whether ellipsis serving mode is enabled.
769 751
770 752 bundlecaps is optional and can be used to specify the set of
771 753 capabilities which can be used to build the bundle. While bundlecaps is
772 754 unused in core Mercurial, extensions rely on this feature to communicate
773 755 capabilities to customize the changegroup packer.
774 756
775 757 shallow indicates whether shallow data might be sent. The packer may
776 758 need to pack file contents not introduced by the changes being packed.
777 759
778 760 fullnodes is the set of changelog nodes which should not be ellipsis
779 761 nodes. We store this rather than the set of nodes that should be
780 762 ellipsis because for very large histories we expect this to be
781 763 significantly smaller.
782 764 """
783 765 assert filematcher
784 766 self._filematcher = filematcher
785 767
786 768 self.version = version
787 769 self._forcedeltaparentprev = forcedeltaparentprev
788 770 self._builddeltaheader = builddeltaheader
789 771 self._manifestsend = manifestsend
790 772 self._ellipses = ellipses
791 773
792 774 # Set of capabilities we can use to build the bundle.
793 775 if bundlecaps is None:
794 776 bundlecaps = set()
795 777 self._bundlecaps = bundlecaps
796 778 self._isshallow = shallow
797 779 self._fullclnodes = fullnodes
798 780
799 781 # Maps ellipsis revs to their roots at the changelog level.
800 782 self._precomputedellipsis = ellipsisroots
801 783
802 784 self._repo = repo
803 785
804 786 if self._repo.ui.verbose and not self._repo.ui.debugflag:
805 787 self._verbosenote = self._repo.ui.note
806 788 else:
807 789 self._verbosenote = lambda s: None
808 790
809 791 def generate(self, commonrevs, clnodes, fastpathlinkrev, source,
810 792 changelog=True):
811 793 """Yield a sequence of changegroup byte chunks.
812 794 If changelog is False, changelog data won't be added to changegroup
813 795 """
814 796
815 797 repo = self._repo
816 798 cl = repo.changelog
817 799
818 800 self._verbosenote(_('uncompressed size of bundle content:\n'))
819 801 size = 0
820 802
821 803 clstate, deltas = self._generatechangelog(cl, clnodes)
822 804 for delta in deltas:
823 805 if changelog:
824 806 for chunk in _revisiondeltatochunks(delta,
825 807 self._builddeltaheader):
826 808 size += len(chunk)
827 809 yield chunk
828 810
829 811 close = closechunk()
830 812 size += len(close)
831 813 yield closechunk()
832 814
833 815 self._verbosenote(_('%8.i (changelog)\n') % size)
834 816
835 817 clrevorder = clstate['clrevorder']
836 818 manifests = clstate['manifests']
837 819 changedfiles = clstate['changedfiles']
838 820
839 821 # We need to make sure that the linkrev in the changegroup refers to
840 822 # the first changeset that introduced the manifest or file revision.
841 823 # The fastpath is usually safer than the slowpath, because the filelogs
842 824 # are walked in revlog order.
843 825 #
844 826 # When taking the slowpath when the manifest revlog uses generaldelta,
845 827 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
846 828 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
847 829 #
848 830 # When taking the fastpath, we are only vulnerable to reordering
849 831 # of the changelog itself. The changelog never uses generaldelta and is
850 832 # never reordered. To handle this case, we simply take the slowpath,
851 833 # which already has the 'clrevorder' logic. This was also fixed in
852 834 # cc0ff93d0c0c.
853 835
854 836 # Treemanifests don't work correctly with fastpathlinkrev
855 837 # either, because we don't discover which directory nodes to
856 838 # send along with files. This could probably be fixed.
857 839 fastpathlinkrev = fastpathlinkrev and (
858 840 'treemanifest' not in repo.requirements)
859 841
860 842 fnodes = {} # needed file nodes
861 843
862 844 size = 0
863 845 it = self.generatemanifests(
864 846 commonrevs, clrevorder, fastpathlinkrev, manifests, fnodes, source,
865 847 clstate['clrevtomanifestrev'])
866 848
867 849 for tree, deltas in it:
868 850 if tree:
869 851 assert self.version == b'03'
870 852 chunk = _fileheader(tree)
871 853 size += len(chunk)
872 854 yield chunk
873 855
874 856 for delta in deltas:
875 857 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
876 858 for chunk in chunks:
877 859 size += len(chunk)
878 860 yield chunk
879 861
880 862 close = closechunk()
881 863 size += len(close)
882 864 yield close
883 865
884 866 self._verbosenote(_('%8.i (manifests)\n') % size)
885 867 yield self._manifestsend
886 868
887 869 mfdicts = None
888 870 if self._ellipses and self._isshallow:
889 871 mfdicts = [(self._repo.manifestlog[n].read(), lr)
890 872 for (n, lr) in manifests.iteritems()]
891 873
892 874 manifests.clear()
893 875 clrevs = set(cl.rev(x) for x in clnodes)
894 876
895 877 it = self.generatefiles(changedfiles, commonrevs,
896 878 source, mfdicts, fastpathlinkrev,
897 879 fnodes, clrevs)
898 880
899 881 for path, deltas in it:
900 882 h = _fileheader(path)
901 883 size = len(h)
902 884 yield h
903 885
904 886 for delta in deltas:
905 887 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
906 888 for chunk in chunks:
907 889 size += len(chunk)
908 890 yield chunk
909 891
910 892 close = closechunk()
911 893 size += len(close)
912 894 yield close
913 895
914 896 self._verbosenote(_('%8.i %s\n') % (size, path))
915 897
916 898 yield closechunk()
917 899
918 900 if clnodes:
919 901 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
920 902
921 903 def _generatechangelog(self, cl, nodes):
922 904 """Generate data for changelog chunks.
923 905
924 906 Returns a 2-tuple of a dict containing state and an iterable of
925 907 byte chunks. The state will not be fully populated until the
926 908 chunk stream has been fully consumed.
927 909 """
928 910 clrevorder = {}
929 911 manifests = {}
930 912 mfl = self._repo.manifestlog
931 913 changedfiles = set()
932 914 clrevtomanifestrev = {}
933 915
934 916 # Callback for the changelog, used to collect changed files and
935 917 # manifest nodes.
936 918 # Returns the linkrev node (identity in the changelog case).
937 919 def lookupcl(x):
938 920 c = cl.changelogrevision(x)
939 921 clrevorder[x] = len(clrevorder)
940 922
941 923 if self._ellipses:
942 924 # Only update manifests if x is going to be sent. Otherwise we
943 925 # end up with bogus linkrevs specified for manifests and
944 926 # we skip some manifest nodes that we should otherwise
945 927 # have sent.
946 928 if (x in self._fullclnodes
947 929 or cl.rev(x) in self._precomputedellipsis):
948 930
949 931 manifestnode = c.manifest
950 932 # Record the first changeset introducing this manifest
951 933 # version.
952 934 manifests.setdefault(manifestnode, x)
953 935 # Set this narrow-specific dict so we have the lowest
954 936 # manifest revnum to look up for this cl revnum. (Part of
955 937 # mapping changelog ellipsis parents to manifest ellipsis
956 938 # parents)
957 939 clrevtomanifestrev.setdefault(
958 940 cl.rev(x), mfl.rev(manifestnode))
959 941 # We can't trust the changed files list in the changeset if the
960 942 # client requested a shallow clone.
961 943 if self._isshallow:
962 944 changedfiles.update(mfl[c.manifest].read().keys())
963 945 else:
964 946 changedfiles.update(c.files)
965 947 else:
966 948 # record the first changeset introducing this manifest version
967 949 manifests.setdefault(c.manifest, x)
968 950 # Record a complete list of potentially-changed files in
969 951 # this manifest.
970 952 changedfiles.update(c.files)
971 953
972 954 return x
973 955
974 956 state = {
975 957 'clrevorder': clrevorder,
976 958 'manifests': manifests,
977 959 'changedfiles': changedfiles,
978 960 'clrevtomanifestrev': clrevtomanifestrev,
979 961 }
980 962
981 963 gen = deltagroup(
982 964 self._repo, cl, nodes, True, lookupcl,
983 965 self._forcedeltaparentprev,
984 966 ellipses=self._ellipses,
985 967 topic=_('changesets'),
986 968 clrevtolocalrev={},
987 969 fullclnodes=self._fullclnodes,
988 970 precomputedellipsis=self._precomputedellipsis)
989 971
990 972 return state, gen
991 973
992 974 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev,
993 975 manifests, fnodes, source, clrevtolocalrev):
994 976 """Returns an iterator of changegroup chunks containing manifests.
995 977
996 978 `source` is unused here, but is used by extensions like remotefilelog to
997 979 change what is sent based in pulls vs pushes, etc.
998 980 """
999 981 repo = self._repo
1000 982 mfl = repo.manifestlog
1001 983 tmfnodes = {'': manifests}
1002 984
1003 985 # Callback for the manifest, used to collect linkrevs for filelog
1004 986 # revisions.
1005 987 # Returns the linkrev node (collected in lookupcl).
1006 988 def makelookupmflinknode(tree, nodes):
1007 989 if fastpathlinkrev:
1008 990 assert not tree
1009 991 return manifests.__getitem__
1010 992
1011 993 def lookupmflinknode(x):
1012 994 """Callback for looking up the linknode for manifests.
1013 995
1014 996 Returns the linkrev node for the specified manifest.
1015 997
1016 998 SIDE EFFECT:
1017 999
1018 1000 1) fclnodes gets populated with the list of relevant
1019 1001 file nodes if we're not using fastpathlinkrev
1020 1002 2) When treemanifests are in use, collects treemanifest nodes
1021 1003 to send
1022 1004
1023 1005 Note that this means manifests must be completely sent to
1024 1006 the client before you can trust the list of files and
1025 1007 treemanifests to send.
1026 1008 """
1027 1009 clnode = nodes[x]
1028 1010 mdata = mfl.get(tree, x).readfast(shallow=True)
1029 1011 for p, n, fl in mdata.iterentries():
1030 1012 if fl == 't': # subdirectory manifest
1031 1013 subtree = tree + p + '/'
1032 1014 tmfclnodes = tmfnodes.setdefault(subtree, {})
1033 1015 tmfclnode = tmfclnodes.setdefault(n, clnode)
1034 1016 if clrevorder[clnode] < clrevorder[tmfclnode]:
1035 1017 tmfclnodes[n] = clnode
1036 1018 else:
1037 1019 f = tree + p
1038 1020 fclnodes = fnodes.setdefault(f, {})
1039 1021 fclnode = fclnodes.setdefault(n, clnode)
1040 1022 if clrevorder[clnode] < clrevorder[fclnode]:
1041 1023 fclnodes[n] = clnode
1042 1024 return clnode
1043 1025 return lookupmflinknode
1044 1026
1045 1027 while tmfnodes:
1046 1028 tree, nodes = tmfnodes.popitem()
1047 1029 store = mfl.getstorage(tree)
1048 1030
1049 1031 if not self._filematcher.visitdir(store.tree[:-1] or '.'):
1050 1032 # No nodes to send because this directory is out of
1051 1033 # the client's view of the repository (probably
1052 1034 # because of narrow clones).
1053 1035 prunednodes = []
1054 1036 else:
1055 1037 # Avoid sending any manifest nodes we can prove the
1056 1038 # client already has by checking linkrevs. See the
1057 1039 # related comment in generatefiles().
1058 1040 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1059 1041 if tree and not prunednodes:
1060 1042 continue
1061 1043
1062 1044 lookupfn = makelookupmflinknode(tree, nodes)
1063 1045
1064 1046 deltas = deltagroup(
1065 1047 self._repo, store, prunednodes, False, lookupfn,
1066 1048 self._forcedeltaparentprev,
1067 1049 ellipses=self._ellipses,
1068 1050 topic=_('manifests'),
1069 1051 clrevtolocalrev=clrevtolocalrev,
1070 1052 fullclnodes=self._fullclnodes,
1071 1053 precomputedellipsis=self._precomputedellipsis)
1072 1054
1073 1055 yield tree, deltas
1074 1056
1075 1057 def _prunemanifests(self, store, nodes, commonrevs):
1076 1058 # This is split out as a separate method to allow filtering
1077 1059 # commonrevs in extension code.
1078 1060 #
1079 1061 # TODO(augie): this shouldn't be required, instead we should
1080 1062 # make filtering of revisions to send delegated to the store
1081 1063 # layer.
1082 1064 frev, flr = store.rev, store.linkrev
1083 1065 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1084 1066
1085 1067 # The 'source' parameter is useful for extensions
1086 1068 def generatefiles(self, changedfiles, commonrevs, source,
1087 1069 mfdicts, fastpathlinkrev, fnodes, clrevs):
1088 1070 changedfiles = list(filter(self._filematcher, changedfiles))
1089 1071
1090 1072 if not fastpathlinkrev:
1091 1073 def normallinknodes(unused, fname):
1092 1074 return fnodes.get(fname, {})
1093 1075 else:
1094 1076 cln = self._repo.changelog.node
1095 1077
1096 1078 def normallinknodes(store, fname):
1097 1079 flinkrev = store.linkrev
1098 1080 fnode = store.node
1099 1081 revs = ((r, flinkrev(r)) for r in store)
1100 1082 return dict((fnode(r), cln(lr))
1101 1083 for r, lr in revs if lr in clrevs)
1102 1084
1103 1085 clrevtolocalrev = {}
1104 1086
1105 1087 if self._isshallow:
1106 1088 # In a shallow clone, the linknodes callback needs to also include
1107 1089 # those file nodes that are in the manifests we sent but weren't
1108 1090 # introduced by those manifests.
1109 1091 commonctxs = [self._repo[c] for c in commonrevs]
1110 1092 clrev = self._repo.changelog.rev
1111 1093
1112 1094 def linknodes(flog, fname):
1113 1095 for c in commonctxs:
1114 1096 try:
1115 1097 fnode = c.filenode(fname)
1116 1098 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1117 1099 except error.ManifestLookupError:
1118 1100 pass
1119 1101 links = normallinknodes(flog, fname)
1120 1102 if len(links) != len(mfdicts):
1121 1103 for mf, lr in mfdicts:
1122 1104 fnode = mf.get(fname, None)
1123 1105 if fnode in links:
1124 1106 links[fnode] = min(links[fnode], lr, key=clrev)
1125 1107 elif fnode:
1126 1108 links[fnode] = lr
1127 1109 return links
1128 1110 else:
1129 1111 linknodes = normallinknodes
1130 1112
1131 1113 repo = self._repo
1132 1114 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1133 1115 total=len(changedfiles))
1134 1116 for i, fname in enumerate(sorted(changedfiles)):
1135 1117 filerevlog = repo.file(fname)
1136 1118 if not filerevlog:
1137 1119 raise error.Abort(_("empty or missing file data for %s") %
1138 1120 fname)
1139 1121
1140 1122 clrevtolocalrev.clear()
1141 1123
1142 1124 linkrevnodes = linknodes(filerevlog, fname)
1143 1125 # Lookup for filenodes, we collected the linkrev nodes above in the
1144 1126 # fastpath case and with lookupmf in the slowpath case.
1145 1127 def lookupfilelog(x):
1146 1128 return linkrevnodes[x]
1147 1129
1148 1130 frev, flr = filerevlog.rev, filerevlog.linkrev
1149 1131 # Skip sending any filenode we know the client already
1150 1132 # has. This avoids over-sending files relatively
1151 1133 # inexpensively, so it's not a problem if we under-filter
1152 1134 # here.
1153 1135 filenodes = [n for n in linkrevnodes
1154 1136 if flr(frev(n)) not in commonrevs]
1155 1137
1156 1138 if not filenodes:
1157 1139 continue
1158 1140
1159 1141 progress.update(i + 1, item=fname)
1160 1142
1161 1143 deltas = deltagroup(
1162 1144 self._repo, filerevlog, filenodes, False, lookupfilelog,
1163 1145 self._forcedeltaparentprev,
1164 1146 ellipses=self._ellipses,
1165 1147 clrevtolocalrev=clrevtolocalrev,
1166 1148 fullclnodes=self._fullclnodes,
1167 1149 precomputedellipsis=self._precomputedellipsis)
1168 1150
1169 1151 yield fname, deltas
1170 1152
1171 1153 progress.complete()
1172 1154
1173 1155 def _makecg1packer(repo, filematcher, bundlecaps, ellipses=False,
1174 1156 shallow=False, ellipsisroots=None, fullnodes=None):
1175 1157 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1176 1158 d.node, d.p1node, d.p2node, d.linknode)
1177 1159
1178 1160 return cgpacker(repo, filematcher, b'01',
1179 1161 builddeltaheader=builddeltaheader,
1180 1162 manifestsend=b'',
1181 1163 forcedeltaparentprev=True,
1182 1164 bundlecaps=bundlecaps,
1183 1165 ellipses=ellipses,
1184 1166 shallow=shallow,
1185 1167 ellipsisroots=ellipsisroots,
1186 1168 fullnodes=fullnodes)
1187 1169
1188 1170 def _makecg2packer(repo, filematcher, bundlecaps, ellipses=False,
1189 1171 shallow=False, ellipsisroots=None, fullnodes=None):
1190 1172 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1191 1173 d.node, d.p1node, d.p2node, d.basenode, d.linknode)
1192 1174
1193 1175 return cgpacker(repo, filematcher, b'02',
1194 1176 builddeltaheader=builddeltaheader,
1195 1177 manifestsend=b'',
1196 1178 bundlecaps=bundlecaps,
1197 1179 ellipses=ellipses,
1198 1180 shallow=shallow,
1199 1181 ellipsisroots=ellipsisroots,
1200 1182 fullnodes=fullnodes)
1201 1183
1202 1184 def _makecg3packer(repo, filematcher, bundlecaps, ellipses=False,
1203 1185 shallow=False, ellipsisroots=None, fullnodes=None):
1204 1186 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1205 1187 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags)
1206 1188
1207 1189 return cgpacker(repo, filematcher, b'03',
1208 1190 builddeltaheader=builddeltaheader,
1209 1191 manifestsend=closechunk(),
1210 1192 bundlecaps=bundlecaps,
1211 1193 ellipses=ellipses,
1212 1194 shallow=shallow,
1213 1195 ellipsisroots=ellipsisroots,
1214 1196 fullnodes=fullnodes)
1215 1197
1216 1198 _packermap = {'01': (_makecg1packer, cg1unpacker),
1217 1199 # cg2 adds support for exchanging generaldelta
1218 1200 '02': (_makecg2packer, cg2unpacker),
1219 1201 # cg3 adds support for exchanging revlog flags and treemanifests
1220 1202 '03': (_makecg3packer, cg3unpacker),
1221 1203 }
1222 1204
1223 1205 def allsupportedversions(repo):
1224 1206 versions = set(_packermap.keys())
1225 1207 if not (repo.ui.configbool('experimental', 'changegroup3') or
1226 1208 repo.ui.configbool('experimental', 'treemanifest') or
1227 1209 'treemanifest' in repo.requirements):
1228 1210 versions.discard('03')
1229 1211 return versions
1230 1212
1231 1213 # Changegroup versions that can be applied to the repo
1232 1214 def supportedincomingversions(repo):
1233 1215 return allsupportedversions(repo)
1234 1216
1235 1217 # Changegroup versions that can be created from the repo
1236 1218 def supportedoutgoingversions(repo):
1237 1219 versions = allsupportedversions(repo)
1238 1220 if 'treemanifest' in repo.requirements:
1239 1221 # Versions 01 and 02 support only flat manifests and it's just too
1240 1222 # expensive to convert between the flat manifest and tree manifest on
1241 1223 # the fly. Since tree manifests are hashed differently, all of history
1242 1224 # would have to be converted. Instead, we simply don't even pretend to
1243 1225 # support versions 01 and 02.
1244 1226 versions.discard('01')
1245 1227 versions.discard('02')
1246 1228 if repository.NARROW_REQUIREMENT in repo.requirements:
1247 1229 # Versions 01 and 02 don't support revlog flags, and we need to
1248 1230 # support that for stripping and unbundling to work.
1249 1231 versions.discard('01')
1250 1232 versions.discard('02')
1251 1233 if LFS_REQUIREMENT in repo.requirements:
1252 1234 # Versions 01 and 02 don't support revlog flags, and we need to
1253 1235 # mark LFS entries with REVIDX_EXTSTORED.
1254 1236 versions.discard('01')
1255 1237 versions.discard('02')
1256 1238
1257 1239 return versions
1258 1240
1259 1241 def localversion(repo):
1260 1242 # Finds the best version to use for bundles that are meant to be used
1261 1243 # locally, such as those from strip and shelve, and temporary bundles.
1262 1244 return max(supportedoutgoingversions(repo))
1263 1245
1264 1246 def safeversion(repo):
1265 1247 # Finds the smallest version that it's safe to assume clients of the repo
1266 1248 # will support. For example, all hg versions that support generaldelta also
1267 1249 # support changegroup 02.
1268 1250 versions = supportedoutgoingversions(repo)
1269 1251 if 'generaldelta' in repo.requirements:
1270 1252 versions.discard('01')
1271 1253 assert versions
1272 1254 return min(versions)
1273 1255
1274 1256 def getbundler(version, repo, bundlecaps=None, filematcher=None,
1275 1257 ellipses=False, shallow=False, ellipsisroots=None,
1276 1258 fullnodes=None):
1277 1259 assert version in supportedoutgoingversions(repo)
1278 1260
1279 1261 if filematcher is None:
1280 1262 filematcher = matchmod.alwaysmatcher(repo.root, '')
1281 1263
1282 1264 if version == '01' and not filematcher.always():
1283 1265 raise error.ProgrammingError('version 01 changegroups do not support '
1284 1266 'sparse file matchers')
1285 1267
1286 1268 if ellipses and version in (b'01', b'02'):
1287 1269 raise error.Abort(
1288 1270 _('ellipsis nodes require at least cg3 on client and server, '
1289 1271 'but negotiated version %s') % version)
1290 1272
1291 1273 # Requested files could include files not in the local store. So
1292 1274 # filter those out.
1293 1275 filematcher = matchmod.intersectmatchers(repo.narrowmatch(),
1294 1276 filematcher)
1295 1277
1296 1278 fn = _packermap[version][0]
1297 1279 return fn(repo, filematcher, bundlecaps, ellipses=ellipses,
1298 1280 shallow=shallow, ellipsisroots=ellipsisroots,
1299 1281 fullnodes=fullnodes)
1300 1282
1301 1283 def getunbundler(version, fh, alg, extras=None):
1302 1284 return _packermap[version][1](fh, alg, extras=extras)
1303 1285
1304 1286 def _changegroupinfo(repo, nodes, source):
1305 1287 if repo.ui.verbose or source == 'bundle':
1306 1288 repo.ui.status(_("%d changesets found\n") % len(nodes))
1307 1289 if repo.ui.debugflag:
1308 1290 repo.ui.debug("list of changesets:\n")
1309 1291 for node in nodes:
1310 1292 repo.ui.debug("%s\n" % hex(node))
1311 1293
1312 1294 def makechangegroup(repo, outgoing, version, source, fastpath=False,
1313 1295 bundlecaps=None):
1314 1296 cgstream = makestream(repo, outgoing, version, source,
1315 1297 fastpath=fastpath, bundlecaps=bundlecaps)
1316 1298 return getunbundler(version, util.chunkbuffer(cgstream), None,
1317 1299 {'clcount': len(outgoing.missing) })
1318 1300
1319 1301 def makestream(repo, outgoing, version, source, fastpath=False,
1320 1302 bundlecaps=None, filematcher=None):
1321 1303 bundler = getbundler(version, repo, bundlecaps=bundlecaps,
1322 1304 filematcher=filematcher)
1323 1305
1324 1306 repo = repo.unfiltered()
1325 1307 commonrevs = outgoing.common
1326 1308 csets = outgoing.missing
1327 1309 heads = outgoing.missingheads
1328 1310 # We go through the fast path if we get told to, or if all (unfiltered
1329 1311 # heads have been requested (since we then know there all linkrevs will
1330 1312 # be pulled by the client).
1331 1313 heads.sort()
1332 1314 fastpathlinkrev = fastpath or (
1333 1315 repo.filtername is None and heads == sorted(repo.heads()))
1334 1316
1335 1317 repo.hook('preoutgoing', throw=True, source=source)
1336 1318 _changegroupinfo(repo, csets, source)
1337 1319 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1338 1320
1339 1321 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1340 1322 revisions = 0
1341 1323 files = 0
1342 1324 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1343 1325 total=expectedfiles)
1344 1326 for chunkdata in iter(source.filelogheader, {}):
1345 1327 files += 1
1346 1328 f = chunkdata["filename"]
1347 1329 repo.ui.debug("adding %s revisions\n" % f)
1348 1330 progress.increment()
1349 1331 fl = repo.file(f)
1350 1332 o = len(fl)
1351 1333 try:
1352 1334 deltas = source.deltaiter()
1353 1335 if not fl.addgroup(deltas, revmap, trp):
1354 1336 raise error.Abort(_("received file revlog group is empty"))
1355 1337 except error.CensoredBaseError as e:
1356 1338 raise error.Abort(_("received delta base is censored: %s") % e)
1357 1339 revisions += len(fl) - o
1358 1340 if f in needfiles:
1359 1341 needs = needfiles[f]
1360 1342 for new in pycompat.xrange(o, len(fl)):
1361 1343 n = fl.node(new)
1362 1344 if n in needs:
1363 1345 needs.remove(n)
1364 1346 else:
1365 1347 raise error.Abort(
1366 1348 _("received spurious file revlog entry"))
1367 1349 if not needs:
1368 1350 del needfiles[f]
1369 1351 progress.complete()
1370 1352
1371 1353 for f, needs in needfiles.iteritems():
1372 1354 fl = repo.file(f)
1373 1355 for n in needs:
1374 1356 try:
1375 1357 fl.rev(n)
1376 1358 except error.LookupError:
1377 1359 raise error.Abort(
1378 1360 _('missing file data for %s:%s - run hg verify') %
1379 1361 (f, hex(n)))
1380 1362
1381 1363 return revisions, files
@@ -1,268 +1,265 b''
1 1 # filelog.py - file history class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from __future__ import absolute_import
9 9
10 10 from . import (
11 11 error,
12 12 repository,
13 13 revlog,
14 14 )
15 15 from .utils import (
16 16 interfaceutil,
17 17 )
18 18
19 19 @interfaceutil.implementer(repository.ifilestorage)
20 20 class filelog(object):
21 21 def __init__(self, opener, path):
22 22 self._revlog = revlog.revlog(opener,
23 23 '/'.join(('data', path + '.i')),
24 24 censorable=True)
25 25 # Full name of the user visible file, relative to the repository root.
26 26 # Used by LFS.
27 27 self._revlog.filename = path
28 28 # Used by changegroup generation.
29 29 self._generaldelta = self._revlog._generaldelta
30 30
31 31 def __len__(self):
32 32 return len(self._revlog)
33 33
34 34 def __iter__(self):
35 35 return self._revlog.__iter__()
36 36
37 37 def revs(self, start=0, stop=None):
38 38 return self._revlog.revs(start=start, stop=stop)
39 39
40 40 def parents(self, node):
41 41 return self._revlog.parents(node)
42 42
43 43 def parentrevs(self, rev):
44 44 return self._revlog.parentrevs(rev)
45 45
46 46 def rev(self, node):
47 47 return self._revlog.rev(node)
48 48
49 49 def node(self, rev):
50 50 return self._revlog.node(rev)
51 51
52 52 def lookup(self, node):
53 53 return self._revlog.lookup(node)
54 54
55 55 def linkrev(self, rev):
56 56 return self._revlog.linkrev(rev)
57 57
58 58 # Used by verify.
59 59 def flags(self, rev):
60 60 return self._revlog.flags(rev)
61 61
62 62 def commonancestorsheads(self, node1, node2):
63 63 return self._revlog.commonancestorsheads(node1, node2)
64 64
65 65 # Used by dagop.blockdescendants().
66 66 def descendants(self, revs):
67 67 return self._revlog.descendants(revs)
68 68
69 69 def heads(self, start=None, stop=None):
70 70 return self._revlog.heads(start, stop)
71 71
72 72 # Used by hgweb, children extension.
73 73 def children(self, node):
74 74 return self._revlog.children(node)
75 75
76 76 def deltaparent(self, rev):
77 77 return self._revlog.deltaparent(rev)
78 78
79 79 def iscensored(self, rev):
80 80 return self._revlog.iscensored(rev)
81 81
82 82 # Used by repo upgrade, verify.
83 83 def rawsize(self, rev):
84 84 return self._revlog.rawsize(rev)
85 85
86 86 # Might be unused.
87 87 def checkhash(self, text, node, p1=None, p2=None, rev=None):
88 88 return self._revlog.checkhash(text, node, p1=p1, p2=p2, rev=rev)
89 89
90 90 def revision(self, node, _df=None, raw=False):
91 91 return self._revlog.revision(node, _df=_df, raw=raw)
92 92
93 93 def revdiff(self, rev1, rev2):
94 94 return self._revlog.revdiff(rev1, rev2)
95 95
96 def emitrevisiondeltas(self, requests):
97 return self._revlog.emitrevisiondeltas(requests)
98
99 96 def emitrevisions(self, nodes, nodesorder=None,
100 97 revisiondata=False, assumehaveparentrevisions=False,
101 98 deltaprevious=False):
102 99 return self._revlog.emitrevisions(
103 100 nodes, nodesorder=nodesorder, revisiondata=revisiondata,
104 101 assumehaveparentrevisions=assumehaveparentrevisions,
105 102 deltaprevious=deltaprevious)
106 103
107 104 def addrevision(self, revisiondata, transaction, linkrev, p1, p2,
108 105 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS,
109 106 cachedelta=None):
110 107 return self._revlog.addrevision(revisiondata, transaction, linkrev,
111 108 p1, p2, node=node, flags=flags,
112 109 cachedelta=cachedelta)
113 110
114 111 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
115 112 return self._revlog.addgroup(deltas, linkmapper, transaction,
116 113 addrevisioncb=addrevisioncb)
117 114
118 115 def getstrippoint(self, minlink):
119 116 return self._revlog.getstrippoint(minlink)
120 117
121 118 def strip(self, minlink, transaction):
122 119 return self._revlog.strip(minlink, transaction)
123 120
124 121 def censorrevision(self, tr, node, tombstone=b''):
125 122 return self._revlog.censorrevision(node, tombstone=tombstone)
126 123
127 124 def files(self):
128 125 return self._revlog.files()
129 126
130 127 def read(self, node):
131 128 t = self.revision(node)
132 129 if not t.startswith('\1\n'):
133 130 return t
134 131 s = t.index('\1\n', 2)
135 132 return t[s + 2:]
136 133
137 134 def add(self, text, meta, transaction, link, p1=None, p2=None):
138 135 if meta or text.startswith('\1\n'):
139 136 text = revlog.packmeta(meta, text)
140 137 return self.addrevision(text, transaction, link, p1, p2)
141 138
142 139 def renamed(self, node):
143 140 if self.parents(node)[0] != revlog.nullid:
144 141 return False
145 142 t = self.revision(node)
146 143 m = revlog.parsemeta(t)[0]
147 144 # copy and copyrev occur in pairs. In rare cases due to bugs,
148 145 # one can occur without the other.
149 146 if m and "copy" in m and "copyrev" in m:
150 147 return (m["copy"], revlog.bin(m["copyrev"]))
151 148 return False
152 149
153 150 def size(self, rev):
154 151 """return the size of a given revision"""
155 152
156 153 # for revisions with renames, we have to go the slow way
157 154 node = self.node(rev)
158 155 if self.renamed(node):
159 156 return len(self.read(node))
160 157 if self.iscensored(rev):
161 158 return 0
162 159
163 160 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
164 161 return self._revlog.size(rev)
165 162
166 163 def cmp(self, node, text):
167 164 """compare text with a given file revision
168 165
169 166 returns True if text is different than what is stored.
170 167 """
171 168
172 169 t = text
173 170 if text.startswith('\1\n'):
174 171 t = '\1\n\1\n' + text
175 172
176 173 samehashes = not self._revlog.cmp(node, t)
177 174 if samehashes:
178 175 return False
179 176
180 177 # censored files compare against the empty file
181 178 if self.iscensored(self.rev(node)):
182 179 return text != ''
183 180
184 181 # renaming a file produces a different hash, even if the data
185 182 # remains unchanged. Check if it's the case (slow):
186 183 if self.renamed(node):
187 184 t2 = self.read(node)
188 185 return t2 != text
189 186
190 187 return True
191 188
192 189 def verifyintegrity(self, state):
193 190 return self._revlog.verifyintegrity(state)
194 191
195 192 # TODO these aren't part of the interface and aren't internal methods.
196 193 # Callers should be fixed to not use them.
197 194
198 195 # Used by bundlefilelog, unionfilelog.
199 196 @property
200 197 def indexfile(self):
201 198 return self._revlog.indexfile
202 199
203 200 @indexfile.setter
204 201 def indexfile(self, value):
205 202 self._revlog.indexfile = value
206 203
207 204 # Used by repo upgrade.
208 205 @property
209 206 def opener(self):
210 207 return self._revlog.opener
211 208
212 209 # Used by repo upgrade.
213 210 def clone(self, tr, destrevlog, **kwargs):
214 211 if not isinstance(destrevlog, filelog):
215 212 raise error.ProgrammingError('expected filelog to clone()')
216 213
217 214 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
218 215
219 216 class narrowfilelog(filelog):
220 217 """Filelog variation to be used with narrow stores."""
221 218
222 219 def __init__(self, opener, path, narrowmatch):
223 220 super(narrowfilelog, self).__init__(opener, path)
224 221 self._narrowmatch = narrowmatch
225 222
226 223 def renamed(self, node):
227 224 res = super(narrowfilelog, self).renamed(node)
228 225
229 226 # Renames that come from outside the narrowspec are problematic
230 227 # because we may lack the base text for the rename. This can result
231 228 # in code attempting to walk the ancestry or compute a diff
232 229 # encountering a missing revision. We address this by silently
233 230 # removing rename metadata if the source file is outside the
234 231 # narrow spec.
235 232 #
236 233 # A better solution would be to see if the base revision is available,
237 234 # rather than assuming it isn't.
238 235 #
239 236 # An even better solution would be to teach all consumers of rename
240 237 # metadata that the base revision may not be available.
241 238 #
242 239 # TODO consider better ways of doing this.
243 240 if res and not self._narrowmatch(res[0]):
244 241 return None
245 242
246 243 return res
247 244
248 245 def size(self, rev):
249 246 # Because we have a custom renamed() that may lie, we need to call
250 247 # the base renamed() to report accurate results.
251 248 node = self.node(rev)
252 249 if super(narrowfilelog, self).renamed(node):
253 250 return len(self.read(node))
254 251 else:
255 252 return super(narrowfilelog, self).size(rev)
256 253
257 254 def cmp(self, node, text):
258 255 different = super(narrowfilelog, self).cmp(node, text)
259 256
260 257 # Because renamed() may lie, we may get false positives for
261 258 # different content. Check for this by comparing against the original
262 259 # renamed() implementation.
263 260 if different:
264 261 if super(narrowfilelog, self).renamed(node):
265 262 t2 = self.read(node)
266 263 return t2 != text
267 264
268 265 return different
@@ -1,2028 +1,2025 b''
1 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from __future__ import absolute_import
9 9
10 10 import heapq
11 11 import itertools
12 12 import struct
13 13 import weakref
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 bin,
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 )
22 22 from . import (
23 23 error,
24 24 mdiff,
25 25 policy,
26 26 pycompat,
27 27 repository,
28 28 revlog,
29 29 util,
30 30 )
31 31 from .utils import (
32 32 interfaceutil,
33 33 )
34 34
35 35 parsers = policy.importmod(r'parsers')
36 36 propertycache = util.propertycache
37 37
38 38 def _parse(data):
39 39 # This method does a little bit of excessive-looking
40 40 # precondition checking. This is so that the behavior of this
41 41 # class exactly matches its C counterpart to try and help
42 42 # prevent surprise breakage for anyone that develops against
43 43 # the pure version.
44 44 if data and data[-1:] != '\n':
45 45 raise ValueError('Manifest did not end in a newline.')
46 46 prev = None
47 47 for l in data.splitlines():
48 48 if prev is not None and prev > l:
49 49 raise ValueError('Manifest lines not in sorted order.')
50 50 prev = l
51 51 f, n = l.split('\0')
52 52 if len(n) > 40:
53 53 yield f, bin(n[:40]), n[40:]
54 54 else:
55 55 yield f, bin(n), ''
56 56
57 57 def _text(it):
58 58 files = []
59 59 lines = []
60 60 for f, n, fl in it:
61 61 files.append(f)
62 62 # if this is changed to support newlines in filenames,
63 63 # be sure to check the templates/ dir again (especially *-raw.tmpl)
64 64 lines.append("%s\0%s%s\n" % (f, hex(n), fl))
65 65
66 66 _checkforbidden(files)
67 67 return ''.join(lines)
68 68
69 69 class lazymanifestiter(object):
70 70 def __init__(self, lm):
71 71 self.pos = 0
72 72 self.lm = lm
73 73
74 74 def __iter__(self):
75 75 return self
76 76
77 77 def next(self):
78 78 try:
79 79 data, pos = self.lm._get(self.pos)
80 80 except IndexError:
81 81 raise StopIteration
82 82 if pos == -1:
83 83 self.pos += 1
84 84 return data[0]
85 85 self.pos += 1
86 86 zeropos = data.find('\x00', pos)
87 87 return data[pos:zeropos]
88 88
89 89 __next__ = next
90 90
91 91 class lazymanifestiterentries(object):
92 92 def __init__(self, lm):
93 93 self.lm = lm
94 94 self.pos = 0
95 95
96 96 def __iter__(self):
97 97 return self
98 98
99 99 def next(self):
100 100 try:
101 101 data, pos = self.lm._get(self.pos)
102 102 except IndexError:
103 103 raise StopIteration
104 104 if pos == -1:
105 105 self.pos += 1
106 106 return data
107 107 zeropos = data.find('\x00', pos)
108 108 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
109 109 zeropos + 1, 40)
110 110 flags = self.lm._getflags(data, self.pos, zeropos)
111 111 self.pos += 1
112 112 return (data[pos:zeropos], hashval, flags)
113 113
114 114 __next__ = next
115 115
116 116 def unhexlify(data, extra, pos, length):
117 117 s = bin(data[pos:pos + length])
118 118 if extra:
119 119 s += chr(extra & 0xff)
120 120 return s
121 121
122 122 def _cmp(a, b):
123 123 return (a > b) - (a < b)
124 124
125 125 class _lazymanifest(object):
126 126 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
127 127 if positions is None:
128 128 self.positions = self.findlines(data)
129 129 self.extrainfo = [0] * len(self.positions)
130 130 self.data = data
131 131 self.extradata = []
132 132 else:
133 133 self.positions = positions[:]
134 134 self.extrainfo = extrainfo[:]
135 135 self.extradata = extradata[:]
136 136 self.data = data
137 137
138 138 def findlines(self, data):
139 139 if not data:
140 140 return []
141 141 pos = data.find("\n")
142 142 if pos == -1 or data[-1:] != '\n':
143 143 raise ValueError("Manifest did not end in a newline.")
144 144 positions = [0]
145 145 prev = data[:data.find('\x00')]
146 146 while pos < len(data) - 1 and pos != -1:
147 147 positions.append(pos + 1)
148 148 nexts = data[pos + 1:data.find('\x00', pos + 1)]
149 149 if nexts < prev:
150 150 raise ValueError("Manifest lines not in sorted order.")
151 151 prev = nexts
152 152 pos = data.find("\n", pos + 1)
153 153 return positions
154 154
155 155 def _get(self, index):
156 156 # get the position encoded in pos:
157 157 # positive number is an index in 'data'
158 158 # negative number is in extrapieces
159 159 pos = self.positions[index]
160 160 if pos >= 0:
161 161 return self.data, pos
162 162 return self.extradata[-pos - 1], -1
163 163
164 164 def _getkey(self, pos):
165 165 if pos >= 0:
166 166 return self.data[pos:self.data.find('\x00', pos + 1)]
167 167 return self.extradata[-pos - 1][0]
168 168
169 169 def bsearch(self, key):
170 170 first = 0
171 171 last = len(self.positions) - 1
172 172
173 173 while first <= last:
174 174 midpoint = (first + last)//2
175 175 nextpos = self.positions[midpoint]
176 176 candidate = self._getkey(nextpos)
177 177 r = _cmp(key, candidate)
178 178 if r == 0:
179 179 return midpoint
180 180 else:
181 181 if r < 0:
182 182 last = midpoint - 1
183 183 else:
184 184 first = midpoint + 1
185 185 return -1
186 186
187 187 def bsearch2(self, key):
188 188 # same as the above, but will always return the position
189 189 # done for performance reasons
190 190 first = 0
191 191 last = len(self.positions) - 1
192 192
193 193 while first <= last:
194 194 midpoint = (first + last)//2
195 195 nextpos = self.positions[midpoint]
196 196 candidate = self._getkey(nextpos)
197 197 r = _cmp(key, candidate)
198 198 if r == 0:
199 199 return (midpoint, True)
200 200 else:
201 201 if r < 0:
202 202 last = midpoint - 1
203 203 else:
204 204 first = midpoint + 1
205 205 return (first, False)
206 206
207 207 def __contains__(self, key):
208 208 return self.bsearch(key) != -1
209 209
210 210 def _getflags(self, data, needle, pos):
211 211 start = pos + 41
212 212 end = data.find("\n", start)
213 213 if end == -1:
214 214 end = len(data) - 1
215 215 if start == end:
216 216 return ''
217 217 return self.data[start:end]
218 218
219 219 def __getitem__(self, key):
220 220 if not isinstance(key, bytes):
221 221 raise TypeError("getitem: manifest keys must be a bytes.")
222 222 needle = self.bsearch(key)
223 223 if needle == -1:
224 224 raise KeyError
225 225 data, pos = self._get(needle)
226 226 if pos == -1:
227 227 return (data[1], data[2])
228 228 zeropos = data.find('\x00', pos)
229 229 assert 0 <= needle <= len(self.positions)
230 230 assert len(self.extrainfo) == len(self.positions)
231 231 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
232 232 flags = self._getflags(data, needle, zeropos)
233 233 return (hashval, flags)
234 234
235 235 def __delitem__(self, key):
236 236 needle, found = self.bsearch2(key)
237 237 if not found:
238 238 raise KeyError
239 239 cur = self.positions[needle]
240 240 self.positions = self.positions[:needle] + self.positions[needle + 1:]
241 241 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
242 242 if cur >= 0:
243 243 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
244 244
245 245 def __setitem__(self, key, value):
246 246 if not isinstance(key, bytes):
247 247 raise TypeError("setitem: manifest keys must be a byte string.")
248 248 if not isinstance(value, tuple) or len(value) != 2:
249 249 raise TypeError("Manifest values must be a tuple of (node, flags).")
250 250 hashval = value[0]
251 251 if not isinstance(hashval, bytes) or not 20 <= len(hashval) <= 22:
252 252 raise TypeError("node must be a 20-byte byte string")
253 253 flags = value[1]
254 254 if len(hashval) == 22:
255 255 hashval = hashval[:-1]
256 256 if not isinstance(flags, bytes) or len(flags) > 1:
257 257 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
258 258 needle, found = self.bsearch2(key)
259 259 if found:
260 260 # put the item
261 261 pos = self.positions[needle]
262 262 if pos < 0:
263 263 self.extradata[-pos - 1] = (key, hashval, value[1])
264 264 else:
265 265 # just don't bother
266 266 self.extradata.append((key, hashval, value[1]))
267 267 self.positions[needle] = -len(self.extradata)
268 268 else:
269 269 # not found, put it in with extra positions
270 270 self.extradata.append((key, hashval, value[1]))
271 271 self.positions = (self.positions[:needle] + [-len(self.extradata)]
272 272 + self.positions[needle:])
273 273 self.extrainfo = (self.extrainfo[:needle] + [0] +
274 274 self.extrainfo[needle:])
275 275
276 276 def copy(self):
277 277 # XXX call _compact like in C?
278 278 return _lazymanifest(self.data, self.positions, self.extrainfo,
279 279 self.extradata)
280 280
281 281 def _compact(self):
282 282 # hopefully not called TOO often
283 283 if len(self.extradata) == 0:
284 284 return
285 285 l = []
286 286 last_cut = 0
287 287 i = 0
288 288 offset = 0
289 289 self.extrainfo = [0] * len(self.positions)
290 290 while i < len(self.positions):
291 291 if self.positions[i] >= 0:
292 292 cur = self.positions[i]
293 293 last_cut = cur
294 294 while True:
295 295 self.positions[i] = offset
296 296 i += 1
297 297 if i == len(self.positions) or self.positions[i] < 0:
298 298 break
299 299 offset += self.positions[i] - cur
300 300 cur = self.positions[i]
301 301 end_cut = self.data.find('\n', cur)
302 302 if end_cut != -1:
303 303 end_cut += 1
304 304 offset += end_cut - cur
305 305 l.append(self.data[last_cut:end_cut])
306 306 else:
307 307 while i < len(self.positions) and self.positions[i] < 0:
308 308 cur = self.positions[i]
309 309 t = self.extradata[-cur - 1]
310 310 l.append(self._pack(t))
311 311 self.positions[i] = offset
312 312 if len(t[1]) > 20:
313 313 self.extrainfo[i] = ord(t[1][21])
314 314 offset += len(l[-1])
315 315 i += 1
316 316 self.data = ''.join(l)
317 317 self.extradata = []
318 318
319 319 def _pack(self, d):
320 320 return d[0] + '\x00' + hex(d[1][:20]) + d[2] + '\n'
321 321
322 322 def text(self):
323 323 self._compact()
324 324 return self.data
325 325
326 326 def diff(self, m2, clean=False):
327 327 '''Finds changes between the current manifest and m2.'''
328 328 # XXX think whether efficiency matters here
329 329 diff = {}
330 330
331 331 for fn, e1, flags in self.iterentries():
332 332 if fn not in m2:
333 333 diff[fn] = (e1, flags), (None, '')
334 334 else:
335 335 e2 = m2[fn]
336 336 if (e1, flags) != e2:
337 337 diff[fn] = (e1, flags), e2
338 338 elif clean:
339 339 diff[fn] = None
340 340
341 341 for fn, e2, flags in m2.iterentries():
342 342 if fn not in self:
343 343 diff[fn] = (None, ''), (e2, flags)
344 344
345 345 return diff
346 346
347 347 def iterentries(self):
348 348 return lazymanifestiterentries(self)
349 349
350 350 def iterkeys(self):
351 351 return lazymanifestiter(self)
352 352
353 353 def __iter__(self):
354 354 return lazymanifestiter(self)
355 355
356 356 def __len__(self):
357 357 return len(self.positions)
358 358
359 359 def filtercopy(self, filterfn):
360 360 # XXX should be optimized
361 361 c = _lazymanifest('')
362 362 for f, n, fl in self.iterentries():
363 363 if filterfn(f):
364 364 c[f] = n, fl
365 365 return c
366 366
367 367 try:
368 368 _lazymanifest = parsers.lazymanifest
369 369 except AttributeError:
370 370 pass
371 371
372 372 @interfaceutil.implementer(repository.imanifestdict)
373 373 class manifestdict(object):
374 374 def __init__(self, data=''):
375 375 self._lm = _lazymanifest(data)
376 376
377 377 def __getitem__(self, key):
378 378 return self._lm[key][0]
379 379
380 380 def find(self, key):
381 381 return self._lm[key]
382 382
383 383 def __len__(self):
384 384 return len(self._lm)
385 385
386 386 def __nonzero__(self):
387 387 # nonzero is covered by the __len__ function, but implementing it here
388 388 # makes it easier for extensions to override.
389 389 return len(self._lm) != 0
390 390
391 391 __bool__ = __nonzero__
392 392
393 393 def __setitem__(self, key, node):
394 394 self._lm[key] = node, self.flags(key, '')
395 395
396 396 def __contains__(self, key):
397 397 if key is None:
398 398 return False
399 399 return key in self._lm
400 400
401 401 def __delitem__(self, key):
402 402 del self._lm[key]
403 403
404 404 def __iter__(self):
405 405 return self._lm.__iter__()
406 406
407 407 def iterkeys(self):
408 408 return self._lm.iterkeys()
409 409
410 410 def keys(self):
411 411 return list(self.iterkeys())
412 412
413 413 def filesnotin(self, m2, match=None):
414 414 '''Set of files in this manifest that are not in the other'''
415 415 if match:
416 416 m1 = self.matches(match)
417 417 m2 = m2.matches(match)
418 418 return m1.filesnotin(m2)
419 419 diff = self.diff(m2)
420 420 files = set(filepath
421 421 for filepath, hashflags in diff.iteritems()
422 422 if hashflags[1][0] is None)
423 423 return files
424 424
425 425 @propertycache
426 426 def _dirs(self):
427 427 return util.dirs(self)
428 428
429 429 def dirs(self):
430 430 return self._dirs
431 431
432 432 def hasdir(self, dir):
433 433 return dir in self._dirs
434 434
435 435 def _filesfastpath(self, match):
436 436 '''Checks whether we can correctly and quickly iterate over matcher
437 437 files instead of over manifest files.'''
438 438 files = match.files()
439 439 return (len(files) < 100 and (match.isexact() or
440 440 (match.prefix() and all(fn in self for fn in files))))
441 441
442 442 def walk(self, match):
443 443 '''Generates matching file names.
444 444
445 445 Equivalent to manifest.matches(match).iterkeys(), but without creating
446 446 an entirely new manifest.
447 447
448 448 It also reports nonexistent files by marking them bad with match.bad().
449 449 '''
450 450 if match.always():
451 451 for f in iter(self):
452 452 yield f
453 453 return
454 454
455 455 fset = set(match.files())
456 456
457 457 # avoid the entire walk if we're only looking for specific files
458 458 if self._filesfastpath(match):
459 459 for fn in sorted(fset):
460 460 yield fn
461 461 return
462 462
463 463 for fn in self:
464 464 if fn in fset:
465 465 # specified pattern is the exact name
466 466 fset.remove(fn)
467 467 if match(fn):
468 468 yield fn
469 469
470 470 # for dirstate.walk, files=['.'] means "walk the whole tree".
471 471 # follow that here, too
472 472 fset.discard('.')
473 473
474 474 for fn in sorted(fset):
475 475 if not self.hasdir(fn):
476 476 match.bad(fn, None)
477 477
478 478 def matches(self, match):
479 479 '''generate a new manifest filtered by the match argument'''
480 480 if match.always():
481 481 return self.copy()
482 482
483 483 if self._filesfastpath(match):
484 484 m = manifestdict()
485 485 lm = self._lm
486 486 for fn in match.files():
487 487 if fn in lm:
488 488 m._lm[fn] = lm[fn]
489 489 return m
490 490
491 491 m = manifestdict()
492 492 m._lm = self._lm.filtercopy(match)
493 493 return m
494 494
495 495 def diff(self, m2, match=None, clean=False):
496 496 '''Finds changes between the current manifest and m2.
497 497
498 498 Args:
499 499 m2: the manifest to which this manifest should be compared.
500 500 clean: if true, include files unchanged between these manifests
501 501 with a None value in the returned dictionary.
502 502
503 503 The result is returned as a dict with filename as key and
504 504 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
505 505 nodeid in the current/other manifest and fl1/fl2 is the flag
506 506 in the current/other manifest. Where the file does not exist,
507 507 the nodeid will be None and the flags will be the empty
508 508 string.
509 509 '''
510 510 if match:
511 511 m1 = self.matches(match)
512 512 m2 = m2.matches(match)
513 513 return m1.diff(m2, clean=clean)
514 514 return self._lm.diff(m2._lm, clean)
515 515
516 516 def setflag(self, key, flag):
517 517 self._lm[key] = self[key], flag
518 518
519 519 def get(self, key, default=None):
520 520 try:
521 521 return self._lm[key][0]
522 522 except KeyError:
523 523 return default
524 524
525 525 def flags(self, key, default=''):
526 526 try:
527 527 return self._lm[key][1]
528 528 except KeyError:
529 529 return default
530 530
531 531 def copy(self):
532 532 c = manifestdict()
533 533 c._lm = self._lm.copy()
534 534 return c
535 535
536 536 def items(self):
537 537 return (x[:2] for x in self._lm.iterentries())
538 538
539 539 def iteritems(self):
540 540 return (x[:2] for x in self._lm.iterentries())
541 541
542 542 def iterentries(self):
543 543 return self._lm.iterentries()
544 544
545 545 def text(self):
546 546 # most likely uses native version
547 547 return self._lm.text()
548 548
549 549 def fastdelta(self, base, changes):
550 550 """Given a base manifest text as a bytearray and a list of changes
551 551 relative to that text, compute a delta that can be used by revlog.
552 552 """
553 553 delta = []
554 554 dstart = None
555 555 dend = None
556 556 dline = [""]
557 557 start = 0
558 558 # zero copy representation of base as a buffer
559 559 addbuf = util.buffer(base)
560 560
561 561 changes = list(changes)
562 562 if len(changes) < 1000:
563 563 # start with a readonly loop that finds the offset of
564 564 # each line and creates the deltas
565 565 for f, todelete in changes:
566 566 # bs will either be the index of the item or the insert point
567 567 start, end = _msearch(addbuf, f, start)
568 568 if not todelete:
569 569 h, fl = self._lm[f]
570 570 l = "%s\0%s%s\n" % (f, hex(h), fl)
571 571 else:
572 572 if start == end:
573 573 # item we want to delete was not found, error out
574 574 raise AssertionError(
575 575 _("failed to remove %s from manifest") % f)
576 576 l = ""
577 577 if dstart is not None and dstart <= start and dend >= start:
578 578 if dend < end:
579 579 dend = end
580 580 if l:
581 581 dline.append(l)
582 582 else:
583 583 if dstart is not None:
584 584 delta.append([dstart, dend, "".join(dline)])
585 585 dstart = start
586 586 dend = end
587 587 dline = [l]
588 588
589 589 if dstart is not None:
590 590 delta.append([dstart, dend, "".join(dline)])
591 591 # apply the delta to the base, and get a delta for addrevision
592 592 deltatext, arraytext = _addlistdelta(base, delta)
593 593 else:
594 594 # For large changes, it's much cheaper to just build the text and
595 595 # diff it.
596 596 arraytext = bytearray(self.text())
597 597 deltatext = mdiff.textdiff(
598 598 util.buffer(base), util.buffer(arraytext))
599 599
600 600 return arraytext, deltatext
601 601
602 602 def _msearch(m, s, lo=0, hi=None):
603 603 '''return a tuple (start, end) that says where to find s within m.
604 604
605 605 If the string is found m[start:end] are the line containing
606 606 that string. If start == end the string was not found and
607 607 they indicate the proper sorted insertion point.
608 608
609 609 m should be a buffer, a memoryview or a byte string.
610 610 s is a byte string'''
611 611 def advance(i, c):
612 612 while i < lenm and m[i:i + 1] != c:
613 613 i += 1
614 614 return i
615 615 if not s:
616 616 return (lo, lo)
617 617 lenm = len(m)
618 618 if not hi:
619 619 hi = lenm
620 620 while lo < hi:
621 621 mid = (lo + hi) // 2
622 622 start = mid
623 623 while start > 0 and m[start - 1:start] != '\n':
624 624 start -= 1
625 625 end = advance(start, '\0')
626 626 if bytes(m[start:end]) < s:
627 627 # we know that after the null there are 40 bytes of sha1
628 628 # this translates to the bisect lo = mid + 1
629 629 lo = advance(end + 40, '\n') + 1
630 630 else:
631 631 # this translates to the bisect hi = mid
632 632 hi = start
633 633 end = advance(lo, '\0')
634 634 found = m[lo:end]
635 635 if s == found:
636 636 # we know that after the null there are 40 bytes of sha1
637 637 end = advance(end + 40, '\n')
638 638 return (lo, end + 1)
639 639 else:
640 640 return (lo, lo)
641 641
642 642 def _checkforbidden(l):
643 643 """Check filenames for illegal characters."""
644 644 for f in l:
645 645 if '\n' in f or '\r' in f:
646 646 raise error.StorageError(
647 647 _("'\\n' and '\\r' disallowed in filenames: %r")
648 648 % pycompat.bytestr(f))
649 649
650 650
651 651 # apply the changes collected during the bisect loop to our addlist
652 652 # return a delta suitable for addrevision
653 653 def _addlistdelta(addlist, x):
654 654 # for large addlist arrays, building a new array is cheaper
655 655 # than repeatedly modifying the existing one
656 656 currentposition = 0
657 657 newaddlist = bytearray()
658 658
659 659 for start, end, content in x:
660 660 newaddlist += addlist[currentposition:start]
661 661 if content:
662 662 newaddlist += bytearray(content)
663 663
664 664 currentposition = end
665 665
666 666 newaddlist += addlist[currentposition:]
667 667
668 668 deltatext = "".join(struct.pack(">lll", start, end, len(content))
669 669 + content for start, end, content in x)
670 670 return deltatext, newaddlist
671 671
672 672 def _splittopdir(f):
673 673 if '/' in f:
674 674 dir, subpath = f.split('/', 1)
675 675 return dir + '/', subpath
676 676 else:
677 677 return '', f
678 678
679 679 _noop = lambda s: None
680 680
681 681 class treemanifest(object):
682 682 def __init__(self, dir='', text=''):
683 683 self._dir = dir
684 684 self._node = nullid
685 685 self._loadfunc = _noop
686 686 self._copyfunc = _noop
687 687 self._dirty = False
688 688 self._dirs = {}
689 689 self._lazydirs = {}
690 690 # Using _lazymanifest here is a little slower than plain old dicts
691 691 self._files = {}
692 692 self._flags = {}
693 693 if text:
694 694 def readsubtree(subdir, subm):
695 695 raise AssertionError('treemanifest constructor only accepts '
696 696 'flat manifests')
697 697 self.parse(text, readsubtree)
698 698 self._dirty = True # Mark flat manifest dirty after parsing
699 699
700 700 def _subpath(self, path):
701 701 return self._dir + path
702 702
703 703 def _loadalllazy(self):
704 704 for k, (path, node, readsubtree) in self._lazydirs.iteritems():
705 705 self._dirs[k] = readsubtree(path, node)
706 706 self._lazydirs = {}
707 707
708 708 def _loadlazy(self, d):
709 709 path, node, readsubtree = self._lazydirs[d]
710 710 self._dirs[d] = readsubtree(path, node)
711 711 del self._lazydirs[d]
712 712
713 713 def _loadchildrensetlazy(self, visit):
714 714 if not visit:
715 715 return None
716 716 if visit == 'all' or visit == 'this':
717 717 self._loadalllazy()
718 718 return None
719 719
720 720 todel = []
721 721 for k in visit:
722 722 kslash = k + '/'
723 723 ld = self._lazydirs.get(kslash)
724 724 if ld:
725 725 path, node, readsubtree = ld
726 726 self._dirs[kslash] = readsubtree(path, node)
727 727 todel.append(kslash)
728 728 for kslash in todel:
729 729 del self._lazydirs[kslash]
730 730 return visit
731 731
732 732 def __len__(self):
733 733 self._load()
734 734 size = len(self._files)
735 735 self._loadalllazy()
736 736 for m in self._dirs.values():
737 737 size += m.__len__()
738 738 return size
739 739
740 740 def __nonzero__(self):
741 741 # Faster than "__len() != 0" since it avoids loading sub-manifests
742 742 return not self._isempty()
743 743
744 744 __bool__ = __nonzero__
745 745
746 746 def _isempty(self):
747 747 self._load() # for consistency; already loaded by all callers
748 748 # See if we can skip loading everything.
749 749 if self._files or (self._dirs and
750 750 any(not m._isempty() for m in self._dirs.values())):
751 751 return False
752 752 self._loadalllazy()
753 753 return (not self._dirs or
754 754 all(m._isempty() for m in self._dirs.values()))
755 755
756 756 def __repr__(self):
757 757 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
758 758 (self._dir, hex(self._node),
759 759 bool(self._loadfunc is _noop),
760 760 self._dirty, id(self)))
761 761
762 762 def dir(self):
763 763 '''The directory that this tree manifest represents, including a
764 764 trailing '/'. Empty string for the repo root directory.'''
765 765 return self._dir
766 766
767 767 def node(self):
768 768 '''This node of this instance. nullid for unsaved instances. Should
769 769 be updated when the instance is read or written from a revlog.
770 770 '''
771 771 assert not self._dirty
772 772 return self._node
773 773
774 774 def setnode(self, node):
775 775 self._node = node
776 776 self._dirty = False
777 777
778 778 def iterentries(self):
779 779 self._load()
780 780 self._loadalllazy()
781 781 for p, n in sorted(itertools.chain(self._dirs.items(),
782 782 self._files.items())):
783 783 if p in self._files:
784 784 yield self._subpath(p), n, self._flags.get(p, '')
785 785 else:
786 786 for x in n.iterentries():
787 787 yield x
788 788
789 789 def items(self):
790 790 self._load()
791 791 self._loadalllazy()
792 792 for p, n in sorted(itertools.chain(self._dirs.items(),
793 793 self._files.items())):
794 794 if p in self._files:
795 795 yield self._subpath(p), n
796 796 else:
797 797 for f, sn in n.iteritems():
798 798 yield f, sn
799 799
800 800 iteritems = items
801 801
802 802 def iterkeys(self):
803 803 self._load()
804 804 self._loadalllazy()
805 805 for p in sorted(itertools.chain(self._dirs, self._files)):
806 806 if p in self._files:
807 807 yield self._subpath(p)
808 808 else:
809 809 for f in self._dirs[p]:
810 810 yield f
811 811
812 812 def keys(self):
813 813 return list(self.iterkeys())
814 814
815 815 def __iter__(self):
816 816 return self.iterkeys()
817 817
818 818 def __contains__(self, f):
819 819 if f is None:
820 820 return False
821 821 self._load()
822 822 dir, subpath = _splittopdir(f)
823 823 if dir:
824 824 if dir in self._lazydirs:
825 825 self._loadlazy(dir)
826 826
827 827 if dir not in self._dirs:
828 828 return False
829 829
830 830 return self._dirs[dir].__contains__(subpath)
831 831 else:
832 832 return f in self._files
833 833
834 834 def get(self, f, default=None):
835 835 self._load()
836 836 dir, subpath = _splittopdir(f)
837 837 if dir:
838 838 if dir in self._lazydirs:
839 839 self._loadlazy(dir)
840 840
841 841 if dir not in self._dirs:
842 842 return default
843 843 return self._dirs[dir].get(subpath, default)
844 844 else:
845 845 return self._files.get(f, default)
846 846
847 847 def __getitem__(self, f):
848 848 self._load()
849 849 dir, subpath = _splittopdir(f)
850 850 if dir:
851 851 if dir in self._lazydirs:
852 852 self._loadlazy(dir)
853 853
854 854 return self._dirs[dir].__getitem__(subpath)
855 855 else:
856 856 return self._files[f]
857 857
858 858 def flags(self, f):
859 859 self._load()
860 860 dir, subpath = _splittopdir(f)
861 861 if dir:
862 862 if dir in self._lazydirs:
863 863 self._loadlazy(dir)
864 864
865 865 if dir not in self._dirs:
866 866 return ''
867 867 return self._dirs[dir].flags(subpath)
868 868 else:
869 869 if f in self._lazydirs or f in self._dirs:
870 870 return ''
871 871 return self._flags.get(f, '')
872 872
873 873 def find(self, f):
874 874 self._load()
875 875 dir, subpath = _splittopdir(f)
876 876 if dir:
877 877 if dir in self._lazydirs:
878 878 self._loadlazy(dir)
879 879
880 880 return self._dirs[dir].find(subpath)
881 881 else:
882 882 return self._files[f], self._flags.get(f, '')
883 883
884 884 def __delitem__(self, f):
885 885 self._load()
886 886 dir, subpath = _splittopdir(f)
887 887 if dir:
888 888 if dir in self._lazydirs:
889 889 self._loadlazy(dir)
890 890
891 891 self._dirs[dir].__delitem__(subpath)
892 892 # If the directory is now empty, remove it
893 893 if self._dirs[dir]._isempty():
894 894 del self._dirs[dir]
895 895 else:
896 896 del self._files[f]
897 897 if f in self._flags:
898 898 del self._flags[f]
899 899 self._dirty = True
900 900
901 901 def __setitem__(self, f, n):
902 902 assert n is not None
903 903 self._load()
904 904 dir, subpath = _splittopdir(f)
905 905 if dir:
906 906 if dir in self._lazydirs:
907 907 self._loadlazy(dir)
908 908 if dir not in self._dirs:
909 909 self._dirs[dir] = treemanifest(self._subpath(dir))
910 910 self._dirs[dir].__setitem__(subpath, n)
911 911 else:
912 912 self._files[f] = n[:21] # to match manifestdict's behavior
913 913 self._dirty = True
914 914
915 915 def _load(self):
916 916 if self._loadfunc is not _noop:
917 917 lf, self._loadfunc = self._loadfunc, _noop
918 918 lf(self)
919 919 elif self._copyfunc is not _noop:
920 920 cf, self._copyfunc = self._copyfunc, _noop
921 921 cf(self)
922 922
923 923 def setflag(self, f, flags):
924 924 """Set the flags (symlink, executable) for path f."""
925 925 self._load()
926 926 dir, subpath = _splittopdir(f)
927 927 if dir:
928 928 if dir in self._lazydirs:
929 929 self._loadlazy(dir)
930 930 if dir not in self._dirs:
931 931 self._dirs[dir] = treemanifest(self._subpath(dir))
932 932 self._dirs[dir].setflag(subpath, flags)
933 933 else:
934 934 self._flags[f] = flags
935 935 self._dirty = True
936 936
937 937 def copy(self):
938 938 copy = treemanifest(self._dir)
939 939 copy._node = self._node
940 940 copy._dirty = self._dirty
941 941 if self._copyfunc is _noop:
942 942 def _copyfunc(s):
943 943 self._load()
944 944 # OPT: it'd be nice to not load everything here. Unfortunately
945 945 # this makes a mess of the "dirty" state tracking if we don't.
946 946 self._loadalllazy()
947 947 sdirs = s._dirs
948 948 for d, v in self._dirs.iteritems():
949 949 sdirs[d] = v.copy()
950 950 s._files = dict.copy(self._files)
951 951 s._flags = dict.copy(self._flags)
952 952 if self._loadfunc is _noop:
953 953 _copyfunc(copy)
954 954 else:
955 955 copy._copyfunc = _copyfunc
956 956 else:
957 957 copy._copyfunc = self._copyfunc
958 958 return copy
959 959
960 960 def filesnotin(self, m2, match=None):
961 961 '''Set of files in this manifest that are not in the other'''
962 962 if match and not match.always():
963 963 m1 = self.matches(match)
964 964 m2 = m2.matches(match)
965 965 return m1.filesnotin(m2)
966 966
967 967 files = set()
968 968 def _filesnotin(t1, t2):
969 969 if t1._node == t2._node and not t1._dirty and not t2._dirty:
970 970 return
971 971 t1._load()
972 972 t2._load()
973 973 t1._loadalllazy()
974 974 t2._loadalllazy()
975 975 for d, m1 in t1._dirs.iteritems():
976 976 if d in t2._dirs:
977 977 m2 = t2._dirs[d]
978 978 _filesnotin(m1, m2)
979 979 else:
980 980 files.update(m1.iterkeys())
981 981
982 982 for fn in t1._files:
983 983 if fn not in t2._files:
984 984 files.add(t1._subpath(fn))
985 985
986 986 _filesnotin(self, m2)
987 987 return files
988 988
989 989 @propertycache
990 990 def _alldirs(self):
991 991 return util.dirs(self)
992 992
993 993 def dirs(self):
994 994 return self._alldirs
995 995
996 996 def hasdir(self, dir):
997 997 self._load()
998 998 topdir, subdir = _splittopdir(dir)
999 999 if topdir:
1000 1000 if topdir in self._lazydirs:
1001 1001 self._loadlazy(topdir)
1002 1002 if topdir in self._dirs:
1003 1003 return self._dirs[topdir].hasdir(subdir)
1004 1004 return False
1005 1005 dirslash = dir + '/'
1006 1006 return dirslash in self._dirs or dirslash in self._lazydirs
1007 1007
1008 1008 def walk(self, match):
1009 1009 '''Generates matching file names.
1010 1010
1011 1011 Equivalent to manifest.matches(match).iterkeys(), but without creating
1012 1012 an entirely new manifest.
1013 1013
1014 1014 It also reports nonexistent files by marking them bad with match.bad().
1015 1015 '''
1016 1016 if match.always():
1017 1017 for f in iter(self):
1018 1018 yield f
1019 1019 return
1020 1020
1021 1021 fset = set(match.files())
1022 1022
1023 1023 for fn in self._walk(match):
1024 1024 if fn in fset:
1025 1025 # specified pattern is the exact name
1026 1026 fset.remove(fn)
1027 1027 yield fn
1028 1028
1029 1029 # for dirstate.walk, files=['.'] means "walk the whole tree".
1030 1030 # follow that here, too
1031 1031 fset.discard('.')
1032 1032
1033 1033 for fn in sorted(fset):
1034 1034 if not self.hasdir(fn):
1035 1035 match.bad(fn, None)
1036 1036
1037 1037 def _walk(self, match):
1038 1038 '''Recursively generates matching file names for walk().'''
1039 1039 visit = match.visitchildrenset(self._dir[:-1] or '.')
1040 1040 if not visit:
1041 1041 return
1042 1042
1043 1043 # yield this dir's files and walk its submanifests
1044 1044 self._load()
1045 1045 visit = self._loadchildrensetlazy(visit)
1046 1046 for p in sorted(list(self._dirs) + list(self._files)):
1047 1047 if p in self._files:
1048 1048 fullp = self._subpath(p)
1049 1049 if match(fullp):
1050 1050 yield fullp
1051 1051 else:
1052 1052 if not visit or p[:-1] in visit:
1053 1053 for f in self._dirs[p]._walk(match):
1054 1054 yield f
1055 1055
1056 1056 def matches(self, match):
1057 1057 '''generate a new manifest filtered by the match argument'''
1058 1058 if match.always():
1059 1059 return self.copy()
1060 1060
1061 1061 return self._matches(match)
1062 1062
1063 1063 def _matches(self, match):
1064 1064 '''recursively generate a new manifest filtered by the match argument.
1065 1065 '''
1066 1066
1067 1067 visit = match.visitchildrenset(self._dir[:-1] or '.')
1068 1068 if visit == 'all':
1069 1069 return self.copy()
1070 1070 ret = treemanifest(self._dir)
1071 1071 if not visit:
1072 1072 return ret
1073 1073
1074 1074 self._load()
1075 1075 for fn in self._files:
1076 1076 # While visitchildrenset *usually* lists only subdirs, this is
1077 1077 # actually up to the matcher and may have some files in the set().
1078 1078 # If visit == 'this', we should obviously look at the files in this
1079 1079 # directory; if visit is a set, and fn is in it, we should inspect
1080 1080 # fn (but no need to inspect things not in the set).
1081 1081 if visit != 'this' and fn not in visit:
1082 1082 continue
1083 1083 fullp = self._subpath(fn)
1084 1084 # visitchildrenset isn't perfect, we still need to call the regular
1085 1085 # matcher code to further filter results.
1086 1086 if not match(fullp):
1087 1087 continue
1088 1088 ret._files[fn] = self._files[fn]
1089 1089 if fn in self._flags:
1090 1090 ret._flags[fn] = self._flags[fn]
1091 1091
1092 1092 visit = self._loadchildrensetlazy(visit)
1093 1093 for dir, subm in self._dirs.iteritems():
1094 1094 if visit and dir[:-1] not in visit:
1095 1095 continue
1096 1096 m = subm._matches(match)
1097 1097 if not m._isempty():
1098 1098 ret._dirs[dir] = m
1099 1099
1100 1100 if not ret._isempty():
1101 1101 ret._dirty = True
1102 1102 return ret
1103 1103
1104 1104 def diff(self, m2, match=None, clean=False):
1105 1105 '''Finds changes between the current manifest and m2.
1106 1106
1107 1107 Args:
1108 1108 m2: the manifest to which this manifest should be compared.
1109 1109 clean: if true, include files unchanged between these manifests
1110 1110 with a None value in the returned dictionary.
1111 1111
1112 1112 The result is returned as a dict with filename as key and
1113 1113 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1114 1114 nodeid in the current/other manifest and fl1/fl2 is the flag
1115 1115 in the current/other manifest. Where the file does not exist,
1116 1116 the nodeid will be None and the flags will be the empty
1117 1117 string.
1118 1118 '''
1119 1119 if match and not match.always():
1120 1120 m1 = self.matches(match)
1121 1121 m2 = m2.matches(match)
1122 1122 return m1.diff(m2, clean=clean)
1123 1123 result = {}
1124 1124 emptytree = treemanifest()
1125 1125 def _diff(t1, t2):
1126 1126 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1127 1127 return
1128 1128 t1._load()
1129 1129 t2._load()
1130 1130 # OPT: do we need to load everything?
1131 1131 t1._loadalllazy()
1132 1132 t2._loadalllazy()
1133 1133 for d, m1 in t1._dirs.iteritems():
1134 1134 m2 = t2._dirs.get(d, emptytree)
1135 1135 _diff(m1, m2)
1136 1136
1137 1137 for d, m2 in t2._dirs.iteritems():
1138 1138 if d not in t1._dirs:
1139 1139 _diff(emptytree, m2)
1140 1140
1141 1141 for fn, n1 in t1._files.iteritems():
1142 1142 fl1 = t1._flags.get(fn, '')
1143 1143 n2 = t2._files.get(fn, None)
1144 1144 fl2 = t2._flags.get(fn, '')
1145 1145 if n1 != n2 or fl1 != fl2:
1146 1146 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1147 1147 elif clean:
1148 1148 result[t1._subpath(fn)] = None
1149 1149
1150 1150 for fn, n2 in t2._files.iteritems():
1151 1151 if fn not in t1._files:
1152 1152 fl2 = t2._flags.get(fn, '')
1153 1153 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1154 1154
1155 1155 _diff(self, m2)
1156 1156 return result
1157 1157
1158 1158 def unmodifiedsince(self, m2):
1159 1159 return not self._dirty and not m2._dirty and self._node == m2._node
1160 1160
1161 1161 def parse(self, text, readsubtree):
1162 1162 selflazy = self._lazydirs
1163 1163 subpath = self._subpath
1164 1164 for f, n, fl in _parse(text):
1165 1165 if fl == 't':
1166 1166 f = f + '/'
1167 1167 selflazy[f] = (subpath(f), n, readsubtree)
1168 1168 elif '/' in f:
1169 1169 # This is a flat manifest, so use __setitem__ and setflag rather
1170 1170 # than assigning directly to _files and _flags, so we can
1171 1171 # assign a path in a subdirectory, and to mark dirty (compared
1172 1172 # to nullid).
1173 1173 self[f] = n
1174 1174 if fl:
1175 1175 self.setflag(f, fl)
1176 1176 else:
1177 1177 # Assigning to _files and _flags avoids marking as dirty,
1178 1178 # and should be a little faster.
1179 1179 self._files[f] = n
1180 1180 if fl:
1181 1181 self._flags[f] = fl
1182 1182
1183 1183 def text(self):
1184 1184 """Get the full data of this manifest as a bytestring."""
1185 1185 self._load()
1186 1186 return _text(self.iterentries())
1187 1187
1188 1188 def dirtext(self):
1189 1189 """Get the full data of this directory as a bytestring. Make sure that
1190 1190 any submanifests have been written first, so their nodeids are correct.
1191 1191 """
1192 1192 self._load()
1193 1193 flags = self.flags
1194 1194 lazydirs = [(d[:-1], node, 't') for
1195 1195 d, (path, node, readsubtree) in self._lazydirs.iteritems()]
1196 1196 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1197 1197 files = [(f, self._files[f], flags(f)) for f in self._files]
1198 1198 return _text(sorted(dirs + files + lazydirs))
1199 1199
1200 1200 def read(self, gettext, readsubtree):
1201 1201 def _load_for_read(s):
1202 1202 s.parse(gettext(), readsubtree)
1203 1203 s._dirty = False
1204 1204 self._loadfunc = _load_for_read
1205 1205
1206 1206 def writesubtrees(self, m1, m2, writesubtree, match):
1207 1207 self._load() # for consistency; should never have any effect here
1208 1208 m1._load()
1209 1209 m2._load()
1210 1210 emptytree = treemanifest()
1211 1211 def getnode(m, d):
1212 1212 ld = m._lazydirs.get(d)
1213 1213 if ld:
1214 1214 return ld[1]
1215 1215 return m._dirs.get(d, emptytree)._node
1216 1216
1217 1217 # we should have always loaded everything by the time we get here for
1218 1218 # `self`, but possibly not in `m1` or `m2`.
1219 1219 assert not self._lazydirs
1220 1220 # let's skip investigating things that `match` says we do not need.
1221 1221 visit = match.visitchildrenset(self._dir[:-1] or '.')
1222 1222 if visit == 'this' or visit == 'all':
1223 1223 visit = None
1224 1224 for d, subm in self._dirs.iteritems():
1225 1225 if visit and d[:-1] not in visit:
1226 1226 continue
1227 1227 subp1 = getnode(m1, d)
1228 1228 subp2 = getnode(m2, d)
1229 1229 if subp1 == nullid:
1230 1230 subp1, subp2 = subp2, subp1
1231 1231 writesubtree(subm, subp1, subp2, match)
1232 1232
1233 1233 def walksubtrees(self, matcher=None):
1234 1234 """Returns an iterator of the subtrees of this manifest, including this
1235 1235 manifest itself.
1236 1236
1237 1237 If `matcher` is provided, it only returns subtrees that match.
1238 1238 """
1239 1239 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1240 1240 return
1241 1241 if not matcher or matcher(self._dir[:-1]):
1242 1242 yield self
1243 1243
1244 1244 self._load()
1245 1245 # OPT: use visitchildrenset to avoid loading everything.
1246 1246 self._loadalllazy()
1247 1247 for d, subm in self._dirs.iteritems():
1248 1248 for subtree in subm.walksubtrees(matcher=matcher):
1249 1249 yield subtree
1250 1250
1251 1251 class manifestfulltextcache(util.lrucachedict):
1252 1252 """File-backed LRU cache for the manifest cache
1253 1253
1254 1254 File consists of entries, up to EOF:
1255 1255
1256 1256 - 20 bytes node, 4 bytes length, <length> manifest data
1257 1257
1258 1258 These are written in reverse cache order (oldest to newest).
1259 1259
1260 1260 """
1261 1261 def __init__(self, max):
1262 1262 super(manifestfulltextcache, self).__init__(max)
1263 1263 self._dirty = False
1264 1264 self._read = False
1265 1265 self._opener = None
1266 1266
1267 1267 def read(self):
1268 1268 if self._read or self._opener is None:
1269 1269 return
1270 1270
1271 1271 try:
1272 1272 with self._opener('manifestfulltextcache') as fp:
1273 1273 set = super(manifestfulltextcache, self).__setitem__
1274 1274 # ignore trailing data, this is a cache, corruption is skipped
1275 1275 while True:
1276 1276 node = fp.read(20)
1277 1277 if len(node) < 20:
1278 1278 break
1279 1279 try:
1280 1280 size = struct.unpack('>L', fp.read(4))[0]
1281 1281 except struct.error:
1282 1282 break
1283 1283 value = bytearray(fp.read(size))
1284 1284 if len(value) != size:
1285 1285 break
1286 1286 set(node, value)
1287 1287 except IOError:
1288 1288 # the file is allowed to be missing
1289 1289 pass
1290 1290
1291 1291 self._read = True
1292 1292 self._dirty = False
1293 1293
1294 1294 def write(self):
1295 1295 if not self._dirty or self._opener is None:
1296 1296 return
1297 1297 # rotate backwards to the first used node
1298 1298 with self._opener(
1299 1299 'manifestfulltextcache', 'w', atomictemp=True, checkambig=True
1300 1300 ) as fp:
1301 1301 node = self._head.prev
1302 1302 while True:
1303 1303 if node.key in self._cache:
1304 1304 fp.write(node.key)
1305 1305 fp.write(struct.pack('>L', len(node.value)))
1306 1306 fp.write(node.value)
1307 1307 if node is self._head:
1308 1308 break
1309 1309 node = node.prev
1310 1310
1311 1311 def __len__(self):
1312 1312 if not self._read:
1313 1313 self.read()
1314 1314 return super(manifestfulltextcache, self).__len__()
1315 1315
1316 1316 def __contains__(self, k):
1317 1317 if not self._read:
1318 1318 self.read()
1319 1319 return super(manifestfulltextcache, self).__contains__(k)
1320 1320
1321 1321 def __iter__(self):
1322 1322 if not self._read:
1323 1323 self.read()
1324 1324 return super(manifestfulltextcache, self).__iter__()
1325 1325
1326 1326 def __getitem__(self, k):
1327 1327 if not self._read:
1328 1328 self.read()
1329 1329 # the cache lru order can change on read
1330 1330 setdirty = self._cache.get(k) is not self._head
1331 1331 value = super(manifestfulltextcache, self).__getitem__(k)
1332 1332 if setdirty:
1333 1333 self._dirty = True
1334 1334 return value
1335 1335
1336 1336 def __setitem__(self, k, v):
1337 1337 if not self._read:
1338 1338 self.read()
1339 1339 super(manifestfulltextcache, self).__setitem__(k, v)
1340 1340 self._dirty = True
1341 1341
1342 1342 def __delitem__(self, k):
1343 1343 if not self._read:
1344 1344 self.read()
1345 1345 super(manifestfulltextcache, self).__delitem__(k)
1346 1346 self._dirty = True
1347 1347
1348 1348 def get(self, k, default=None):
1349 1349 if not self._read:
1350 1350 self.read()
1351 1351 return super(manifestfulltextcache, self).get(k, default=default)
1352 1352
1353 1353 def clear(self, clear_persisted_data=False):
1354 1354 super(manifestfulltextcache, self).clear()
1355 1355 if clear_persisted_data:
1356 1356 self._dirty = True
1357 1357 self.write()
1358 1358 self._read = False
1359 1359
1360 1360 @interfaceutil.implementer(repository.imanifeststorage)
1361 1361 class manifestrevlog(object):
1362 1362 '''A revlog that stores manifest texts. This is responsible for caching the
1363 1363 full-text manifest contents.
1364 1364 '''
1365 1365 def __init__(self, opener, tree='', dirlogcache=None, indexfile=None,
1366 1366 treemanifest=False):
1367 1367 """Constructs a new manifest revlog
1368 1368
1369 1369 `indexfile` - used by extensions to have two manifests at once, like
1370 1370 when transitioning between flatmanifeset and treemanifests.
1371 1371
1372 1372 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1373 1373 options can also be used to make this a tree manifest revlog. The opener
1374 1374 option takes precedence, so if it is set to True, we ignore whatever
1375 1375 value is passed in to the constructor.
1376 1376 """
1377 1377 # During normal operations, we expect to deal with not more than four
1378 1378 # revs at a time (such as during commit --amend). When rebasing large
1379 1379 # stacks of commits, the number can go up, hence the config knob below.
1380 1380 cachesize = 4
1381 1381 optiontreemanifest = False
1382 1382 opts = getattr(opener, 'options', None)
1383 1383 if opts is not None:
1384 1384 cachesize = opts.get('manifestcachesize', cachesize)
1385 1385 optiontreemanifest = opts.get('treemanifest', False)
1386 1386
1387 1387 self._treeondisk = optiontreemanifest or treemanifest
1388 1388
1389 1389 self._fulltextcache = manifestfulltextcache(cachesize)
1390 1390
1391 1391 if tree:
1392 1392 assert self._treeondisk, 'opts is %r' % opts
1393 1393
1394 1394 if indexfile is None:
1395 1395 indexfile = '00manifest.i'
1396 1396 if tree:
1397 1397 indexfile = "meta/" + tree + indexfile
1398 1398
1399 1399 self.tree = tree
1400 1400
1401 1401 # The dirlogcache is kept on the root manifest log
1402 1402 if tree:
1403 1403 self._dirlogcache = dirlogcache
1404 1404 else:
1405 1405 self._dirlogcache = {'': self}
1406 1406
1407 1407 self._revlog = revlog.revlog(opener, indexfile,
1408 1408 # only root indexfile is cached
1409 1409 checkambig=not bool(tree),
1410 1410 mmaplargeindex=True)
1411 1411
1412 1412 self.index = self._revlog.index
1413 1413 self.version = self._revlog.version
1414 1414 self._generaldelta = self._revlog._generaldelta
1415 1415
1416 1416 def _setupmanifestcachehooks(self, repo):
1417 1417 """Persist the manifestfulltextcache on lock release"""
1418 1418 if not util.safehasattr(repo, '_lockref'):
1419 1419 return
1420 1420
1421 1421 self._fulltextcache._opener = repo.cachevfs
1422 1422 reporef = weakref.ref(repo)
1423 1423 manifestrevlogref = weakref.ref(self)
1424 1424
1425 1425 def persistmanifestcache():
1426 1426 repo = reporef()
1427 1427 self = manifestrevlogref()
1428 1428 if repo is None or self is None:
1429 1429 return
1430 1430 if repo.manifestlog.getstorage(b'') is not self:
1431 1431 # there's a different manifest in play now, abort
1432 1432 return
1433 1433 self._fulltextcache.write()
1434 1434
1435 1435 if repo._currentlock(repo._lockref) is not None:
1436 1436 repo._afterlock(persistmanifestcache)
1437 1437
1438 1438 @property
1439 1439 def fulltextcache(self):
1440 1440 return self._fulltextcache
1441 1441
1442 1442 def clearcaches(self, clear_persisted_data=False):
1443 1443 self._revlog.clearcaches()
1444 1444 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1445 1445 self._dirlogcache = {self.tree: self}
1446 1446
1447 1447 def dirlog(self, d):
1448 1448 if d:
1449 1449 assert self._treeondisk
1450 1450 if d not in self._dirlogcache:
1451 1451 mfrevlog = manifestrevlog(self.opener, d,
1452 1452 self._dirlogcache,
1453 1453 treemanifest=self._treeondisk)
1454 1454 self._dirlogcache[d] = mfrevlog
1455 1455 return self._dirlogcache[d]
1456 1456
1457 1457 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None,
1458 1458 match=None):
1459 1459 if p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta'):
1460 1460 # If our first parent is in the manifest cache, we can
1461 1461 # compute a delta here using properties we know about the
1462 1462 # manifest up-front, which may save time later for the
1463 1463 # revlog layer.
1464 1464
1465 1465 _checkforbidden(added)
1466 1466 # combine the changed lists into one sorted iterator
1467 1467 work = heapq.merge([(x, False) for x in added],
1468 1468 [(x, True) for x in removed])
1469 1469
1470 1470 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1471 1471 cachedelta = self._revlog.rev(p1), deltatext
1472 1472 text = util.buffer(arraytext)
1473 1473 n = self._revlog.addrevision(text, transaction, link, p1, p2,
1474 1474 cachedelta)
1475 1475 else:
1476 1476 # The first parent manifest isn't already loaded, so we'll
1477 1477 # just encode a fulltext of the manifest and pass that
1478 1478 # through to the revlog layer, and let it handle the delta
1479 1479 # process.
1480 1480 if self._treeondisk:
1481 1481 assert readtree, "readtree must be set for treemanifest writes"
1482 1482 assert match, "match must be specified for treemanifest writes"
1483 1483 m1 = readtree(self.tree, p1)
1484 1484 m2 = readtree(self.tree, p2)
1485 1485 n = self._addtree(m, transaction, link, m1, m2, readtree,
1486 1486 match=match)
1487 1487 arraytext = None
1488 1488 else:
1489 1489 text = m.text()
1490 1490 n = self._revlog.addrevision(text, transaction, link, p1, p2)
1491 1491 arraytext = bytearray(text)
1492 1492
1493 1493 if arraytext is not None:
1494 1494 self.fulltextcache[n] = arraytext
1495 1495
1496 1496 return n
1497 1497
1498 1498 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1499 1499 # If the manifest is unchanged compared to one parent,
1500 1500 # don't write a new revision
1501 1501 if self.tree != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(
1502 1502 m2)):
1503 1503 return m.node()
1504 1504 def writesubtree(subm, subp1, subp2, match):
1505 1505 sublog = self.dirlog(subm.dir())
1506 1506 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1507 1507 readtree=readtree, match=match)
1508 1508 m.writesubtrees(m1, m2, writesubtree, match)
1509 1509 text = m.dirtext()
1510 1510 n = None
1511 1511 if self.tree != '':
1512 1512 # Double-check whether contents are unchanged to one parent
1513 1513 if text == m1.dirtext():
1514 1514 n = m1.node()
1515 1515 elif text == m2.dirtext():
1516 1516 n = m2.node()
1517 1517
1518 1518 if not n:
1519 1519 n = self._revlog.addrevision(text, transaction, link, m1.node(),
1520 1520 m2.node())
1521 1521
1522 1522 # Save nodeid so parent manifest can calculate its nodeid
1523 1523 m.setnode(n)
1524 1524 return n
1525 1525
1526 1526 def __len__(self):
1527 1527 return len(self._revlog)
1528 1528
1529 1529 def __iter__(self):
1530 1530 return self._revlog.__iter__()
1531 1531
1532 1532 def rev(self, node):
1533 1533 return self._revlog.rev(node)
1534 1534
1535 1535 def node(self, rev):
1536 1536 return self._revlog.node(rev)
1537 1537
1538 1538 def lookup(self, value):
1539 1539 return self._revlog.lookup(value)
1540 1540
1541 1541 def parentrevs(self, rev):
1542 1542 return self._revlog.parentrevs(rev)
1543 1543
1544 1544 def parents(self, node):
1545 1545 return self._revlog.parents(node)
1546 1546
1547 1547 def linkrev(self, rev):
1548 1548 return self._revlog.linkrev(rev)
1549 1549
1550 1550 def checksize(self):
1551 1551 return self._revlog.checksize()
1552 1552
1553 1553 def revision(self, node, _df=None, raw=False):
1554 1554 return self._revlog.revision(node, _df=_df, raw=raw)
1555 1555
1556 1556 def revdiff(self, rev1, rev2):
1557 1557 return self._revlog.revdiff(rev1, rev2)
1558 1558
1559 1559 def cmp(self, node, text):
1560 1560 return self._revlog.cmp(node, text)
1561 1561
1562 1562 def deltaparent(self, rev):
1563 1563 return self._revlog.deltaparent(rev)
1564 1564
1565 def emitrevisiondeltas(self, requests):
1566 return self._revlog.emitrevisiondeltas(requests)
1567
1568 1565 def emitrevisions(self, nodes, nodesorder=None,
1569 1566 revisiondata=False, assumehaveparentrevisions=False,
1570 1567 deltaprevious=False):
1571 1568 return self._revlog.emitrevisions(
1572 1569 nodes, nodesorder=nodesorder, revisiondata=revisiondata,
1573 1570 assumehaveparentrevisions=assumehaveparentrevisions,
1574 1571 deltaprevious=deltaprevious)
1575 1572
1576 1573 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
1577 1574 return self._revlog.addgroup(deltas, linkmapper, transaction,
1578 1575 addrevisioncb=addrevisioncb)
1579 1576
1580 1577 def rawsize(self, rev):
1581 1578 return self._revlog.rawsize(rev)
1582 1579
1583 1580 def getstrippoint(self, minlink):
1584 1581 return self._revlog.getstrippoint(minlink)
1585 1582
1586 1583 def strip(self, minlink, transaction):
1587 1584 return self._revlog.strip(minlink, transaction)
1588 1585
1589 1586 def files(self):
1590 1587 return self._revlog.files()
1591 1588
1592 1589 def clone(self, tr, destrevlog, **kwargs):
1593 1590 if not isinstance(destrevlog, manifestrevlog):
1594 1591 raise error.ProgrammingError('expected manifestrevlog to clone()')
1595 1592
1596 1593 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1597 1594
1598 1595 @property
1599 1596 def indexfile(self):
1600 1597 return self._revlog.indexfile
1601 1598
1602 1599 @indexfile.setter
1603 1600 def indexfile(self, value):
1604 1601 self._revlog.indexfile = value
1605 1602
1606 1603 @property
1607 1604 def opener(self):
1608 1605 return self._revlog.opener
1609 1606
1610 1607 @opener.setter
1611 1608 def opener(self, value):
1612 1609 self._revlog.opener = value
1613 1610
1614 1611 @interfaceutil.implementer(repository.imanifestlog)
1615 1612 class manifestlog(object):
1616 1613 """A collection class representing the collection of manifest snapshots
1617 1614 referenced by commits in the repository.
1618 1615
1619 1616 In this situation, 'manifest' refers to the abstract concept of a snapshot
1620 1617 of the list of files in the given commit. Consumers of the output of this
1621 1618 class do not care about the implementation details of the actual manifests
1622 1619 they receive (i.e. tree or flat or lazily loaded, etc)."""
1623 1620 def __init__(self, opener, repo, rootstore):
1624 1621 usetreemanifest = False
1625 1622 cachesize = 4
1626 1623
1627 1624 opts = getattr(opener, 'options', None)
1628 1625 if opts is not None:
1629 1626 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1630 1627 cachesize = opts.get('manifestcachesize', cachesize)
1631 1628
1632 1629 self._treemanifests = usetreemanifest
1633 1630
1634 1631 self._rootstore = rootstore
1635 1632 self._rootstore._setupmanifestcachehooks(repo)
1636 1633 self._narrowmatch = repo.narrowmatch()
1637 1634
1638 1635 # A cache of the manifestctx or treemanifestctx for each directory
1639 1636 self._dirmancache = {}
1640 1637 self._dirmancache[''] = util.lrucachedict(cachesize)
1641 1638
1642 1639 self._cachesize = cachesize
1643 1640
1644 1641 def __getitem__(self, node):
1645 1642 """Retrieves the manifest instance for the given node. Throws a
1646 1643 LookupError if not found.
1647 1644 """
1648 1645 return self.get('', node)
1649 1646
1650 1647 def get(self, tree, node, verify=True):
1651 1648 """Retrieves the manifest instance for the given node. Throws a
1652 1649 LookupError if not found.
1653 1650
1654 1651 `verify` - if True an exception will be thrown if the node is not in
1655 1652 the revlog
1656 1653 """
1657 1654 if node in self._dirmancache.get(tree, ()):
1658 1655 return self._dirmancache[tree][node]
1659 1656
1660 1657 if not self._narrowmatch.always():
1661 1658 if not self._narrowmatch.visitdir(tree[:-1] or '.'):
1662 1659 return excludeddirmanifestctx(tree, node)
1663 1660 if tree:
1664 1661 if self._rootstore._treeondisk:
1665 1662 if verify:
1666 1663 # Side-effect is LookupError is raised if node doesn't
1667 1664 # exist.
1668 1665 self.getstorage(tree).rev(node)
1669 1666
1670 1667 m = treemanifestctx(self, tree, node)
1671 1668 else:
1672 1669 raise error.Abort(
1673 1670 _("cannot ask for manifest directory '%s' in a flat "
1674 1671 "manifest") % tree)
1675 1672 else:
1676 1673 if verify:
1677 1674 # Side-effect is LookupError is raised if node doesn't exist.
1678 1675 self._rootstore.rev(node)
1679 1676
1680 1677 if self._treemanifests:
1681 1678 m = treemanifestctx(self, '', node)
1682 1679 else:
1683 1680 m = manifestctx(self, node)
1684 1681
1685 1682 if node != nullid:
1686 1683 mancache = self._dirmancache.get(tree)
1687 1684 if not mancache:
1688 1685 mancache = util.lrucachedict(self._cachesize)
1689 1686 self._dirmancache[tree] = mancache
1690 1687 mancache[node] = m
1691 1688 return m
1692 1689
1693 1690 def getstorage(self, tree):
1694 1691 return self._rootstore.dirlog(tree)
1695 1692
1696 1693 def clearcaches(self, clear_persisted_data=False):
1697 1694 self._dirmancache.clear()
1698 1695 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
1699 1696
1700 1697 def rev(self, node):
1701 1698 return self._rootstore.rev(node)
1702 1699
1703 1700 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1704 1701 class memmanifestctx(object):
1705 1702 def __init__(self, manifestlog):
1706 1703 self._manifestlog = manifestlog
1707 1704 self._manifestdict = manifestdict()
1708 1705
1709 1706 def _storage(self):
1710 1707 return self._manifestlog.getstorage(b'')
1711 1708
1712 1709 def new(self):
1713 1710 return memmanifestctx(self._manifestlog)
1714 1711
1715 1712 def copy(self):
1716 1713 memmf = memmanifestctx(self._manifestlog)
1717 1714 memmf._manifestdict = self.read().copy()
1718 1715 return memmf
1719 1716
1720 1717 def read(self):
1721 1718 return self._manifestdict
1722 1719
1723 1720 def write(self, transaction, link, p1, p2, added, removed, match=None):
1724 1721 return self._storage().add(self._manifestdict, transaction, link,
1725 1722 p1, p2, added, removed, match=match)
1726 1723
1727 1724 @interfaceutil.implementer(repository.imanifestrevisionstored)
1728 1725 class manifestctx(object):
1729 1726 """A class representing a single revision of a manifest, including its
1730 1727 contents, its parent revs, and its linkrev.
1731 1728 """
1732 1729 def __init__(self, manifestlog, node):
1733 1730 self._manifestlog = manifestlog
1734 1731 self._data = None
1735 1732
1736 1733 self._node = node
1737 1734
1738 1735 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1739 1736 # but let's add it later when something needs it and we can load it
1740 1737 # lazily.
1741 1738 #self.p1, self.p2 = store.parents(node)
1742 1739 #rev = store.rev(node)
1743 1740 #self.linkrev = store.linkrev(rev)
1744 1741
1745 1742 def _storage(self):
1746 1743 return self._manifestlog.getstorage(b'')
1747 1744
1748 1745 def node(self):
1749 1746 return self._node
1750 1747
1751 1748 def new(self):
1752 1749 return memmanifestctx(self._manifestlog)
1753 1750
1754 1751 def copy(self):
1755 1752 memmf = memmanifestctx(self._manifestlog)
1756 1753 memmf._manifestdict = self.read().copy()
1757 1754 return memmf
1758 1755
1759 1756 @propertycache
1760 1757 def parents(self):
1761 1758 return self._storage().parents(self._node)
1762 1759
1763 1760 def read(self):
1764 1761 if self._data is None:
1765 1762 if self._node == nullid:
1766 1763 self._data = manifestdict()
1767 1764 else:
1768 1765 store = self._storage()
1769 1766 if self._node in store.fulltextcache:
1770 1767 text = pycompat.bytestr(store.fulltextcache[self._node])
1771 1768 else:
1772 1769 text = store.revision(self._node)
1773 1770 arraytext = bytearray(text)
1774 1771 store.fulltextcache[self._node] = arraytext
1775 1772 self._data = manifestdict(text)
1776 1773 return self._data
1777 1774
1778 1775 def readfast(self, shallow=False):
1779 1776 '''Calls either readdelta or read, based on which would be less work.
1780 1777 readdelta is called if the delta is against the p1, and therefore can be
1781 1778 read quickly.
1782 1779
1783 1780 If `shallow` is True, nothing changes since this is a flat manifest.
1784 1781 '''
1785 1782 store = self._storage()
1786 1783 r = store.rev(self._node)
1787 1784 deltaparent = store.deltaparent(r)
1788 1785 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
1789 1786 return self.readdelta()
1790 1787 return self.read()
1791 1788
1792 1789 def readdelta(self, shallow=False):
1793 1790 '''Returns a manifest containing just the entries that are present
1794 1791 in this manifest, but not in its p1 manifest. This is efficient to read
1795 1792 if the revlog delta is already p1.
1796 1793
1797 1794 Changing the value of `shallow` has no effect on flat manifests.
1798 1795 '''
1799 1796 store = self._storage()
1800 1797 r = store.rev(self._node)
1801 1798 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1802 1799 return manifestdict(d)
1803 1800
1804 1801 def find(self, key):
1805 1802 return self.read().find(key)
1806 1803
1807 1804 @interfaceutil.implementer(repository.imanifestrevisionwritable)
1808 1805 class memtreemanifestctx(object):
1809 1806 def __init__(self, manifestlog, dir=''):
1810 1807 self._manifestlog = manifestlog
1811 1808 self._dir = dir
1812 1809 self._treemanifest = treemanifest()
1813 1810
1814 1811 def _storage(self):
1815 1812 return self._manifestlog.getstorage(b'')
1816 1813
1817 1814 def new(self, dir=''):
1818 1815 return memtreemanifestctx(self._manifestlog, dir=dir)
1819 1816
1820 1817 def copy(self):
1821 1818 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1822 1819 memmf._treemanifest = self._treemanifest.copy()
1823 1820 return memmf
1824 1821
1825 1822 def read(self):
1826 1823 return self._treemanifest
1827 1824
1828 1825 def write(self, transaction, link, p1, p2, added, removed, match=None):
1829 1826 def readtree(dir, node):
1830 1827 return self._manifestlog.get(dir, node).read()
1831 1828 return self._storage().add(self._treemanifest, transaction, link,
1832 1829 p1, p2, added, removed, readtree=readtree,
1833 1830 match=match)
1834 1831
1835 1832 @interfaceutil.implementer(repository.imanifestrevisionstored)
1836 1833 class treemanifestctx(object):
1837 1834 def __init__(self, manifestlog, dir, node):
1838 1835 self._manifestlog = manifestlog
1839 1836 self._dir = dir
1840 1837 self._data = None
1841 1838
1842 1839 self._node = node
1843 1840
1844 1841 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1845 1842 # we can instantiate treemanifestctx objects for directories we don't
1846 1843 # have on disk.
1847 1844 #self.p1, self.p2 = store.parents(node)
1848 1845 #rev = store.rev(node)
1849 1846 #self.linkrev = store.linkrev(rev)
1850 1847
1851 1848 def _storage(self):
1852 1849 narrowmatch = self._manifestlog._narrowmatch
1853 1850 if not narrowmatch.always():
1854 1851 if not narrowmatch.visitdir(self._dir[:-1] or '.'):
1855 1852 return excludedmanifestrevlog(self._dir)
1856 1853 return self._manifestlog.getstorage(self._dir)
1857 1854
1858 1855 def read(self):
1859 1856 if self._data is None:
1860 1857 store = self._storage()
1861 1858 if self._node == nullid:
1862 1859 self._data = treemanifest()
1863 1860 # TODO accessing non-public API
1864 1861 elif store._treeondisk:
1865 1862 m = treemanifest(dir=self._dir)
1866 1863 def gettext():
1867 1864 return store.revision(self._node)
1868 1865 def readsubtree(dir, subm):
1869 1866 # Set verify to False since we need to be able to create
1870 1867 # subtrees for trees that don't exist on disk.
1871 1868 return self._manifestlog.get(dir, subm, verify=False).read()
1872 1869 m.read(gettext, readsubtree)
1873 1870 m.setnode(self._node)
1874 1871 self._data = m
1875 1872 else:
1876 1873 if self._node in store.fulltextcache:
1877 1874 text = pycompat.bytestr(store.fulltextcache[self._node])
1878 1875 else:
1879 1876 text = store.revision(self._node)
1880 1877 arraytext = bytearray(text)
1881 1878 store.fulltextcache[self._node] = arraytext
1882 1879 self._data = treemanifest(dir=self._dir, text=text)
1883 1880
1884 1881 return self._data
1885 1882
1886 1883 def node(self):
1887 1884 return self._node
1888 1885
1889 1886 def new(self, dir=''):
1890 1887 return memtreemanifestctx(self._manifestlog, dir=dir)
1891 1888
1892 1889 def copy(self):
1893 1890 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1894 1891 memmf._treemanifest = self.read().copy()
1895 1892 return memmf
1896 1893
1897 1894 @propertycache
1898 1895 def parents(self):
1899 1896 return self._storage().parents(self._node)
1900 1897
1901 1898 def readdelta(self, shallow=False):
1902 1899 '''Returns a manifest containing just the entries that are present
1903 1900 in this manifest, but not in its p1 manifest. This is efficient to read
1904 1901 if the revlog delta is already p1.
1905 1902
1906 1903 If `shallow` is True, this will read the delta for this directory,
1907 1904 without recursively reading subdirectory manifests. Instead, any
1908 1905 subdirectory entry will be reported as it appears in the manifest, i.e.
1909 1906 the subdirectory will be reported among files and distinguished only by
1910 1907 its 't' flag.
1911 1908 '''
1912 1909 store = self._storage()
1913 1910 if shallow:
1914 1911 r = store.rev(self._node)
1915 1912 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
1916 1913 return manifestdict(d)
1917 1914 else:
1918 1915 # Need to perform a slow delta
1919 1916 r0 = store.deltaparent(store.rev(self._node))
1920 1917 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
1921 1918 m1 = self.read()
1922 1919 md = treemanifest(dir=self._dir)
1923 1920 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1924 1921 if n1:
1925 1922 md[f] = n1
1926 1923 if fl1:
1927 1924 md.setflag(f, fl1)
1928 1925 return md
1929 1926
1930 1927 def readfast(self, shallow=False):
1931 1928 '''Calls either readdelta or read, based on which would be less work.
1932 1929 readdelta is called if the delta is against the p1, and therefore can be
1933 1930 read quickly.
1934 1931
1935 1932 If `shallow` is True, it only returns the entries from this manifest,
1936 1933 and not any submanifests.
1937 1934 '''
1938 1935 store = self._storage()
1939 1936 r = store.rev(self._node)
1940 1937 deltaparent = store.deltaparent(r)
1941 1938 if (deltaparent != nullrev and
1942 1939 deltaparent in store.parentrevs(r)):
1943 1940 return self.readdelta(shallow=shallow)
1944 1941
1945 1942 if shallow:
1946 1943 return manifestdict(store.revision(self._node))
1947 1944 else:
1948 1945 return self.read()
1949 1946
1950 1947 def find(self, key):
1951 1948 return self.read().find(key)
1952 1949
1953 1950 class excludeddir(treemanifest):
1954 1951 """Stand-in for a directory that is excluded from the repository.
1955 1952
1956 1953 With narrowing active on a repository that uses treemanifests,
1957 1954 some of the directory revlogs will be excluded from the resulting
1958 1955 clone. This is a huge storage win for clients, but means we need
1959 1956 some sort of pseudo-manifest to surface to internals so we can
1960 1957 detect a merge conflict outside the narrowspec. That's what this
1961 1958 class is: it stands in for a directory whose node is known, but
1962 1959 whose contents are unknown.
1963 1960 """
1964 1961 def __init__(self, dir, node):
1965 1962 super(excludeddir, self).__init__(dir)
1966 1963 self._node = node
1967 1964 # Add an empty file, which will be included by iterators and such,
1968 1965 # appearing as the directory itself (i.e. something like "dir/")
1969 1966 self._files[''] = node
1970 1967 self._flags[''] = 't'
1971 1968
1972 1969 # Manifests outside the narrowspec should never be modified, so avoid
1973 1970 # copying. This makes a noticeable difference when there are very many
1974 1971 # directories outside the narrowspec. Also, it makes sense for the copy to
1975 1972 # be of the same type as the original, which would not happen with the
1976 1973 # super type's copy().
1977 1974 def copy(self):
1978 1975 return self
1979 1976
1980 1977 class excludeddirmanifestctx(treemanifestctx):
1981 1978 """context wrapper for excludeddir - see that docstring for rationale"""
1982 1979 def __init__(self, dir, node):
1983 1980 self._dir = dir
1984 1981 self._node = node
1985 1982
1986 1983 def read(self):
1987 1984 return excludeddir(self._dir, self._node)
1988 1985
1989 1986 def write(self, *args):
1990 1987 raise error.ProgrammingError(
1991 1988 'attempt to write manifest from excluded dir %s' % self._dir)
1992 1989
1993 1990 class excludedmanifestrevlog(manifestrevlog):
1994 1991 """Stand-in for excluded treemanifest revlogs.
1995 1992
1996 1993 When narrowing is active on a treemanifest repository, we'll have
1997 1994 references to directories we can't see due to the revlog being
1998 1995 skipped. This class exists to conform to the manifestrevlog
1999 1996 interface for those directories and proactively prevent writes to
2000 1997 outside the narrowspec.
2001 1998 """
2002 1999
2003 2000 def __init__(self, dir):
2004 2001 self._dir = dir
2005 2002
2006 2003 def __len__(self):
2007 2004 raise error.ProgrammingError(
2008 2005 'attempt to get length of excluded dir %s' % self._dir)
2009 2006
2010 2007 def rev(self, node):
2011 2008 raise error.ProgrammingError(
2012 2009 'attempt to get rev from excluded dir %s' % self._dir)
2013 2010
2014 2011 def linkrev(self, node):
2015 2012 raise error.ProgrammingError(
2016 2013 'attempt to get linkrev from excluded dir %s' % self._dir)
2017 2014
2018 2015 def node(self, rev):
2019 2016 raise error.ProgrammingError(
2020 2017 'attempt to get node from excluded dir %s' % self._dir)
2021 2018
2022 2019 def add(self, *args, **kwargs):
2023 2020 # We should never write entries in dirlogs outside the narrow clone.
2024 2021 # However, the method still gets called from writesubtree() in
2025 2022 # _addtree(), so we need to handle it. We should possibly make that
2026 2023 # avoid calling add() with a clean manifest (_dirty is always False
2027 2024 # in excludeddir instances).
2028 2025 pass
@@ -1,1701 +1,1640 b''
1 1 # repository.py - Interfaces and base classes for repositories and peers.
2 2 #
3 3 # Copyright 2017 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 from __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11 from . import (
12 12 error,
13 13 )
14 14 from .utils import (
15 15 interfaceutil,
16 16 )
17 17
18 18 # When narrowing is finalized and no longer subject to format changes,
19 19 # we should move this to just "narrow" or similar.
20 20 NARROW_REQUIREMENT = 'narrowhg-experimental'
21 21
22 22 # Local repository feature string.
23 23
24 24 # Revlogs are being used for file storage.
25 25 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
26 26 # The storage part of the repository is shared from an external source.
27 27 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
28 28 # LFS supported for backing file storage.
29 29 REPO_FEATURE_LFS = b'lfs'
30 30
31 31 class ipeerconnection(interfaceutil.Interface):
32 32 """Represents a "connection" to a repository.
33 33
34 34 This is the base interface for representing a connection to a repository.
35 35 It holds basic properties and methods applicable to all peer types.
36 36
37 37 This is not a complete interface definition and should not be used
38 38 outside of this module.
39 39 """
40 40 ui = interfaceutil.Attribute("""ui.ui instance""")
41 41
42 42 def url():
43 43 """Returns a URL string representing this peer.
44 44
45 45 Currently, implementations expose the raw URL used to construct the
46 46 instance. It may contain credentials as part of the URL. The
47 47 expectations of the value aren't well-defined and this could lead to
48 48 data leakage.
49 49
50 50 TODO audit/clean consumers and more clearly define the contents of this
51 51 value.
52 52 """
53 53
54 54 def local():
55 55 """Returns a local repository instance.
56 56
57 57 If the peer represents a local repository, returns an object that
58 58 can be used to interface with it. Otherwise returns ``None``.
59 59 """
60 60
61 61 def peer():
62 62 """Returns an object conforming to this interface.
63 63
64 64 Most implementations will ``return self``.
65 65 """
66 66
67 67 def canpush():
68 68 """Returns a boolean indicating if this peer can be pushed to."""
69 69
70 70 def close():
71 71 """Close the connection to this peer.
72 72
73 73 This is called when the peer will no longer be used. Resources
74 74 associated with the peer should be cleaned up.
75 75 """
76 76
77 77 class ipeercapabilities(interfaceutil.Interface):
78 78 """Peer sub-interface related to capabilities."""
79 79
80 80 def capable(name):
81 81 """Determine support for a named capability.
82 82
83 83 Returns ``False`` if capability not supported.
84 84
85 85 Returns ``True`` if boolean capability is supported. Returns a string
86 86 if capability support is non-boolean.
87 87
88 88 Capability strings may or may not map to wire protocol capabilities.
89 89 """
90 90
91 91 def requirecap(name, purpose):
92 92 """Require a capability to be present.
93 93
94 94 Raises a ``CapabilityError`` if the capability isn't present.
95 95 """
96 96
97 97 class ipeercommands(interfaceutil.Interface):
98 98 """Client-side interface for communicating over the wire protocol.
99 99
100 100 This interface is used as a gateway to the Mercurial wire protocol.
101 101 methods commonly call wire protocol commands of the same name.
102 102 """
103 103
104 104 def branchmap():
105 105 """Obtain heads in named branches.
106 106
107 107 Returns a dict mapping branch name to an iterable of nodes that are
108 108 heads on that branch.
109 109 """
110 110
111 111 def capabilities():
112 112 """Obtain capabilities of the peer.
113 113
114 114 Returns a set of string capabilities.
115 115 """
116 116
117 117 def clonebundles():
118 118 """Obtains the clone bundles manifest for the repo.
119 119
120 120 Returns the manifest as unparsed bytes.
121 121 """
122 122
123 123 def debugwireargs(one, two, three=None, four=None, five=None):
124 124 """Used to facilitate debugging of arguments passed over the wire."""
125 125
126 126 def getbundle(source, **kwargs):
127 127 """Obtain remote repository data as a bundle.
128 128
129 129 This command is how the bulk of repository data is transferred from
130 130 the peer to the local repository
131 131
132 132 Returns a generator of bundle data.
133 133 """
134 134
135 135 def heads():
136 136 """Determine all known head revisions in the peer.
137 137
138 138 Returns an iterable of binary nodes.
139 139 """
140 140
141 141 def known(nodes):
142 142 """Determine whether multiple nodes are known.
143 143
144 144 Accepts an iterable of nodes whose presence to check for.
145 145
146 146 Returns an iterable of booleans indicating of the corresponding node
147 147 at that index is known to the peer.
148 148 """
149 149
150 150 def listkeys(namespace):
151 151 """Obtain all keys in a pushkey namespace.
152 152
153 153 Returns an iterable of key names.
154 154 """
155 155
156 156 def lookup(key):
157 157 """Resolve a value to a known revision.
158 158
159 159 Returns a binary node of the resolved revision on success.
160 160 """
161 161
162 162 def pushkey(namespace, key, old, new):
163 163 """Set a value using the ``pushkey`` protocol.
164 164
165 165 Arguments correspond to the pushkey namespace and key to operate on and
166 166 the old and new values for that key.
167 167
168 168 Returns a string with the peer result. The value inside varies by the
169 169 namespace.
170 170 """
171 171
172 172 def stream_out():
173 173 """Obtain streaming clone data.
174 174
175 175 Successful result should be a generator of data chunks.
176 176 """
177 177
178 178 def unbundle(bundle, heads, url):
179 179 """Transfer repository data to the peer.
180 180
181 181 This is how the bulk of data during a push is transferred.
182 182
183 183 Returns the integer number of heads added to the peer.
184 184 """
185 185
186 186 class ipeerlegacycommands(interfaceutil.Interface):
187 187 """Interface for implementing support for legacy wire protocol commands.
188 188
189 189 Wire protocol commands transition to legacy status when they are no longer
190 190 used by modern clients. To facilitate identifying which commands are
191 191 legacy, the interfaces are split.
192 192 """
193 193
194 194 def between(pairs):
195 195 """Obtain nodes between pairs of nodes.
196 196
197 197 ``pairs`` is an iterable of node pairs.
198 198
199 199 Returns an iterable of iterables of nodes corresponding to each
200 200 requested pair.
201 201 """
202 202
203 203 def branches(nodes):
204 204 """Obtain ancestor changesets of specific nodes back to a branch point.
205 205
206 206 For each requested node, the peer finds the first ancestor node that is
207 207 a DAG root or is a merge.
208 208
209 209 Returns an iterable of iterables with the resolved values for each node.
210 210 """
211 211
212 212 def changegroup(nodes, source):
213 213 """Obtain a changegroup with data for descendants of specified nodes."""
214 214
215 215 def changegroupsubset(bases, heads, source):
216 216 pass
217 217
218 218 class ipeercommandexecutor(interfaceutil.Interface):
219 219 """Represents a mechanism to execute remote commands.
220 220
221 221 This is the primary interface for requesting that wire protocol commands
222 222 be executed. Instances of this interface are active in a context manager
223 223 and have a well-defined lifetime. When the context manager exits, all
224 224 outstanding requests are waited on.
225 225 """
226 226
227 227 def callcommand(name, args):
228 228 """Request that a named command be executed.
229 229
230 230 Receives the command name and a dictionary of command arguments.
231 231
232 232 Returns a ``concurrent.futures.Future`` that will resolve to the
233 233 result of that command request. That exact value is left up to
234 234 the implementation and possibly varies by command.
235 235
236 236 Not all commands can coexist with other commands in an executor
237 237 instance: it depends on the underlying wire protocol transport being
238 238 used and the command itself.
239 239
240 240 Implementations MAY call ``sendcommands()`` automatically if the
241 241 requested command can not coexist with other commands in this executor.
242 242
243 243 Implementations MAY call ``sendcommands()`` automatically when the
244 244 future's ``result()`` is called. So, consumers using multiple
245 245 commands with an executor MUST ensure that ``result()`` is not called
246 246 until all command requests have been issued.
247 247 """
248 248
249 249 def sendcommands():
250 250 """Trigger submission of queued command requests.
251 251
252 252 Not all transports submit commands as soon as they are requested to
253 253 run. When called, this method forces queued command requests to be
254 254 issued. It will no-op if all commands have already been sent.
255 255
256 256 When called, no more new commands may be issued with this executor.
257 257 """
258 258
259 259 def close():
260 260 """Signal that this command request is finished.
261 261
262 262 When called, no more new commands may be issued. All outstanding
263 263 commands that have previously been issued are waited on before
264 264 returning. This not only includes waiting for the futures to resolve,
265 265 but also waiting for all response data to arrive. In other words,
266 266 calling this waits for all on-wire state for issued command requests
267 267 to finish.
268 268
269 269 When used as a context manager, this method is called when exiting the
270 270 context manager.
271 271
272 272 This method may call ``sendcommands()`` if there are buffered commands.
273 273 """
274 274
275 275 class ipeerrequests(interfaceutil.Interface):
276 276 """Interface for executing commands on a peer."""
277 277
278 278 def commandexecutor():
279 279 """A context manager that resolves to an ipeercommandexecutor.
280 280
281 281 The object this resolves to can be used to issue command requests
282 282 to the peer.
283 283
284 284 Callers should call its ``callcommand`` method to issue command
285 285 requests.
286 286
287 287 A new executor should be obtained for each distinct set of commands
288 288 (possibly just a single command) that the consumer wants to execute
289 289 as part of a single operation or round trip. This is because some
290 290 peers are half-duplex and/or don't support persistent connections.
291 291 e.g. in the case of HTTP peers, commands sent to an executor represent
292 292 a single HTTP request. While some peers may support multiple command
293 293 sends over the wire per executor, consumers need to code to the least
294 294 capable peer. So it should be assumed that command executors buffer
295 295 called commands until they are told to send them and that each
296 296 command executor could result in a new connection or wire-level request
297 297 being issued.
298 298 """
299 299
300 300 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
301 301 """Unified interface for peer repositories.
302 302
303 303 All peer instances must conform to this interface.
304 304 """
305 305
306 306 @interfaceutil.implementer(ipeerbase)
307 307 class peer(object):
308 308 """Base class for peer repositories."""
309 309
310 310 def capable(self, name):
311 311 caps = self.capabilities()
312 312 if name in caps:
313 313 return True
314 314
315 315 name = '%s=' % name
316 316 for cap in caps:
317 317 if cap.startswith(name):
318 318 return cap[len(name):]
319 319
320 320 return False
321 321
322 322 def requirecap(self, name, purpose):
323 323 if self.capable(name):
324 324 return
325 325
326 326 raise error.CapabilityError(
327 327 _('cannot %s; remote repository does not support the %r '
328 328 'capability') % (purpose, name))
329 329
330 330 class iverifyproblem(interfaceutil.Interface):
331 331 """Represents a problem with the integrity of the repository.
332 332
333 333 Instances of this interface are emitted to describe an integrity issue
334 334 with a repository (e.g. corrupt storage, missing data, etc).
335 335
336 336 Instances are essentially messages associated with severity.
337 337 """
338 338 warning = interfaceutil.Attribute(
339 339 """Message indicating a non-fatal problem.""")
340 340
341 341 error = interfaceutil.Attribute(
342 342 """Message indicating a fatal problem.""")
343 343
344 344 class irevisiondelta(interfaceutil.Interface):
345 345 """Represents a delta between one revision and another.
346 346
347 347 Instances convey enough information to allow a revision to be exchanged
348 348 with another repository.
349 349
350 350 Instances represent the fulltext revision data or a delta against
351 351 another revision. Therefore the ``revision`` and ``delta`` attributes
352 352 are mutually exclusive.
353 353
354 354 Typically used for changegroup generation.
355 355 """
356 356
357 357 node = interfaceutil.Attribute(
358 358 """20 byte node of this revision.""")
359 359
360 360 p1node = interfaceutil.Attribute(
361 361 """20 byte node of 1st parent of this revision.""")
362 362
363 363 p2node = interfaceutil.Attribute(
364 364 """20 byte node of 2nd parent of this revision.""")
365 365
366 366 linknode = interfaceutil.Attribute(
367 367 """20 byte node of the changelog revision this node is linked to.""")
368 368
369 369 flags = interfaceutil.Attribute(
370 370 """2 bytes of integer flags that apply to this revision.""")
371 371
372 372 basenode = interfaceutil.Attribute(
373 373 """20 byte node of the revision this data is a delta against.
374 374
375 375 ``nullid`` indicates that the revision is a full revision and not
376 376 a delta.
377 377 """)
378 378
379 379 baserevisionsize = interfaceutil.Attribute(
380 380 """Size of base revision this delta is against.
381 381
382 382 May be ``None`` if ``basenode`` is ``nullid``.
383 383 """)
384 384
385 385 revision = interfaceutil.Attribute(
386 386 """Raw fulltext of revision data for this node.""")
387 387
388 388 delta = interfaceutil.Attribute(
389 389 """Delta between ``basenode`` and ``node``.
390 390
391 391 Stored in the bdiff delta format.
392 392 """)
393 393
394 class irevisiondeltarequest(interfaceutil.Interface):
395 """Represents a request to generate an ``irevisiondelta``."""
396
397 node = interfaceutil.Attribute(
398 """20 byte node of revision being requested.""")
399
400 p1node = interfaceutil.Attribute(
401 """20 byte node of 1st parent of revision.""")
402
403 p2node = interfaceutil.Attribute(
404 """20 byte node of 2nd parent of revision.""")
405
406 linknode = interfaceutil.Attribute(
407 """20 byte node to store in ``linknode`` attribute.""")
408
409 basenode = interfaceutil.Attribute(
410 """Base revision that delta should be generated against.
411
412 If ``nullid``, the derived ``irevisiondelta`` should have its
413 ``revision`` field populated and no delta should be generated.
414
415 If ``None``, the delta may be generated against any revision that
416 is an ancestor of this revision. Or a full revision may be used.
417
418 If any other value, the delta should be produced against that
419 revision.
420 """)
421
422 ellipsis = interfaceutil.Attribute(
423 """Boolean on whether the ellipsis flag should be set.""")
424
425 394 class ifilerevisionssequence(interfaceutil.Interface):
426 395 """Contains index data for all revisions of a file.
427 396
428 397 Types implementing this behave like lists of tuples. The index
429 398 in the list corresponds to the revision number. The values contain
430 399 index metadata.
431 400
432 401 The *null* revision (revision number -1) is always the last item
433 402 in the index.
434 403 """
435 404
436 405 def __len__():
437 406 """The total number of revisions."""
438 407
439 408 def __getitem__(rev):
440 409 """Returns the object having a specific revision number.
441 410
442 411 Returns an 8-tuple with the following fields:
443 412
444 413 offset+flags
445 414 Contains the offset and flags for the revision. 64-bit unsigned
446 415 integer where first 6 bytes are the offset and the next 2 bytes
447 416 are flags. The offset can be 0 if it is not used by the store.
448 417 compressed size
449 418 Size of the revision data in the store. It can be 0 if it isn't
450 419 needed by the store.
451 420 uncompressed size
452 421 Fulltext size. It can be 0 if it isn't needed by the store.
453 422 base revision
454 423 Revision number of revision the delta for storage is encoded
455 424 against. -1 indicates not encoded against a base revision.
456 425 link revision
457 426 Revision number of changelog revision this entry is related to.
458 427 p1 revision
459 428 Revision number of 1st parent. -1 if no 1st parent.
460 429 p2 revision
461 430 Revision number of 2nd parent. -1 if no 1st parent.
462 431 node
463 432 Binary node value for this revision number.
464 433
465 434 Negative values should index off the end of the sequence. ``-1``
466 435 should return the null revision. ``-2`` should return the most
467 436 recent revision.
468 437 """
469 438
470 439 def __contains__(rev):
471 440 """Whether a revision number exists."""
472 441
473 442 def insert(self, i, entry):
474 443 """Add an item to the index at specific revision."""
475 444
476 445 class ifileindex(interfaceutil.Interface):
477 446 """Storage interface for index data of a single file.
478 447
479 448 File storage data is divided into index metadata and data storage.
480 449 This interface defines the index portion of the interface.
481 450
482 451 The index logically consists of:
483 452
484 453 * A mapping between revision numbers and nodes.
485 454 * DAG data (storing and querying the relationship between nodes).
486 455 * Metadata to facilitate storage.
487 456 """
488 457 def __len__():
489 458 """Obtain the number of revisions stored for this file."""
490 459
491 460 def __iter__():
492 461 """Iterate over revision numbers for this file."""
493 462
494 463 def revs(start=0, stop=None):
495 464 """Iterate over revision numbers for this file, with control."""
496 465
497 466 def parents(node):
498 467 """Returns a 2-tuple of parent nodes for a revision.
499 468
500 469 Values will be ``nullid`` if the parent is empty.
501 470 """
502 471
503 472 def parentrevs(rev):
504 473 """Like parents() but operates on revision numbers."""
505 474
506 475 def rev(node):
507 476 """Obtain the revision number given a node.
508 477
509 478 Raises ``error.LookupError`` if the node is not known.
510 479 """
511 480
512 481 def node(rev):
513 482 """Obtain the node value given a revision number.
514 483
515 484 Raises ``IndexError`` if the node is not known.
516 485 """
517 486
518 487 def lookup(node):
519 488 """Attempt to resolve a value to a node.
520 489
521 490 Value can be a binary node, hex node, revision number, or a string
522 491 that can be converted to an integer.
523 492
524 493 Raises ``error.LookupError`` if a node could not be resolved.
525 494 """
526 495
527 496 def linkrev(rev):
528 497 """Obtain the changeset revision number a revision is linked to."""
529 498
530 499 def flags(rev):
531 500 """Obtain flags used to affect storage of a revision."""
532 501
533 502 def iscensored(rev):
534 503 """Return whether a revision's content has been censored."""
535 504
536 505 def commonancestorsheads(node1, node2):
537 506 """Obtain an iterable of nodes containing heads of common ancestors.
538 507
539 508 See ``ancestor.commonancestorsheads()``.
540 509 """
541 510
542 511 def descendants(revs):
543 512 """Obtain descendant revision numbers for a set of revision numbers.
544 513
545 514 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
546 515 """
547 516
548 517 def heads(start=None, stop=None):
549 518 """Obtain a list of nodes that are DAG heads, with control.
550 519
551 520 The set of revisions examined can be limited by specifying
552 521 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
553 522 iterable of nodes. DAG traversal starts at earlier revision
554 523 ``start`` and iterates forward until any node in ``stop`` is
555 524 encountered.
556 525 """
557 526
558 527 def children(node):
559 528 """Obtain nodes that are children of a node.
560 529
561 530 Returns a list of nodes.
562 531 """
563 532
564 533 def deltaparent(rev):
565 534 """"Return the revision that is a suitable parent to delta against."""
566 535
567 536 class ifiledata(interfaceutil.Interface):
568 537 """Storage interface for data storage of a specific file.
569 538
570 539 This complements ``ifileindex`` and provides an interface for accessing
571 540 data for a tracked file.
572 541 """
573 542 def rawsize(rev):
574 543 """The size of the fulltext data for a revision as stored."""
575 544
576 545 def size(rev):
577 546 """Obtain the fulltext size of file data.
578 547
579 548 Any metadata is excluded from size measurements. Use ``rawsize()`` if
580 549 metadata size is important.
581 550 """
582 551
583 552 def checkhash(fulltext, node, p1=None, p2=None, rev=None):
584 553 """Validate the stored hash of a given fulltext and node.
585 554
586 555 Raises ``error.StorageError`` is hash validation fails.
587 556 """
588 557
589 558 def revision(node, raw=False):
590 559 """"Obtain fulltext data for a node.
591 560
592 561 By default, any storage transformations are applied before the data
593 562 is returned. If ``raw`` is True, non-raw storage transformations
594 563 are not applied.
595 564
596 565 The fulltext data may contain a header containing metadata. Most
597 566 consumers should use ``read()`` to obtain the actual file data.
598 567 """
599 568
600 569 def read(node):
601 570 """Resolve file fulltext data.
602 571
603 572 This is similar to ``revision()`` except any metadata in the data
604 573 headers is stripped.
605 574 """
606 575
607 576 def renamed(node):
608 577 """Obtain copy metadata for a node.
609 578
610 579 Returns ``False`` if no copy metadata is stored or a 2-tuple of
611 580 (path, node) from which this revision was copied.
612 581 """
613 582
614 583 def cmp(node, fulltext):
615 584 """Compare fulltext to another revision.
616 585
617 586 Returns True if the fulltext is different from what is stored.
618 587
619 588 This takes copy metadata into account.
620 589
621 590 TODO better document the copy metadata and censoring logic.
622 591 """
623 592
624 593 def revdiff(rev1, rev2):
625 594 """Obtain a delta between two revision numbers.
626 595
627 596 Operates on raw data in the store (``revision(node, raw=True)``).
628 597
629 598 The returned data is the result of ``bdiff.bdiff`` on the raw
630 599 revision data.
631 600 """
632 601
633 def emitrevisiondeltas(requests):
634 """Produce ``irevisiondelta`` from ``irevisiondeltarequest``s.
635
636 Given an iterable of objects conforming to the ``irevisiondeltarequest``
637 interface, emits objects conforming to the ``irevisiondelta``
638 interface.
639
640 This method is a generator.
641
642 ``irevisiondelta`` should be emitted in the same order of
643 ``irevisiondeltarequest`` that was passed in.
644
645 The emitted objects MUST conform by the results of
646 ``irevisiondeltarequest``. Namely, they must respect any requests
647 for building a delta from a specific ``basenode`` if defined.
648
649 When sending deltas, implementations must take into account whether
650 the client has the base delta before encoding a delta against that
651 revision. A revision encountered previously in ``requests`` is
652 always a suitable base revision. An example of a bad delta is a delta
653 against a non-ancestor revision. Another example of a bad delta is a
654 delta against a censored revision.
655 """
656
657 602 def emitrevisions(nodes,
658 603 nodesorder=None,
659 604 revisiondata=False,
660 605 assumehaveparentrevisions=False,
661 606 deltaprevious=False):
662 607 """Produce ``irevisiondelta`` for revisions.
663 608
664 609 Given an iterable of nodes, emits objects conforming to the
665 610 ``irevisiondelta`` interface that describe revisions in storage.
666 611
667 612 This method is a generator.
668 613
669 614 The input nodes may be unordered. Implementations must ensure that a
670 615 node's parents are emitted before the node itself. Transitively, this
671 616 means that a node may only be emitted once all its ancestors in
672 617 ``nodes`` have also been emitted.
673 618
674 619 By default, emits "index" data (the ``node``, ``p1node``, and
675 620 ``p2node`` attributes). If ``revisiondata`` is set, revision data
676 621 will also be present on the emitted objects.
677 622
678 623 With default argument values, implementations can choose to emit
679 624 either fulltext revision data or a delta. When emitting deltas,
680 625 implementations must consider whether the delta's base revision
681 626 fulltext is available to the receiver.
682 627
683 628 The base revision fulltext is guaranteed to be available if any of
684 629 the following are met:
685 630
686 631 * Its fulltext revision was emitted by this method call.
687 632 * A delta for that revision was emitted by this method call.
688 633 * ``assumehaveparentrevisions`` is True and the base revision is a
689 634 parent of the node.
690 635
691 636 ``nodesorder`` can be used to control the order that revisions are
692 637 emitted. By default, revisions can be reordered as long as they are
693 638 in DAG topological order (see above). If the value is ``nodes``,
694 639 the iteration order from ``nodes`` should be used. If the value is
695 640 ``storage``, then the native order from the backing storage layer
696 641 is used. (Not all storage layers will have strong ordering and behavior
697 642 of this mode is storage-dependent.) ``nodes`` ordering can force
698 643 revisions to be emitted before their ancestors, so consumers should
699 644 use it with care.
700 645
701 646 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
702 647 be set and it is the caller's responsibility to resolve it, if needed.
703 648
704 649 If ``deltaprevious`` is True and revision data is requested, all
705 650 revision data should be emitted as deltas against the revision
706 651 emitted just prior. The initial revision should be a delta against
707 652 its 1st parent.
708 653 """
709 654
710 655 class ifilemutation(interfaceutil.Interface):
711 656 """Storage interface for mutation events of a tracked file."""
712 657
713 658 def add(filedata, meta, transaction, linkrev, p1, p2):
714 659 """Add a new revision to the store.
715 660
716 661 Takes file data, dictionary of metadata, a transaction, linkrev,
717 662 and parent nodes.
718 663
719 664 Returns the node that was added.
720 665
721 666 May no-op if a revision matching the supplied data is already stored.
722 667 """
723 668
724 669 def addrevision(revisiondata, transaction, linkrev, p1, p2, node=None,
725 670 flags=0, cachedelta=None):
726 671 """Add a new revision to the store.
727 672
728 673 This is similar to ``add()`` except it operates at a lower level.
729 674
730 675 The data passed in already contains a metadata header, if any.
731 676
732 677 ``node`` and ``flags`` can be used to define the expected node and
733 678 the flags to use with storage.
734 679
735 680 ``add()`` is usually called when adding files from e.g. the working
736 681 directory. ``addrevision()`` is often called by ``add()`` and for
737 682 scenarios where revision data has already been computed, such as when
738 683 applying raw data from a peer repo.
739 684 """
740 685
741 686 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
742 687 """Process a series of deltas for storage.
743 688
744 689 ``deltas`` is an iterable of 7-tuples of
745 690 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
746 691 to add.
747 692
748 693 The ``delta`` field contains ``mpatch`` data to apply to a base
749 694 revision, identified by ``deltabase``. The base node can be
750 695 ``nullid``, in which case the header from the delta can be ignored
751 696 and the delta used as the fulltext.
752 697
753 698 ``addrevisioncb`` should be called for each node as it is committed.
754 699
755 700 Returns a list of nodes that were processed. A node will be in the list
756 701 even if it existed in the store previously.
757 702 """
758 703
759 704 def censorrevision(tr, node, tombstone=b''):
760 705 """Remove the content of a single revision.
761 706
762 707 The specified ``node`` will have its content purged from storage.
763 708 Future attempts to access the revision data for this node will
764 709 result in failure.
765 710
766 711 A ``tombstone`` message can optionally be stored. This message may be
767 712 displayed to users when they attempt to access the missing revision
768 713 data.
769 714
770 715 Storage backends may have stored deltas against the previous content
771 716 in this revision. As part of censoring a revision, these storage
772 717 backends are expected to rewrite any internally stored deltas such
773 718 that they no longer reference the deleted content.
774 719 """
775 720
776 721 def getstrippoint(minlink):
777 722 """Find the minimum revision that must be stripped to strip a linkrev.
778 723
779 724 Returns a 2-tuple containing the minimum revision number and a set
780 725 of all revisions numbers that would be broken by this strip.
781 726
782 727 TODO this is highly revlog centric and should be abstracted into
783 728 a higher-level deletion API. ``repair.strip()`` relies on this.
784 729 """
785 730
786 731 def strip(minlink, transaction):
787 732 """Remove storage of items starting at a linkrev.
788 733
789 734 This uses ``getstrippoint()`` to determine the first node to remove.
790 735 Then it effectively truncates storage for all revisions after that.
791 736
792 737 TODO this is highly revlog centric and should be abstracted into a
793 738 higher-level deletion API.
794 739 """
795 740
796 741 class ifilestorage(ifileindex, ifiledata, ifilemutation):
797 742 """Complete storage interface for a single tracked file."""
798 743
799 744 _generaldelta = interfaceutil.Attribute(
800 745 """Whether deltas can be against any parent revision.
801 746
802 747 TODO this is used by changegroup code and it could probably be
803 748 folded into another API.
804 749 """)
805 750
806 751 def files():
807 752 """Obtain paths that are backing storage for this file.
808 753
809 754 TODO this is used heavily by verify code and there should probably
810 755 be a better API for that.
811 756 """
812 757
813 758 def verifyintegrity(state):
814 759 """Verifies the integrity of file storage.
815 760
816 761 ``state`` is a dict holding state of the verifier process. It can be
817 762 used to communicate data between invocations of multiple storage
818 763 primitives.
819 764
820 765 The method yields objects conforming to the ``iverifyproblem``
821 766 interface.
822 767 """
823 768
824 769 class idirs(interfaceutil.Interface):
825 770 """Interface representing a collection of directories from paths.
826 771
827 772 This interface is essentially a derived data structure representing
828 773 directories from a collection of paths.
829 774 """
830 775
831 776 def addpath(path):
832 777 """Add a path to the collection.
833 778
834 779 All directories in the path will be added to the collection.
835 780 """
836 781
837 782 def delpath(path):
838 783 """Remove a path from the collection.
839 784
840 785 If the removal was the last path in a particular directory, the
841 786 directory is removed from the collection.
842 787 """
843 788
844 789 def __iter__():
845 790 """Iterate over the directories in this collection of paths."""
846 791
847 792 def __contains__(path):
848 793 """Whether a specific directory is in this collection."""
849 794
850 795 class imanifestdict(interfaceutil.Interface):
851 796 """Interface representing a manifest data structure.
852 797
853 798 A manifest is effectively a dict mapping paths to entries. Each entry
854 799 consists of a binary node and extra flags affecting that entry.
855 800 """
856 801
857 802 def __getitem__(path):
858 803 """Returns the binary node value for a path in the manifest.
859 804
860 805 Raises ``KeyError`` if the path does not exist in the manifest.
861 806
862 807 Equivalent to ``self.find(path)[0]``.
863 808 """
864 809
865 810 def find(path):
866 811 """Returns the entry for a path in the manifest.
867 812
868 813 Returns a 2-tuple of (node, flags).
869 814
870 815 Raises ``KeyError`` if the path does not exist in the manifest.
871 816 """
872 817
873 818 def __len__():
874 819 """Return the number of entries in the manifest."""
875 820
876 821 def __nonzero__():
877 822 """Returns True if the manifest has entries, False otherwise."""
878 823
879 824 __bool__ = __nonzero__
880 825
881 826 def __setitem__(path, node):
882 827 """Define the node value for a path in the manifest.
883 828
884 829 If the path is already in the manifest, its flags will be copied to
885 830 the new entry.
886 831 """
887 832
888 833 def __contains__(path):
889 834 """Whether a path exists in the manifest."""
890 835
891 836 def __delitem__(path):
892 837 """Remove a path from the manifest.
893 838
894 839 Raises ``KeyError`` if the path is not in the manifest.
895 840 """
896 841
897 842 def __iter__():
898 843 """Iterate over paths in the manifest."""
899 844
900 845 def iterkeys():
901 846 """Iterate over paths in the manifest."""
902 847
903 848 def keys():
904 849 """Obtain a list of paths in the manifest."""
905 850
906 851 def filesnotin(other, match=None):
907 852 """Obtain the set of paths in this manifest but not in another.
908 853
909 854 ``match`` is an optional matcher function to be applied to both
910 855 manifests.
911 856
912 857 Returns a set of paths.
913 858 """
914 859
915 860 def dirs():
916 861 """Returns an object implementing the ``idirs`` interface."""
917 862
918 863 def hasdir(dir):
919 864 """Returns a bool indicating if a directory is in this manifest."""
920 865
921 866 def matches(match):
922 867 """Generate a new manifest filtered through a matcher.
923 868
924 869 Returns an object conforming to the ``imanifestdict`` interface.
925 870 """
926 871
927 872 def walk(match):
928 873 """Generator of paths in manifest satisfying a matcher.
929 874
930 875 This is equivalent to ``self.matches(match).iterkeys()`` except a new
931 876 manifest object is not created.
932 877
933 878 If the matcher has explicit files listed and they don't exist in
934 879 the manifest, ``match.bad()`` is called for each missing file.
935 880 """
936 881
937 882 def diff(other, match=None, clean=False):
938 883 """Find differences between this manifest and another.
939 884
940 885 This manifest is compared to ``other``.
941 886
942 887 If ``match`` is provided, the two manifests are filtered against this
943 888 matcher and only entries satisfying the matcher are compared.
944 889
945 890 If ``clean`` is True, unchanged files are included in the returned
946 891 object.
947 892
948 893 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
949 894 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
950 895 represents the node and flags for this manifest and ``(node2, flag2)``
951 896 are the same for the other manifest.
952 897 """
953 898
954 899 def setflag(path, flag):
955 900 """Set the flag value for a given path.
956 901
957 902 Raises ``KeyError`` if the path is not already in the manifest.
958 903 """
959 904
960 905 def get(path, default=None):
961 906 """Obtain the node value for a path or a default value if missing."""
962 907
963 908 def flags(path, default=''):
964 909 """Return the flags value for a path or a default value if missing."""
965 910
966 911 def copy():
967 912 """Return a copy of this manifest."""
968 913
969 914 def items():
970 915 """Returns an iterable of (path, node) for items in this manifest."""
971 916
972 917 def iteritems():
973 918 """Identical to items()."""
974 919
975 920 def iterentries():
976 921 """Returns an iterable of (path, node, flags) for this manifest.
977 922
978 923 Similar to ``iteritems()`` except items are a 3-tuple and include
979 924 flags.
980 925 """
981 926
982 927 def text():
983 928 """Obtain the raw data representation for this manifest.
984 929
985 930 Result is used to create a manifest revision.
986 931 """
987 932
988 933 def fastdelta(base, changes):
989 934 """Obtain a delta between this manifest and another given changes.
990 935
991 936 ``base`` in the raw data representation for another manifest.
992 937
993 938 ``changes`` is an iterable of ``(path, to_delete)``.
994 939
995 940 Returns a 2-tuple containing ``bytearray(self.text())`` and the
996 941 delta between ``base`` and this manifest.
997 942 """
998 943
999 944 class imanifestrevisionbase(interfaceutil.Interface):
1000 945 """Base interface representing a single revision of a manifest.
1001 946
1002 947 Should not be used as a primary interface: should always be inherited
1003 948 as part of a larger interface.
1004 949 """
1005 950
1006 951 def new():
1007 952 """Obtain a new manifest instance.
1008 953
1009 954 Returns an object conforming to the ``imanifestrevisionwritable``
1010 955 interface. The instance will be associated with the same
1011 956 ``imanifestlog`` collection as this instance.
1012 957 """
1013 958
1014 959 def copy():
1015 960 """Obtain a copy of this manifest instance.
1016 961
1017 962 Returns an object conforming to the ``imanifestrevisionwritable``
1018 963 interface. The instance will be associated with the same
1019 964 ``imanifestlog`` collection as this instance.
1020 965 """
1021 966
1022 967 def read():
1023 968 """Obtain the parsed manifest data structure.
1024 969
1025 970 The returned object conforms to the ``imanifestdict`` interface.
1026 971 """
1027 972
1028 973 class imanifestrevisionstored(imanifestrevisionbase):
1029 974 """Interface representing a manifest revision committed to storage."""
1030 975
1031 976 def node():
1032 977 """The binary node for this manifest."""
1033 978
1034 979 parents = interfaceutil.Attribute(
1035 980 """List of binary nodes that are parents for this manifest revision."""
1036 981 )
1037 982
1038 983 def readdelta(shallow=False):
1039 984 """Obtain the manifest data structure representing changes from parent.
1040 985
1041 986 This manifest is compared to its 1st parent. A new manifest representing
1042 987 those differences is constructed.
1043 988
1044 989 The returned object conforms to the ``imanifestdict`` interface.
1045 990 """
1046 991
1047 992 def readfast(shallow=False):
1048 993 """Calls either ``read()`` or ``readdelta()``.
1049 994
1050 995 The faster of the two options is called.
1051 996 """
1052 997
1053 998 def find(key):
1054 999 """Calls self.read().find(key)``.
1055 1000
1056 1001 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1057 1002 """
1058 1003
1059 1004 class imanifestrevisionwritable(imanifestrevisionbase):
1060 1005 """Interface representing a manifest revision that can be committed."""
1061 1006
1062 1007 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1063 1008 """Add this revision to storage.
1064 1009
1065 1010 Takes a transaction object, the changeset revision number it will
1066 1011 be associated with, its parent nodes, and lists of added and
1067 1012 removed paths.
1068 1013
1069 1014 If match is provided, storage can choose not to inspect or write out
1070 1015 items that do not match. Storage is still required to be able to provide
1071 1016 the full manifest in the future for any directories written (these
1072 1017 manifests should not be "narrowed on disk").
1073 1018
1074 1019 Returns the binary node of the created revision.
1075 1020 """
1076 1021
1077 1022 class imanifeststorage(interfaceutil.Interface):
1078 1023 """Storage interface for manifest data."""
1079 1024
1080 1025 tree = interfaceutil.Attribute(
1081 1026 """The path to the directory this manifest tracks.
1082 1027
1083 1028 The empty bytestring represents the root manifest.
1084 1029 """)
1085 1030
1086 1031 index = interfaceutil.Attribute(
1087 1032 """An ``ifilerevisionssequence`` instance.""")
1088 1033
1089 1034 indexfile = interfaceutil.Attribute(
1090 1035 """Path of revlog index file.
1091 1036
1092 1037 TODO this is revlog specific and should not be exposed.
1093 1038 """)
1094 1039
1095 1040 opener = interfaceutil.Attribute(
1096 1041 """VFS opener to use to access underlying files used for storage.
1097 1042
1098 1043 TODO this is revlog specific and should not be exposed.
1099 1044 """)
1100 1045
1101 1046 version = interfaceutil.Attribute(
1102 1047 """Revlog version number.
1103 1048
1104 1049 TODO this is revlog specific and should not be exposed.
1105 1050 """)
1106 1051
1107 1052 _generaldelta = interfaceutil.Attribute(
1108 1053 """Whether generaldelta storage is being used.
1109 1054
1110 1055 TODO this is revlog specific and should not be exposed.
1111 1056 """)
1112 1057
1113 1058 fulltextcache = interfaceutil.Attribute(
1114 1059 """Dict with cache of fulltexts.
1115 1060
1116 1061 TODO this doesn't feel appropriate for the storage interface.
1117 1062 """)
1118 1063
1119 1064 def __len__():
1120 1065 """Obtain the number of revisions stored for this manifest."""
1121 1066
1122 1067 def __iter__():
1123 1068 """Iterate over revision numbers for this manifest."""
1124 1069
1125 1070 def rev(node):
1126 1071 """Obtain the revision number given a binary node.
1127 1072
1128 1073 Raises ``error.LookupError`` if the node is not known.
1129 1074 """
1130 1075
1131 1076 def node(rev):
1132 1077 """Obtain the node value given a revision number.
1133 1078
1134 1079 Raises ``error.LookupError`` if the revision is not known.
1135 1080 """
1136 1081
1137 1082 def lookup(value):
1138 1083 """Attempt to resolve a value to a node.
1139 1084
1140 1085 Value can be a binary node, hex node, revision number, or a bytes
1141 1086 that can be converted to an integer.
1142 1087
1143 1088 Raises ``error.LookupError`` if a ndoe could not be resolved.
1144 1089
1145 1090 TODO this is only used by debug* commands and can probably be deleted
1146 1091 easily.
1147 1092 """
1148 1093
1149 1094 def parents(node):
1150 1095 """Returns a 2-tuple of parent nodes for a node.
1151 1096
1152 1097 Values will be ``nullid`` if the parent is empty.
1153 1098 """
1154 1099
1155 1100 def parentrevs(rev):
1156 1101 """Like parents() but operates on revision numbers."""
1157 1102
1158 1103 def linkrev(rev):
1159 1104 """Obtain the changeset revision number a revision is linked to."""
1160 1105
1161 1106 def revision(node, _df=None, raw=False):
1162 1107 """Obtain fulltext data for a node."""
1163 1108
1164 1109 def revdiff(rev1, rev2):
1165 1110 """Obtain a delta between two revision numbers.
1166 1111
1167 1112 The returned data is the result of ``bdiff.bdiff()`` on the raw
1168 1113 revision data.
1169 1114 """
1170 1115
1171 1116 def cmp(node, fulltext):
1172 1117 """Compare fulltext to another revision.
1173 1118
1174 1119 Returns True if the fulltext is different from what is stored.
1175 1120 """
1176 1121
1177 def emitrevisiondeltas(requests):
1178 """Produce ``irevisiondelta`` from ``irevisiondeltarequest``s.
1179
1180 See the documentation for ``ifiledata`` for more.
1181 """
1182
1183 1122 def emitrevisions(nodes,
1184 1123 nodesorder=None,
1185 1124 revisiondata=False,
1186 1125 assumehaveparentrevisions=False):
1187 1126 """Produce ``irevisiondelta`` describing revisions.
1188 1127
1189 1128 See the documentation for ``ifiledata`` for more.
1190 1129 """
1191 1130
1192 1131 def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
1193 1132 """Process a series of deltas for storage.
1194 1133
1195 1134 See the documentation in ``ifilemutation`` for more.
1196 1135 """
1197 1136
1198 1137 def rawsize(rev):
1199 1138 """Obtain the size of tracked data.
1200 1139
1201 1140 Is equivalent to ``len(m.revision(node, raw=True))``.
1202 1141
1203 1142 TODO this method is only used by upgrade code and may be removed.
1204 1143 """
1205 1144
1206 1145 def getstrippoint(minlink):
1207 1146 """Find minimum revision that must be stripped to strip a linkrev.
1208 1147
1209 1148 See the documentation in ``ifilemutation`` for more.
1210 1149 """
1211 1150
1212 1151 def strip(minlink, transaction):
1213 1152 """Remove storage of items starting at a linkrev.
1214 1153
1215 1154 See the documentation in ``ifilemutation`` for more.
1216 1155 """
1217 1156
1218 1157 def checksize():
1219 1158 """Obtain the expected sizes of backing files.
1220 1159
1221 1160 TODO this is used by verify and it should not be part of the interface.
1222 1161 """
1223 1162
1224 1163 def files():
1225 1164 """Obtain paths that are backing storage for this manifest.
1226 1165
1227 1166 TODO this is used by verify and there should probably be a better API
1228 1167 for this functionality.
1229 1168 """
1230 1169
1231 1170 def deltaparent(rev):
1232 1171 """Obtain the revision that a revision is delta'd against.
1233 1172
1234 1173 TODO delta encoding is an implementation detail of storage and should
1235 1174 not be exposed to the storage interface.
1236 1175 """
1237 1176
1238 1177 def clone(tr, dest, **kwargs):
1239 1178 """Clone this instance to another."""
1240 1179
1241 1180 def clearcaches(clear_persisted_data=False):
1242 1181 """Clear any caches associated with this instance."""
1243 1182
1244 1183 def dirlog(d):
1245 1184 """Obtain a manifest storage instance for a tree."""
1246 1185
1247 1186 def add(m, transaction, link, p1, p2, added, removed, readtree=None,
1248 1187 match=None):
1249 1188 """Add a revision to storage.
1250 1189
1251 1190 ``m`` is an object conforming to ``imanifestdict``.
1252 1191
1253 1192 ``link`` is the linkrev revision number.
1254 1193
1255 1194 ``p1`` and ``p2`` are the parent revision numbers.
1256 1195
1257 1196 ``added`` and ``removed`` are iterables of added and removed paths,
1258 1197 respectively.
1259 1198
1260 1199 ``readtree`` is a function that can be used to read the child tree(s)
1261 1200 when recursively writing the full tree structure when using
1262 1201 treemanifets.
1263 1202
1264 1203 ``match`` is a matcher that can be used to hint to storage that not all
1265 1204 paths must be inspected; this is an optimization and can be safely
1266 1205 ignored. Note that the storage must still be able to reproduce a full
1267 1206 manifest including files that did not match.
1268 1207 """
1269 1208
1270 1209 class imanifestlog(interfaceutil.Interface):
1271 1210 """Interface representing a collection of manifest snapshots.
1272 1211
1273 1212 Represents the root manifest in a repository.
1274 1213
1275 1214 Also serves as a means to access nested tree manifests and to cache
1276 1215 tree manifests.
1277 1216 """
1278 1217
1279 1218 def __getitem__(node):
1280 1219 """Obtain a manifest instance for a given binary node.
1281 1220
1282 1221 Equivalent to calling ``self.get('', node)``.
1283 1222
1284 1223 The returned object conforms to the ``imanifestrevisionstored``
1285 1224 interface.
1286 1225 """
1287 1226
1288 1227 def get(tree, node, verify=True):
1289 1228 """Retrieve the manifest instance for a given directory and binary node.
1290 1229
1291 1230 ``node`` always refers to the node of the root manifest (which will be
1292 1231 the only manifest if flat manifests are being used).
1293 1232
1294 1233 If ``tree`` is the empty string, the root manifest is returned.
1295 1234 Otherwise the manifest for the specified directory will be returned
1296 1235 (requires tree manifests).
1297 1236
1298 1237 If ``verify`` is True, ``LookupError`` is raised if the node is not
1299 1238 known.
1300 1239
1301 1240 The returned object conforms to the ``imanifestrevisionstored``
1302 1241 interface.
1303 1242 """
1304 1243
1305 1244 def getstorage(tree):
1306 1245 """Retrieve an interface to storage for a particular tree.
1307 1246
1308 1247 If ``tree`` is the empty bytestring, storage for the root manifest will
1309 1248 be returned. Otherwise storage for a tree manifest is returned.
1310 1249
1311 1250 TODO formalize interface for returned object.
1312 1251 """
1313 1252
1314 1253 def clearcaches():
1315 1254 """Clear caches associated with this collection."""
1316 1255
1317 1256 def rev(node):
1318 1257 """Obtain the revision number for a binary node.
1319 1258
1320 1259 Raises ``error.LookupError`` if the node is not known.
1321 1260 """
1322 1261
1323 1262 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1324 1263 """Local repository sub-interface providing access to tracked file storage.
1325 1264
1326 1265 This interface defines how a repository accesses storage for a single
1327 1266 tracked file path.
1328 1267 """
1329 1268
1330 1269 def file(f):
1331 1270 """Obtain a filelog for a tracked path.
1332 1271
1333 1272 The returned type conforms to the ``ifilestorage`` interface.
1334 1273 """
1335 1274
1336 1275 class ilocalrepositorymain(interfaceutil.Interface):
1337 1276 """Main interface for local repositories.
1338 1277
1339 1278 This currently captures the reality of things - not how things should be.
1340 1279 """
1341 1280
1342 1281 supportedformats = interfaceutil.Attribute(
1343 1282 """Set of requirements that apply to stream clone.
1344 1283
1345 1284 This is actually a class attribute and is shared among all instances.
1346 1285 """)
1347 1286
1348 1287 supported = interfaceutil.Attribute(
1349 1288 """Set of requirements that this repo is capable of opening.""")
1350 1289
1351 1290 requirements = interfaceutil.Attribute(
1352 1291 """Set of requirements this repo uses.""")
1353 1292
1354 1293 features = interfaceutil.Attribute(
1355 1294 """Set of "features" this repository supports.
1356 1295
1357 1296 A "feature" is a loosely-defined term. It can refer to a feature
1358 1297 in the classical sense or can describe an implementation detail
1359 1298 of the repository. For example, a ``readonly`` feature may denote
1360 1299 the repository as read-only. Or a ``revlogfilestore`` feature may
1361 1300 denote that the repository is using revlogs for file storage.
1362 1301
1363 1302 The intent of features is to provide a machine-queryable mechanism
1364 1303 for repo consumers to test for various repository characteristics.
1365 1304
1366 1305 Features are similar to ``requirements``. The main difference is that
1367 1306 requirements are stored on-disk and represent requirements to open the
1368 1307 repository. Features are more run-time capabilities of the repository
1369 1308 and more granular capabilities (which may be derived from requirements).
1370 1309 """)
1371 1310
1372 1311 filtername = interfaceutil.Attribute(
1373 1312 """Name of the repoview that is active on this repo.""")
1374 1313
1375 1314 wvfs = interfaceutil.Attribute(
1376 1315 """VFS used to access the working directory.""")
1377 1316
1378 1317 vfs = interfaceutil.Attribute(
1379 1318 """VFS rooted at the .hg directory.
1380 1319
1381 1320 Used to access repository data not in the store.
1382 1321 """)
1383 1322
1384 1323 svfs = interfaceutil.Attribute(
1385 1324 """VFS rooted at the store.
1386 1325
1387 1326 Used to access repository data in the store. Typically .hg/store.
1388 1327 But can point elsewhere if the store is shared.
1389 1328 """)
1390 1329
1391 1330 root = interfaceutil.Attribute(
1392 1331 """Path to the root of the working directory.""")
1393 1332
1394 1333 path = interfaceutil.Attribute(
1395 1334 """Path to the .hg directory.""")
1396 1335
1397 1336 origroot = interfaceutil.Attribute(
1398 1337 """The filesystem path that was used to construct the repo.""")
1399 1338
1400 1339 auditor = interfaceutil.Attribute(
1401 1340 """A pathauditor for the working directory.
1402 1341
1403 1342 This checks if a path refers to a nested repository.
1404 1343
1405 1344 Operates on the filesystem.
1406 1345 """)
1407 1346
1408 1347 nofsauditor = interfaceutil.Attribute(
1409 1348 """A pathauditor for the working directory.
1410 1349
1411 1350 This is like ``auditor`` except it doesn't do filesystem checks.
1412 1351 """)
1413 1352
1414 1353 baseui = interfaceutil.Attribute(
1415 1354 """Original ui instance passed into constructor.""")
1416 1355
1417 1356 ui = interfaceutil.Attribute(
1418 1357 """Main ui instance for this instance.""")
1419 1358
1420 1359 sharedpath = interfaceutil.Attribute(
1421 1360 """Path to the .hg directory of the repo this repo was shared from.""")
1422 1361
1423 1362 store = interfaceutil.Attribute(
1424 1363 """A store instance.""")
1425 1364
1426 1365 spath = interfaceutil.Attribute(
1427 1366 """Path to the store.""")
1428 1367
1429 1368 sjoin = interfaceutil.Attribute(
1430 1369 """Alias to self.store.join.""")
1431 1370
1432 1371 cachevfs = interfaceutil.Attribute(
1433 1372 """A VFS used to access the cache directory.
1434 1373
1435 1374 Typically .hg/cache.
1436 1375 """)
1437 1376
1438 1377 filteredrevcache = interfaceutil.Attribute(
1439 1378 """Holds sets of revisions to be filtered.""")
1440 1379
1441 1380 names = interfaceutil.Attribute(
1442 1381 """A ``namespaces`` instance.""")
1443 1382
1444 1383 def close():
1445 1384 """Close the handle on this repository."""
1446 1385
1447 1386 def peer():
1448 1387 """Obtain an object conforming to the ``peer`` interface."""
1449 1388
1450 1389 def unfiltered():
1451 1390 """Obtain an unfiltered/raw view of this repo."""
1452 1391
1453 1392 def filtered(name, visibilityexceptions=None):
1454 1393 """Obtain a named view of this repository."""
1455 1394
1456 1395 obsstore = interfaceutil.Attribute(
1457 1396 """A store of obsolescence data.""")
1458 1397
1459 1398 changelog = interfaceutil.Attribute(
1460 1399 """A handle on the changelog revlog.""")
1461 1400
1462 1401 manifestlog = interfaceutil.Attribute(
1463 1402 """An instance conforming to the ``imanifestlog`` interface.
1464 1403
1465 1404 Provides access to manifests for the repository.
1466 1405 """)
1467 1406
1468 1407 dirstate = interfaceutil.Attribute(
1469 1408 """Working directory state.""")
1470 1409
1471 1410 narrowpats = interfaceutil.Attribute(
1472 1411 """Matcher patterns for this repository's narrowspec.""")
1473 1412
1474 1413 def narrowmatch():
1475 1414 """Obtain a matcher for the narrowspec."""
1476 1415
1477 1416 def setnarrowpats(newincludes, newexcludes):
1478 1417 """Define the narrowspec for this repository."""
1479 1418
1480 1419 def __getitem__(changeid):
1481 1420 """Try to resolve a changectx."""
1482 1421
1483 1422 def __contains__(changeid):
1484 1423 """Whether a changeset exists."""
1485 1424
1486 1425 def __nonzero__():
1487 1426 """Always returns True."""
1488 1427 return True
1489 1428
1490 1429 __bool__ = __nonzero__
1491 1430
1492 1431 def __len__():
1493 1432 """Returns the number of changesets in the repo."""
1494 1433
1495 1434 def __iter__():
1496 1435 """Iterate over revisions in the changelog."""
1497 1436
1498 1437 def revs(expr, *args):
1499 1438 """Evaluate a revset.
1500 1439
1501 1440 Emits revisions.
1502 1441 """
1503 1442
1504 1443 def set(expr, *args):
1505 1444 """Evaluate a revset.
1506 1445
1507 1446 Emits changectx instances.
1508 1447 """
1509 1448
1510 1449 def anyrevs(specs, user=False, localalias=None):
1511 1450 """Find revisions matching one of the given revsets."""
1512 1451
1513 1452 def url():
1514 1453 """Returns a string representing the location of this repo."""
1515 1454
1516 1455 def hook(name, throw=False, **args):
1517 1456 """Call a hook."""
1518 1457
1519 1458 def tags():
1520 1459 """Return a mapping of tag to node."""
1521 1460
1522 1461 def tagtype(tagname):
1523 1462 """Return the type of a given tag."""
1524 1463
1525 1464 def tagslist():
1526 1465 """Return a list of tags ordered by revision."""
1527 1466
1528 1467 def nodetags(node):
1529 1468 """Return the tags associated with a node."""
1530 1469
1531 1470 def nodebookmarks(node):
1532 1471 """Return the list of bookmarks pointing to the specified node."""
1533 1472
1534 1473 def branchmap():
1535 1474 """Return a mapping of branch to heads in that branch."""
1536 1475
1537 1476 def revbranchcache():
1538 1477 pass
1539 1478
1540 1479 def branchtip(branchtip, ignoremissing=False):
1541 1480 """Return the tip node for a given branch."""
1542 1481
1543 1482 def lookup(key):
1544 1483 """Resolve the node for a revision."""
1545 1484
1546 1485 def lookupbranch(key):
1547 1486 """Look up the branch name of the given revision or branch name."""
1548 1487
1549 1488 def known(nodes):
1550 1489 """Determine whether a series of nodes is known.
1551 1490
1552 1491 Returns a list of bools.
1553 1492 """
1554 1493
1555 1494 def local():
1556 1495 """Whether the repository is local."""
1557 1496 return True
1558 1497
1559 1498 def publishing():
1560 1499 """Whether the repository is a publishing repository."""
1561 1500
1562 1501 def cancopy():
1563 1502 pass
1564 1503
1565 1504 def shared():
1566 1505 """The type of shared repository or None."""
1567 1506
1568 1507 def wjoin(f, *insidef):
1569 1508 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1570 1509
1571 1510 def setparents(p1, p2):
1572 1511 """Set the parent nodes of the working directory."""
1573 1512
1574 1513 def filectx(path, changeid=None, fileid=None):
1575 1514 """Obtain a filectx for the given file revision."""
1576 1515
1577 1516 def getcwd():
1578 1517 """Obtain the current working directory from the dirstate."""
1579 1518
1580 1519 def pathto(f, cwd=None):
1581 1520 """Obtain the relative path to a file."""
1582 1521
1583 1522 def adddatafilter(name, fltr):
1584 1523 pass
1585 1524
1586 1525 def wread(filename):
1587 1526 """Read a file from wvfs, using data filters."""
1588 1527
1589 1528 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1590 1529 """Write data to a file in the wvfs, using data filters."""
1591 1530
1592 1531 def wwritedata(filename, data):
1593 1532 """Resolve data for writing to the wvfs, using data filters."""
1594 1533
1595 1534 def currenttransaction():
1596 1535 """Obtain the current transaction instance or None."""
1597 1536
1598 1537 def transaction(desc, report=None):
1599 1538 """Open a new transaction to write to the repository."""
1600 1539
1601 1540 def undofiles():
1602 1541 """Returns a list of (vfs, path) for files to undo transactions."""
1603 1542
1604 1543 def recover():
1605 1544 """Roll back an interrupted transaction."""
1606 1545
1607 1546 def rollback(dryrun=False, force=False):
1608 1547 """Undo the last transaction.
1609 1548
1610 1549 DANGEROUS.
1611 1550 """
1612 1551
1613 1552 def updatecaches(tr=None, full=False):
1614 1553 """Warm repo caches."""
1615 1554
1616 1555 def invalidatecaches():
1617 1556 """Invalidate cached data due to the repository mutating."""
1618 1557
1619 1558 def invalidatevolatilesets():
1620 1559 pass
1621 1560
1622 1561 def invalidatedirstate():
1623 1562 """Invalidate the dirstate."""
1624 1563
1625 1564 def invalidate(clearfilecache=False):
1626 1565 pass
1627 1566
1628 1567 def invalidateall():
1629 1568 pass
1630 1569
1631 1570 def lock(wait=True):
1632 1571 """Lock the repository store and return a lock instance."""
1633 1572
1634 1573 def wlock(wait=True):
1635 1574 """Lock the non-store parts of the repository."""
1636 1575
1637 1576 def currentwlock():
1638 1577 """Return the wlock if it's held or None."""
1639 1578
1640 1579 def checkcommitpatterns(wctx, vdirs, match, status, fail):
1641 1580 pass
1642 1581
1643 1582 def commit(text='', user=None, date=None, match=None, force=False,
1644 1583 editor=False, extra=None):
1645 1584 """Add a new revision to the repository."""
1646 1585
1647 1586 def commitctx(ctx, error=False):
1648 1587 """Commit a commitctx instance to the repository."""
1649 1588
1650 1589 def destroying():
1651 1590 """Inform the repository that nodes are about to be destroyed."""
1652 1591
1653 1592 def destroyed():
1654 1593 """Inform the repository that nodes have been destroyed."""
1655 1594
1656 1595 def status(node1='.', node2=None, match=None, ignored=False,
1657 1596 clean=False, unknown=False, listsubrepos=False):
1658 1597 """Convenience method to call repo[x].status()."""
1659 1598
1660 1599 def addpostdsstatus(ps):
1661 1600 pass
1662 1601
1663 1602 def postdsstatus():
1664 1603 pass
1665 1604
1666 1605 def clearpostdsstatus():
1667 1606 pass
1668 1607
1669 1608 def heads(start=None):
1670 1609 """Obtain list of nodes that are DAG heads."""
1671 1610
1672 1611 def branchheads(branch=None, start=None, closed=False):
1673 1612 pass
1674 1613
1675 1614 def branches(nodes):
1676 1615 pass
1677 1616
1678 1617 def between(pairs):
1679 1618 pass
1680 1619
1681 1620 def checkpush(pushop):
1682 1621 pass
1683 1622
1684 1623 prepushoutgoinghooks = interfaceutil.Attribute(
1685 1624 """util.hooks instance.""")
1686 1625
1687 1626 def pushkey(namespace, key, old, new):
1688 1627 pass
1689 1628
1690 1629 def listkeys(namespace):
1691 1630 pass
1692 1631
1693 1632 def debugwireargs(one, two, three=None, four=None, five=None):
1694 1633 pass
1695 1634
1696 1635 def savecommitmessage(text):
1697 1636 pass
1698 1637
1699 1638 class completelocalrepository(ilocalrepositorymain,
1700 1639 ilocalrepositoryfilestorage):
1701 1640 """Complete interface for a local repository."""
@@ -1,2726 +1,2645 b''
1 1 # revlog.py - storage back-end for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 """Storage back-end for Mercurial.
9 9
10 10 This provides efficient delta storage with O(1) retrieve and append
11 11 and O(changes) merge between branches.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import collections
17 17 import contextlib
18 18 import errno
19 19 import hashlib
20 20 import os
21 21 import re
22 22 import struct
23 23 import zlib
24 24
25 25 # import stuff from node for others to import from revlog
26 26 from .node import (
27 27 bin,
28 28 hex,
29 29 nullhex,
30 30 nullid,
31 31 nullrev,
32 32 wdirfilenodeids,
33 33 wdirhex,
34 34 wdirid,
35 35 wdirrev,
36 36 )
37 37 from .i18n import _
38 38 from .revlogutils.constants import (
39 39 FLAG_GENERALDELTA,
40 40 FLAG_INLINE_DATA,
41 41 REVIDX_DEFAULT_FLAGS,
42 42 REVIDX_ELLIPSIS,
43 43 REVIDX_EXTSTORED,
44 44 REVIDX_FLAGS_ORDER,
45 45 REVIDX_ISCENSORED,
46 46 REVIDX_KNOWN_FLAGS,
47 47 REVIDX_RAWTEXT_CHANGING_FLAGS,
48 48 REVLOGV0,
49 49 REVLOGV1,
50 50 REVLOGV1_FLAGS,
51 51 REVLOGV2,
52 52 REVLOGV2_FLAGS,
53 53 REVLOG_DEFAULT_FLAGS,
54 54 REVLOG_DEFAULT_FORMAT,
55 55 REVLOG_DEFAULT_VERSION,
56 56 )
57 57 from .thirdparty import (
58 58 attr,
59 59 )
60 60 from . import (
61 61 ancestor,
62 62 dagop,
63 63 error,
64 64 mdiff,
65 65 policy,
66 66 pycompat,
67 67 repository,
68 68 templatefilters,
69 69 util,
70 70 )
71 71 from .revlogutils import (
72 72 deltas as deltautil,
73 73 )
74 74 from .utils import (
75 75 interfaceutil,
76 76 stringutil,
77 77 )
78 78
79 79 # blanked usage of all the name to prevent pyflakes constraints
80 80 # We need these name available in the module for extensions.
81 81 REVLOGV0
82 82 REVLOGV1
83 83 REVLOGV2
84 84 FLAG_INLINE_DATA
85 85 FLAG_GENERALDELTA
86 86 REVLOG_DEFAULT_FLAGS
87 87 REVLOG_DEFAULT_FORMAT
88 88 REVLOG_DEFAULT_VERSION
89 89 REVLOGV1_FLAGS
90 90 REVLOGV2_FLAGS
91 91 REVIDX_ISCENSORED
92 92 REVIDX_ELLIPSIS
93 93 REVIDX_EXTSTORED
94 94 REVIDX_DEFAULT_FLAGS
95 95 REVIDX_FLAGS_ORDER
96 96 REVIDX_KNOWN_FLAGS
97 97 REVIDX_RAWTEXT_CHANGING_FLAGS
98 98
99 99 parsers = policy.importmod(r'parsers')
100 100
101 101 # Aliased for performance.
102 102 _zlibdecompress = zlib.decompress
103 103
104 104 # max size of revlog with inline data
105 105 _maxinline = 131072
106 106 _chunksize = 1048576
107 107
108 108 # Store flag processors (cf. 'addflagprocessor()' to register)
109 109 _flagprocessors = {
110 110 REVIDX_ISCENSORED: None,
111 111 }
112 112
113 113 # Flag processors for REVIDX_ELLIPSIS.
114 114 def ellipsisreadprocessor(rl, text):
115 115 return text, False
116 116
117 117 def ellipsiswriteprocessor(rl, text):
118 118 return text, False
119 119
120 120 def ellipsisrawprocessor(rl, text):
121 121 return False
122 122
123 123 ellipsisprocessor = (
124 124 ellipsisreadprocessor,
125 125 ellipsiswriteprocessor,
126 126 ellipsisrawprocessor,
127 127 )
128 128
129 129 _mdre = re.compile('\1\n')
130 130 def parsemeta(text):
131 131 """return (metadatadict, metadatasize)"""
132 132 # text can be buffer, so we can't use .startswith or .index
133 133 if text[:2] != '\1\n':
134 134 return None, None
135 135 s = _mdre.search(text, 2).start()
136 136 mtext = text[2:s]
137 137 meta = {}
138 138 for l in mtext.splitlines():
139 139 k, v = l.split(": ", 1)
140 140 meta[k] = v
141 141 return meta, (s + 2)
142 142
143 143 def packmeta(meta, text):
144 144 keys = sorted(meta)
145 145 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
146 146 return "\1\n%s\1\n%s" % (metatext, text)
147 147
148 148 def _censoredtext(text):
149 149 m, offs = parsemeta(text)
150 150 return m and "censored" in m
151 151
152 152 def addflagprocessor(flag, processor):
153 153 """Register a flag processor on a revision data flag.
154 154
155 155 Invariant:
156 156 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
157 157 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
158 158 - Only one flag processor can be registered on a specific flag.
159 159 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
160 160 following signatures:
161 161 - (read) f(self, rawtext) -> text, bool
162 162 - (write) f(self, text) -> rawtext, bool
163 163 - (raw) f(self, rawtext) -> bool
164 164 "text" is presented to the user. "rawtext" is stored in revlog data, not
165 165 directly visible to the user.
166 166 The boolean returned by these transforms is used to determine whether
167 167 the returned text can be used for hash integrity checking. For example,
168 168 if "write" returns False, then "text" is used to generate hash. If
169 169 "write" returns True, that basically means "rawtext" returned by "write"
170 170 should be used to generate hash. Usually, "write" and "read" return
171 171 different booleans. And "raw" returns a same boolean as "write".
172 172
173 173 Note: The 'raw' transform is used for changegroup generation and in some
174 174 debug commands. In this case the transform only indicates whether the
175 175 contents can be used for hash integrity checks.
176 176 """
177 177 if not flag & REVIDX_KNOWN_FLAGS:
178 178 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
179 179 raise error.ProgrammingError(msg)
180 180 if flag not in REVIDX_FLAGS_ORDER:
181 181 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
182 182 raise error.ProgrammingError(msg)
183 183 if flag in _flagprocessors:
184 184 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
185 185 raise error.Abort(msg)
186 186 _flagprocessors[flag] = processor
187 187
188 188 def getoffset(q):
189 189 return int(q >> 16)
190 190
191 191 def gettype(q):
192 192 return int(q & 0xFFFF)
193 193
194 194 def offset_type(offset, type):
195 195 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
196 196 raise ValueError('unknown revlog index flags')
197 197 return int(int(offset) << 16 | type)
198 198
199 199 _nullhash = hashlib.sha1(nullid)
200 200
201 201 def hash(text, p1, p2):
202 202 """generate a hash from the given text and its parent hashes
203 203
204 204 This hash combines both the current file contents and its history
205 205 in a manner that makes it easy to distinguish nodes with the same
206 206 content in the revision graph.
207 207 """
208 208 # As of now, if one of the parent node is null, p2 is null
209 209 if p2 == nullid:
210 210 # deep copy of a hash is faster than creating one
211 211 s = _nullhash.copy()
212 212 s.update(p1)
213 213 else:
214 214 # none of the parent nodes are nullid
215 215 if p1 < p2:
216 216 a = p1
217 217 b = p2
218 218 else:
219 219 a = p2
220 220 b = p1
221 221 s = hashlib.sha1(a)
222 222 s.update(b)
223 223 s.update(text)
224 224 return s.digest()
225 225
226 226 @attr.s(slots=True, frozen=True)
227 227 class _revisioninfo(object):
228 228 """Information about a revision that allows building its fulltext
229 229 node: expected hash of the revision
230 230 p1, p2: parent revs of the revision
231 231 btext: built text cache consisting of a one-element list
232 232 cachedelta: (baserev, uncompressed_delta) or None
233 233 flags: flags associated to the revision storage
234 234
235 235 One of btext[0] or cachedelta must be set.
236 236 """
237 237 node = attr.ib()
238 238 p1 = attr.ib()
239 239 p2 = attr.ib()
240 240 btext = attr.ib()
241 241 textlen = attr.ib()
242 242 cachedelta = attr.ib()
243 243 flags = attr.ib()
244 244
245 245 @interfaceutil.implementer(repository.irevisiondelta)
246 246 @attr.s(slots=True)
247 247 class revlogrevisiondelta(object):
248 248 node = attr.ib()
249 249 p1node = attr.ib()
250 250 p2node = attr.ib()
251 251 basenode = attr.ib()
252 252 flags = attr.ib()
253 253 baserevisionsize = attr.ib()
254 254 revision = attr.ib()
255 255 delta = attr.ib()
256 256 linknode = attr.ib(default=None)
257 257
258 258 @interfaceutil.implementer(repository.iverifyproblem)
259 259 @attr.s(frozen=True)
260 260 class revlogproblem(object):
261 261 warning = attr.ib(default=None)
262 262 error = attr.ib(default=None)
263 263
264 264 # index v0:
265 265 # 4 bytes: offset
266 266 # 4 bytes: compressed length
267 267 # 4 bytes: base rev
268 268 # 4 bytes: link rev
269 269 # 20 bytes: parent 1 nodeid
270 270 # 20 bytes: parent 2 nodeid
271 271 # 20 bytes: nodeid
272 272 indexformatv0 = struct.Struct(">4l20s20s20s")
273 273 indexformatv0_pack = indexformatv0.pack
274 274 indexformatv0_unpack = indexformatv0.unpack
275 275
276 276 class revlogoldindex(list):
277 277 def __getitem__(self, i):
278 278 if i == -1:
279 279 return (0, 0, 0, -1, -1, -1, -1, nullid)
280 280 return list.__getitem__(self, i)
281 281
282 282 class revlogoldio(object):
283 283 def __init__(self):
284 284 self.size = indexformatv0.size
285 285
286 286 def parseindex(self, data, inline):
287 287 s = self.size
288 288 index = []
289 289 nodemap = {nullid: nullrev}
290 290 n = off = 0
291 291 l = len(data)
292 292 while off + s <= l:
293 293 cur = data[off:off + s]
294 294 off += s
295 295 e = indexformatv0_unpack(cur)
296 296 # transform to revlogv1 format
297 297 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
298 298 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
299 299 index.append(e2)
300 300 nodemap[e[6]] = n
301 301 n += 1
302 302
303 303 return revlogoldindex(index), nodemap, None
304 304
305 305 def packentry(self, entry, node, version, rev):
306 306 if gettype(entry[0]):
307 307 raise error.RevlogError(_('index entry flags need revlog '
308 308 'version 1'))
309 309 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
310 310 node(entry[5]), node(entry[6]), entry[7])
311 311 return indexformatv0_pack(*e2)
312 312
313 313 # index ng:
314 314 # 6 bytes: offset
315 315 # 2 bytes: flags
316 316 # 4 bytes: compressed length
317 317 # 4 bytes: uncompressed length
318 318 # 4 bytes: base rev
319 319 # 4 bytes: link rev
320 320 # 4 bytes: parent 1 rev
321 321 # 4 bytes: parent 2 rev
322 322 # 32 bytes: nodeid
323 323 indexformatng = struct.Struct(">Qiiiiii20s12x")
324 324 indexformatng_pack = indexformatng.pack
325 325 versionformat = struct.Struct(">I")
326 326 versionformat_pack = versionformat.pack
327 327 versionformat_unpack = versionformat.unpack
328 328
329 329 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
330 330 # signed integer)
331 331 _maxentrysize = 0x7fffffff
332 332
333 333 class revlogio(object):
334 334 def __init__(self):
335 335 self.size = indexformatng.size
336 336
337 337 def parseindex(self, data, inline):
338 338 # call the C implementation to parse the index data
339 339 index, cache = parsers.parse_index2(data, inline)
340 340 return index, getattr(index, 'nodemap', None), cache
341 341
342 342 def packentry(self, entry, node, version, rev):
343 343 p = indexformatng_pack(*entry)
344 344 if rev == 0:
345 345 p = versionformat_pack(version) + p[4:]
346 346 return p
347 347
348 348 class revlog(object):
349 349 """
350 350 the underlying revision storage object
351 351
352 352 A revlog consists of two parts, an index and the revision data.
353 353
354 354 The index is a file with a fixed record size containing
355 355 information on each revision, including its nodeid (hash), the
356 356 nodeids of its parents, the position and offset of its data within
357 357 the data file, and the revision it's based on. Finally, each entry
358 358 contains a linkrev entry that can serve as a pointer to external
359 359 data.
360 360
361 361 The revision data itself is a linear collection of data chunks.
362 362 Each chunk represents a revision and is usually represented as a
363 363 delta against the previous chunk. To bound lookup time, runs of
364 364 deltas are limited to about 2 times the length of the original
365 365 version data. This makes retrieval of a version proportional to
366 366 its size, or O(1) relative to the number of revisions.
367 367
368 368 Both pieces of the revlog are written to in an append-only
369 369 fashion, which means we never need to rewrite a file to insert or
370 370 remove data, and can use some simple techniques to avoid the need
371 371 for locking while reading.
372 372
373 373 If checkambig, indexfile is opened with checkambig=True at
374 374 writing, to avoid file stat ambiguity.
375 375
376 376 If mmaplargeindex is True, and an mmapindexthreshold is set, the
377 377 index will be mmapped rather than read if it is larger than the
378 378 configured threshold.
379 379
380 380 If censorable is True, the revlog can have censored revisions.
381 381 """
382 382 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
383 383 mmaplargeindex=False, censorable=False):
384 384 """
385 385 create a revlog object
386 386
387 387 opener is a function that abstracts the file opening operation
388 388 and can be used to implement COW semantics or the like.
389 389 """
390 390 self.indexfile = indexfile
391 391 self.datafile = datafile or (indexfile[:-2] + ".d")
392 392 self.opener = opener
393 393 # When True, indexfile is opened with checkambig=True at writing, to
394 394 # avoid file stat ambiguity.
395 395 self._checkambig = checkambig
396 396 self._censorable = censorable
397 397 # 3-tuple of (node, rev, text) for a raw revision.
398 398 self._cache = None
399 399 # Maps rev to chain base rev.
400 400 self._chainbasecache = util.lrucachedict(100)
401 401 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
402 402 self._chunkcache = (0, '')
403 403 # How much data to read and cache into the raw revlog data cache.
404 404 self._chunkcachesize = 65536
405 405 self._maxchainlen = None
406 406 self._deltabothparents = True
407 407 self.index = []
408 408 # Mapping of partial identifiers to full nodes.
409 409 self._pcache = {}
410 410 # Mapping of revision integer to full node.
411 411 self._nodecache = {nullid: nullrev}
412 412 self._nodepos = None
413 413 self._compengine = 'zlib'
414 414 self._maxdeltachainspan = -1
415 415 self._withsparseread = False
416 416 self._sparserevlog = False
417 417 self._srdensitythreshold = 0.50
418 418 self._srmingapsize = 262144
419 419
420 420 # Make copy of flag processors so each revlog instance can support
421 421 # custom flags.
422 422 self._flagprocessors = dict(_flagprocessors)
423 423
424 424 mmapindexthreshold = None
425 425 v = REVLOG_DEFAULT_VERSION
426 426 opts = getattr(opener, 'options', None)
427 427 if opts is not None:
428 428 if 'revlogv2' in opts:
429 429 # version 2 revlogs always use generaldelta.
430 430 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
431 431 elif 'revlogv1' in opts:
432 432 if 'generaldelta' in opts:
433 433 v |= FLAG_GENERALDELTA
434 434 else:
435 435 v = 0
436 436 if 'chunkcachesize' in opts:
437 437 self._chunkcachesize = opts['chunkcachesize']
438 438 if 'maxchainlen' in opts:
439 439 self._maxchainlen = opts['maxchainlen']
440 440 if 'deltabothparents' in opts:
441 441 self._deltabothparents = opts['deltabothparents']
442 442 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
443 443 if 'compengine' in opts:
444 444 self._compengine = opts['compengine']
445 445 if 'maxdeltachainspan' in opts:
446 446 self._maxdeltachainspan = opts['maxdeltachainspan']
447 447 if mmaplargeindex and 'mmapindexthreshold' in opts:
448 448 mmapindexthreshold = opts['mmapindexthreshold']
449 449 self._sparserevlog = bool(opts.get('sparse-revlog', False))
450 450 withsparseread = bool(opts.get('with-sparse-read', False))
451 451 # sparse-revlog forces sparse-read
452 452 self._withsparseread = self._sparserevlog or withsparseread
453 453 if 'sparse-read-density-threshold' in opts:
454 454 self._srdensitythreshold = opts['sparse-read-density-threshold']
455 455 if 'sparse-read-min-gap-size' in opts:
456 456 self._srmingapsize = opts['sparse-read-min-gap-size']
457 457 if opts.get('enableellipsis'):
458 458 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
459 459
460 460 if self._chunkcachesize <= 0:
461 461 raise error.RevlogError(_('revlog chunk cache size %r is not '
462 462 'greater than 0') % self._chunkcachesize)
463 463 elif self._chunkcachesize & (self._chunkcachesize - 1):
464 464 raise error.RevlogError(_('revlog chunk cache size %r is not a '
465 465 'power of 2') % self._chunkcachesize)
466 466
467 467 indexdata = ''
468 468 self._initempty = True
469 469 try:
470 470 with self._indexfp() as f:
471 471 if (mmapindexthreshold is not None and
472 472 self.opener.fstat(f).st_size >= mmapindexthreshold):
473 473 indexdata = util.buffer(util.mmapread(f))
474 474 else:
475 475 indexdata = f.read()
476 476 if len(indexdata) > 0:
477 477 v = versionformat_unpack(indexdata[:4])[0]
478 478 self._initempty = False
479 479 except IOError as inst:
480 480 if inst.errno != errno.ENOENT:
481 481 raise
482 482
483 483 self.version = v
484 484 self._inline = v & FLAG_INLINE_DATA
485 485 self._generaldelta = v & FLAG_GENERALDELTA
486 486 flags = v & ~0xFFFF
487 487 fmt = v & 0xFFFF
488 488 if fmt == REVLOGV0:
489 489 if flags:
490 490 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
491 491 'revlog %s') %
492 492 (flags >> 16, fmt, self.indexfile))
493 493 elif fmt == REVLOGV1:
494 494 if flags & ~REVLOGV1_FLAGS:
495 495 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
496 496 'revlog %s') %
497 497 (flags >> 16, fmt, self.indexfile))
498 498 elif fmt == REVLOGV2:
499 499 if flags & ~REVLOGV2_FLAGS:
500 500 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
501 501 'revlog %s') %
502 502 (flags >> 16, fmt, self.indexfile))
503 503 else:
504 504 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
505 505 (fmt, self.indexfile))
506 506
507 507 self._storedeltachains = True
508 508
509 509 self._io = revlogio()
510 510 if self.version == REVLOGV0:
511 511 self._io = revlogoldio()
512 512 try:
513 513 d = self._io.parseindex(indexdata, self._inline)
514 514 except (ValueError, IndexError):
515 515 raise error.RevlogError(_("index %s is corrupted") %
516 516 self.indexfile)
517 517 self.index, nodemap, self._chunkcache = d
518 518 if nodemap is not None:
519 519 self.nodemap = self._nodecache = nodemap
520 520 if not self._chunkcache:
521 521 self._chunkclear()
522 522 # revnum -> (chain-length, sum-delta-length)
523 523 self._chaininfocache = {}
524 524 # revlog header -> revlog compressor
525 525 self._decompressors = {}
526 526
527 527 @util.propertycache
528 528 def _compressor(self):
529 529 return util.compengines[self._compengine].revlogcompressor()
530 530
531 531 def _indexfp(self, mode='r'):
532 532 """file object for the revlog's index file"""
533 533 args = {r'mode': mode}
534 534 if mode != 'r':
535 535 args[r'checkambig'] = self._checkambig
536 536 if mode == 'w':
537 537 args[r'atomictemp'] = True
538 538 return self.opener(self.indexfile, **args)
539 539
540 540 def _datafp(self, mode='r'):
541 541 """file object for the revlog's data file"""
542 542 return self.opener(self.datafile, mode=mode)
543 543
544 544 @contextlib.contextmanager
545 545 def _datareadfp(self, existingfp=None):
546 546 """file object suitable to read data"""
547 547 if existingfp is not None:
548 548 yield existingfp
549 549 else:
550 550 if self._inline:
551 551 func = self._indexfp
552 552 else:
553 553 func = self._datafp
554 554 with func() as fp:
555 555 yield fp
556 556
557 557 def tip(self):
558 558 return self.node(len(self.index) - 1)
559 559 def __contains__(self, rev):
560 560 return 0 <= rev < len(self)
561 561 def __len__(self):
562 562 return len(self.index)
563 563 def __iter__(self):
564 564 return iter(pycompat.xrange(len(self)))
565 565 def revs(self, start=0, stop=None):
566 566 """iterate over all rev in this revlog (from start to stop)"""
567 567 step = 1
568 568 length = len(self)
569 569 if stop is not None:
570 570 if start > stop:
571 571 step = -1
572 572 stop += step
573 573 if stop > length:
574 574 stop = length
575 575 else:
576 576 stop = length
577 577 return pycompat.xrange(start, stop, step)
578 578
579 579 @util.propertycache
580 580 def nodemap(self):
581 581 if self.index:
582 582 # populate mapping down to the initial node
583 583 node0 = self.index[0][7] # get around changelog filtering
584 584 self.rev(node0)
585 585 return self._nodecache
586 586
587 587 def hasnode(self, node):
588 588 try:
589 589 self.rev(node)
590 590 return True
591 591 except KeyError:
592 592 return False
593 593
594 594 def candelta(self, baserev, rev):
595 595 """whether two revisions (baserev, rev) can be delta-ed or not"""
596 596 # Disable delta if either rev requires a content-changing flag
597 597 # processor (ex. LFS). This is because such flag processor can alter
598 598 # the rawtext content that the delta will be based on, and two clients
599 599 # could have a same revlog node with different flags (i.e. different
600 600 # rawtext contents) and the delta could be incompatible.
601 601 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
602 602 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
603 603 return False
604 604 return True
605 605
606 606 def clearcaches(self):
607 607 self._cache = None
608 608 self._chainbasecache.clear()
609 609 self._chunkcache = (0, '')
610 610 self._pcache = {}
611 611
612 612 try:
613 613 self._nodecache.clearcaches()
614 614 except AttributeError:
615 615 self._nodecache = {nullid: nullrev}
616 616 self._nodepos = None
617 617
618 618 def rev(self, node):
619 619 try:
620 620 return self._nodecache[node]
621 621 except TypeError:
622 622 raise
623 623 except error.RevlogError:
624 624 # parsers.c radix tree lookup failed
625 625 if node == wdirid or node in wdirfilenodeids:
626 626 raise error.WdirUnsupported
627 627 raise error.LookupError(node, self.indexfile, _('no node'))
628 628 except KeyError:
629 629 # pure python cache lookup failed
630 630 n = self._nodecache
631 631 i = self.index
632 632 p = self._nodepos
633 633 if p is None:
634 634 p = len(i) - 1
635 635 else:
636 636 assert p < len(i)
637 637 for r in pycompat.xrange(p, -1, -1):
638 638 v = i[r][7]
639 639 n[v] = r
640 640 if v == node:
641 641 self._nodepos = r - 1
642 642 return r
643 643 if node == wdirid or node in wdirfilenodeids:
644 644 raise error.WdirUnsupported
645 645 raise error.LookupError(node, self.indexfile, _('no node'))
646 646
647 647 # Accessors for index entries.
648 648
649 649 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
650 650 # are flags.
651 651 def start(self, rev):
652 652 return int(self.index[rev][0] >> 16)
653 653
654 654 def flags(self, rev):
655 655 return self.index[rev][0] & 0xFFFF
656 656
657 657 def length(self, rev):
658 658 return self.index[rev][1]
659 659
660 660 def rawsize(self, rev):
661 661 """return the length of the uncompressed text for a given revision"""
662 662 l = self.index[rev][2]
663 663 if l >= 0:
664 664 return l
665 665
666 666 t = self.revision(rev, raw=True)
667 667 return len(t)
668 668
669 669 def size(self, rev):
670 670 """length of non-raw text (processed by a "read" flag processor)"""
671 671 # fast path: if no "read" flag processor could change the content,
672 672 # size is rawsize. note: ELLIPSIS is known to not change the content.
673 673 flags = self.flags(rev)
674 674 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
675 675 return self.rawsize(rev)
676 676
677 677 return len(self.revision(rev, raw=False))
678 678
679 679 def chainbase(self, rev):
680 680 base = self._chainbasecache.get(rev)
681 681 if base is not None:
682 682 return base
683 683
684 684 index = self.index
685 685 iterrev = rev
686 686 base = index[iterrev][3]
687 687 while base != iterrev:
688 688 iterrev = base
689 689 base = index[iterrev][3]
690 690
691 691 self._chainbasecache[rev] = base
692 692 return base
693 693
694 694 def linkrev(self, rev):
695 695 return self.index[rev][4]
696 696
697 697 def parentrevs(self, rev):
698 698 try:
699 699 entry = self.index[rev]
700 700 except IndexError:
701 701 if rev == wdirrev:
702 702 raise error.WdirUnsupported
703 703 raise
704 704
705 705 return entry[5], entry[6]
706 706
707 707 def node(self, rev):
708 708 try:
709 709 return self.index[rev][7]
710 710 except IndexError:
711 711 if rev == wdirrev:
712 712 raise error.WdirUnsupported
713 713 raise
714 714
715 715 # Derived from index values.
716 716
717 717 def end(self, rev):
718 718 return self.start(rev) + self.length(rev)
719 719
720 720 def parents(self, node):
721 721 i = self.index
722 722 d = i[self.rev(node)]
723 723 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
724 724
725 725 def chainlen(self, rev):
726 726 return self._chaininfo(rev)[0]
727 727
728 728 def _chaininfo(self, rev):
729 729 chaininfocache = self._chaininfocache
730 730 if rev in chaininfocache:
731 731 return chaininfocache[rev]
732 732 index = self.index
733 733 generaldelta = self._generaldelta
734 734 iterrev = rev
735 735 e = index[iterrev]
736 736 clen = 0
737 737 compresseddeltalen = 0
738 738 while iterrev != e[3]:
739 739 clen += 1
740 740 compresseddeltalen += e[1]
741 741 if generaldelta:
742 742 iterrev = e[3]
743 743 else:
744 744 iterrev -= 1
745 745 if iterrev in chaininfocache:
746 746 t = chaininfocache[iterrev]
747 747 clen += t[0]
748 748 compresseddeltalen += t[1]
749 749 break
750 750 e = index[iterrev]
751 751 else:
752 752 # Add text length of base since decompressing that also takes
753 753 # work. For cache hits the length is already included.
754 754 compresseddeltalen += e[1]
755 755 r = (clen, compresseddeltalen)
756 756 chaininfocache[rev] = r
757 757 return r
758 758
759 759 def _deltachain(self, rev, stoprev=None):
760 760 """Obtain the delta chain for a revision.
761 761
762 762 ``stoprev`` specifies a revision to stop at. If not specified, we
763 763 stop at the base of the chain.
764 764
765 765 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
766 766 revs in ascending order and ``stopped`` is a bool indicating whether
767 767 ``stoprev`` was hit.
768 768 """
769 769 # Try C implementation.
770 770 try:
771 771 return self.index.deltachain(rev, stoprev, self._generaldelta)
772 772 except AttributeError:
773 773 pass
774 774
775 775 chain = []
776 776
777 777 # Alias to prevent attribute lookup in tight loop.
778 778 index = self.index
779 779 generaldelta = self._generaldelta
780 780
781 781 iterrev = rev
782 782 e = index[iterrev]
783 783 while iterrev != e[3] and iterrev != stoprev:
784 784 chain.append(iterrev)
785 785 if generaldelta:
786 786 iterrev = e[3]
787 787 else:
788 788 iterrev -= 1
789 789 e = index[iterrev]
790 790
791 791 if iterrev == stoprev:
792 792 stopped = True
793 793 else:
794 794 chain.append(iterrev)
795 795 stopped = False
796 796
797 797 chain.reverse()
798 798 return chain, stopped
799 799
800 800 def ancestors(self, revs, stoprev=0, inclusive=False):
801 801 """Generate the ancestors of 'revs' in reverse topological order.
802 802 Does not generate revs lower than stoprev.
803 803
804 804 See the documentation for ancestor.lazyancestors for more details."""
805 805
806 806 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
807 807 inclusive=inclusive)
808 808
809 809 def descendants(self, revs):
810 810 """Generate the descendants of 'revs' in revision order.
811 811
812 812 Yield a sequence of revision numbers starting with a child of
813 813 some rev in revs, i.e., each revision is *not* considered a
814 814 descendant of itself. Results are ordered by revision number (a
815 815 topological sort)."""
816 816 first = min(revs)
817 817 if first == nullrev:
818 818 for i in self:
819 819 yield i
820 820 return
821 821
822 822 seen = set(revs)
823 823 for i in self.revs(start=first + 1):
824 824 for x in self.parentrevs(i):
825 825 if x != nullrev and x in seen:
826 826 seen.add(i)
827 827 yield i
828 828 break
829 829
830 830 def findcommonmissing(self, common=None, heads=None):
831 831 """Return a tuple of the ancestors of common and the ancestors of heads
832 832 that are not ancestors of common. In revset terminology, we return the
833 833 tuple:
834 834
835 835 ::common, (::heads) - (::common)
836 836
837 837 The list is sorted by revision number, meaning it is
838 838 topologically sorted.
839 839
840 840 'heads' and 'common' are both lists of node IDs. If heads is
841 841 not supplied, uses all of the revlog's heads. If common is not
842 842 supplied, uses nullid."""
843 843 if common is None:
844 844 common = [nullid]
845 845 if heads is None:
846 846 heads = self.heads()
847 847
848 848 common = [self.rev(n) for n in common]
849 849 heads = [self.rev(n) for n in heads]
850 850
851 851 # we want the ancestors, but inclusive
852 852 class lazyset(object):
853 853 def __init__(self, lazyvalues):
854 854 self.addedvalues = set()
855 855 self.lazyvalues = lazyvalues
856 856
857 857 def __contains__(self, value):
858 858 return value in self.addedvalues or value in self.lazyvalues
859 859
860 860 def __iter__(self):
861 861 added = self.addedvalues
862 862 for r in added:
863 863 yield r
864 864 for r in self.lazyvalues:
865 865 if not r in added:
866 866 yield r
867 867
868 868 def add(self, value):
869 869 self.addedvalues.add(value)
870 870
871 871 def update(self, values):
872 872 self.addedvalues.update(values)
873 873
874 874 has = lazyset(self.ancestors(common))
875 875 has.add(nullrev)
876 876 has.update(common)
877 877
878 878 # take all ancestors from heads that aren't in has
879 879 missing = set()
880 880 visit = collections.deque(r for r in heads if r not in has)
881 881 while visit:
882 882 r = visit.popleft()
883 883 if r in missing:
884 884 continue
885 885 else:
886 886 missing.add(r)
887 887 for p in self.parentrevs(r):
888 888 if p not in has:
889 889 visit.append(p)
890 890 missing = list(missing)
891 891 missing.sort()
892 892 return has, [self.node(miss) for miss in missing]
893 893
894 894 def incrementalmissingrevs(self, common=None):
895 895 """Return an object that can be used to incrementally compute the
896 896 revision numbers of the ancestors of arbitrary sets that are not
897 897 ancestors of common. This is an ancestor.incrementalmissingancestors
898 898 object.
899 899
900 900 'common' is a list of revision numbers. If common is not supplied, uses
901 901 nullrev.
902 902 """
903 903 if common is None:
904 904 common = [nullrev]
905 905
906 906 return ancestor.incrementalmissingancestors(self.parentrevs, common)
907 907
908 908 def findmissingrevs(self, common=None, heads=None):
909 909 """Return the revision numbers of the ancestors of heads that
910 910 are not ancestors of common.
911 911
912 912 More specifically, return a list of revision numbers corresponding to
913 913 nodes N such that every N satisfies the following constraints:
914 914
915 915 1. N is an ancestor of some node in 'heads'
916 916 2. N is not an ancestor of any node in 'common'
917 917
918 918 The list is sorted by revision number, meaning it is
919 919 topologically sorted.
920 920
921 921 'heads' and 'common' are both lists of revision numbers. If heads is
922 922 not supplied, uses all of the revlog's heads. If common is not
923 923 supplied, uses nullid."""
924 924 if common is None:
925 925 common = [nullrev]
926 926 if heads is None:
927 927 heads = self.headrevs()
928 928
929 929 inc = self.incrementalmissingrevs(common=common)
930 930 return inc.missingancestors(heads)
931 931
932 932 def findmissing(self, common=None, heads=None):
933 933 """Return the ancestors of heads that are not ancestors of common.
934 934
935 935 More specifically, return a list of nodes N such that every N
936 936 satisfies the following constraints:
937 937
938 938 1. N is an ancestor of some node in 'heads'
939 939 2. N is not an ancestor of any node in 'common'
940 940
941 941 The list is sorted by revision number, meaning it is
942 942 topologically sorted.
943 943
944 944 'heads' and 'common' are both lists of node IDs. If heads is
945 945 not supplied, uses all of the revlog's heads. If common is not
946 946 supplied, uses nullid."""
947 947 if common is None:
948 948 common = [nullid]
949 949 if heads is None:
950 950 heads = self.heads()
951 951
952 952 common = [self.rev(n) for n in common]
953 953 heads = [self.rev(n) for n in heads]
954 954
955 955 inc = self.incrementalmissingrevs(common=common)
956 956 return [self.node(r) for r in inc.missingancestors(heads)]
957 957
958 958 def nodesbetween(self, roots=None, heads=None):
959 959 """Return a topological path from 'roots' to 'heads'.
960 960
961 961 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
962 962 topologically sorted list of all nodes N that satisfy both of
963 963 these constraints:
964 964
965 965 1. N is a descendant of some node in 'roots'
966 966 2. N is an ancestor of some node in 'heads'
967 967
968 968 Every node is considered to be both a descendant and an ancestor
969 969 of itself, so every reachable node in 'roots' and 'heads' will be
970 970 included in 'nodes'.
971 971
972 972 'outroots' is the list of reachable nodes in 'roots', i.e., the
973 973 subset of 'roots' that is returned in 'nodes'. Likewise,
974 974 'outheads' is the subset of 'heads' that is also in 'nodes'.
975 975
976 976 'roots' and 'heads' are both lists of node IDs. If 'roots' is
977 977 unspecified, uses nullid as the only root. If 'heads' is
978 978 unspecified, uses list of all of the revlog's heads."""
979 979 nonodes = ([], [], [])
980 980 if roots is not None:
981 981 roots = list(roots)
982 982 if not roots:
983 983 return nonodes
984 984 lowestrev = min([self.rev(n) for n in roots])
985 985 else:
986 986 roots = [nullid] # Everybody's a descendant of nullid
987 987 lowestrev = nullrev
988 988 if (lowestrev == nullrev) and (heads is None):
989 989 # We want _all_ the nodes!
990 990 return ([self.node(r) for r in self], [nullid], list(self.heads()))
991 991 if heads is None:
992 992 # All nodes are ancestors, so the latest ancestor is the last
993 993 # node.
994 994 highestrev = len(self) - 1
995 995 # Set ancestors to None to signal that every node is an ancestor.
996 996 ancestors = None
997 997 # Set heads to an empty dictionary for later discovery of heads
998 998 heads = {}
999 999 else:
1000 1000 heads = list(heads)
1001 1001 if not heads:
1002 1002 return nonodes
1003 1003 ancestors = set()
1004 1004 # Turn heads into a dictionary so we can remove 'fake' heads.
1005 1005 # Also, later we will be using it to filter out the heads we can't
1006 1006 # find from roots.
1007 1007 heads = dict.fromkeys(heads, False)
1008 1008 # Start at the top and keep marking parents until we're done.
1009 1009 nodestotag = set(heads)
1010 1010 # Remember where the top was so we can use it as a limit later.
1011 1011 highestrev = max([self.rev(n) for n in nodestotag])
1012 1012 while nodestotag:
1013 1013 # grab a node to tag
1014 1014 n = nodestotag.pop()
1015 1015 # Never tag nullid
1016 1016 if n == nullid:
1017 1017 continue
1018 1018 # A node's revision number represents its place in a
1019 1019 # topologically sorted list of nodes.
1020 1020 r = self.rev(n)
1021 1021 if r >= lowestrev:
1022 1022 if n not in ancestors:
1023 1023 # If we are possibly a descendant of one of the roots
1024 1024 # and we haven't already been marked as an ancestor
1025 1025 ancestors.add(n) # Mark as ancestor
1026 1026 # Add non-nullid parents to list of nodes to tag.
1027 1027 nodestotag.update([p for p in self.parents(n) if
1028 1028 p != nullid])
1029 1029 elif n in heads: # We've seen it before, is it a fake head?
1030 1030 # So it is, real heads should not be the ancestors of
1031 1031 # any other heads.
1032 1032 heads.pop(n)
1033 1033 if not ancestors:
1034 1034 return nonodes
1035 1035 # Now that we have our set of ancestors, we want to remove any
1036 1036 # roots that are not ancestors.
1037 1037
1038 1038 # If one of the roots was nullid, everything is included anyway.
1039 1039 if lowestrev > nullrev:
1040 1040 # But, since we weren't, let's recompute the lowest rev to not
1041 1041 # include roots that aren't ancestors.
1042 1042
1043 1043 # Filter out roots that aren't ancestors of heads
1044 1044 roots = [root for root in roots if root in ancestors]
1045 1045 # Recompute the lowest revision
1046 1046 if roots:
1047 1047 lowestrev = min([self.rev(root) for root in roots])
1048 1048 else:
1049 1049 # No more roots? Return empty list
1050 1050 return nonodes
1051 1051 else:
1052 1052 # We are descending from nullid, and don't need to care about
1053 1053 # any other roots.
1054 1054 lowestrev = nullrev
1055 1055 roots = [nullid]
1056 1056 # Transform our roots list into a set.
1057 1057 descendants = set(roots)
1058 1058 # Also, keep the original roots so we can filter out roots that aren't
1059 1059 # 'real' roots (i.e. are descended from other roots).
1060 1060 roots = descendants.copy()
1061 1061 # Our topologically sorted list of output nodes.
1062 1062 orderedout = []
1063 1063 # Don't start at nullid since we don't want nullid in our output list,
1064 1064 # and if nullid shows up in descendants, empty parents will look like
1065 1065 # they're descendants.
1066 1066 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1067 1067 n = self.node(r)
1068 1068 isdescendant = False
1069 1069 if lowestrev == nullrev: # Everybody is a descendant of nullid
1070 1070 isdescendant = True
1071 1071 elif n in descendants:
1072 1072 # n is already a descendant
1073 1073 isdescendant = True
1074 1074 # This check only needs to be done here because all the roots
1075 1075 # will start being marked is descendants before the loop.
1076 1076 if n in roots:
1077 1077 # If n was a root, check if it's a 'real' root.
1078 1078 p = tuple(self.parents(n))
1079 1079 # If any of its parents are descendants, it's not a root.
1080 1080 if (p[0] in descendants) or (p[1] in descendants):
1081 1081 roots.remove(n)
1082 1082 else:
1083 1083 p = tuple(self.parents(n))
1084 1084 # A node is a descendant if either of its parents are
1085 1085 # descendants. (We seeded the dependents list with the roots
1086 1086 # up there, remember?)
1087 1087 if (p[0] in descendants) or (p[1] in descendants):
1088 1088 descendants.add(n)
1089 1089 isdescendant = True
1090 1090 if isdescendant and ((ancestors is None) or (n in ancestors)):
1091 1091 # Only include nodes that are both descendants and ancestors.
1092 1092 orderedout.append(n)
1093 1093 if (ancestors is not None) and (n in heads):
1094 1094 # We're trying to figure out which heads are reachable
1095 1095 # from roots.
1096 1096 # Mark this head as having been reached
1097 1097 heads[n] = True
1098 1098 elif ancestors is None:
1099 1099 # Otherwise, we're trying to discover the heads.
1100 1100 # Assume this is a head because if it isn't, the next step
1101 1101 # will eventually remove it.
1102 1102 heads[n] = True
1103 1103 # But, obviously its parents aren't.
1104 1104 for p in self.parents(n):
1105 1105 heads.pop(p, None)
1106 1106 heads = [head for head, flag in heads.iteritems() if flag]
1107 1107 roots = list(roots)
1108 1108 assert orderedout
1109 1109 assert roots
1110 1110 assert heads
1111 1111 return (orderedout, roots, heads)
1112 1112
1113 1113 def headrevs(self):
1114 1114 try:
1115 1115 return self.index.headrevs()
1116 1116 except AttributeError:
1117 1117 return self._headrevs()
1118 1118
1119 1119 def computephases(self, roots):
1120 1120 return self.index.computephasesmapsets(roots)
1121 1121
1122 1122 def _headrevs(self):
1123 1123 count = len(self)
1124 1124 if not count:
1125 1125 return [nullrev]
1126 1126 # we won't iter over filtered rev so nobody is a head at start
1127 1127 ishead = [0] * (count + 1)
1128 1128 index = self.index
1129 1129 for r in self:
1130 1130 ishead[r] = 1 # I may be an head
1131 1131 e = index[r]
1132 1132 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1133 1133 return [r for r, val in enumerate(ishead) if val]
1134 1134
1135 1135 def heads(self, start=None, stop=None):
1136 1136 """return the list of all nodes that have no children
1137 1137
1138 1138 if start is specified, only heads that are descendants of
1139 1139 start will be returned
1140 1140 if stop is specified, it will consider all the revs from stop
1141 1141 as if they had no children
1142 1142 """
1143 1143 if start is None and stop is None:
1144 1144 if not len(self):
1145 1145 return [nullid]
1146 1146 return [self.node(r) for r in self.headrevs()]
1147 1147
1148 1148 if start is None:
1149 1149 start = nullid
1150 1150 if stop is None:
1151 1151 stop = []
1152 1152 stoprevs = set([self.rev(n) for n in stop])
1153 1153 startrev = self.rev(start)
1154 1154 reachable = {startrev}
1155 1155 heads = {startrev}
1156 1156
1157 1157 parentrevs = self.parentrevs
1158 1158 for r in self.revs(start=startrev + 1):
1159 1159 for p in parentrevs(r):
1160 1160 if p in reachable:
1161 1161 if r not in stoprevs:
1162 1162 reachable.add(r)
1163 1163 heads.add(r)
1164 1164 if p in heads and p not in stoprevs:
1165 1165 heads.remove(p)
1166 1166
1167 1167 return [self.node(r) for r in heads]
1168 1168
1169 1169 def children(self, node):
1170 1170 """find the children of a given node"""
1171 1171 c = []
1172 1172 p = self.rev(node)
1173 1173 for r in self.revs(start=p + 1):
1174 1174 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1175 1175 if prevs:
1176 1176 for pr in prevs:
1177 1177 if pr == p:
1178 1178 c.append(self.node(r))
1179 1179 elif p == nullrev:
1180 1180 c.append(self.node(r))
1181 1181 return c
1182 1182
1183 1183 def commonancestorsheads(self, a, b):
1184 1184 """calculate all the heads of the common ancestors of nodes a and b"""
1185 1185 a, b = self.rev(a), self.rev(b)
1186 1186 ancs = self._commonancestorsheads(a, b)
1187 1187 return pycompat.maplist(self.node, ancs)
1188 1188
1189 1189 def _commonancestorsheads(self, *revs):
1190 1190 """calculate all the heads of the common ancestors of revs"""
1191 1191 try:
1192 1192 ancs = self.index.commonancestorsheads(*revs)
1193 1193 except (AttributeError, OverflowError): # C implementation failed
1194 1194 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1195 1195 return ancs
1196 1196
1197 1197 def isancestor(self, a, b):
1198 1198 """return True if node a is an ancestor of node b
1199 1199
1200 1200 A revision is considered an ancestor of itself."""
1201 1201 a, b = self.rev(a), self.rev(b)
1202 1202 return self.isancestorrev(a, b)
1203 1203
1204 1204 def isancestorrev(self, a, b):
1205 1205 """return True if revision a is an ancestor of revision b
1206 1206
1207 1207 A revision is considered an ancestor of itself.
1208 1208
1209 1209 The implementation of this is trivial but the use of
1210 1210 commonancestorsheads is not."""
1211 1211 if a == nullrev:
1212 1212 return True
1213 1213 elif a == b:
1214 1214 return True
1215 1215 elif a > b:
1216 1216 return False
1217 1217 return a in self._commonancestorsheads(a, b)
1218 1218
1219 1219 def ancestor(self, a, b):
1220 1220 """calculate the "best" common ancestor of nodes a and b"""
1221 1221
1222 1222 a, b = self.rev(a), self.rev(b)
1223 1223 try:
1224 1224 ancs = self.index.ancestors(a, b)
1225 1225 except (AttributeError, OverflowError):
1226 1226 ancs = ancestor.ancestors(self.parentrevs, a, b)
1227 1227 if ancs:
1228 1228 # choose a consistent winner when there's a tie
1229 1229 return min(map(self.node, ancs))
1230 1230 return nullid
1231 1231
1232 1232 def _match(self, id):
1233 1233 if isinstance(id, int):
1234 1234 # rev
1235 1235 return self.node(id)
1236 1236 if len(id) == 20:
1237 1237 # possibly a binary node
1238 1238 # odds of a binary node being all hex in ASCII are 1 in 10**25
1239 1239 try:
1240 1240 node = id
1241 1241 self.rev(node) # quick search the index
1242 1242 return node
1243 1243 except error.LookupError:
1244 1244 pass # may be partial hex id
1245 1245 try:
1246 1246 # str(rev)
1247 1247 rev = int(id)
1248 1248 if "%d" % rev != id:
1249 1249 raise ValueError
1250 1250 if rev < 0:
1251 1251 rev = len(self) + rev
1252 1252 if rev < 0 or rev >= len(self):
1253 1253 raise ValueError
1254 1254 return self.node(rev)
1255 1255 except (ValueError, OverflowError):
1256 1256 pass
1257 1257 if len(id) == 40:
1258 1258 try:
1259 1259 # a full hex nodeid?
1260 1260 node = bin(id)
1261 1261 self.rev(node)
1262 1262 return node
1263 1263 except (TypeError, error.LookupError):
1264 1264 pass
1265 1265
1266 1266 def _partialmatch(self, id):
1267 1267 # we don't care wdirfilenodeids as they should be always full hash
1268 1268 maybewdir = wdirhex.startswith(id)
1269 1269 try:
1270 1270 partial = self.index.partialmatch(id)
1271 1271 if partial and self.hasnode(partial):
1272 1272 if maybewdir:
1273 1273 # single 'ff...' match in radix tree, ambiguous with wdir
1274 1274 raise error.RevlogError
1275 1275 return partial
1276 1276 if maybewdir:
1277 1277 # no 'ff...' match in radix tree, wdir identified
1278 1278 raise error.WdirUnsupported
1279 1279 return None
1280 1280 except error.RevlogError:
1281 1281 # parsers.c radix tree lookup gave multiple matches
1282 1282 # fast path: for unfiltered changelog, radix tree is accurate
1283 1283 if not getattr(self, 'filteredrevs', None):
1284 1284 raise error.AmbiguousPrefixLookupError(
1285 1285 id, self.indexfile, _('ambiguous identifier'))
1286 1286 # fall through to slow path that filters hidden revisions
1287 1287 except (AttributeError, ValueError):
1288 1288 # we are pure python, or key was too short to search radix tree
1289 1289 pass
1290 1290
1291 1291 if id in self._pcache:
1292 1292 return self._pcache[id]
1293 1293
1294 1294 if len(id) <= 40:
1295 1295 try:
1296 1296 # hex(node)[:...]
1297 1297 l = len(id) // 2 # grab an even number of digits
1298 1298 prefix = bin(id[:l * 2])
1299 1299 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1300 1300 nl = [n for n in nl if hex(n).startswith(id) and
1301 1301 self.hasnode(n)]
1302 1302 if nullhex.startswith(id):
1303 1303 nl.append(nullid)
1304 1304 if len(nl) > 0:
1305 1305 if len(nl) == 1 and not maybewdir:
1306 1306 self._pcache[id] = nl[0]
1307 1307 return nl[0]
1308 1308 raise error.AmbiguousPrefixLookupError(
1309 1309 id, self.indexfile, _('ambiguous identifier'))
1310 1310 if maybewdir:
1311 1311 raise error.WdirUnsupported
1312 1312 return None
1313 1313 except TypeError:
1314 1314 pass
1315 1315
1316 1316 def lookup(self, id):
1317 1317 """locate a node based on:
1318 1318 - revision number or str(revision number)
1319 1319 - nodeid or subset of hex nodeid
1320 1320 """
1321 1321 n = self._match(id)
1322 1322 if n is not None:
1323 1323 return n
1324 1324 n = self._partialmatch(id)
1325 1325 if n:
1326 1326 return n
1327 1327
1328 1328 raise error.LookupError(id, self.indexfile, _('no match found'))
1329 1329
1330 1330 def shortest(self, node, minlength=1):
1331 1331 """Find the shortest unambiguous prefix that matches node."""
1332 1332 def isvalid(prefix):
1333 1333 try:
1334 1334 node = self._partialmatch(prefix)
1335 1335 except error.AmbiguousPrefixLookupError:
1336 1336 return False
1337 1337 except error.WdirUnsupported:
1338 1338 # single 'ff...' match
1339 1339 return True
1340 1340 if node is None:
1341 1341 raise error.LookupError(node, self.indexfile, _('no node'))
1342 1342 return True
1343 1343
1344 1344 def maybewdir(prefix):
1345 1345 return all(c == 'f' for c in prefix)
1346 1346
1347 1347 hexnode = hex(node)
1348 1348
1349 1349 def disambiguate(hexnode, minlength):
1350 1350 """Disambiguate against wdirid."""
1351 1351 for length in range(minlength, 41):
1352 1352 prefix = hexnode[:length]
1353 1353 if not maybewdir(prefix):
1354 1354 return prefix
1355 1355
1356 1356 if not getattr(self, 'filteredrevs', None):
1357 1357 try:
1358 1358 length = max(self.index.shortest(node), minlength)
1359 1359 return disambiguate(hexnode, length)
1360 1360 except error.RevlogError:
1361 1361 if node != wdirid:
1362 1362 raise error.LookupError(node, self.indexfile, _('no node'))
1363 1363 except AttributeError:
1364 1364 # Fall through to pure code
1365 1365 pass
1366 1366
1367 1367 if node == wdirid:
1368 1368 for length in range(minlength, 41):
1369 1369 prefix = hexnode[:length]
1370 1370 if isvalid(prefix):
1371 1371 return prefix
1372 1372
1373 1373 for length in range(minlength, 41):
1374 1374 prefix = hexnode[:length]
1375 1375 if isvalid(prefix):
1376 1376 return disambiguate(hexnode, length)
1377 1377
1378 1378 def cmp(self, node, text):
1379 1379 """compare text with a given file revision
1380 1380
1381 1381 returns True if text is different than what is stored.
1382 1382 """
1383 1383 p1, p2 = self.parents(node)
1384 1384 return hash(text, p1, p2) != node
1385 1385
1386 1386 def _cachesegment(self, offset, data):
1387 1387 """Add a segment to the revlog cache.
1388 1388
1389 1389 Accepts an absolute offset and the data that is at that location.
1390 1390 """
1391 1391 o, d = self._chunkcache
1392 1392 # try to add to existing cache
1393 1393 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1394 1394 self._chunkcache = o, d + data
1395 1395 else:
1396 1396 self._chunkcache = offset, data
1397 1397
1398 1398 def _readsegment(self, offset, length, df=None):
1399 1399 """Load a segment of raw data from the revlog.
1400 1400
1401 1401 Accepts an absolute offset, length to read, and an optional existing
1402 1402 file handle to read from.
1403 1403
1404 1404 If an existing file handle is passed, it will be seeked and the
1405 1405 original seek position will NOT be restored.
1406 1406
1407 1407 Returns a str or buffer of raw byte data.
1408 1408 """
1409 1409 # Cache data both forward and backward around the requested
1410 1410 # data, in a fixed size window. This helps speed up operations
1411 1411 # involving reading the revlog backwards.
1412 1412 cachesize = self._chunkcachesize
1413 1413 realoffset = offset & ~(cachesize - 1)
1414 1414 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1415 1415 - realoffset)
1416 1416 with self._datareadfp(df) as df:
1417 1417 df.seek(realoffset)
1418 1418 d = df.read(reallength)
1419 1419 self._cachesegment(realoffset, d)
1420 1420 if offset != realoffset or reallength != length:
1421 1421 return util.buffer(d, offset - realoffset, length)
1422 1422 return d
1423 1423
1424 1424 def _getsegment(self, offset, length, df=None):
1425 1425 """Obtain a segment of raw data from the revlog.
1426 1426
1427 1427 Accepts an absolute offset, length of bytes to obtain, and an
1428 1428 optional file handle to the already-opened revlog. If the file
1429 1429 handle is used, it's original seek position will not be preserved.
1430 1430
1431 1431 Requests for data may be returned from a cache.
1432 1432
1433 1433 Returns a str or a buffer instance of raw byte data.
1434 1434 """
1435 1435 o, d = self._chunkcache
1436 1436 l = len(d)
1437 1437
1438 1438 # is it in the cache?
1439 1439 cachestart = offset - o
1440 1440 cacheend = cachestart + length
1441 1441 if cachestart >= 0 and cacheend <= l:
1442 1442 if cachestart == 0 and cacheend == l:
1443 1443 return d # avoid a copy
1444 1444 return util.buffer(d, cachestart, cacheend - cachestart)
1445 1445
1446 1446 return self._readsegment(offset, length, df=df)
1447 1447
1448 1448 def _getsegmentforrevs(self, startrev, endrev, df=None):
1449 1449 """Obtain a segment of raw data corresponding to a range of revisions.
1450 1450
1451 1451 Accepts the start and end revisions and an optional already-open
1452 1452 file handle to be used for reading. If the file handle is read, its
1453 1453 seek position will not be preserved.
1454 1454
1455 1455 Requests for data may be satisfied by a cache.
1456 1456
1457 1457 Returns a 2-tuple of (offset, data) for the requested range of
1458 1458 revisions. Offset is the integer offset from the beginning of the
1459 1459 revlog and data is a str or buffer of the raw byte data.
1460 1460
1461 1461 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1462 1462 to determine where each revision's data begins and ends.
1463 1463 """
1464 1464 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1465 1465 # (functions are expensive).
1466 1466 index = self.index
1467 1467 istart = index[startrev]
1468 1468 start = int(istart[0] >> 16)
1469 1469 if startrev == endrev:
1470 1470 end = start + istart[1]
1471 1471 else:
1472 1472 iend = index[endrev]
1473 1473 end = int(iend[0] >> 16) + iend[1]
1474 1474
1475 1475 if self._inline:
1476 1476 start += (startrev + 1) * self._io.size
1477 1477 end += (endrev + 1) * self._io.size
1478 1478 length = end - start
1479 1479
1480 1480 return start, self._getsegment(start, length, df=df)
1481 1481
1482 1482 def _chunk(self, rev, df=None):
1483 1483 """Obtain a single decompressed chunk for a revision.
1484 1484
1485 1485 Accepts an integer revision and an optional already-open file handle
1486 1486 to be used for reading. If used, the seek position of the file will not
1487 1487 be preserved.
1488 1488
1489 1489 Returns a str holding uncompressed data for the requested revision.
1490 1490 """
1491 1491 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1492 1492
1493 1493 def _chunks(self, revs, df=None, targetsize=None):
1494 1494 """Obtain decompressed chunks for the specified revisions.
1495 1495
1496 1496 Accepts an iterable of numeric revisions that are assumed to be in
1497 1497 ascending order. Also accepts an optional already-open file handle
1498 1498 to be used for reading. If used, the seek position of the file will
1499 1499 not be preserved.
1500 1500
1501 1501 This function is similar to calling ``self._chunk()`` multiple times,
1502 1502 but is faster.
1503 1503
1504 1504 Returns a list with decompressed data for each requested revision.
1505 1505 """
1506 1506 if not revs:
1507 1507 return []
1508 1508 start = self.start
1509 1509 length = self.length
1510 1510 inline = self._inline
1511 1511 iosize = self._io.size
1512 1512 buffer = util.buffer
1513 1513
1514 1514 l = []
1515 1515 ladd = l.append
1516 1516
1517 1517 if not self._withsparseread:
1518 1518 slicedchunks = (revs,)
1519 1519 else:
1520 1520 slicedchunks = deltautil.slicechunk(self, revs,
1521 1521 targetsize=targetsize)
1522 1522
1523 1523 for revschunk in slicedchunks:
1524 1524 firstrev = revschunk[0]
1525 1525 # Skip trailing revisions with empty diff
1526 1526 for lastrev in revschunk[::-1]:
1527 1527 if length(lastrev) != 0:
1528 1528 break
1529 1529
1530 1530 try:
1531 1531 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1532 1532 except OverflowError:
1533 1533 # issue4215 - we can't cache a run of chunks greater than
1534 1534 # 2G on Windows
1535 1535 return [self._chunk(rev, df=df) for rev in revschunk]
1536 1536
1537 1537 decomp = self.decompress
1538 1538 for rev in revschunk:
1539 1539 chunkstart = start(rev)
1540 1540 if inline:
1541 1541 chunkstart += (rev + 1) * iosize
1542 1542 chunklength = length(rev)
1543 1543 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1544 1544
1545 1545 return l
1546 1546
1547 1547 def _chunkclear(self):
1548 1548 """Clear the raw chunk cache."""
1549 1549 self._chunkcache = (0, '')
1550 1550
1551 1551 def deltaparent(self, rev):
1552 1552 """return deltaparent of the given revision"""
1553 1553 base = self.index[rev][3]
1554 1554 if base == rev:
1555 1555 return nullrev
1556 1556 elif self._generaldelta:
1557 1557 return base
1558 1558 else:
1559 1559 return rev - 1
1560 1560
1561 1561 def issnapshot(self, rev):
1562 1562 """tells whether rev is a snapshot
1563 1563 """
1564 1564 if rev == nullrev:
1565 1565 return True
1566 1566 deltap = self.deltaparent(rev)
1567 1567 if deltap == nullrev:
1568 1568 return True
1569 1569 p1, p2 = self.parentrevs(rev)
1570 1570 if deltap in (p1, p2):
1571 1571 return False
1572 1572 return self.issnapshot(deltap)
1573 1573
1574 1574 def snapshotdepth(self, rev):
1575 1575 """number of snapshot in the chain before this one"""
1576 1576 if not self.issnapshot(rev):
1577 1577 raise error.ProgrammingError('revision %d not a snapshot')
1578 1578 return len(self._deltachain(rev)[0]) - 1
1579 1579
1580 1580 def revdiff(self, rev1, rev2):
1581 1581 """return or calculate a delta between two revisions
1582 1582
1583 1583 The delta calculated is in binary form and is intended to be written to
1584 1584 revlog data directly. So this function needs raw revision data.
1585 1585 """
1586 1586 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1587 1587 return bytes(self._chunk(rev2))
1588 1588
1589 1589 return mdiff.textdiff(self.revision(rev1, raw=True),
1590 1590 self.revision(rev2, raw=True))
1591 1591
1592 1592 def revision(self, nodeorrev, _df=None, raw=False):
1593 1593 """return an uncompressed revision of a given node or revision
1594 1594 number.
1595 1595
1596 1596 _df - an existing file handle to read from. (internal-only)
1597 1597 raw - an optional argument specifying if the revision data is to be
1598 1598 treated as raw data when applying flag transforms. 'raw' should be set
1599 1599 to True when generating changegroups or in debug commands.
1600 1600 """
1601 1601 if isinstance(nodeorrev, int):
1602 1602 rev = nodeorrev
1603 1603 node = self.node(rev)
1604 1604 else:
1605 1605 node = nodeorrev
1606 1606 rev = None
1607 1607
1608 1608 cachedrev = None
1609 1609 flags = None
1610 1610 rawtext = None
1611 1611 if node == nullid:
1612 1612 return ""
1613 1613 if self._cache:
1614 1614 if self._cache[0] == node:
1615 1615 # _cache only stores rawtext
1616 1616 if raw:
1617 1617 return self._cache[2]
1618 1618 # duplicated, but good for perf
1619 1619 if rev is None:
1620 1620 rev = self.rev(node)
1621 1621 if flags is None:
1622 1622 flags = self.flags(rev)
1623 1623 # no extra flags set, no flag processor runs, text = rawtext
1624 1624 if flags == REVIDX_DEFAULT_FLAGS:
1625 1625 return self._cache[2]
1626 1626 # rawtext is reusable. need to run flag processor
1627 1627 rawtext = self._cache[2]
1628 1628
1629 1629 cachedrev = self._cache[1]
1630 1630
1631 1631 # look up what we need to read
1632 1632 if rawtext is None:
1633 1633 if rev is None:
1634 1634 rev = self.rev(node)
1635 1635
1636 1636 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1637 1637 if stopped:
1638 1638 rawtext = self._cache[2]
1639 1639
1640 1640 # drop cache to save memory
1641 1641 self._cache = None
1642 1642
1643 1643 targetsize = None
1644 1644 rawsize = self.index[rev][2]
1645 1645 if 0 <= rawsize:
1646 1646 targetsize = 4 * rawsize
1647 1647
1648 1648 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1649 1649 if rawtext is None:
1650 1650 rawtext = bytes(bins[0])
1651 1651 bins = bins[1:]
1652 1652
1653 1653 rawtext = mdiff.patches(rawtext, bins)
1654 1654 self._cache = (node, rev, rawtext)
1655 1655
1656 1656 if flags is None:
1657 1657 if rev is None:
1658 1658 rev = self.rev(node)
1659 1659 flags = self.flags(rev)
1660 1660
1661 1661 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1662 1662 if validatehash:
1663 1663 self.checkhash(text, node, rev=rev)
1664 1664
1665 1665 return text
1666 1666
1667 1667 def hash(self, text, p1, p2):
1668 1668 """Compute a node hash.
1669 1669
1670 1670 Available as a function so that subclasses can replace the hash
1671 1671 as needed.
1672 1672 """
1673 1673 return hash(text, p1, p2)
1674 1674
1675 1675 def _processflags(self, text, flags, operation, raw=False):
1676 1676 """Inspect revision data flags and applies transforms defined by
1677 1677 registered flag processors.
1678 1678
1679 1679 ``text`` - the revision data to process
1680 1680 ``flags`` - the revision flags
1681 1681 ``operation`` - the operation being performed (read or write)
1682 1682 ``raw`` - an optional argument describing if the raw transform should be
1683 1683 applied.
1684 1684
1685 1685 This method processes the flags in the order (or reverse order if
1686 1686 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1687 1687 flag processors registered for present flags. The order of flags defined
1688 1688 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1689 1689
1690 1690 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1691 1691 processed text and ``validatehash`` is a bool indicating whether the
1692 1692 returned text should be checked for hash integrity.
1693 1693
1694 1694 Note: If the ``raw`` argument is set, it has precedence over the
1695 1695 operation and will only update the value of ``validatehash``.
1696 1696 """
1697 1697 # fast path: no flag processors will run
1698 1698 if flags == 0:
1699 1699 return text, True
1700 1700 if not operation in ('read', 'write'):
1701 1701 raise error.ProgrammingError(_("invalid '%s' operation") %
1702 1702 operation)
1703 1703 # Check all flags are known.
1704 1704 if flags & ~REVIDX_KNOWN_FLAGS:
1705 1705 raise error.RevlogError(_("incompatible revision flag '%#x'") %
1706 1706 (flags & ~REVIDX_KNOWN_FLAGS))
1707 1707 validatehash = True
1708 1708 # Depending on the operation (read or write), the order might be
1709 1709 # reversed due to non-commutative transforms.
1710 1710 orderedflags = REVIDX_FLAGS_ORDER
1711 1711 if operation == 'write':
1712 1712 orderedflags = reversed(orderedflags)
1713 1713
1714 1714 for flag in orderedflags:
1715 1715 # If a flagprocessor has been registered for a known flag, apply the
1716 1716 # related operation transform and update result tuple.
1717 1717 if flag & flags:
1718 1718 vhash = True
1719 1719
1720 1720 if flag not in self._flagprocessors:
1721 1721 message = _("missing processor for flag '%#x'") % (flag)
1722 1722 raise error.RevlogError(message)
1723 1723
1724 1724 processor = self._flagprocessors[flag]
1725 1725 if processor is not None:
1726 1726 readtransform, writetransform, rawtransform = processor
1727 1727
1728 1728 if raw:
1729 1729 vhash = rawtransform(self, text)
1730 1730 elif operation == 'read':
1731 1731 text, vhash = readtransform(self, text)
1732 1732 else: # write operation
1733 1733 text, vhash = writetransform(self, text)
1734 1734 validatehash = validatehash and vhash
1735 1735
1736 1736 return text, validatehash
1737 1737
1738 1738 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1739 1739 """Check node hash integrity.
1740 1740
1741 1741 Available as a function so that subclasses can extend hash mismatch
1742 1742 behaviors as needed.
1743 1743 """
1744 1744 try:
1745 1745 if p1 is None and p2 is None:
1746 1746 p1, p2 = self.parents(node)
1747 1747 if node != self.hash(text, p1, p2):
1748 1748 revornode = rev
1749 1749 if revornode is None:
1750 1750 revornode = templatefilters.short(hex(node))
1751 1751 raise error.RevlogError(_("integrity check failed on %s:%s")
1752 1752 % (self.indexfile, pycompat.bytestr(revornode)))
1753 1753 except error.RevlogError:
1754 1754 if self._censorable and _censoredtext(text):
1755 1755 raise error.CensoredNodeError(self.indexfile, node, text)
1756 1756 raise
1757 1757
1758 1758 def _enforceinlinesize(self, tr, fp=None):
1759 1759 """Check if the revlog is too big for inline and convert if so.
1760 1760
1761 1761 This should be called after revisions are added to the revlog. If the
1762 1762 revlog has grown too large to be an inline revlog, it will convert it
1763 1763 to use multiple index and data files.
1764 1764 """
1765 1765 tiprev = len(self) - 1
1766 1766 if (not self._inline or
1767 1767 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1768 1768 return
1769 1769
1770 1770 trinfo = tr.find(self.indexfile)
1771 1771 if trinfo is None:
1772 1772 raise error.RevlogError(_("%s not found in the transaction")
1773 1773 % self.indexfile)
1774 1774
1775 1775 trindex = trinfo[2]
1776 1776 if trindex is not None:
1777 1777 dataoff = self.start(trindex)
1778 1778 else:
1779 1779 # revlog was stripped at start of transaction, use all leftover data
1780 1780 trindex = len(self) - 1
1781 1781 dataoff = self.end(tiprev)
1782 1782
1783 1783 tr.add(self.datafile, dataoff)
1784 1784
1785 1785 if fp:
1786 1786 fp.flush()
1787 1787 fp.close()
1788 1788
1789 1789 with self._datafp('w') as df:
1790 1790 for r in self:
1791 1791 df.write(self._getsegmentforrevs(r, r)[1])
1792 1792
1793 1793 with self._indexfp('w') as fp:
1794 1794 self.version &= ~FLAG_INLINE_DATA
1795 1795 self._inline = False
1796 1796 io = self._io
1797 1797 for i in self:
1798 1798 e = io.packentry(self.index[i], self.node, self.version, i)
1799 1799 fp.write(e)
1800 1800
1801 1801 # the temp file replace the real index when we exit the context
1802 1802 # manager
1803 1803
1804 1804 tr.replace(self.indexfile, trindex * self._io.size)
1805 1805 self._chunkclear()
1806 1806
1807 1807 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1808 1808 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1809 1809 """add a revision to the log
1810 1810
1811 1811 text - the revision data to add
1812 1812 transaction - the transaction object used for rollback
1813 1813 link - the linkrev data to add
1814 1814 p1, p2 - the parent nodeids of the revision
1815 1815 cachedelta - an optional precomputed delta
1816 1816 node - nodeid of revision; typically node is not specified, and it is
1817 1817 computed by default as hash(text, p1, p2), however subclasses might
1818 1818 use different hashing method (and override checkhash() in such case)
1819 1819 flags - the known flags to set on the revision
1820 1820 deltacomputer - an optional deltacomputer instance shared between
1821 1821 multiple calls
1822 1822 """
1823 1823 if link == nullrev:
1824 1824 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1825 1825 % self.indexfile)
1826 1826
1827 1827 if flags:
1828 1828 node = node or self.hash(text, p1, p2)
1829 1829
1830 1830 rawtext, validatehash = self._processflags(text, flags, 'write')
1831 1831
1832 1832 # If the flag processor modifies the revision data, ignore any provided
1833 1833 # cachedelta.
1834 1834 if rawtext != text:
1835 1835 cachedelta = None
1836 1836
1837 1837 if len(rawtext) > _maxentrysize:
1838 1838 raise error.RevlogError(
1839 1839 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1840 1840 % (self.indexfile, len(rawtext)))
1841 1841
1842 1842 node = node or self.hash(rawtext, p1, p2)
1843 1843 if node in self.nodemap:
1844 1844 return node
1845 1845
1846 1846 if validatehash:
1847 1847 self.checkhash(rawtext, node, p1=p1, p2=p2)
1848 1848
1849 1849 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1850 1850 flags, cachedelta=cachedelta,
1851 1851 deltacomputer=deltacomputer)
1852 1852
1853 1853 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1854 1854 cachedelta=None, deltacomputer=None):
1855 1855 """add a raw revision with known flags, node and parents
1856 1856 useful when reusing a revision not stored in this revlog (ex: received
1857 1857 over wire, or read from an external bundle).
1858 1858 """
1859 1859 dfh = None
1860 1860 if not self._inline:
1861 1861 dfh = self._datafp("a+")
1862 1862 ifh = self._indexfp("a+")
1863 1863 try:
1864 1864 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1865 1865 flags, cachedelta, ifh, dfh,
1866 1866 deltacomputer=deltacomputer)
1867 1867 finally:
1868 1868 if dfh:
1869 1869 dfh.close()
1870 1870 ifh.close()
1871 1871
1872 1872 def compress(self, data):
1873 1873 """Generate a possibly-compressed representation of data."""
1874 1874 if not data:
1875 1875 return '', data
1876 1876
1877 1877 compressed = self._compressor.compress(data)
1878 1878
1879 1879 if compressed:
1880 1880 # The revlog compressor added the header in the returned data.
1881 1881 return '', compressed
1882 1882
1883 1883 if data[0:1] == '\0':
1884 1884 return '', data
1885 1885 return 'u', data
1886 1886
1887 1887 def decompress(self, data):
1888 1888 """Decompress a revlog chunk.
1889 1889
1890 1890 The chunk is expected to begin with a header identifying the
1891 1891 format type so it can be routed to an appropriate decompressor.
1892 1892 """
1893 1893 if not data:
1894 1894 return data
1895 1895
1896 1896 # Revlogs are read much more frequently than they are written and many
1897 1897 # chunks only take microseconds to decompress, so performance is
1898 1898 # important here.
1899 1899 #
1900 1900 # We can make a few assumptions about revlogs:
1901 1901 #
1902 1902 # 1) the majority of chunks will be compressed (as opposed to inline
1903 1903 # raw data).
1904 1904 # 2) decompressing *any* data will likely by at least 10x slower than
1905 1905 # returning raw inline data.
1906 1906 # 3) we want to prioritize common and officially supported compression
1907 1907 # engines
1908 1908 #
1909 1909 # It follows that we want to optimize for "decompress compressed data
1910 1910 # when encoded with common and officially supported compression engines"
1911 1911 # case over "raw data" and "data encoded by less common or non-official
1912 1912 # compression engines." That is why we have the inline lookup first
1913 1913 # followed by the compengines lookup.
1914 1914 #
1915 1915 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1916 1916 # compressed chunks. And this matters for changelog and manifest reads.
1917 1917 t = data[0:1]
1918 1918
1919 1919 if t == 'x':
1920 1920 try:
1921 1921 return _zlibdecompress(data)
1922 1922 except zlib.error as e:
1923 1923 raise error.RevlogError(_('revlog decompress error: %s') %
1924 1924 stringutil.forcebytestr(e))
1925 1925 # '\0' is more common than 'u' so it goes first.
1926 1926 elif t == '\0':
1927 1927 return data
1928 1928 elif t == 'u':
1929 1929 return util.buffer(data, 1)
1930 1930
1931 1931 try:
1932 1932 compressor = self._decompressors[t]
1933 1933 except KeyError:
1934 1934 try:
1935 1935 engine = util.compengines.forrevlogheader(t)
1936 1936 compressor = engine.revlogcompressor()
1937 1937 self._decompressors[t] = compressor
1938 1938 except KeyError:
1939 1939 raise error.RevlogError(_('unknown compression type %r') % t)
1940 1940
1941 1941 return compressor.decompress(data)
1942 1942
1943 1943 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1944 1944 cachedelta, ifh, dfh, alwayscache=False,
1945 1945 deltacomputer=None):
1946 1946 """internal function to add revisions to the log
1947 1947
1948 1948 see addrevision for argument descriptions.
1949 1949
1950 1950 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1951 1951
1952 1952 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
1953 1953 be used.
1954 1954
1955 1955 invariants:
1956 1956 - rawtext is optional (can be None); if not set, cachedelta must be set.
1957 1957 if both are set, they must correspond to each other.
1958 1958 """
1959 1959 if node == nullid:
1960 1960 raise error.RevlogError(_("%s: attempt to add null revision") %
1961 1961 self.indexfile)
1962 1962 if node == wdirid or node in wdirfilenodeids:
1963 1963 raise error.RevlogError(_("%s: attempt to add wdir revision") %
1964 1964 self.indexfile)
1965 1965
1966 1966 if self._inline:
1967 1967 fh = ifh
1968 1968 else:
1969 1969 fh = dfh
1970 1970
1971 1971 btext = [rawtext]
1972 1972
1973 1973 curr = len(self)
1974 1974 prev = curr - 1
1975 1975 offset = self.end(prev)
1976 1976 p1r, p2r = self.rev(p1), self.rev(p2)
1977 1977
1978 1978 # full versions are inserted when the needed deltas
1979 1979 # become comparable to the uncompressed text
1980 1980 if rawtext is None:
1981 1981 # need rawtext size, before changed by flag processors, which is
1982 1982 # the non-raw size. use revlog explicitly to avoid filelog's extra
1983 1983 # logic that might remove metadata size.
1984 1984 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
1985 1985 cachedelta[1])
1986 1986 else:
1987 1987 textlen = len(rawtext)
1988 1988
1989 1989 if deltacomputer is None:
1990 1990 deltacomputer = deltautil.deltacomputer(self)
1991 1991
1992 1992 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
1993 1993
1994 1994 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
1995 1995
1996 1996 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
1997 1997 deltainfo.base, link, p1r, p2r, node)
1998 1998 self.index.append(e)
1999 1999 self.nodemap[node] = curr
2000 2000
2001 2001 entry = self._io.packentry(e, self.node, self.version, curr)
2002 2002 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
2003 2003 link, offset)
2004 2004
2005 2005 rawtext = btext[0]
2006 2006
2007 2007 if alwayscache and rawtext is None:
2008 2008 rawtext = deltacomputer.buildtext(revinfo, fh)
2009 2009
2010 2010 if type(rawtext) == bytes: # only accept immutable objects
2011 2011 self._cache = (node, curr, rawtext)
2012 2012 self._chainbasecache[curr] = deltainfo.chainbase
2013 2013 return node
2014 2014
2015 2015 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2016 2016 # Files opened in a+ mode have inconsistent behavior on various
2017 2017 # platforms. Windows requires that a file positioning call be made
2018 2018 # when the file handle transitions between reads and writes. See
2019 2019 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2020 2020 # platforms, Python or the platform itself can be buggy. Some versions
2021 2021 # of Solaris have been observed to not append at the end of the file
2022 2022 # if the file was seeked to before the end. See issue4943 for more.
2023 2023 #
2024 2024 # We work around this issue by inserting a seek() before writing.
2025 2025 # Note: This is likely not necessary on Python 3.
2026 2026 ifh.seek(0, os.SEEK_END)
2027 2027 if dfh:
2028 2028 dfh.seek(0, os.SEEK_END)
2029 2029
2030 2030 curr = len(self) - 1
2031 2031 if not self._inline:
2032 2032 transaction.add(self.datafile, offset)
2033 2033 transaction.add(self.indexfile, curr * len(entry))
2034 2034 if data[0]:
2035 2035 dfh.write(data[0])
2036 2036 dfh.write(data[1])
2037 2037 ifh.write(entry)
2038 2038 else:
2039 2039 offset += curr * self._io.size
2040 2040 transaction.add(self.indexfile, offset, curr)
2041 2041 ifh.write(entry)
2042 2042 ifh.write(data[0])
2043 2043 ifh.write(data[1])
2044 2044 self._enforceinlinesize(transaction, ifh)
2045 2045
2046 2046 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2047 2047 """
2048 2048 add a delta group
2049 2049
2050 2050 given a set of deltas, add them to the revision log. the
2051 2051 first delta is against its parent, which should be in our
2052 2052 log, the rest are against the previous delta.
2053 2053
2054 2054 If ``addrevisioncb`` is defined, it will be called with arguments of
2055 2055 this revlog and the node that was added.
2056 2056 """
2057 2057
2058 2058 nodes = []
2059 2059
2060 2060 r = len(self)
2061 2061 end = 0
2062 2062 if r:
2063 2063 end = self.end(r - 1)
2064 2064 ifh = self._indexfp("a+")
2065 2065 isize = r * self._io.size
2066 2066 if self._inline:
2067 2067 transaction.add(self.indexfile, end + isize, r)
2068 2068 dfh = None
2069 2069 else:
2070 2070 transaction.add(self.indexfile, isize, r)
2071 2071 transaction.add(self.datafile, end)
2072 2072 dfh = self._datafp("a+")
2073 2073 def flush():
2074 2074 if dfh:
2075 2075 dfh.flush()
2076 2076 ifh.flush()
2077 2077 try:
2078 2078 deltacomputer = deltautil.deltacomputer(self)
2079 2079 # loop through our set of deltas
2080 2080 for data in deltas:
2081 2081 node, p1, p2, linknode, deltabase, delta, flags = data
2082 2082 link = linkmapper(linknode)
2083 2083 flags = flags or REVIDX_DEFAULT_FLAGS
2084 2084
2085 2085 nodes.append(node)
2086 2086
2087 2087 if node in self.nodemap:
2088 2088 # this can happen if two branches make the same change
2089 2089 continue
2090 2090
2091 2091 for p in (p1, p2):
2092 2092 if p not in self.nodemap:
2093 2093 raise error.LookupError(p, self.indexfile,
2094 2094 _('unknown parent'))
2095 2095
2096 2096 if deltabase not in self.nodemap:
2097 2097 raise error.LookupError(deltabase, self.indexfile,
2098 2098 _('unknown delta base'))
2099 2099
2100 2100 baserev = self.rev(deltabase)
2101 2101
2102 2102 if baserev != nullrev and self.iscensored(baserev):
2103 2103 # if base is censored, delta must be full replacement in a
2104 2104 # single patch operation
2105 2105 hlen = struct.calcsize(">lll")
2106 2106 oldlen = self.rawsize(baserev)
2107 2107 newlen = len(delta) - hlen
2108 2108 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2109 2109 raise error.CensoredBaseError(self.indexfile,
2110 2110 self.node(baserev))
2111 2111
2112 2112 if not flags and self._peek_iscensored(baserev, delta, flush):
2113 2113 flags |= REVIDX_ISCENSORED
2114 2114
2115 2115 # We assume consumers of addrevisioncb will want to retrieve
2116 2116 # the added revision, which will require a call to
2117 2117 # revision(). revision() will fast path if there is a cache
2118 2118 # hit. So, we tell _addrevision() to always cache in this case.
2119 2119 # We're only using addgroup() in the context of changegroup
2120 2120 # generation so the revision data can always be handled as raw
2121 2121 # by the flagprocessor.
2122 2122 self._addrevision(node, None, transaction, link,
2123 2123 p1, p2, flags, (baserev, delta),
2124 2124 ifh, dfh,
2125 2125 alwayscache=bool(addrevisioncb),
2126 2126 deltacomputer=deltacomputer)
2127 2127
2128 2128 if addrevisioncb:
2129 2129 addrevisioncb(self, node)
2130 2130
2131 2131 if not dfh and not self._inline:
2132 2132 # addrevision switched from inline to conventional
2133 2133 # reopen the index
2134 2134 ifh.close()
2135 2135 dfh = self._datafp("a+")
2136 2136 ifh = self._indexfp("a+")
2137 2137 finally:
2138 2138 if dfh:
2139 2139 dfh.close()
2140 2140 ifh.close()
2141 2141
2142 2142 return nodes
2143 2143
2144 2144 def iscensored(self, rev):
2145 2145 """Check if a file revision is censored."""
2146 2146 if not self._censorable:
2147 2147 return False
2148 2148
2149 2149 return self.flags(rev) & REVIDX_ISCENSORED
2150 2150
2151 2151 def _peek_iscensored(self, baserev, delta, flush):
2152 2152 """Quickly check if a delta produces a censored revision."""
2153 2153 if not self._censorable:
2154 2154 return False
2155 2155
2156 2156 # Fragile heuristic: unless new file meta keys are added alphabetically
2157 2157 # preceding "censored", all censored revisions are prefixed by
2158 2158 # "\1\ncensored:". A delta producing such a censored revision must be a
2159 2159 # full-replacement delta, so we inspect the first and only patch in the
2160 2160 # delta for this prefix.
2161 2161 hlen = struct.calcsize(">lll")
2162 2162 if len(delta) <= hlen:
2163 2163 return False
2164 2164
2165 2165 oldlen = self.rawsize(baserev)
2166 2166 newlen = len(delta) - hlen
2167 2167 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2168 2168 return False
2169 2169
2170 2170 add = "\1\ncensored:"
2171 2171 addlen = len(add)
2172 2172 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2173 2173
2174 2174 def getstrippoint(self, minlink):
2175 2175 """find the minimum rev that must be stripped to strip the linkrev
2176 2176
2177 2177 Returns a tuple containing the minimum rev and a set of all revs that
2178 2178 have linkrevs that will be broken by this strip.
2179 2179 """
2180 2180 brokenrevs = set()
2181 2181 strippoint = len(self)
2182 2182
2183 2183 heads = {}
2184 2184 futurelargelinkrevs = set()
2185 2185 for head in self.headrevs():
2186 2186 headlinkrev = self.linkrev(head)
2187 2187 heads[head] = headlinkrev
2188 2188 if headlinkrev >= minlink:
2189 2189 futurelargelinkrevs.add(headlinkrev)
2190 2190
2191 2191 # This algorithm involves walking down the rev graph, starting at the
2192 2192 # heads. Since the revs are topologically sorted according to linkrev,
2193 2193 # once all head linkrevs are below the minlink, we know there are
2194 2194 # no more revs that could have a linkrev greater than minlink.
2195 2195 # So we can stop walking.
2196 2196 while futurelargelinkrevs:
2197 2197 strippoint -= 1
2198 2198 linkrev = heads.pop(strippoint)
2199 2199
2200 2200 if linkrev < minlink:
2201 2201 brokenrevs.add(strippoint)
2202 2202 else:
2203 2203 futurelargelinkrevs.remove(linkrev)
2204 2204
2205 2205 for p in self.parentrevs(strippoint):
2206 2206 if p != nullrev:
2207 2207 plinkrev = self.linkrev(p)
2208 2208 heads[p] = plinkrev
2209 2209 if plinkrev >= minlink:
2210 2210 futurelargelinkrevs.add(plinkrev)
2211 2211
2212 2212 return strippoint, brokenrevs
2213 2213
2214 2214 def strip(self, minlink, transaction):
2215 2215 """truncate the revlog on the first revision with a linkrev >= minlink
2216 2216
2217 2217 This function is called when we're stripping revision minlink and
2218 2218 its descendants from the repository.
2219 2219
2220 2220 We have to remove all revisions with linkrev >= minlink, because
2221 2221 the equivalent changelog revisions will be renumbered after the
2222 2222 strip.
2223 2223
2224 2224 So we truncate the revlog on the first of these revisions, and
2225 2225 trust that the caller has saved the revisions that shouldn't be
2226 2226 removed and that it'll re-add them after this truncation.
2227 2227 """
2228 2228 if len(self) == 0:
2229 2229 return
2230 2230
2231 2231 rev, _ = self.getstrippoint(minlink)
2232 2232 if rev == len(self):
2233 2233 return
2234 2234
2235 2235 # first truncate the files on disk
2236 2236 end = self.start(rev)
2237 2237 if not self._inline:
2238 2238 transaction.add(self.datafile, end)
2239 2239 end = rev * self._io.size
2240 2240 else:
2241 2241 end += rev * self._io.size
2242 2242
2243 2243 transaction.add(self.indexfile, end)
2244 2244
2245 2245 # then reset internal state in memory to forget those revisions
2246 2246 self._cache = None
2247 2247 self._chaininfocache = {}
2248 2248 self._chunkclear()
2249 2249 for x in pycompat.xrange(rev, len(self)):
2250 2250 del self.nodemap[self.node(x)]
2251 2251
2252 2252 del self.index[rev:-1]
2253 2253 self._nodepos = None
2254 2254
2255 2255 def checksize(self):
2256 2256 expected = 0
2257 2257 if len(self):
2258 2258 expected = max(0, self.end(len(self) - 1))
2259 2259
2260 2260 try:
2261 2261 with self._datafp() as f:
2262 2262 f.seek(0, 2)
2263 2263 actual = f.tell()
2264 2264 dd = actual - expected
2265 2265 except IOError as inst:
2266 2266 if inst.errno != errno.ENOENT:
2267 2267 raise
2268 2268 dd = 0
2269 2269
2270 2270 try:
2271 2271 f = self.opener(self.indexfile)
2272 2272 f.seek(0, 2)
2273 2273 actual = f.tell()
2274 2274 f.close()
2275 2275 s = self._io.size
2276 2276 i = max(0, actual // s)
2277 2277 di = actual - (i * s)
2278 2278 if self._inline:
2279 2279 databytes = 0
2280 2280 for r in self:
2281 2281 databytes += max(0, self.length(r))
2282 2282 dd = 0
2283 2283 di = actual - len(self) * s - databytes
2284 2284 except IOError as inst:
2285 2285 if inst.errno != errno.ENOENT:
2286 2286 raise
2287 2287 di = 0
2288 2288
2289 2289 return (dd, di)
2290 2290
2291 2291 def files(self):
2292 2292 res = [self.indexfile]
2293 2293 if not self._inline:
2294 2294 res.append(self.datafile)
2295 2295 return res
2296 2296
2297 def emitrevisiondeltas(self, requests):
2298 frev = self.rev
2299
2300 prevrev = None
2301 for request in requests:
2302 node = request.node
2303 rev = frev(node)
2304
2305 if prevrev is None:
2306 prevrev = self.index[rev][5]
2307
2308 # Requesting a full revision.
2309 if request.basenode == nullid:
2310 baserev = nullrev
2311 # Requesting an explicit revision.
2312 elif request.basenode is not None:
2313 baserev = frev(request.basenode)
2314 # Allowing us to choose.
2315 else:
2316 p1rev, p2rev = self.parentrevs(rev)
2317 deltaparentrev = self.deltaparent(rev)
2318
2319 # Avoid sending full revisions when delta parent is null. Pick
2320 # prev in that case. It's tempting to pick p1 in this case, as
2321 # p1 will be smaller in the common case. However, computing a
2322 # delta against p1 may require resolving the raw text of p1,
2323 # which could be expensive. The revlog caches should have prev
2324 # cached, meaning less CPU for delta generation. There is
2325 # likely room to add a flag and/or config option to control this
2326 # behavior.
2327 if deltaparentrev == nullrev and self._storedeltachains:
2328 baserev = prevrev
2329
2330 # Revlog is configured to use full snapshot for a reason.
2331 # Stick to full snapshot.
2332 elif deltaparentrev == nullrev:
2333 baserev = nullrev
2334
2335 # Pick previous when we can't be sure the base is available
2336 # on consumer.
2337 elif deltaparentrev not in (p1rev, p2rev, prevrev):
2338 baserev = prevrev
2339 else:
2340 baserev = deltaparentrev
2341
2342 if baserev != nullrev and not self.candelta(baserev, rev):
2343 baserev = nullrev
2344
2345 revision = None
2346 delta = None
2347 baserevisionsize = None
2348
2349 if self.iscensored(baserev) or self.iscensored(rev):
2350 try:
2351 revision = self.revision(node, raw=True)
2352 except error.CensoredNodeError as e:
2353 revision = e.tombstone
2354
2355 if baserev != nullrev:
2356 baserevisionsize = self.rawsize(baserev)
2357
2358 elif baserev == nullrev:
2359 revision = self.revision(node, raw=True)
2360 else:
2361 delta = self.revdiff(baserev, rev)
2362
2363 extraflags = REVIDX_ELLIPSIS if request.ellipsis else 0
2364
2365 yield revlogrevisiondelta(
2366 node=node,
2367 p1node=request.p1node,
2368 p2node=request.p2node,
2369 linknode=request.linknode,
2370 basenode=self.node(baserev),
2371 flags=self.flags(rev) | extraflags,
2372 baserevisionsize=baserevisionsize,
2373 revision=revision,
2374 delta=delta)
2375
2376 prevrev = rev
2377
2378 2297 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2379 2298 assumehaveparentrevisions=False, deltaprevious=False):
2380 2299 if nodesorder not in ('nodes', 'storage', None):
2381 2300 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2382 2301 nodesorder)
2383 2302
2384 2303 if nodesorder is None and not self._generaldelta:
2385 2304 nodesorder = 'storage'
2386 2305
2387 2306 frev = self.rev
2388 2307 fnode = self.node
2389 2308
2390 2309 if nodesorder == 'nodes':
2391 2310 revs = [frev(n) for n in nodes]
2392 2311 elif nodesorder == 'storage':
2393 2312 revs = sorted(frev(n) for n in nodes)
2394 2313 else:
2395 2314 assert self._generaldelta
2396 2315 revs = set(frev(n) for n in nodes)
2397 2316 revs = dagop.linearize(revs, self.parentrevs)
2398 2317
2399 2318 prevrev = None
2400 2319
2401 2320 if deltaprevious or assumehaveparentrevisions:
2402 2321 prevrev = self.parentrevs(revs[0])[0]
2403 2322
2404 2323 # Set of revs available to delta against.
2405 2324 available = set()
2406 2325
2407 2326 for rev in revs:
2408 2327 if rev == nullrev:
2409 2328 continue
2410 2329
2411 2330 node = fnode(rev)
2412 2331 deltaparentrev = self.deltaparent(rev)
2413 2332 p1rev, p2rev = self.parentrevs(rev)
2414 2333
2415 2334 # Forced delta against previous mode.
2416 2335 if deltaprevious:
2417 2336 baserev = prevrev
2418 2337
2419 2338 # Revlog is configured to use full snapshots. Stick to that.
2420 2339 elif not self._storedeltachains:
2421 2340 baserev = nullrev
2422 2341
2423 2342 # There is a delta in storage. We try to use that because it
2424 2343 # amounts to effectively copying data from storage and is
2425 2344 # therefore the fastest.
2426 2345 elif deltaparentrev != nullrev:
2427 2346 # Base revision was already emitted in this group. We can
2428 2347 # always safely use the delta.
2429 2348 if deltaparentrev in available:
2430 2349 baserev = deltaparentrev
2431 2350
2432 2351 # Base revision is a parent that hasn't been emitted already.
2433 2352 # Use it if we can assume the receiver has the parent revision.
2434 2353 elif (assumehaveparentrevisions
2435 2354 and deltaparentrev in (p1rev, p2rev)):
2436 2355 baserev = deltaparentrev
2437 2356
2438 2357 # No guarantee the receiver has the delta parent. Send delta
2439 2358 # against last revision (if possible), which in the common case
2440 2359 # should be similar enough to this revision that the delta is
2441 2360 # reasonable.
2442 2361 elif prevrev is not None:
2443 2362 baserev = prevrev
2444 2363 else:
2445 2364 baserev = nullrev
2446 2365
2447 2366 # Storage has a fulltext revision.
2448 2367
2449 2368 # Let's use the previous revision, which is as good a guess as any.
2450 2369 # There is definitely room to improve this logic.
2451 2370 elif prevrev is not None:
2452 2371 baserev = prevrev
2453 2372 else:
2454 2373 baserev = nullrev
2455 2374
2456 2375 # But we can't actually use our chosen delta base for whatever
2457 2376 # reason. Reset to fulltext.
2458 2377 if baserev != nullrev and not self.candelta(baserev, rev):
2459 2378 baserev = nullrev
2460 2379
2461 2380 revision = None
2462 2381 delta = None
2463 2382 baserevisionsize = None
2464 2383
2465 2384 if revisiondata:
2466 2385 if self.iscensored(baserev) or self.iscensored(rev):
2467 2386 try:
2468 2387 revision = self.revision(node, raw=True)
2469 2388 except error.CensoredNodeError as e:
2470 2389 revision = e.tombstone
2471 2390
2472 2391 if baserev != nullrev:
2473 2392 baserevisionsize = self.rawsize(baserev)
2474 2393
2475 2394 elif baserev == nullrev and not deltaprevious:
2476 2395 revision = self.revision(node, raw=True)
2477 2396 available.add(rev)
2478 2397 else:
2479 2398 delta = self.revdiff(baserev, rev)
2480 2399 available.add(rev)
2481 2400
2482 2401 yield revlogrevisiondelta(
2483 2402 node=node,
2484 2403 p1node=fnode(p1rev),
2485 2404 p2node=fnode(p2rev),
2486 2405 basenode=fnode(baserev),
2487 2406 flags=self.flags(rev),
2488 2407 baserevisionsize=baserevisionsize,
2489 2408 revision=revision,
2490 2409 delta=delta)
2491 2410
2492 2411 prevrev = rev
2493 2412
2494 2413 DELTAREUSEALWAYS = 'always'
2495 2414 DELTAREUSESAMEREVS = 'samerevs'
2496 2415 DELTAREUSENEVER = 'never'
2497 2416
2498 2417 DELTAREUSEFULLADD = 'fulladd'
2499 2418
2500 2419 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2501 2420
2502 2421 def clone(self, tr, destrevlog, addrevisioncb=None,
2503 2422 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
2504 2423 """Copy this revlog to another, possibly with format changes.
2505 2424
2506 2425 The destination revlog will contain the same revisions and nodes.
2507 2426 However, it may not be bit-for-bit identical due to e.g. delta encoding
2508 2427 differences.
2509 2428
2510 2429 The ``deltareuse`` argument control how deltas from the existing revlog
2511 2430 are preserved in the destination revlog. The argument can have the
2512 2431 following values:
2513 2432
2514 2433 DELTAREUSEALWAYS
2515 2434 Deltas will always be reused (if possible), even if the destination
2516 2435 revlog would not select the same revisions for the delta. This is the
2517 2436 fastest mode of operation.
2518 2437 DELTAREUSESAMEREVS
2519 2438 Deltas will be reused if the destination revlog would pick the same
2520 2439 revisions for the delta. This mode strikes a balance between speed
2521 2440 and optimization.
2522 2441 DELTAREUSENEVER
2523 2442 Deltas will never be reused. This is the slowest mode of execution.
2524 2443 This mode can be used to recompute deltas (e.g. if the diff/delta
2525 2444 algorithm changes).
2526 2445
2527 2446 Delta computation can be slow, so the choice of delta reuse policy can
2528 2447 significantly affect run time.
2529 2448
2530 2449 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2531 2450 two extremes. Deltas will be reused if they are appropriate. But if the
2532 2451 delta could choose a better revision, it will do so. This means if you
2533 2452 are converting a non-generaldelta revlog to a generaldelta revlog,
2534 2453 deltas will be recomputed if the delta's parent isn't a parent of the
2535 2454 revision.
2536 2455
2537 2456 In addition to the delta policy, the ``deltabothparents`` argument
2538 2457 controls whether to compute deltas against both parents for merges.
2539 2458 By default, the current default is used.
2540 2459 """
2541 2460 if deltareuse not in self.DELTAREUSEALL:
2542 2461 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2543 2462
2544 2463 if len(destrevlog):
2545 2464 raise ValueError(_('destination revlog is not empty'))
2546 2465
2547 2466 if getattr(self, 'filteredrevs', None):
2548 2467 raise ValueError(_('source revlog has filtered revisions'))
2549 2468 if getattr(destrevlog, 'filteredrevs', None):
2550 2469 raise ValueError(_('destination revlog has filtered revisions'))
2551 2470
2552 2471 # lazydeltabase controls whether to reuse a cached delta, if possible.
2553 2472 oldlazydeltabase = destrevlog._lazydeltabase
2554 2473 oldamd = destrevlog._deltabothparents
2555 2474
2556 2475 try:
2557 2476 if deltareuse == self.DELTAREUSEALWAYS:
2558 2477 destrevlog._lazydeltabase = True
2559 2478 elif deltareuse == self.DELTAREUSESAMEREVS:
2560 2479 destrevlog._lazydeltabase = False
2561 2480
2562 2481 destrevlog._deltabothparents = deltabothparents or oldamd
2563 2482
2564 2483 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2565 2484 self.DELTAREUSESAMEREVS)
2566 2485
2567 2486 deltacomputer = deltautil.deltacomputer(destrevlog)
2568 2487 index = self.index
2569 2488 for rev in self:
2570 2489 entry = index[rev]
2571 2490
2572 2491 # Some classes override linkrev to take filtered revs into
2573 2492 # account. Use raw entry from index.
2574 2493 flags = entry[0] & 0xffff
2575 2494 linkrev = entry[4]
2576 2495 p1 = index[entry[5]][7]
2577 2496 p2 = index[entry[6]][7]
2578 2497 node = entry[7]
2579 2498
2580 2499 # (Possibly) reuse the delta from the revlog if allowed and
2581 2500 # the revlog chunk is a delta.
2582 2501 cachedelta = None
2583 2502 rawtext = None
2584 2503 if populatecachedelta:
2585 2504 dp = self.deltaparent(rev)
2586 2505 if dp != nullrev:
2587 2506 cachedelta = (dp, bytes(self._chunk(rev)))
2588 2507
2589 2508 if not cachedelta:
2590 2509 rawtext = self.revision(rev, raw=True)
2591 2510
2592 2511
2593 2512 if deltareuse == self.DELTAREUSEFULLADD:
2594 2513 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2595 2514 cachedelta=cachedelta,
2596 2515 node=node, flags=flags,
2597 2516 deltacomputer=deltacomputer)
2598 2517 else:
2599 2518 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2600 2519 checkambig=False)
2601 2520 dfh = None
2602 2521 if not destrevlog._inline:
2603 2522 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2604 2523 try:
2605 2524 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2606 2525 p2, flags, cachedelta, ifh, dfh,
2607 2526 deltacomputer=deltacomputer)
2608 2527 finally:
2609 2528 if dfh:
2610 2529 dfh.close()
2611 2530 ifh.close()
2612 2531
2613 2532 if addrevisioncb:
2614 2533 addrevisioncb(self, rev, node)
2615 2534 finally:
2616 2535 destrevlog._lazydeltabase = oldlazydeltabase
2617 2536 destrevlog._deltabothparents = oldamd
2618 2537
2619 2538 def censorrevision(self, node, tombstone=b''):
2620 2539 if (self.version & 0xFFFF) == REVLOGV0:
2621 2540 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2622 2541 self.version)
2623 2542
2624 2543 rev = self.rev(node)
2625 2544 tombstone = packmeta({b'censored': tombstone}, b'')
2626 2545
2627 2546 if len(tombstone) > self.rawsize(rev):
2628 2547 raise error.Abort(_('censor tombstone must be no longer than '
2629 2548 'censored data'))
2630 2549
2631 2550 # Using two files instead of one makes it easy to rewrite entry-by-entry
2632 2551 idxread = self.opener(self.indexfile, 'r')
2633 2552 idxwrite = self.opener(self.indexfile, 'wb', atomictemp=True)
2634 2553 if self.version & FLAG_INLINE_DATA:
2635 2554 dataread, datawrite = idxread, idxwrite
2636 2555 else:
2637 2556 dataread = self.opener(self.datafile, 'r')
2638 2557 datawrite = self.opener(self.datafile, 'wb', atomictemp=True)
2639 2558
2640 2559 # Copy all revlog data up to the entry to be censored.
2641 2560 offset = self.start(rev)
2642 2561
2643 2562 for chunk in util.filechunkiter(idxread, limit=rev * self._io.size):
2644 2563 idxwrite.write(chunk)
2645 2564 for chunk in util.filechunkiter(dataread, limit=offset):
2646 2565 datawrite.write(chunk)
2647 2566
2648 2567 def rewriteindex(r, newoffs, newdata=None):
2649 2568 """Rewrite the index entry with a new data offset and new data.
2650 2569
2651 2570 The newdata argument, if given, is a tuple of three positive
2652 2571 integers: (new compressed, new uncompressed, added flag bits).
2653 2572 """
2654 2573 offlags, comp, uncomp, base, link, p1, p2, nodeid = self.index[r]
2655 2574 flags = gettype(offlags)
2656 2575 if newdata:
2657 2576 comp, uncomp, nflags = newdata
2658 2577 flags |= nflags
2659 2578 offlags = offset_type(newoffs, flags)
2660 2579 e = (offlags, comp, uncomp, r, link, p1, p2, nodeid)
2661 2580 idxwrite.write(self._io.packentry(e, None, self.version, r))
2662 2581 idxread.seek(self._io.size, 1)
2663 2582
2664 2583 def rewrite(r, offs, data, nflags=REVIDX_DEFAULT_FLAGS):
2665 2584 """Write the given fulltext with the given data offset.
2666 2585
2667 2586 Returns:
2668 2587 The integer number of data bytes written, for tracking data
2669 2588 offsets.
2670 2589 """
2671 2590 flag, compdata = self.compress(data)
2672 2591 newcomp = len(flag) + len(compdata)
2673 2592 rewriteindex(r, offs, (newcomp, len(data), nflags))
2674 2593 datawrite.write(flag)
2675 2594 datawrite.write(compdata)
2676 2595 dataread.seek(self.length(r), 1)
2677 2596 return newcomp
2678 2597
2679 2598 # Rewrite censored entry with (padded) tombstone data.
2680 2599 pad = ' ' * (self.rawsize(rev) - len(tombstone))
2681 2600 offset += rewrite(rev, offset, tombstone + pad, REVIDX_ISCENSORED)
2682 2601
2683 2602 # Rewrite all following filelog revisions fixing up offsets and deltas.
2684 2603 for srev in pycompat.xrange(rev + 1, len(self)):
2685 2604 if rev in self.parentrevs(srev):
2686 2605 # Immediate children of censored node must be re-added as
2687 2606 # fulltext.
2688 2607 try:
2689 2608 revdata = self.revision(srev)
2690 2609 except error.CensoredNodeError as e:
2691 2610 revdata = e.tombstone
2692 2611 dlen = rewrite(srev, offset, revdata)
2693 2612 else:
2694 2613 # Copy any other revision data verbatim after fixing up the
2695 2614 # offset.
2696 2615 rewriteindex(srev, offset)
2697 2616 dlen = self.length(srev)
2698 2617 for chunk in util.filechunkiter(dataread, limit=dlen):
2699 2618 datawrite.write(chunk)
2700 2619 offset += dlen
2701 2620
2702 2621 idxread.close()
2703 2622 idxwrite.close()
2704 2623 if dataread is not idxread:
2705 2624 dataread.close()
2706 2625 datawrite.close()
2707 2626
2708 2627 def verifyintegrity(self, state):
2709 2628 """Verifies the integrity of the revlog.
2710 2629
2711 2630 Yields ``revlogproblem`` instances describing problems that are
2712 2631 found.
2713 2632 """
2714 2633 dd, di = self.checksize()
2715 2634 if dd:
2716 2635 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2717 2636 if di:
2718 2637 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2719 2638
2720 2639 version = self.version & 0xFFFF
2721 2640
2722 2641 # The verifier tells us what version revlog we should be.
2723 2642 if version != state['expectedversion']:
2724 2643 yield revlogproblem(
2725 2644 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2726 2645 (self.indexfile, version, state['expectedversion']))
@@ -1,1229 +1,1105 b''
1 1 # storage.py - Testing of storage primitives.
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 from __future__ import absolute_import
9 9
10 10 import unittest
11 11
12 12 from ..node import (
13 13 hex,
14 14 nullid,
15 15 nullrev,
16 16 )
17 17 from .. import (
18 18 error,
19 19 mdiff,
20 20 revlog,
21 21 )
22 22
23 23 class basetestcase(unittest.TestCase):
24 24 if not getattr(unittest.TestCase, r'assertRaisesRegex', False):
25 25 assertRaisesRegex = (# camelcase-required
26 26 unittest.TestCase.assertRaisesRegexp)
27 27
28 class revisiondeltarequest(object):
29 def __init__(self, node, p1, p2, linknode, basenode, ellipsis):
30 self.node = node
31 self.p1node = p1
32 self.p2node = p2
33 self.linknode = linknode
34 self.basenode = basenode
35 self.ellipsis = ellipsis
36
37 28 class ifileindextests(basetestcase):
38 29 """Generic tests for the ifileindex interface.
39 30
40 31 All file storage backends for index data should conform to the tests in this
41 32 class.
42 33
43 34 Use ``makeifileindextests()`` to create an instance of this type.
44 35 """
45 36 def testempty(self):
46 37 f = self._makefilefn()
47 38 self.assertEqual(len(f), 0, 'new file store has 0 length by default')
48 39 self.assertEqual(list(f), [], 'iter yields nothing by default')
49 40
50 41 gen = iter(f)
51 42 with self.assertRaises(StopIteration):
52 43 next(gen)
53 44
54 45 # revs() should evaluate to an empty list.
55 46 self.assertEqual(list(f.revs()), [])
56 47
57 48 revs = iter(f.revs())
58 49 with self.assertRaises(StopIteration):
59 50 next(revs)
60 51
61 52 self.assertEqual(list(f.revs(start=20)), [])
62 53
63 54 # parents() and parentrevs() work with nullid/nullrev.
64 55 self.assertEqual(f.parents(nullid), (nullid, nullid))
65 56 self.assertEqual(f.parentrevs(nullrev), (nullrev, nullrev))
66 57
67 58 with self.assertRaises(error.LookupError):
68 59 f.parents(b'\x01' * 20)
69 60
70 61 for i in range(-5, 5):
71 62 if i == nullrev:
72 63 continue
73 64
74 65 with self.assertRaises(IndexError):
75 66 f.parentrevs(i)
76 67
77 68 # nullid/nullrev lookup always works.
78 69 self.assertEqual(f.rev(nullid), nullrev)
79 70 self.assertEqual(f.node(nullrev), nullid)
80 71
81 72 with self.assertRaises(error.LookupError):
82 73 f.rev(b'\x01' * 20)
83 74
84 75 for i in range(-5, 5):
85 76 if i == nullrev:
86 77 continue
87 78
88 79 with self.assertRaises(IndexError):
89 80 f.node(i)
90 81
91 82 self.assertEqual(f.lookup(nullid), nullid)
92 83 self.assertEqual(f.lookup(nullrev), nullid)
93 84 self.assertEqual(f.lookup(hex(nullid)), nullid)
94 85
95 86 # String converted to integer doesn't work for nullrev.
96 87 with self.assertRaises(error.LookupError):
97 88 f.lookup(b'%d' % nullrev)
98 89
99 90 self.assertEqual(f.linkrev(nullrev), nullrev)
100 91
101 92 for i in range(-5, 5):
102 93 if i == nullrev:
103 94 continue
104 95
105 96 with self.assertRaises(IndexError):
106 97 f.linkrev(i)
107 98
108 99 self.assertEqual(f.flags(nullrev), 0)
109 100
110 101 for i in range(-5, 5):
111 102 if i == nullrev:
112 103 continue
113 104
114 105 with self.assertRaises(IndexError):
115 106 f.flags(i)
116 107
117 108 self.assertFalse(f.iscensored(nullrev))
118 109
119 110 for i in range(-5, 5):
120 111 if i == nullrev:
121 112 continue
122 113
123 114 with self.assertRaises(IndexError):
124 115 f.iscensored(i)
125 116
126 117 self.assertEqual(list(f.commonancestorsheads(nullid, nullid)), [])
127 118
128 119 with self.assertRaises(ValueError):
129 120 self.assertEqual(list(f.descendants([])), [])
130 121
131 122 self.assertEqual(list(f.descendants([nullrev])), [])
132 123
133 124 self.assertEqual(f.heads(), [nullid])
134 125 self.assertEqual(f.heads(nullid), [nullid])
135 126 self.assertEqual(f.heads(None, [nullid]), [nullid])
136 127 self.assertEqual(f.heads(nullid, [nullid]), [nullid])
137 128
138 129 self.assertEqual(f.children(nullid), [])
139 130
140 131 with self.assertRaises(error.LookupError):
141 132 f.children(b'\x01' * 20)
142 133
143 134 self.assertEqual(f.deltaparent(nullrev), nullrev)
144 135
145 136 for i in range(-5, 5):
146 137 if i == nullrev:
147 138 continue
148 139
149 140 with self.assertRaises(IndexError):
150 141 f.deltaparent(i)
151 142
152 143 def testsinglerevision(self):
153 144 f = self._makefilefn()
154 145 with self._maketransactionfn() as tr:
155 146 node = f.add(b'initial', None, tr, 0, nullid, nullid)
156 147
157 148 self.assertEqual(len(f), 1)
158 149 self.assertEqual(list(f), [0])
159 150
160 151 gen = iter(f)
161 152 self.assertEqual(next(gen), 0)
162 153
163 154 with self.assertRaises(StopIteration):
164 155 next(gen)
165 156
166 157 self.assertEqual(list(f.revs()), [0])
167 158 self.assertEqual(list(f.revs(start=1)), [])
168 159 self.assertEqual(list(f.revs(start=0)), [0])
169 160 self.assertEqual(list(f.revs(stop=0)), [0])
170 161 self.assertEqual(list(f.revs(stop=1)), [0])
171 162 self.assertEqual(list(f.revs(1, 1)), [])
172 163 # TODO buggy
173 164 self.assertEqual(list(f.revs(1, 0)), [1, 0])
174 165 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
175 166
176 167 self.assertEqual(f.parents(node), (nullid, nullid))
177 168 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
178 169
179 170 with self.assertRaises(error.LookupError):
180 171 f.parents(b'\x01' * 20)
181 172
182 173 with self.assertRaises(IndexError):
183 174 f.parentrevs(1)
184 175
185 176 self.assertEqual(f.rev(node), 0)
186 177
187 178 with self.assertRaises(error.LookupError):
188 179 f.rev(b'\x01' * 20)
189 180
190 181 self.assertEqual(f.node(0), node)
191 182
192 183 with self.assertRaises(IndexError):
193 184 f.node(1)
194 185
195 186 self.assertEqual(f.lookup(node), node)
196 187 self.assertEqual(f.lookup(0), node)
197 188 self.assertEqual(f.lookup(b'0'), node)
198 189 self.assertEqual(f.lookup(hex(node)), node)
199 190
200 191 self.assertEqual(f.linkrev(0), 0)
201 192
202 193 with self.assertRaises(IndexError):
203 194 f.linkrev(1)
204 195
205 196 self.assertEqual(f.flags(0), 0)
206 197
207 198 with self.assertRaises(IndexError):
208 199 f.flags(1)
209 200
210 201 self.assertFalse(f.iscensored(0))
211 202
212 203 with self.assertRaises(IndexError):
213 204 f.iscensored(1)
214 205
215 206 self.assertEqual(list(f.descendants([0])), [])
216 207
217 208 self.assertEqual(f.heads(), [node])
218 209 self.assertEqual(f.heads(node), [node])
219 210 self.assertEqual(f.heads(stop=[node]), [node])
220 211
221 212 with self.assertRaises(error.LookupError):
222 213 f.heads(stop=[b'\x01' * 20])
223 214
224 215 self.assertEqual(f.children(node), [])
225 216
226 217 self.assertEqual(f.deltaparent(0), nullrev)
227 218
228 219 def testmultiplerevisions(self):
229 220 fulltext0 = b'x' * 1024
230 221 fulltext1 = fulltext0 + b'y'
231 222 fulltext2 = b'y' + fulltext0 + b'z'
232 223
233 224 f = self._makefilefn()
234 225 with self._maketransactionfn() as tr:
235 226 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
236 227 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
237 228 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
238 229
239 230 self.assertEqual(len(f), 3)
240 231 self.assertEqual(list(f), [0, 1, 2])
241 232
242 233 gen = iter(f)
243 234 self.assertEqual(next(gen), 0)
244 235 self.assertEqual(next(gen), 1)
245 236 self.assertEqual(next(gen), 2)
246 237
247 238 with self.assertRaises(StopIteration):
248 239 next(gen)
249 240
250 241 self.assertEqual(list(f.revs()), [0, 1, 2])
251 242 self.assertEqual(list(f.revs(0)), [0, 1, 2])
252 243 self.assertEqual(list(f.revs(1)), [1, 2])
253 244 self.assertEqual(list(f.revs(2)), [2])
254 245 self.assertEqual(list(f.revs(3)), [])
255 246 self.assertEqual(list(f.revs(stop=1)), [0, 1])
256 247 self.assertEqual(list(f.revs(stop=2)), [0, 1, 2])
257 248 self.assertEqual(list(f.revs(stop=3)), [0, 1, 2])
258 249 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
259 250 self.assertEqual(list(f.revs(2, 1)), [2, 1])
260 251 # TODO this is wrong
261 252 self.assertEqual(list(f.revs(3, 2)), [3, 2])
262 253
263 254 self.assertEqual(f.parents(node0), (nullid, nullid))
264 255 self.assertEqual(f.parents(node1), (node0, nullid))
265 256 self.assertEqual(f.parents(node2), (node1, nullid))
266 257
267 258 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
268 259 self.assertEqual(f.parentrevs(1), (0, nullrev))
269 260 self.assertEqual(f.parentrevs(2), (1, nullrev))
270 261
271 262 self.assertEqual(f.rev(node0), 0)
272 263 self.assertEqual(f.rev(node1), 1)
273 264 self.assertEqual(f.rev(node2), 2)
274 265
275 266 with self.assertRaises(error.LookupError):
276 267 f.rev(b'\x01' * 20)
277 268
278 269 self.assertEqual(f.node(0), node0)
279 270 self.assertEqual(f.node(1), node1)
280 271 self.assertEqual(f.node(2), node2)
281 272
282 273 with self.assertRaises(IndexError):
283 274 f.node(3)
284 275
285 276 self.assertEqual(f.lookup(node0), node0)
286 277 self.assertEqual(f.lookup(0), node0)
287 278 self.assertEqual(f.lookup(b'0'), node0)
288 279 self.assertEqual(f.lookup(hex(node0)), node0)
289 280
290 281 self.assertEqual(f.lookup(node1), node1)
291 282 self.assertEqual(f.lookup(1), node1)
292 283 self.assertEqual(f.lookup(b'1'), node1)
293 284 self.assertEqual(f.lookup(hex(node1)), node1)
294 285
295 286 self.assertEqual(f.linkrev(0), 0)
296 287 self.assertEqual(f.linkrev(1), 1)
297 288 self.assertEqual(f.linkrev(2), 3)
298 289
299 290 with self.assertRaises(IndexError):
300 291 f.linkrev(3)
301 292
302 293 self.assertEqual(f.flags(0), 0)
303 294 self.assertEqual(f.flags(1), 0)
304 295 self.assertEqual(f.flags(2), 0)
305 296
306 297 with self.assertRaises(IndexError):
307 298 f.flags(3)
308 299
309 300 self.assertFalse(f.iscensored(0))
310 301 self.assertFalse(f.iscensored(1))
311 302 self.assertFalse(f.iscensored(2))
312 303
313 304 with self.assertRaises(IndexError):
314 305 f.iscensored(3)
315 306
316 307 self.assertEqual(f.commonancestorsheads(node1, nullid), [])
317 308 self.assertEqual(f.commonancestorsheads(node1, node0), [node0])
318 309 self.assertEqual(f.commonancestorsheads(node1, node1), [node1])
319 310 self.assertEqual(f.commonancestorsheads(node0, node1), [node0])
320 311 self.assertEqual(f.commonancestorsheads(node1, node2), [node1])
321 312 self.assertEqual(f.commonancestorsheads(node2, node1), [node1])
322 313
323 314 self.assertEqual(list(f.descendants([0])), [1, 2])
324 315 self.assertEqual(list(f.descendants([1])), [2])
325 316 self.assertEqual(list(f.descendants([0, 1])), [1, 2])
326 317
327 318 self.assertEqual(f.heads(), [node2])
328 319 self.assertEqual(f.heads(node0), [node2])
329 320 self.assertEqual(f.heads(node1), [node2])
330 321 self.assertEqual(f.heads(node2), [node2])
331 322
332 323 # TODO this behavior seems wonky. Is it correct? If so, the
333 324 # docstring for heads() should be updated to reflect desired
334 325 # behavior.
335 326 self.assertEqual(f.heads(stop=[node1]), [node1, node2])
336 327 self.assertEqual(f.heads(stop=[node0]), [node0, node2])
337 328 self.assertEqual(f.heads(stop=[node1, node2]), [node1, node2])
338 329
339 330 with self.assertRaises(error.LookupError):
340 331 f.heads(stop=[b'\x01' * 20])
341 332
342 333 self.assertEqual(f.children(node0), [node1])
343 334 self.assertEqual(f.children(node1), [node2])
344 335 self.assertEqual(f.children(node2), [])
345 336
346 337 self.assertEqual(f.deltaparent(0), nullrev)
347 338 self.assertEqual(f.deltaparent(1), 0)
348 339 self.assertEqual(f.deltaparent(2), 1)
349 340
350 341 def testmultipleheads(self):
351 342 f = self._makefilefn()
352 343
353 344 with self._maketransactionfn() as tr:
354 345 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
355 346 node1 = f.add(b'1', None, tr, 1, node0, nullid)
356 347 node2 = f.add(b'2', None, tr, 2, node1, nullid)
357 348 node3 = f.add(b'3', None, tr, 3, node0, nullid)
358 349 node4 = f.add(b'4', None, tr, 4, node3, nullid)
359 350 node5 = f.add(b'5', None, tr, 5, node0, nullid)
360 351
361 352 self.assertEqual(len(f), 6)
362 353
363 354 self.assertEqual(list(f.descendants([0])), [1, 2, 3, 4, 5])
364 355 self.assertEqual(list(f.descendants([1])), [2])
365 356 self.assertEqual(list(f.descendants([2])), [])
366 357 self.assertEqual(list(f.descendants([3])), [4])
367 358 self.assertEqual(list(f.descendants([0, 1])), [1, 2, 3, 4, 5])
368 359 self.assertEqual(list(f.descendants([1, 3])), [2, 4])
369 360
370 361 self.assertEqual(f.heads(), [node2, node4, node5])
371 362 self.assertEqual(f.heads(node0), [node2, node4, node5])
372 363 self.assertEqual(f.heads(node1), [node2])
373 364 self.assertEqual(f.heads(node2), [node2])
374 365 self.assertEqual(f.heads(node3), [node4])
375 366 self.assertEqual(f.heads(node4), [node4])
376 367 self.assertEqual(f.heads(node5), [node5])
377 368
378 369 # TODO this seems wrong.
379 370 self.assertEqual(f.heads(stop=[node0]), [node0, node2, node4, node5])
380 371 self.assertEqual(f.heads(stop=[node1]), [node1, node2, node4, node5])
381 372
382 373 self.assertEqual(f.children(node0), [node1, node3, node5])
383 374 self.assertEqual(f.children(node1), [node2])
384 375 self.assertEqual(f.children(node2), [])
385 376 self.assertEqual(f.children(node3), [node4])
386 377 self.assertEqual(f.children(node4), [])
387 378 self.assertEqual(f.children(node5), [])
388 379
389 380 class ifiledatatests(basetestcase):
390 381 """Generic tests for the ifiledata interface.
391 382
392 383 All file storage backends for data should conform to the tests in this
393 384 class.
394 385
395 386 Use ``makeifiledatatests()`` to create an instance of this type.
396 387 """
397 388 def testempty(self):
398 389 f = self._makefilefn()
399 390
400 391 self.assertEqual(f.rawsize(nullrev), 0)
401 392
402 393 for i in range(-5, 5):
403 394 if i == nullrev:
404 395 continue
405 396
406 397 with self.assertRaises(IndexError):
407 398 f.rawsize(i)
408 399
409 400 self.assertEqual(f.size(nullrev), 0)
410 401
411 402 for i in range(-5, 5):
412 403 if i == nullrev:
413 404 continue
414 405
415 406 with self.assertRaises(IndexError):
416 407 f.size(i)
417 408
418 409 with self.assertRaises(error.StorageError):
419 410 f.checkhash(b'', nullid)
420 411
421 412 with self.assertRaises(error.LookupError):
422 413 f.checkhash(b'', b'\x01' * 20)
423 414
424 415 self.assertEqual(f.revision(nullid), b'')
425 416 self.assertEqual(f.revision(nullid, raw=True), b'')
426 417
427 418 with self.assertRaises(error.LookupError):
428 419 f.revision(b'\x01' * 20)
429 420
430 421 self.assertEqual(f.read(nullid), b'')
431 422
432 423 with self.assertRaises(error.LookupError):
433 424 f.read(b'\x01' * 20)
434 425
435 426 self.assertFalse(f.renamed(nullid))
436 427
437 428 with self.assertRaises(error.LookupError):
438 429 f.read(b'\x01' * 20)
439 430
440 431 self.assertTrue(f.cmp(nullid, b''))
441 432 self.assertTrue(f.cmp(nullid, b'foo'))
442 433
443 434 with self.assertRaises(error.LookupError):
444 435 f.cmp(b'\x01' * 20, b'irrelevant')
445 436
446 437 self.assertEqual(f.revdiff(nullrev, nullrev), b'')
447 438
448 439 with self.assertRaises(IndexError):
449 440 f.revdiff(0, nullrev)
450 441
451 442 with self.assertRaises(IndexError):
452 443 f.revdiff(nullrev, 0)
453 444
454 445 with self.assertRaises(IndexError):
455 446 f.revdiff(0, 0)
456 447
457 gen = f.emitrevisiondeltas([])
458 with self.assertRaises(StopIteration):
459 next(gen)
460
461 requests = [
462 revisiondeltarequest(nullid, nullid, nullid, nullid, nullid, False),
463 ]
464 gen = f.emitrevisiondeltas(requests)
465
466 delta = next(gen)
467
468 self.assertEqual(delta.node, nullid)
469 self.assertEqual(delta.p1node, nullid)
470 self.assertEqual(delta.p2node, nullid)
471 self.assertEqual(delta.linknode, nullid)
472 self.assertEqual(delta.basenode, nullid)
473 self.assertIsNone(delta.baserevisionsize)
474 self.assertEqual(delta.revision, b'')
475 self.assertIsNone(delta.delta)
476
477 with self.assertRaises(StopIteration):
478 next(gen)
479
480 requests = [
481 revisiondeltarequest(nullid, nullid, nullid, nullid, nullid, False),
482 revisiondeltarequest(nullid, b'\x01' * 20, b'\x02' * 20,
483 b'\x03' * 20, nullid, False)
484 ]
485
486 gen = f.emitrevisiondeltas(requests)
487
488 next(gen)
489 delta = next(gen)
490
491 self.assertEqual(delta.node, nullid)
492 self.assertEqual(delta.p1node, b'\x01' * 20)
493 self.assertEqual(delta.p2node, b'\x02' * 20)
494 self.assertEqual(delta.linknode, b'\x03' * 20)
495 self.assertEqual(delta.basenode, nullid)
496 self.assertIsNone(delta.baserevisionsize)
497 self.assertEqual(delta.revision, b'')
498 self.assertIsNone(delta.delta)
499
500 with self.assertRaises(StopIteration):
501 next(gen)
502
503 448 # Emitting empty list is an empty generator.
504 449 gen = f.emitrevisions([])
505 450 with self.assertRaises(StopIteration):
506 451 next(gen)
507 452
508 453 # Emitting null node yields nothing.
509 454 gen = f.emitrevisions([nullid])
510 455 with self.assertRaises(StopIteration):
511 456 next(gen)
512 457
513 458 # Requesting unknown node fails.
514 459 with self.assertRaises(error.LookupError):
515 460 list(f.emitrevisions([b'\x01' * 20]))
516 461
517 462 def testsinglerevision(self):
518 463 fulltext = b'initial'
519 464
520 465 f = self._makefilefn()
521 466 with self._maketransactionfn() as tr:
522 467 node = f.add(fulltext, None, tr, 0, nullid, nullid)
523 468
524 469 self.assertEqual(f.rawsize(0), len(fulltext))
525 470
526 471 with self.assertRaises(IndexError):
527 472 f.rawsize(1)
528 473
529 474 self.assertEqual(f.size(0), len(fulltext))
530 475
531 476 with self.assertRaises(IndexError):
532 477 f.size(1)
533 478
534 479 f.checkhash(fulltext, node)
535 480 f.checkhash(fulltext, node, nullid, nullid)
536 481
537 482 with self.assertRaises(error.StorageError):
538 483 f.checkhash(fulltext + b'extra', node)
539 484
540 485 with self.assertRaises(error.StorageError):
541 486 f.checkhash(fulltext, node, b'\x01' * 20, nullid)
542 487
543 488 with self.assertRaises(error.StorageError):
544 489 f.checkhash(fulltext, node, nullid, b'\x01' * 20)
545 490
546 491 self.assertEqual(f.revision(node), fulltext)
547 492 self.assertEqual(f.revision(node, raw=True), fulltext)
548 493
549 494 self.assertEqual(f.read(node), fulltext)
550 495
551 496 self.assertFalse(f.renamed(node))
552 497
553 498 self.assertFalse(f.cmp(node, fulltext))
554 499 self.assertTrue(f.cmp(node, fulltext + b'extra'))
555 500
556 501 self.assertEqual(f.revdiff(0, 0), b'')
557 502 self.assertEqual(f.revdiff(nullrev, 0),
558 503 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07%s' %
559 504 fulltext)
560 505
561 506 self.assertEqual(f.revdiff(0, nullrev),
562 507 b'\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00')
563 508
564 requests = [
565 revisiondeltarequest(node, nullid, nullid, nullid, nullid, False),
566 ]
567 gen = f.emitrevisiondeltas(requests)
568
569 delta = next(gen)
570
571 self.assertEqual(delta.node, node)
572 self.assertEqual(delta.p1node, nullid)
573 self.assertEqual(delta.p2node, nullid)
574 self.assertEqual(delta.linknode, nullid)
575 self.assertEqual(delta.basenode, nullid)
576 self.assertIsNone(delta.baserevisionsize)
577 self.assertEqual(delta.revision, fulltext)
578 self.assertIsNone(delta.delta)
579
580 with self.assertRaises(StopIteration):
581 next(gen)
582
583 509 # Emitting a single revision works.
584 510 gen = f.emitrevisions([node])
585 511 rev = next(gen)
586 512
587 513 self.assertEqual(rev.node, node)
588 514 self.assertEqual(rev.p1node, nullid)
589 515 self.assertEqual(rev.p2node, nullid)
590 516 self.assertIsNone(rev.linknode)
591 517 self.assertEqual(rev.basenode, nullid)
592 518 self.assertIsNone(rev.baserevisionsize)
593 519 self.assertIsNone(rev.revision)
594 520 self.assertIsNone(rev.delta)
595 521
596 522 with self.assertRaises(StopIteration):
597 523 next(gen)
598 524
599 525 # Requesting revision data works.
600 526 gen = f.emitrevisions([node], revisiondata=True)
601 527 rev = next(gen)
602 528
603 529 self.assertEqual(rev.node, node)
604 530 self.assertEqual(rev.p1node, nullid)
605 531 self.assertEqual(rev.p2node, nullid)
606 532 self.assertIsNone(rev.linknode)
607 533 self.assertEqual(rev.basenode, nullid)
608 534 self.assertIsNone(rev.baserevisionsize)
609 535 self.assertEqual(rev.revision, fulltext)
610 536 self.assertIsNone(rev.delta)
611 537
612 538 with self.assertRaises(StopIteration):
613 539 next(gen)
614 540
615 541 # Emitting an unknown node after a known revision results in error.
616 542 with self.assertRaises(error.LookupError):
617 543 list(f.emitrevisions([node, b'\x01' * 20]))
618 544
619 545 def testmultiplerevisions(self):
620 546 fulltext0 = b'x' * 1024
621 547 fulltext1 = fulltext0 + b'y'
622 548 fulltext2 = b'y' + fulltext0 + b'z'
623 549
624 550 f = self._makefilefn()
625 551 with self._maketransactionfn() as tr:
626 552 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
627 553 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
628 554 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
629 555
630 556 self.assertEqual(f.rawsize(0), len(fulltext0))
631 557 self.assertEqual(f.rawsize(1), len(fulltext1))
632 558 self.assertEqual(f.rawsize(2), len(fulltext2))
633 559
634 560 with self.assertRaises(IndexError):
635 561 f.rawsize(3)
636 562
637 563 self.assertEqual(f.size(0), len(fulltext0))
638 564 self.assertEqual(f.size(1), len(fulltext1))
639 565 self.assertEqual(f.size(2), len(fulltext2))
640 566
641 567 with self.assertRaises(IndexError):
642 568 f.size(3)
643 569
644 570 f.checkhash(fulltext0, node0)
645 571 f.checkhash(fulltext1, node1)
646 572 f.checkhash(fulltext1, node1, node0, nullid)
647 573 f.checkhash(fulltext2, node2, node1, nullid)
648 574
649 575 with self.assertRaises(error.StorageError):
650 576 f.checkhash(fulltext1, b'\x01' * 20)
651 577
652 578 with self.assertRaises(error.StorageError):
653 579 f.checkhash(fulltext1 + b'extra', node1, node0, nullid)
654 580
655 581 with self.assertRaises(error.StorageError):
656 582 f.checkhash(fulltext1, node1, node0, node0)
657 583
658 584 self.assertEqual(f.revision(node0), fulltext0)
659 585 self.assertEqual(f.revision(node0, raw=True), fulltext0)
660 586 self.assertEqual(f.revision(node1), fulltext1)
661 587 self.assertEqual(f.revision(node1, raw=True), fulltext1)
662 588 self.assertEqual(f.revision(node2), fulltext2)
663 589 self.assertEqual(f.revision(node2, raw=True), fulltext2)
664 590
665 591 with self.assertRaises(error.LookupError):
666 592 f.revision(b'\x01' * 20)
667 593
668 594 self.assertEqual(f.read(node0), fulltext0)
669 595 self.assertEqual(f.read(node1), fulltext1)
670 596 self.assertEqual(f.read(node2), fulltext2)
671 597
672 598 with self.assertRaises(error.LookupError):
673 599 f.read(b'\x01' * 20)
674 600
675 601 self.assertFalse(f.renamed(node0))
676 602 self.assertFalse(f.renamed(node1))
677 603 self.assertFalse(f.renamed(node2))
678 604
679 605 with self.assertRaises(error.LookupError):
680 606 f.renamed(b'\x01' * 20)
681 607
682 608 self.assertFalse(f.cmp(node0, fulltext0))
683 609 self.assertFalse(f.cmp(node1, fulltext1))
684 610 self.assertFalse(f.cmp(node2, fulltext2))
685 611
686 612 self.assertTrue(f.cmp(node1, fulltext0))
687 613 self.assertTrue(f.cmp(node2, fulltext1))
688 614
689 615 with self.assertRaises(error.LookupError):
690 616 f.cmp(b'\x01' * 20, b'irrelevant')
691 617
692 618 self.assertEqual(f.revdiff(0, 1),
693 619 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
694 620 fulltext1)
695 621
696 622 self.assertEqual(f.revdiff(0, 2),
697 623 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x02' +
698 624 fulltext2)
699 625
700 requests = [
701 revisiondeltarequest(node0, nullid, nullid, b'\x01' * 20, nullid,
702 False),
703 revisiondeltarequest(node1, node0, nullid, b'\x02' * 20, node0,
704 False),
705 revisiondeltarequest(node2, node1, nullid, b'\x03' * 20, node1,
706 False),
707 ]
708 gen = f.emitrevisiondeltas(requests)
709
710 delta = next(gen)
711
712 self.assertEqual(delta.node, node0)
713 self.assertEqual(delta.p1node, nullid)
714 self.assertEqual(delta.p2node, nullid)
715 self.assertEqual(delta.linknode, b'\x01' * 20)
716 self.assertEqual(delta.basenode, nullid)
717 self.assertIsNone(delta.baserevisionsize)
718 self.assertEqual(delta.revision, fulltext0)
719 self.assertIsNone(delta.delta)
720
721 delta = next(gen)
722
723 self.assertEqual(delta.node, node1)
724 self.assertEqual(delta.p1node, node0)
725 self.assertEqual(delta.p2node, nullid)
726 self.assertEqual(delta.linknode, b'\x02' * 20)
727 self.assertEqual(delta.basenode, node0)
728 self.assertIsNone(delta.baserevisionsize)
729 self.assertIsNone(delta.revision)
730 self.assertEqual(delta.delta,
731 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
732 fulltext1)
733
734 delta = next(gen)
735
736 self.assertEqual(delta.node, node2)
737 self.assertEqual(delta.p1node, node1)
738 self.assertEqual(delta.p2node, nullid)
739 self.assertEqual(delta.linknode, b'\x03' * 20)
740 self.assertEqual(delta.basenode, node1)
741 self.assertIsNone(delta.baserevisionsize)
742 self.assertIsNone(delta.revision)
743 self.assertEqual(delta.delta,
744 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
745 fulltext2)
746
747 with self.assertRaises(StopIteration):
748 next(gen)
749
750 626 # Nodes should be emitted in order.
751 627 gen = f.emitrevisions([node0, node1, node2], revisiondata=True)
752 628
753 629 rev = next(gen)
754 630
755 631 self.assertEqual(rev.node, node0)
756 632 self.assertEqual(rev.p1node, nullid)
757 633 self.assertEqual(rev.p2node, nullid)
758 634 self.assertIsNone(rev.linknode)
759 635 self.assertEqual(rev.basenode, nullid)
760 636 self.assertIsNone(rev.baserevisionsize)
761 637 self.assertEqual(rev.revision, fulltext0)
762 638 self.assertIsNone(rev.delta)
763 639
764 640 rev = next(gen)
765 641
766 642 self.assertEqual(rev.node, node1)
767 643 self.assertEqual(rev.p1node, node0)
768 644 self.assertEqual(rev.p2node, nullid)
769 645 self.assertIsNone(rev.linknode)
770 646 self.assertEqual(rev.basenode, node0)
771 647 self.assertIsNone(rev.baserevisionsize)
772 648 self.assertIsNone(rev.revision)
773 649 self.assertEqual(rev.delta,
774 650 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
775 651 fulltext1)
776 652
777 653 rev = next(gen)
778 654
779 655 self.assertEqual(rev.node, node2)
780 656 self.assertEqual(rev.p1node, node1)
781 657 self.assertEqual(rev.p2node, nullid)
782 658 self.assertIsNone(rev.linknode)
783 659 self.assertEqual(rev.basenode, node1)
784 660 self.assertIsNone(rev.baserevisionsize)
785 661 self.assertIsNone(rev.revision)
786 662 self.assertEqual(rev.delta,
787 663 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
788 664 fulltext2)
789 665
790 666 with self.assertRaises(StopIteration):
791 667 next(gen)
792 668
793 669 # Request not in DAG order is reordered to be in DAG order.
794 670 gen = f.emitrevisions([node2, node1, node0], revisiondata=True)
795 671
796 672 rev = next(gen)
797 673
798 674 self.assertEqual(rev.node, node0)
799 675 self.assertEqual(rev.p1node, nullid)
800 676 self.assertEqual(rev.p2node, nullid)
801 677 self.assertIsNone(rev.linknode)
802 678 self.assertEqual(rev.basenode, nullid)
803 679 self.assertIsNone(rev.baserevisionsize)
804 680 self.assertEqual(rev.revision, fulltext0)
805 681 self.assertIsNone(rev.delta)
806 682
807 683 rev = next(gen)
808 684
809 685 self.assertEqual(rev.node, node1)
810 686 self.assertEqual(rev.p1node, node0)
811 687 self.assertEqual(rev.p2node, nullid)
812 688 self.assertIsNone(rev.linknode)
813 689 self.assertEqual(rev.basenode, node0)
814 690 self.assertIsNone(rev.baserevisionsize)
815 691 self.assertIsNone(rev.revision)
816 692 self.assertEqual(rev.delta,
817 693 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
818 694 fulltext1)
819 695
820 696 rev = next(gen)
821 697
822 698 self.assertEqual(rev.node, node2)
823 699 self.assertEqual(rev.p1node, node1)
824 700 self.assertEqual(rev.p2node, nullid)
825 701 self.assertIsNone(rev.linknode)
826 702 self.assertEqual(rev.basenode, node1)
827 703 self.assertIsNone(rev.baserevisionsize)
828 704 self.assertIsNone(rev.revision)
829 705 self.assertEqual(rev.delta,
830 706 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
831 707 fulltext2)
832 708
833 709 with self.assertRaises(StopIteration):
834 710 next(gen)
835 711
836 712 # Unrecognized nodesorder value raises ProgrammingError.
837 713 with self.assertRaises(error.ProgrammingError):
838 714 list(f.emitrevisions([], nodesorder='bad'))
839 715
840 716 # nodesorder=storage is recognized. But we can't test it thoroughly
841 717 # because behavior is storage-dependent.
842 718 res = list(f.emitrevisions([node2, node1, node0],
843 719 nodesorder='storage'))
844 720 self.assertEqual(len(res), 3)
845 721 self.assertEqual({o.node for o in res}, {node0, node1, node2})
846 722
847 723 # nodesorder=nodes forces the order.
848 724 gen = f.emitrevisions([node2, node0], nodesorder='nodes',
849 725 revisiondata=True)
850 726
851 727 rev = next(gen)
852 728 self.assertEqual(rev.node, node2)
853 729 self.assertEqual(rev.p1node, node1)
854 730 self.assertEqual(rev.p2node, nullid)
855 731 self.assertEqual(rev.basenode, nullid)
856 732 self.assertIsNone(rev.baserevisionsize)
857 733 self.assertEqual(rev.revision, fulltext2)
858 734 self.assertIsNone(rev.delta)
859 735
860 736 rev = next(gen)
861 737 self.assertEqual(rev.node, node0)
862 738 self.assertEqual(rev.p1node, nullid)
863 739 self.assertEqual(rev.p2node, nullid)
864 740 # Delta behavior is storage dependent, so we can't easily test it.
865 741
866 742 with self.assertRaises(StopIteration):
867 743 next(gen)
868 744
869 745 # assumehaveparentrevisions=False (the default) won't send a delta for
870 746 # the first revision.
871 747 gen = f.emitrevisions({node2, node1}, revisiondata=True)
872 748
873 749 rev = next(gen)
874 750 self.assertEqual(rev.node, node1)
875 751 self.assertEqual(rev.p1node, node0)
876 752 self.assertEqual(rev.p2node, nullid)
877 753 self.assertEqual(rev.basenode, nullid)
878 754 self.assertIsNone(rev.baserevisionsize)
879 755 self.assertEqual(rev.revision, fulltext1)
880 756 self.assertIsNone(rev.delta)
881 757
882 758 rev = next(gen)
883 759 self.assertEqual(rev.node, node2)
884 760 self.assertEqual(rev.p1node, node1)
885 761 self.assertEqual(rev.p2node, nullid)
886 762 self.assertEqual(rev.basenode, node1)
887 763 self.assertIsNone(rev.baserevisionsize)
888 764 self.assertIsNone(rev.revision)
889 765 self.assertEqual(rev.delta,
890 766 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
891 767 fulltext2)
892 768
893 769 with self.assertRaises(StopIteration):
894 770 next(gen)
895 771
896 772 # assumehaveparentrevisions=True allows delta against initial revision.
897 773 gen = f.emitrevisions([node2, node1],
898 774 revisiondata=True, assumehaveparentrevisions=True)
899 775
900 776 rev = next(gen)
901 777 self.assertEqual(rev.node, node1)
902 778 self.assertEqual(rev.p1node, node0)
903 779 self.assertEqual(rev.p2node, nullid)
904 780 self.assertEqual(rev.basenode, node0)
905 781 self.assertIsNone(rev.baserevisionsize)
906 782 self.assertIsNone(rev.revision)
907 783 self.assertEqual(rev.delta,
908 784 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
909 785 fulltext1)
910 786
911 787 # forceprevious=True forces a delta against the previous revision.
912 788 # Special case for initial revision.
913 789 gen = f.emitrevisions([node0], revisiondata=True, deltaprevious=True)
914 790
915 791 rev = next(gen)
916 792 self.assertEqual(rev.node, node0)
917 793 self.assertEqual(rev.p1node, nullid)
918 794 self.assertEqual(rev.p2node, nullid)
919 795 self.assertEqual(rev.basenode, nullid)
920 796 self.assertIsNone(rev.baserevisionsize)
921 797 self.assertIsNone(rev.revision)
922 798 self.assertEqual(rev.delta,
923 799 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' +
924 800 fulltext0)
925 801
926 802 with self.assertRaises(StopIteration):
927 803 next(gen)
928 804
929 805 gen = f.emitrevisions([node0, node2], revisiondata=True,
930 806 deltaprevious=True)
931 807
932 808 rev = next(gen)
933 809 self.assertEqual(rev.node, node0)
934 810 self.assertEqual(rev.p1node, nullid)
935 811 self.assertEqual(rev.p2node, nullid)
936 812 self.assertEqual(rev.basenode, nullid)
937 813 self.assertIsNone(rev.baserevisionsize)
938 814 self.assertIsNone(rev.revision)
939 815 self.assertEqual(rev.delta,
940 816 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' +
941 817 fulltext0)
942 818
943 819 rev = next(gen)
944 820 self.assertEqual(rev.node, node2)
945 821 self.assertEqual(rev.p1node, node1)
946 822 self.assertEqual(rev.p2node, nullid)
947 823 self.assertEqual(rev.basenode, node0)
948 824
949 825 with self.assertRaises(StopIteration):
950 826 next(gen)
951 827
952 828 def testrenamed(self):
953 829 fulltext0 = b'foo'
954 830 fulltext1 = b'bar'
955 831 fulltext2 = b'baz'
956 832
957 833 meta1 = {
958 834 b'copy': b'source0',
959 835 b'copyrev': b'a' * 40,
960 836 }
961 837
962 838 meta2 = {
963 839 b'copy': b'source1',
964 840 b'copyrev': b'b' * 40,
965 841 }
966 842
967 843 stored1 = b''.join([
968 844 b'\x01\ncopy: source0\n',
969 845 b'copyrev: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n\x01\n',
970 846 fulltext1,
971 847 ])
972 848
973 849 stored2 = b''.join([
974 850 b'\x01\ncopy: source1\n',
975 851 b'copyrev: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n\x01\n',
976 852 fulltext2,
977 853 ])
978 854
979 855 f = self._makefilefn()
980 856 with self._maketransactionfn() as tr:
981 857 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
982 858 node1 = f.add(fulltext1, meta1, tr, 1, node0, nullid)
983 859 node2 = f.add(fulltext2, meta2, tr, 2, nullid, nullid)
984 860
985 861 self.assertEqual(f.rawsize(1), len(stored1))
986 862 self.assertEqual(f.rawsize(2), len(stored2))
987 863
988 864 # Metadata header isn't recognized when parent isn't nullid.
989 865 self.assertEqual(f.size(1), len(stored1))
990 866 self.assertEqual(f.size(2), len(fulltext2))
991 867
992 868 self.assertEqual(f.revision(node1), stored1)
993 869 self.assertEqual(f.revision(node1, raw=True), stored1)
994 870 self.assertEqual(f.revision(node2), stored2)
995 871 self.assertEqual(f.revision(node2, raw=True), stored2)
996 872
997 873 self.assertEqual(f.read(node1), fulltext1)
998 874 self.assertEqual(f.read(node2), fulltext2)
999 875
1000 876 # Returns False when first parent is set.
1001 877 self.assertFalse(f.renamed(node1))
1002 878 self.assertEqual(f.renamed(node2), (b'source1', b'\xbb' * 20))
1003 879
1004 880 self.assertTrue(f.cmp(node1, fulltext1))
1005 881 self.assertTrue(f.cmp(node1, stored1))
1006 882 self.assertFalse(f.cmp(node2, fulltext2))
1007 883 self.assertTrue(f.cmp(node2, stored2))
1008 884
1009 885 def testmetadataprefix(self):
1010 886 # Content with metadata prefix has extra prefix inserted in storage.
1011 887 fulltext0 = b'\x01\nfoo'
1012 888 stored0 = b'\x01\n\x01\n\x01\nfoo'
1013 889
1014 890 fulltext1 = b'\x01\nbar'
1015 891 meta1 = {
1016 892 b'copy': b'source0',
1017 893 b'copyrev': b'b' * 40,
1018 894 }
1019 895 stored1 = b''.join([
1020 896 b'\x01\ncopy: source0\n',
1021 897 b'copyrev: %s\n' % (b'b' * 40),
1022 898 b'\x01\n\x01\nbar',
1023 899 ])
1024 900
1025 901 f = self._makefilefn()
1026 902 with self._maketransactionfn() as tr:
1027 903 node0 = f.add(fulltext0, {}, tr, 0, nullid, nullid)
1028 904 node1 = f.add(fulltext1, meta1, tr, 1, nullid, nullid)
1029 905
1030 906 self.assertEqual(f.rawsize(0), len(stored0))
1031 907 self.assertEqual(f.rawsize(1), len(stored1))
1032 908
1033 909 # TODO this is buggy.
1034 910 self.assertEqual(f.size(0), len(fulltext0) + 4)
1035 911
1036 912 self.assertEqual(f.size(1), len(fulltext1))
1037 913
1038 914 self.assertEqual(f.revision(node0), stored0)
1039 915 self.assertEqual(f.revision(node0, raw=True), stored0)
1040 916
1041 917 self.assertEqual(f.revision(node1), stored1)
1042 918 self.assertEqual(f.revision(node1, raw=True), stored1)
1043 919
1044 920 self.assertEqual(f.read(node0), fulltext0)
1045 921 self.assertEqual(f.read(node1), fulltext1)
1046 922
1047 923 self.assertFalse(f.cmp(node0, fulltext0))
1048 924 self.assertTrue(f.cmp(node0, stored0))
1049 925
1050 926 self.assertFalse(f.cmp(node1, fulltext1))
1051 927 self.assertTrue(f.cmp(node1, stored0))
1052 928
1053 929 def testcensored(self):
1054 930 f = self._makefilefn()
1055 931
1056 932 stored1 = revlog.packmeta({
1057 933 b'censored': b'tombstone',
1058 934 }, b'')
1059 935
1060 936 # TODO tests are incomplete because we need the node to be
1061 937 # different due to presence of censor metadata. But we can't
1062 938 # do this with addrevision().
1063 939 with self._maketransactionfn() as tr:
1064 940 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1065 941 f.addrevision(stored1, tr, 1, node0, nullid,
1066 942 flags=revlog.REVIDX_ISCENSORED)
1067 943
1068 944 self.assertEqual(f.flags(1), revlog.REVIDX_ISCENSORED)
1069 945 self.assertTrue(f.iscensored(1))
1070 946
1071 947 self.assertEqual(f.revision(1), stored1)
1072 948 self.assertEqual(f.revision(1, raw=True), stored1)
1073 949
1074 950 self.assertEqual(f.read(1), b'')
1075 951
1076 952 class ifilemutationtests(basetestcase):
1077 953 """Generic tests for the ifilemutation interface.
1078 954
1079 955 All file storage backends that support writing should conform to this
1080 956 interface.
1081 957
1082 958 Use ``makeifilemutationtests()`` to create an instance of this type.
1083 959 """
1084 960 def testaddnoop(self):
1085 961 f = self._makefilefn()
1086 962 with self._maketransactionfn() as tr:
1087 963 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1088 964 node1 = f.add(b'foo', None, tr, 0, nullid, nullid)
1089 965 # Varying by linkrev shouldn't impact hash.
1090 966 node2 = f.add(b'foo', None, tr, 1, nullid, nullid)
1091 967
1092 968 self.assertEqual(node1, node0)
1093 969 self.assertEqual(node2, node0)
1094 970 self.assertEqual(len(f), 1)
1095 971
1096 972 def testaddrevisionbadnode(self):
1097 973 f = self._makefilefn()
1098 974 with self._maketransactionfn() as tr:
1099 975 # Adding a revision with bad node value fails.
1100 976 with self.assertRaises(error.StorageError):
1101 977 f.addrevision(b'foo', tr, 0, nullid, nullid, node=b'\x01' * 20)
1102 978
1103 979 def testaddrevisionunknownflag(self):
1104 980 f = self._makefilefn()
1105 981 with self._maketransactionfn() as tr:
1106 982 for i in range(15, 0, -1):
1107 983 if (1 << i) & ~revlog.REVIDX_KNOWN_FLAGS:
1108 984 flags = 1 << i
1109 985 break
1110 986
1111 987 with self.assertRaises(error.StorageError):
1112 988 f.addrevision(b'foo', tr, 0, nullid, nullid, flags=flags)
1113 989
1114 990 def testaddgroupsimple(self):
1115 991 f = self._makefilefn()
1116 992
1117 993 callbackargs = []
1118 994 def cb(*args, **kwargs):
1119 995 callbackargs.append((args, kwargs))
1120 996
1121 997 def linkmapper(node):
1122 998 return 0
1123 999
1124 1000 with self._maketransactionfn() as tr:
1125 1001 nodes = f.addgroup([], None, tr, addrevisioncb=cb)
1126 1002
1127 1003 self.assertEqual(nodes, [])
1128 1004 self.assertEqual(callbackargs, [])
1129 1005 self.assertEqual(len(f), 0)
1130 1006
1131 1007 fulltext0 = b'foo'
1132 1008 delta0 = mdiff.trivialdiffheader(len(fulltext0)) + fulltext0
1133 1009
1134 1010 deltas = [
1135 1011 (b'\x01' * 20, nullid, nullid, nullid, nullid, delta0, 0),
1136 1012 ]
1137 1013
1138 1014 with self._maketransactionfn() as tr:
1139 1015 with self.assertRaises(error.StorageError):
1140 1016 f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
1141 1017
1142 1018 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
1143 1019
1144 1020 f = self._makefilefn()
1145 1021
1146 1022 deltas = [
1147 1023 (node0, nullid, nullid, nullid, nullid, delta0, 0),
1148 1024 ]
1149 1025
1150 1026 with self._maketransactionfn() as tr:
1151 1027 nodes = f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
1152 1028
1153 1029 self.assertEqual(nodes, [
1154 1030 b'\x49\xd8\xcb\xb1\x5c\xe2\x57\x92\x04\x47'
1155 1031 b'\x00\x6b\x46\x97\x8b\x7a\xf9\x80\xa9\x79'])
1156 1032
1157 1033 self.assertEqual(len(callbackargs), 1)
1158 1034 self.assertEqual(callbackargs[0][0][1], nodes[0])
1159 1035
1160 1036 self.assertEqual(list(f.revs()), [0])
1161 1037 self.assertEqual(f.rev(nodes[0]), 0)
1162 1038 self.assertEqual(f.node(0), nodes[0])
1163 1039
1164 1040 def testaddgroupmultiple(self):
1165 1041 f = self._makefilefn()
1166 1042
1167 1043 fulltexts = [
1168 1044 b'foo',
1169 1045 b'bar',
1170 1046 b'x' * 1024,
1171 1047 ]
1172 1048
1173 1049 nodes = []
1174 1050 with self._maketransactionfn() as tr:
1175 1051 for fulltext in fulltexts:
1176 1052 nodes.append(f.add(fulltext, None, tr, 0, nullid, nullid))
1177 1053
1178 1054 f = self._makefilefn()
1179 1055 deltas = []
1180 1056 for i, fulltext in enumerate(fulltexts):
1181 1057 delta = mdiff.trivialdiffheader(len(fulltext)) + fulltext
1182 1058
1183 1059 deltas.append((nodes[i], nullid, nullid, nullid, nullid, delta, 0))
1184 1060
1185 1061 with self._maketransactionfn() as tr:
1186 1062 self.assertEqual(f.addgroup(deltas, lambda x: 0, tr), nodes)
1187 1063
1188 1064 self.assertEqual(len(f), len(deltas))
1189 1065 self.assertEqual(list(f.revs()), [0, 1, 2])
1190 1066 self.assertEqual(f.rev(nodes[0]), 0)
1191 1067 self.assertEqual(f.rev(nodes[1]), 1)
1192 1068 self.assertEqual(f.rev(nodes[2]), 2)
1193 1069 self.assertEqual(f.node(0), nodes[0])
1194 1070 self.assertEqual(f.node(1), nodes[1])
1195 1071 self.assertEqual(f.node(2), nodes[2])
1196 1072
1197 1073 def makeifileindextests(makefilefn, maketransactionfn):
1198 1074 """Create a unittest.TestCase class suitable for testing file storage.
1199 1075
1200 1076 ``makefilefn`` is a callable which receives the test case as an
1201 1077 argument and returns an object implementing the ``ifilestorage`` interface.
1202 1078
1203 1079 ``maketransactionfn`` is a callable which receives the test case as an
1204 1080 argument and returns a transaction object.
1205 1081
1206 1082 Returns a type that is a ``unittest.TestCase`` that can be used for
1207 1083 testing the object implementing the file storage interface. Simply
1208 1084 assign the returned value to a module-level attribute and a test loader
1209 1085 should find and run it automatically.
1210 1086 """
1211 1087 d = {
1212 1088 r'_makefilefn': makefilefn,
1213 1089 r'_maketransactionfn': maketransactionfn,
1214 1090 }
1215 1091 return type(r'ifileindextests', (ifileindextests,), d)
1216 1092
1217 1093 def makeifiledatatests(makefilefn, maketransactionfn):
1218 1094 d = {
1219 1095 r'_makefilefn': makefilefn,
1220 1096 r'_maketransactionfn': maketransactionfn,
1221 1097 }
1222 1098 return type(r'ifiledatatests', (ifiledatatests,), d)
1223 1099
1224 1100 def makeifilemutationtests(makefilefn, maketransactionfn):
1225 1101 d = {
1226 1102 r'_makefilefn': makefilefn,
1227 1103 r'_maketransactionfn': maketransactionfn,
1228 1104 }
1229 1105 return type(r'ifilemutationtests', (ifilemutationtests,), d)
@@ -1,735 +1,687 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 import stat
16 16
17 17 from mercurial.i18n import _
18 18 from mercurial.node import (
19 19 bin,
20 20 hex,
21 21 nullid,
22 22 nullrev,
23 23 )
24 24 from mercurial.thirdparty import (
25 25 attr,
26 26 cbor,
27 27 )
28 28 from mercurial import (
29 29 ancestor,
30 30 bundlerepo,
31 31 error,
32 32 extensions,
33 33 localrepo,
34 34 mdiff,
35 35 pycompat,
36 36 repository,
37 37 revlog,
38 38 store,
39 39 verify,
40 40 )
41 41 from mercurial.utils import (
42 42 interfaceutil,
43 43 )
44 44
45 45 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
46 46 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
47 47 # be specifying the version(s) of Mercurial they are tested with, or
48 48 # leave the attribute unspecified.
49 49 testedwith = 'ships-with-hg-core'
50 50
51 51 REQUIREMENT = 'testonly-simplestore'
52 52
53 53 def validatenode(node):
54 54 if isinstance(node, int):
55 55 raise ValueError('expected node; got int')
56 56
57 57 if len(node) != 20:
58 58 raise ValueError('expected 20 byte node')
59 59
60 60 def validaterev(rev):
61 61 if not isinstance(rev, int):
62 62 raise ValueError('expected int')
63 63
64 64 class simplestoreerror(error.StorageError):
65 65 pass
66 66
67 67 @interfaceutil.implementer(repository.irevisiondelta)
68 68 @attr.s(slots=True, frozen=True)
69 69 class simplestorerevisiondelta(object):
70 70 node = attr.ib()
71 71 p1node = attr.ib()
72 72 p2node = attr.ib()
73 73 basenode = attr.ib()
74 74 linknode = attr.ib()
75 75 flags = attr.ib()
76 76 baserevisionsize = attr.ib()
77 77 revision = attr.ib()
78 78 delta = attr.ib()
79 79
80 80 @interfaceutil.implementer(repository.ifilestorage)
81 81 class filestorage(object):
82 82 """Implements storage for a tracked path.
83 83
84 84 Data is stored in the VFS in a directory corresponding to the tracked
85 85 path.
86 86
87 87 Index data is stored in an ``index`` file using CBOR.
88 88
89 89 Fulltext data is stored in files having names of the node.
90 90 """
91 91
92 92 def __init__(self, svfs, path):
93 93 self._svfs = svfs
94 94 self._path = path
95 95
96 96 self._storepath = b'/'.join([b'data', path])
97 97 self._indexpath = b'/'.join([self._storepath, b'index'])
98 98
99 99 indexdata = self._svfs.tryread(self._indexpath)
100 100 if indexdata:
101 101 indexdata = cbor.loads(indexdata)
102 102
103 103 self._indexdata = indexdata or []
104 104 self._indexbynode = {}
105 105 self._indexbyrev = {}
106 106 self._index = []
107 107 self._refreshindex()
108 108
109 109 # This is used by changegroup code :/
110 110 self._generaldelta = True
111 111
112 112 def _refreshindex(self):
113 113 self._indexbynode.clear()
114 114 self._indexbyrev.clear()
115 115 self._index = []
116 116
117 117 for i, entry in enumerate(self._indexdata):
118 118 self._indexbynode[entry[b'node']] = entry
119 119 self._indexbyrev[i] = entry
120 120
121 121 self._indexbynode[nullid] = {
122 122 b'node': nullid,
123 123 b'p1': nullid,
124 124 b'p2': nullid,
125 125 b'linkrev': nullrev,
126 126 b'flags': 0,
127 127 }
128 128
129 129 self._indexbyrev[nullrev] = {
130 130 b'node': nullid,
131 131 b'p1': nullid,
132 132 b'p2': nullid,
133 133 b'linkrev': nullrev,
134 134 b'flags': 0,
135 135 }
136 136
137 137 for i, entry in enumerate(self._indexdata):
138 138 p1rev, p2rev = self.parentrevs(self.rev(entry[b'node']))
139 139
140 140 # start, length, rawsize, chainbase, linkrev, p1, p2, node
141 141 self._index.append((0, 0, 0, -1, entry[b'linkrev'], p1rev, p2rev,
142 142 entry[b'node']))
143 143
144 144 self._index.append((0, 0, 0, -1, -1, -1, -1, nullid))
145 145
146 146 def __len__(self):
147 147 return len(self._indexdata)
148 148
149 149 def __iter__(self):
150 150 return iter(range(len(self)))
151 151
152 152 def revs(self, start=0, stop=None):
153 153 step = 1
154 154 if stop is not None:
155 155 if start > stop:
156 156 step = -1
157 157
158 158 stop += step
159 159 else:
160 160 stop = len(self)
161 161
162 162 return range(start, stop, step)
163 163
164 164 def parents(self, node):
165 165 validatenode(node)
166 166
167 167 if node not in self._indexbynode:
168 168 raise KeyError('unknown node')
169 169
170 170 entry = self._indexbynode[node]
171 171
172 172 return entry[b'p1'], entry[b'p2']
173 173
174 174 def parentrevs(self, rev):
175 175 p1, p2 = self.parents(self._indexbyrev[rev][b'node'])
176 176 return self.rev(p1), self.rev(p2)
177 177
178 178 def rev(self, node):
179 179 validatenode(node)
180 180
181 181 try:
182 182 self._indexbynode[node]
183 183 except KeyError:
184 184 raise error.LookupError(node, self._indexpath, _('no node'))
185 185
186 186 for rev, entry in self._indexbyrev.items():
187 187 if entry[b'node'] == node:
188 188 return rev
189 189
190 190 raise error.ProgrammingError('this should not occur')
191 191
192 192 def node(self, rev):
193 193 validaterev(rev)
194 194
195 195 return self._indexbyrev[rev][b'node']
196 196
197 197 def lookup(self, node):
198 198 if isinstance(node, int):
199 199 return self.node(node)
200 200
201 201 if len(node) == 20:
202 202 self.rev(node)
203 203 return node
204 204
205 205 try:
206 206 rev = int(node)
207 207 if '%d' % rev != node:
208 208 raise ValueError
209 209
210 210 if rev < 0:
211 211 rev = len(self) + rev
212 212 if rev < 0 or rev >= len(self):
213 213 raise ValueError
214 214
215 215 return self.node(rev)
216 216 except (ValueError, OverflowError):
217 217 pass
218 218
219 219 if len(node) == 40:
220 220 try:
221 221 rawnode = bin(node)
222 222 self.rev(rawnode)
223 223 return rawnode
224 224 except TypeError:
225 225 pass
226 226
227 227 raise error.LookupError(node, self._path, _('invalid lookup input'))
228 228
229 229 def linkrev(self, rev):
230 230 validaterev(rev)
231 231
232 232 return self._indexbyrev[rev][b'linkrev']
233 233
234 234 def flags(self, rev):
235 235 validaterev(rev)
236 236
237 237 return self._indexbyrev[rev][b'flags']
238 238
239 239 def deltaparent(self, rev):
240 240 validaterev(rev)
241 241
242 242 p1node = self.parents(self.node(rev))[0]
243 243 return self.rev(p1node)
244 244
245 245 def _candelta(self, baserev, rev):
246 246 validaterev(baserev)
247 247 validaterev(rev)
248 248
249 249 if ((self.flags(baserev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS)
250 250 or (self.flags(rev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS)):
251 251 return False
252 252
253 253 return True
254 254
255 255 def rawsize(self, rev):
256 256 validaterev(rev)
257 257 node = self.node(rev)
258 258 return len(self.revision(node, raw=True))
259 259
260 260 def _processflags(self, text, flags, operation, raw=False):
261 261 if flags == 0:
262 262 return text, True
263 263
264 264 if flags & ~revlog.REVIDX_KNOWN_FLAGS:
265 265 raise simplestoreerror(_("incompatible revision flag '%#x'") %
266 266 (flags & ~revlog.REVIDX_KNOWN_FLAGS))
267 267
268 268 validatehash = True
269 269 # Depending on the operation (read or write), the order might be
270 270 # reversed due to non-commutative transforms.
271 271 orderedflags = revlog.REVIDX_FLAGS_ORDER
272 272 if operation == 'write':
273 273 orderedflags = reversed(orderedflags)
274 274
275 275 for flag in orderedflags:
276 276 # If a flagprocessor has been registered for a known flag, apply the
277 277 # related operation transform and update result tuple.
278 278 if flag & flags:
279 279 vhash = True
280 280
281 281 if flag not in revlog._flagprocessors:
282 282 message = _("missing processor for flag '%#x'") % (flag)
283 283 raise simplestoreerror(message)
284 284
285 285 processor = revlog._flagprocessors[flag]
286 286 if processor is not None:
287 287 readtransform, writetransform, rawtransform = processor
288 288
289 289 if raw:
290 290 vhash = rawtransform(self, text)
291 291 elif operation == 'read':
292 292 text, vhash = readtransform(self, text)
293 293 else: # write operation
294 294 text, vhash = writetransform(self, text)
295 295 validatehash = validatehash and vhash
296 296
297 297 return text, validatehash
298 298
299 299 def checkhash(self, text, node, p1=None, p2=None, rev=None):
300 300 if p1 is None and p2 is None:
301 301 p1, p2 = self.parents(node)
302 302 if node != revlog.hash(text, p1, p2):
303 303 raise simplestoreerror(_("integrity check failed on %s") %
304 304 self._path)
305 305
306 306 def revision(self, node, raw=False):
307 307 validatenode(node)
308 308
309 309 if node == nullid:
310 310 return b''
311 311
312 312 rev = self.rev(node)
313 313 flags = self.flags(rev)
314 314
315 315 path = b'/'.join([self._storepath, hex(node)])
316 316 rawtext = self._svfs.read(path)
317 317
318 318 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
319 319 if validatehash:
320 320 self.checkhash(text, node, rev=rev)
321 321
322 322 return text
323 323
324 324 def read(self, node):
325 325 validatenode(node)
326 326
327 327 revision = self.revision(node)
328 328
329 329 if not revision.startswith(b'\1\n'):
330 330 return revision
331 331
332 332 start = revision.index(b'\1\n', 2)
333 333 return revision[start + 2:]
334 334
335 335 def renamed(self, node):
336 336 validatenode(node)
337 337
338 338 if self.parents(node)[0] != nullid:
339 339 return False
340 340
341 341 fulltext = self.revision(node)
342 342 m = revlog.parsemeta(fulltext)[0]
343 343
344 344 if m and 'copy' in m:
345 345 return m['copy'], bin(m['copyrev'])
346 346
347 347 return False
348 348
349 349 def cmp(self, node, text):
350 350 validatenode(node)
351 351
352 352 t = text
353 353
354 354 if text.startswith(b'\1\n'):
355 355 t = b'\1\n\1\n' + text
356 356
357 357 p1, p2 = self.parents(node)
358 358
359 359 if revlog.hash(t, p1, p2) == node:
360 360 return False
361 361
362 362 if self.iscensored(self.rev(node)):
363 363 return text != b''
364 364
365 365 if self.renamed(node):
366 366 t2 = self.read(node)
367 367 return t2 != text
368 368
369 369 return True
370 370
371 371 def size(self, rev):
372 372 validaterev(rev)
373 373
374 374 node = self._indexbyrev[rev][b'node']
375 375
376 376 if self.renamed(node):
377 377 return len(self.read(node))
378 378
379 379 if self.iscensored(rev):
380 380 return 0
381 381
382 382 return len(self.revision(node))
383 383
384 384 def iscensored(self, rev):
385 385 validaterev(rev)
386 386
387 387 return self.flags(rev) & revlog.REVIDX_ISCENSORED
388 388
389 389 def commonancestorsheads(self, a, b):
390 390 validatenode(a)
391 391 validatenode(b)
392 392
393 393 a = self.rev(a)
394 394 b = self.rev(b)
395 395
396 396 ancestors = ancestor.commonancestorsheads(self.parentrevs, a, b)
397 397 return pycompat.maplist(self.node, ancestors)
398 398
399 399 def descendants(self, revs):
400 400 # This is a copy of revlog.descendants()
401 401 first = min(revs)
402 402 if first == nullrev:
403 403 for i in self:
404 404 yield i
405 405 return
406 406
407 407 seen = set(revs)
408 408 for i in self.revs(start=first + 1):
409 409 for x in self.parentrevs(i):
410 410 if x != nullrev and x in seen:
411 411 seen.add(i)
412 412 yield i
413 413 break
414 414
415 415 # Required by verify.
416 416 def files(self):
417 417 entries = self._svfs.listdir(self._storepath)
418 418
419 419 # Strip out undo.backup.* files created as part of transaction
420 420 # recording.
421 421 entries = [f for f in entries if not f.startswith('undo.backup.')]
422 422
423 423 return [b'/'.join((self._storepath, f)) for f in entries]
424 424
425 425 def add(self, text, meta, transaction, linkrev, p1, p2):
426 426 if meta or text.startswith(b'\1\n'):
427 427 text = revlog.packmeta(meta, text)
428 428
429 429 return self.addrevision(text, transaction, linkrev, p1, p2)
430 430
431 431 def addrevision(self, text, transaction, linkrev, p1, p2, node=None,
432 432 flags=revlog.REVIDX_DEFAULT_FLAGS, cachedelta=None):
433 433 validatenode(p1)
434 434 validatenode(p2)
435 435
436 436 if flags:
437 437 node = node or revlog.hash(text, p1, p2)
438 438
439 439 rawtext, validatehash = self._processflags(text, flags, 'write')
440 440
441 441 node = node or revlog.hash(text, p1, p2)
442 442
443 443 if node in self._indexbynode:
444 444 return node
445 445
446 446 if validatehash:
447 447 self.checkhash(rawtext, node, p1=p1, p2=p2)
448 448
449 449 return self._addrawrevision(node, rawtext, transaction, linkrev, p1, p2,
450 450 flags)
451 451
452 452 def _addrawrevision(self, node, rawtext, transaction, link, p1, p2, flags):
453 453 transaction.addbackup(self._indexpath)
454 454
455 455 path = b'/'.join([self._storepath, hex(node)])
456 456
457 457 self._svfs.write(path, rawtext)
458 458
459 459 self._indexdata.append({
460 460 b'node': node,
461 461 b'p1': p1,
462 462 b'p2': p2,
463 463 b'linkrev': link,
464 464 b'flags': flags,
465 465 })
466 466
467 467 self._reflectindexupdate()
468 468
469 469 return node
470 470
471 471 def _reflectindexupdate(self):
472 472 self._refreshindex()
473 473 self._svfs.write(self._indexpath, cbor.dumps(self._indexdata))
474 474
475 475 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
476 476 nodes = []
477 477
478 478 transaction.addbackup(self._indexpath)
479 479
480 480 for node, p1, p2, linknode, deltabase, delta, flags in deltas:
481 481 linkrev = linkmapper(linknode)
482 482 flags = flags or revlog.REVIDX_DEFAULT_FLAGS
483 483
484 484 nodes.append(node)
485 485
486 486 if node in self._indexbynode:
487 487 continue
488 488
489 489 # Need to resolve the fulltext from the delta base.
490 490 if deltabase == nullid:
491 491 text = mdiff.patch(b'', delta)
492 492 else:
493 493 text = mdiff.patch(self.revision(deltabase), delta)
494 494
495 495 self._addrawrevision(node, text, transaction, linkrev, p1, p2,
496 496 flags)
497 497
498 498 if addrevisioncb:
499 499 addrevisioncb(self, node)
500 500
501 501 return nodes
502 502
503 503 def revdiff(self, rev1, rev2):
504 504 validaterev(rev1)
505 505 validaterev(rev2)
506 506
507 507 node1 = self.node(rev1)
508 508 node2 = self.node(rev2)
509 509
510 510 return mdiff.textdiff(self.revision(node1, raw=True),
511 511 self.revision(node2, raw=True))
512 512
513 def emitrevisiondeltas(self, requests):
514 for request in requests:
515 node = request.node
516 rev = self.rev(node)
517
518 if request.basenode == nullid:
519 baserev = nullrev
520 elif request.basenode is not None:
521 baserev = self.rev(request.basenode)
522 else:
523 # This is a test extension and we can do simple things
524 # for choosing a delta parent.
525 baserev = self.deltaparent(rev)
526
527 if baserev != nullrev and not self._candelta(baserev, rev):
528 baserev = nullrev
529
530 revision = None
531 delta = None
532 baserevisionsize = None
533
534 if self.iscensored(baserev) or self.iscensored(rev):
535 try:
536 revision = self.revision(node, raw=True)
537 except error.CensoredNodeError as e:
538 revision = e.tombstone
539
540 if baserev != nullrev:
541 baserevisionsize = self.rawsize(baserev)
542
543 elif baserev == nullrev:
544 revision = self.revision(node, raw=True)
545 else:
546 delta = self.revdiff(baserev, rev)
547
548 extraflags = revlog.REVIDX_ELLIPSIS if request.ellipsis else 0
549
550 yield simplestorerevisiondelta(
551 node=node,
552 p1node=request.p1node,
553 p2node=request.p2node,
554 linknode=request.linknode,
555 basenode=self.node(baserev),
556 flags=self.flags(rev) | extraflags,
557 baserevisionsize=baserevisionsize,
558 revision=revision,
559 delta=delta)
560
561 513 def heads(self, start=None, stop=None):
562 514 # This is copied from revlog.py.
563 515 if start is None and stop is None:
564 516 if not len(self):
565 517 return [nullid]
566 518 return [self.node(r) for r in self.headrevs()]
567 519
568 520 if start is None:
569 521 start = nullid
570 522 if stop is None:
571 523 stop = []
572 524 stoprevs = set([self.rev(n) for n in stop])
573 525 startrev = self.rev(start)
574 526 reachable = {startrev}
575 527 heads = {startrev}
576 528
577 529 parentrevs = self.parentrevs
578 530 for r in self.revs(start=startrev + 1):
579 531 for p in parentrevs(r):
580 532 if p in reachable:
581 533 if r not in stoprevs:
582 534 reachable.add(r)
583 535 heads.add(r)
584 536 if p in heads and p not in stoprevs:
585 537 heads.remove(p)
586 538
587 539 return [self.node(r) for r in heads]
588 540
589 541 def children(self, node):
590 542 validatenode(node)
591 543
592 544 # This is a copy of revlog.children().
593 545 c = []
594 546 p = self.rev(node)
595 547 for r in self.revs(start=p + 1):
596 548 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
597 549 if prevs:
598 550 for pr in prevs:
599 551 if pr == p:
600 552 c.append(self.node(r))
601 553 elif p == nullrev:
602 554 c.append(self.node(r))
603 555 return c
604 556
605 557 def getstrippoint(self, minlink):
606 558
607 559 # This is largely a copy of revlog.getstrippoint().
608 560 brokenrevs = set()
609 561 strippoint = len(self)
610 562
611 563 heads = {}
612 564 futurelargelinkrevs = set()
613 565 for head in self.heads():
614 566 headlinkrev = self.linkrev(self.rev(head))
615 567 heads[head] = headlinkrev
616 568 if headlinkrev >= minlink:
617 569 futurelargelinkrevs.add(headlinkrev)
618 570
619 571 # This algorithm involves walking down the rev graph, starting at the
620 572 # heads. Since the revs are topologically sorted according to linkrev,
621 573 # once all head linkrevs are below the minlink, we know there are
622 574 # no more revs that could have a linkrev greater than minlink.
623 575 # So we can stop walking.
624 576 while futurelargelinkrevs:
625 577 strippoint -= 1
626 578 linkrev = heads.pop(strippoint)
627 579
628 580 if linkrev < minlink:
629 581 brokenrevs.add(strippoint)
630 582 else:
631 583 futurelargelinkrevs.remove(linkrev)
632 584
633 585 for p in self.parentrevs(strippoint):
634 586 if p != nullrev:
635 587 plinkrev = self.linkrev(p)
636 588 heads[p] = plinkrev
637 589 if plinkrev >= minlink:
638 590 futurelargelinkrevs.add(plinkrev)
639 591
640 592 return strippoint, brokenrevs
641 593
642 594 def strip(self, minlink, transaction):
643 595 if not len(self):
644 596 return
645 597
646 598 rev, _ignored = self.getstrippoint(minlink)
647 599 if rev == len(self):
648 600 return
649 601
650 602 # Purge index data starting at the requested revision.
651 603 self._indexdata[rev:] = []
652 604 self._reflectindexupdate()
653 605
654 606 def issimplestorefile(f, kind, st):
655 607 if kind != stat.S_IFREG:
656 608 return False
657 609
658 610 if store.isrevlog(f, kind, st):
659 611 return False
660 612
661 613 # Ignore transaction undo files.
662 614 if f.startswith('undo.'):
663 615 return False
664 616
665 617 # Otherwise assume it belongs to the simple store.
666 618 return True
667 619
668 620 class simplestore(store.encodedstore):
669 621 def datafiles(self):
670 622 for x in super(simplestore, self).datafiles():
671 623 yield x
672 624
673 625 # Supplement with non-revlog files.
674 626 extrafiles = self._walk('data', True, filefilter=issimplestorefile)
675 627
676 628 for unencoded, encoded, size in extrafiles:
677 629 try:
678 630 unencoded = store.decodefilename(unencoded)
679 631 except KeyError:
680 632 unencoded = None
681 633
682 634 yield unencoded, encoded, size
683 635
684 636 def reposetup(ui, repo):
685 637 if not repo.local():
686 638 return
687 639
688 640 if isinstance(repo, bundlerepo.bundlerepository):
689 641 raise error.Abort(_('cannot use simple store with bundlerepo'))
690 642
691 643 class simplestorerepo(repo.__class__):
692 644 def file(self, f):
693 645 return filestorage(self.svfs, f)
694 646
695 647 repo.__class__ = simplestorerepo
696 648
697 649 def featuresetup(ui, supported):
698 650 supported.add(REQUIREMENT)
699 651
700 652 def newreporequirements(orig, ui):
701 653 """Modifies default requirements for new repos to use the simple store."""
702 654 requirements = orig(ui)
703 655
704 656 # These requirements are only used to affect creation of the store
705 657 # object. We have our own store. So we can remove them.
706 658 # TODO do this once we feel like taking the test hit.
707 659 #if 'fncache' in requirements:
708 660 # requirements.remove('fncache')
709 661 #if 'dotencode' in requirements:
710 662 # requirements.remove('dotencode')
711 663
712 664 requirements.add(REQUIREMENT)
713 665
714 666 return requirements
715 667
716 668 def makestore(orig, requirements, path, vfstype):
717 669 if REQUIREMENT not in requirements:
718 670 return orig(requirements, path, vfstype)
719 671
720 672 return simplestore(path, vfstype)
721 673
722 674 def verifierinit(orig, self, *args, **kwargs):
723 675 orig(self, *args, **kwargs)
724 676
725 677 # We don't care that files in the store don't align with what is
726 678 # advertised. So suppress these warnings.
727 679 self.warnorphanstorefiles = False
728 680
729 681 def extsetup(ui):
730 682 localrepo.featuresetupfuncs.add(featuresetup)
731 683
732 684 extensions.wrapfunction(localrepo, 'newreporequirements',
733 685 newreporequirements)
734 686 extensions.wrapfunction(store, 'store', makestore)
735 687 extensions.wrapfunction(verify.verifier, '__init__', verifierinit)
@@ -1,236 +1,225 b''
1 1 # Test that certain objects conform to well-defined interfaces.
2 2
3 3 from __future__ import absolute_import, print_function
4 4
5 5 from mercurial import encoding
6 6 encoding.environ[b'HGREALINTERFACES'] = b'1'
7 7
8 8 import os
9 9 import subprocess
10 10 import sys
11 11
12 12 # Only run if tests are run in a repo
13 13 if subprocess.call(['python', '%s/hghave' % os.environ['TESTDIR'],
14 14 'test-repo']):
15 15 sys.exit(80)
16 16
17 17 from mercurial.thirdparty.zope import (
18 18 interface as zi,
19 19 )
20 20 from mercurial.thirdparty.zope.interface import (
21 21 verify as ziverify,
22 22 )
23 23 from mercurial import (
24 changegroup,
25 24 bundlerepo,
26 25 filelog,
27 26 httppeer,
28 27 localrepo,
29 28 manifest,
30 29 pycompat,
31 30 repository,
32 31 revlog,
33 32 sshpeer,
34 33 statichttprepo,
35 34 ui as uimod,
36 35 unionrepo,
37 36 vfs as vfsmod,
38 37 wireprotoserver,
39 38 wireprototypes,
40 39 wireprotov1peer,
41 40 wireprotov2server,
42 41 )
43 42
44 43 rootdir = pycompat.fsencode(
45 44 os.path.normpath(os.path.join(os.path.dirname(__file__), '..')))
46 45
47 46 def checkzobject(o, allowextra=False):
48 47 """Verify an object with a zope interface."""
49 48 ifaces = zi.providedBy(o)
50 49 if not ifaces:
51 50 print('%r does not provide any zope interfaces' % o)
52 51 return
53 52
54 53 # Run zope.interface's built-in verification routine. This verifies that
55 54 # everything that is supposed to be present is present.
56 55 for iface in ifaces:
57 56 ziverify.verifyObject(iface, o)
58 57
59 58 if allowextra:
60 59 return
61 60
62 61 # Now verify that the object provides no extra public attributes that
63 62 # aren't declared as part of interfaces.
64 63 allowed = set()
65 64 for iface in ifaces:
66 65 allowed |= set(iface.names(all=True))
67 66
68 67 public = {a for a in dir(o) if not a.startswith('_')}
69 68
70 69 for attr in sorted(public - allowed):
71 70 print('public attribute not declared in interfaces: %s.%s' % (
72 71 o.__class__.__name__, attr))
73 72
74 73 # Facilitates testing localpeer.
75 74 class dummyrepo(object):
76 75 def __init__(self):
77 76 self.ui = uimod.ui()
78 77 def filtered(self, name):
79 78 pass
80 79 def _restrictcapabilities(self, caps):
81 80 pass
82 81
83 82 class dummyopener(object):
84 83 handlers = []
85 84
86 85 # Facilitates testing sshpeer without requiring a server.
87 86 class badpeer(httppeer.httppeer):
88 87 def __init__(self):
89 88 super(badpeer, self).__init__(None, None, None, dummyopener(), None,
90 89 None)
91 90 self.badattribute = True
92 91
93 92 def badmethod(self):
94 93 pass
95 94
96 95 class dummypipe(object):
97 96 def close(self):
98 97 pass
99 98
100 99 def main():
101 100 ui = uimod.ui()
102 101 # Needed so we can open a local repo with obsstore without a warning.
103 102 ui.setconfig('experimental', 'evolution.createmarkers', True)
104 103
105 104 checkzobject(badpeer())
106 105
107 106 ziverify.verifyClass(repository.ipeerbase, httppeer.httppeer)
108 107 checkzobject(httppeer.httppeer(None, None, None, dummyopener(), None, None))
109 108
110 109 ziverify.verifyClass(repository.ipeerconnection,
111 110 httppeer.httpv2peer)
112 111 ziverify.verifyClass(repository.ipeercapabilities,
113 112 httppeer.httpv2peer)
114 113 checkzobject(httppeer.httpv2peer(None, b'', b'', None, None, None))
115 114
116 115 ziverify.verifyClass(repository.ipeerbase,
117 116 localrepo.localpeer)
118 117 checkzobject(localrepo.localpeer(dummyrepo()))
119 118
120 119 ziverify.verifyClass(repository.ipeercommandexecutor,
121 120 localrepo.localcommandexecutor)
122 121 checkzobject(localrepo.localcommandexecutor(None))
123 122
124 123 ziverify.verifyClass(repository.ipeercommandexecutor,
125 124 wireprotov1peer.peerexecutor)
126 125 checkzobject(wireprotov1peer.peerexecutor(None))
127 126
128 127 ziverify.verifyClass(repository.ipeerbase, sshpeer.sshv1peer)
129 128 checkzobject(sshpeer.sshv1peer(ui, b'ssh://localhost/foo', b'', dummypipe(),
130 129 dummypipe(), None, None))
131 130
132 131 ziverify.verifyClass(repository.ipeerbase, sshpeer.sshv2peer)
133 132 checkzobject(sshpeer.sshv2peer(ui, b'ssh://localhost/foo', b'', dummypipe(),
134 133 dummypipe(), None, None))
135 134
136 135 ziverify.verifyClass(repository.ipeerbase, bundlerepo.bundlepeer)
137 136 checkzobject(bundlerepo.bundlepeer(dummyrepo()))
138 137
139 138 ziverify.verifyClass(repository.ipeerbase, statichttprepo.statichttppeer)
140 139 checkzobject(statichttprepo.statichttppeer(dummyrepo()))
141 140
142 141 ziverify.verifyClass(repository.ipeerbase, unionrepo.unionpeer)
143 142 checkzobject(unionrepo.unionpeer(dummyrepo()))
144 143
145 144 ziverify.verifyClass(repository.ilocalrepositorymain,
146 145 localrepo.localrepository)
147 146 ziverify.verifyClass(repository.ilocalrepositoryfilestorage,
148 147 localrepo.revlogfilestorage)
149 148 repo = localrepo.makelocalrepository(ui, rootdir)
150 149 checkzobject(repo)
151 150
152 151 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
153 152 wireprotoserver.sshv1protocolhandler)
154 153 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
155 154 wireprotoserver.sshv2protocolhandler)
156 155 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
157 156 wireprotoserver.httpv1protocolhandler)
158 157 ziverify.verifyClass(wireprototypes.baseprotocolhandler,
159 158 wireprotov2server.httpv2protocolhandler)
160 159
161 160 sshv1 = wireprotoserver.sshv1protocolhandler(None, None, None)
162 161 checkzobject(sshv1)
163 162 sshv2 = wireprotoserver.sshv2protocolhandler(None, None, None)
164 163 checkzobject(sshv2)
165 164
166 165 httpv1 = wireprotoserver.httpv1protocolhandler(None, None, None)
167 166 checkzobject(httpv1)
168 167 httpv2 = wireprotov2server.httpv2protocolhandler(None, None)
169 168 checkzobject(httpv2)
170 169
171 170 ziverify.verifyClass(repository.ifilestorage, filelog.filelog)
172 171 ziverify.verifyClass(repository.imanifestdict, manifest.manifestdict)
173 172 ziverify.verifyClass(repository.imanifestrevisionstored,
174 173 manifest.manifestctx)
175 174 ziverify.verifyClass(repository.imanifestrevisionwritable,
176 175 manifest.memmanifestctx)
177 176 ziverify.verifyClass(repository.imanifestrevisionstored,
178 177 manifest.treemanifestctx)
179 178 ziverify.verifyClass(repository.imanifestrevisionwritable,
180 179 manifest.memtreemanifestctx)
181 180 ziverify.verifyClass(repository.imanifestlog, manifest.manifestlog)
182 181 ziverify.verifyClass(repository.imanifeststorage, manifest.manifestrevlog)
183 182
184 183 vfs = vfsmod.vfs(b'.')
185 184 fl = filelog.filelog(vfs, b'dummy.i')
186 185 checkzobject(fl, allowextra=True)
187 186
188 187 # Conforms to imanifestlog.
189 188 ml = manifest.manifestlog(vfs, repo, manifest.manifestrevlog(repo.svfs))
190 189 checkzobject(ml)
191 190 checkzobject(repo.manifestlog)
192 191
193 192 # Conforms to imanifestrevision.
194 193 mctx = ml[repo[0].manifestnode()]
195 194 checkzobject(mctx)
196 195
197 196 # Conforms to imanifestrevisionwritable.
198 197 checkzobject(mctx.new())
199 198 checkzobject(mctx.copy())
200 199
201 200 # Conforms to imanifestdict.
202 201 checkzobject(mctx.read())
203 202
204 203 mrl = manifest.manifestrevlog(vfs)
205 204 checkzobject(mrl)
206 205
207 206 ziverify.verifyClass(repository.irevisiondelta,
208 207 revlog.revlogrevisiondelta)
209 ziverify.verifyClass(repository.irevisiondeltarequest,
210 changegroup.revisiondeltarequest)
211 208
212 209 rd = revlog.revlogrevisiondelta(
213 210 node=b'',
214 211 p1node=b'',
215 212 p2node=b'',
216 213 basenode=b'',
217 214 linknode=b'',
218 215 flags=b'',
219 216 baserevisionsize=None,
220 217 revision=b'',
221 218 delta=None)
222 219 checkzobject(rd)
223 220
224 rdr = changegroup.revisiondeltarequest(
225 node=b'',
226 linknode=b'',
227 p1node=b'',
228 p2node=b'',
229 basenode=b'')
230 checkzobject(rdr)
231
232 221 ziverify.verifyClass(repository.iverifyproblem,
233 222 revlog.revlogproblem)
234 223 checkzobject(revlog.revlogproblem())
235 224
236 225 main()
General Comments 0
You need to be logged in to leave comments. Login now