##// END OF EJS Templates
interfaces: convert `repository.ipeerrequests` from zope `Attribute` attrs...
Matt Harbison -
r53362:4cc18625 default
parent child Browse files
Show More
@@ -1,2181 +1,2180
1 1 # repository.py - Interfaces and base classes for repositories and peers.
2 2 # coding: utf-8
3 3 #
4 4 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import annotations
10 10
11 11 import typing
12 12
13 13 from typing import (
14 14 Any,
15 15 Protocol,
16 16 )
17 17
18 18 from ..i18n import _
19 19 from .. import error
20 20 from . import util as interfaceutil
21 21
22 22 if typing.TYPE_CHECKING:
23 23 # Almost all mercurial modules are only imported in the type checking phase
24 24 # to avoid circular imports
25 25 from ..utils import (
26 26 urlutil,
27 27 )
28 28
29 29 # TODO: create a Protocol class, since importing uimod here causes a cycle
30 30 # that confuses pytype.
31 31 Ui = Any
32 32
33 33 # Local repository feature string.
34 34
35 35 # Revlogs are being used for file storage.
36 36 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
37 37 # The storage part of the repository is shared from an external source.
38 38 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
39 39 # LFS supported for backing file storage.
40 40 REPO_FEATURE_LFS = b'lfs'
41 41 # Repository supports being stream cloned.
42 42 REPO_FEATURE_STREAM_CLONE = b'streamclone'
43 43 # Repository supports (at least) some sidedata to be stored
44 44 REPO_FEATURE_SIDE_DATA = b'side-data'
45 45 # Files storage may lack data for all ancestors.
46 46 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
47 47
48 48 REVISION_FLAG_CENSORED = 1 << 15
49 49 REVISION_FLAG_ELLIPSIS = 1 << 14
50 50 REVISION_FLAG_EXTSTORED = 1 << 13
51 51 REVISION_FLAG_HASCOPIESINFO = 1 << 12
52 52
53 53 REVISION_FLAGS_KNOWN = (
54 54 REVISION_FLAG_CENSORED
55 55 | REVISION_FLAG_ELLIPSIS
56 56 | REVISION_FLAG_EXTSTORED
57 57 | REVISION_FLAG_HASCOPIESINFO
58 58 )
59 59
60 60 CG_DELTAMODE_STD = b'default'
61 61 CG_DELTAMODE_PREV = b'previous'
62 62 CG_DELTAMODE_FULL = b'fulltext'
63 63 CG_DELTAMODE_P1 = b'p1'
64 64
65 65
66 66 ## Cache related constants:
67 67 #
68 68 # Used to control which cache should be warmed in a repo.updatecaches(…) call.
69 69
70 70 # Warm branchmaps of all known repoview's filter-level
71 71 CACHE_BRANCHMAP_ALL = b"branchmap-all"
72 72 # Warm branchmaps of repoview's filter-level used by server
73 73 CACHE_BRANCHMAP_SERVED = b"branchmap-served"
74 74 # Warm internal changelog cache (eg: persistent nodemap)
75 75 CACHE_CHANGELOG_CACHE = b"changelog-cache"
76 76 # check of a branchmap can use the "pure topo" mode
77 77 CACHE_BRANCHMAP_DETECT_PURE_TOPO = b"branchmap-detect-pure-topo"
78 78 # Warm full manifest cache
79 79 CACHE_FULL_MANIFEST = b"full-manifest"
80 80 # Warm file-node-tags cache
81 81 CACHE_FILE_NODE_TAGS = b"file-node-tags"
82 82 # Warm internal manifestlog cache (eg: persistent nodemap)
83 83 CACHE_MANIFESTLOG_CACHE = b"manifestlog-cache"
84 84 # Warn rev branch cache
85 85 CACHE_REV_BRANCH = b"rev-branch-cache"
86 86 # Warm tags' cache for default repoview'
87 87 CACHE_TAGS_DEFAULT = b"tags-default"
88 88 # Warm tags' cache for repoview's filter-level used by server
89 89 CACHE_TAGS_SERVED = b"tags-served"
90 90
91 91 # the cache to warm by default after a simple transaction
92 92 # (this is a mutable set to let extension update it)
93 93 CACHES_DEFAULT = {
94 94 CACHE_BRANCHMAP_SERVED,
95 95 }
96 96
97 97 # the caches to warm when warming all of them
98 98 # (this is a mutable set to let extension update it)
99 99 CACHES_ALL = {
100 100 CACHE_BRANCHMAP_SERVED,
101 101 CACHE_BRANCHMAP_ALL,
102 102 CACHE_BRANCHMAP_DETECT_PURE_TOPO,
103 103 CACHE_REV_BRANCH,
104 104 CACHE_CHANGELOG_CACHE,
105 105 CACHE_FILE_NODE_TAGS,
106 106 CACHE_FULL_MANIFEST,
107 107 CACHE_MANIFESTLOG_CACHE,
108 108 CACHE_TAGS_DEFAULT,
109 109 CACHE_TAGS_SERVED,
110 110 }
111 111
112 112 # the cache to warm by default on simple call
113 113 # (this is a mutable set to let extension update it)
114 114 CACHES_POST_CLONE = CACHES_ALL.copy()
115 115 CACHES_POST_CLONE.discard(CACHE_FILE_NODE_TAGS)
116 116 CACHES_POST_CLONE.discard(CACHE_REV_BRANCH)
117 117
118 118
119 119 class _ipeerconnection(Protocol):
120 120 """Represents a "connection" to a repository.
121 121
122 122 This is the base interface for representing a connection to a repository.
123 123 It holds basic properties and methods applicable to all peer types.
124 124
125 125 This is not a complete interface definition and should not be used
126 126 outside of this module.
127 127 """
128 128
129 129 ui: Ui
130 130 """ui.ui instance"""
131 131
132 132 path: urlutil.path | None
133 133 """a urlutil.path instance or None"""
134 134
135 135 def url(self):
136 136 """Returns a URL string representing this peer.
137 137
138 138 Currently, implementations expose the raw URL used to construct the
139 139 instance. It may contain credentials as part of the URL. The
140 140 expectations of the value aren't well-defined and this could lead to
141 141 data leakage.
142 142
143 143 TODO audit/clean consumers and more clearly define the contents of this
144 144 value.
145 145 """
146 146
147 147 def local(self):
148 148 """Returns a local repository instance.
149 149
150 150 If the peer represents a local repository, returns an object that
151 151 can be used to interface with it. Otherwise returns ``None``.
152 152 """
153 153
154 154 def canpush(self):
155 155 """Returns a boolean indicating if this peer can be pushed to."""
156 156
157 157 def close(self):
158 158 """Close the connection to this peer.
159 159
160 160 This is called when the peer will no longer be used. Resources
161 161 associated with the peer should be cleaned up.
162 162 """
163 163
164 164
165 165 class ipeercapabilities(Protocol):
166 166 """Peer sub-interface related to capabilities."""
167 167
168 168 def capable(self, name):
169 169 """Determine support for a named capability.
170 170
171 171 Returns ``False`` if capability not supported.
172 172
173 173 Returns ``True`` if boolean capability is supported. Returns a string
174 174 if capability support is non-boolean.
175 175
176 176 Capability strings may or may not map to wire protocol capabilities.
177 177 """
178 178
179 179 def requirecap(self, name, purpose):
180 180 """Require a capability to be present.
181 181
182 182 Raises a ``CapabilityError`` if the capability isn't present.
183 183 """
184 184
185 185
186 186 class ipeercommands(Protocol):
187 187 """Client-side interface for communicating over the wire protocol.
188 188
189 189 This interface is used as a gateway to the Mercurial wire protocol.
190 190 methods commonly call wire protocol commands of the same name.
191 191 """
192 192
193 193 def branchmap(self):
194 194 """Obtain heads in named branches.
195 195
196 196 Returns a dict mapping branch name to an iterable of nodes that are
197 197 heads on that branch.
198 198 """
199 199
200 200 def capabilities(self):
201 201 """Obtain capabilities of the peer.
202 202
203 203 Returns a set of string capabilities.
204 204 """
205 205
206 206 def get_cached_bundle_inline(self, path):
207 207 """Retrieve a clonebundle across the wire.
208 208
209 209 Returns a chunkbuffer
210 210 """
211 211
212 212 def clonebundles(self):
213 213 """Obtains the clone bundles manifest for the repo.
214 214
215 215 Returns the manifest as unparsed bytes.
216 216 """
217 217
218 218 def debugwireargs(self, one, two, three=None, four=None, five=None):
219 219 """Used to facilitate debugging of arguments passed over the wire."""
220 220
221 221 def getbundle(self, source, **kwargs):
222 222 """Obtain remote repository data as a bundle.
223 223
224 224 This command is how the bulk of repository data is transferred from
225 225 the peer to the local repository
226 226
227 227 Returns a generator of bundle data.
228 228 """
229 229
230 230 def heads(self):
231 231 """Determine all known head revisions in the peer.
232 232
233 233 Returns an iterable of binary nodes.
234 234 """
235 235
236 236 def known(self, nodes):
237 237 """Determine whether multiple nodes are known.
238 238
239 239 Accepts an iterable of nodes whose presence to check for.
240 240
241 241 Returns an iterable of booleans indicating of the corresponding node
242 242 at that index is known to the peer.
243 243 """
244 244
245 245 def listkeys(self, namespace):
246 246 """Obtain all keys in a pushkey namespace.
247 247
248 248 Returns an iterable of key names.
249 249 """
250 250
251 251 def lookup(self, key):
252 252 """Resolve a value to a known revision.
253 253
254 254 Returns a binary node of the resolved revision on success.
255 255 """
256 256
257 257 def pushkey(self, namespace, key, old, new):
258 258 """Set a value using the ``pushkey`` protocol.
259 259
260 260 Arguments correspond to the pushkey namespace and key to operate on and
261 261 the old and new values for that key.
262 262
263 263 Returns a string with the peer result. The value inside varies by the
264 264 namespace.
265 265 """
266 266
267 267 def stream_out(self):
268 268 """Obtain streaming clone data.
269 269
270 270 Successful result should be a generator of data chunks.
271 271 """
272 272
273 273 def unbundle(self, bundle, heads, url):
274 274 """Transfer repository data to the peer.
275 275
276 276 This is how the bulk of data during a push is transferred.
277 277
278 278 Returns the integer number of heads added to the peer.
279 279 """
280 280
281 281
282 282 class ipeerlegacycommands(Protocol):
283 283 """Interface for implementing support for legacy wire protocol commands.
284 284
285 285 Wire protocol commands transition to legacy status when they are no longer
286 286 used by modern clients. To facilitate identifying which commands are
287 287 legacy, the interfaces are split.
288 288 """
289 289
290 290 def between(self, pairs):
291 291 """Obtain nodes between pairs of nodes.
292 292
293 293 ``pairs`` is an iterable of node pairs.
294 294
295 295 Returns an iterable of iterables of nodes corresponding to each
296 296 requested pair.
297 297 """
298 298
299 299 def branches(self, nodes):
300 300 """Obtain ancestor changesets of specific nodes back to a branch point.
301 301
302 302 For each requested node, the peer finds the first ancestor node that is
303 303 a DAG root or is a merge.
304 304
305 305 Returns an iterable of iterables with the resolved values for each node.
306 306 """
307 307
308 308 def changegroup(self, nodes, source):
309 309 """Obtain a changegroup with data for descendants of specified nodes."""
310 310
311 311 def changegroupsubset(self, bases, heads, source):
312 312 pass
313 313
314 314
315 315 class ipeercommandexecutor(Protocol):
316 316 """Represents a mechanism to execute remote commands.
317 317
318 318 This is the primary interface for requesting that wire protocol commands
319 319 be executed. Instances of this interface are active in a context manager
320 320 and have a well-defined lifetime. When the context manager exits, all
321 321 outstanding requests are waited on.
322 322 """
323 323
324 324 def callcommand(self, name, args):
325 325 """Request that a named command be executed.
326 326
327 327 Receives the command name and a dictionary of command arguments.
328 328
329 329 Returns a ``concurrent.futures.Future`` that will resolve to the
330 330 result of that command request. That exact value is left up to
331 331 the implementation and possibly varies by command.
332 332
333 333 Not all commands can coexist with other commands in an executor
334 334 instance: it depends on the underlying wire protocol transport being
335 335 used and the command itself.
336 336
337 337 Implementations MAY call ``sendcommands()`` automatically if the
338 338 requested command can not coexist with other commands in this executor.
339 339
340 340 Implementations MAY call ``sendcommands()`` automatically when the
341 341 future's ``result()`` is called. So, consumers using multiple
342 342 commands with an executor MUST ensure that ``result()`` is not called
343 343 until all command requests have been issued.
344 344 """
345 345
346 346 def sendcommands(self):
347 347 """Trigger submission of queued command requests.
348 348
349 349 Not all transports submit commands as soon as they are requested to
350 350 run. When called, this method forces queued command requests to be
351 351 issued. It will no-op if all commands have already been sent.
352 352
353 353 When called, no more new commands may be issued with this executor.
354 354 """
355 355
356 356 def close(self):
357 357 """Signal that this command request is finished.
358 358
359 359 When called, no more new commands may be issued. All outstanding
360 360 commands that have previously been issued are waited on before
361 361 returning. This not only includes waiting for the futures to resolve,
362 362 but also waiting for all response data to arrive. In other words,
363 363 calling this waits for all on-wire state for issued command requests
364 364 to finish.
365 365
366 366 When used as a context manager, this method is called when exiting the
367 367 context manager.
368 368
369 369 This method may call ``sendcommands()`` if there are buffered commands.
370 370 """
371 371
372 372
373 373 class ipeerrequests(Protocol):
374 374 """Interface for executing commands on a peer."""
375 375
376 limitedarguments = interfaceutil.Attribute(
376 limitedarguments: bool
377 377 """True if the peer cannot receive large argument value for commands."""
378 )
379 378
380 379 def commandexecutor(self):
381 380 """A context manager that resolves to an ipeercommandexecutor.
382 381
383 382 The object this resolves to can be used to issue command requests
384 383 to the peer.
385 384
386 385 Callers should call its ``callcommand`` method to issue command
387 386 requests.
388 387
389 388 A new executor should be obtained for each distinct set of commands
390 389 (possibly just a single command) that the consumer wants to execute
391 390 as part of a single operation or round trip. This is because some
392 391 peers are half-duplex and/or don't support persistent connections.
393 392 e.g. in the case of HTTP peers, commands sent to an executor represent
394 393 a single HTTP request. While some peers may support multiple command
395 394 sends over the wire per executor, consumers need to code to the least
396 395 capable peer. So it should be assumed that command executors buffer
397 396 called commands until they are told to send them and that each
398 397 command executor could result in a new connection or wire-level request
399 398 being issued.
400 399 """
401 400
402 401
403 402 class ipeerbase(_ipeerconnection, ipeercapabilities, ipeerrequests):
404 403 """Unified interface for peer repositories.
405 404
406 405 All peer instances must conform to this interface.
407 406 """
408 407
409 408
410 409 @interfaceutil.implementer(ipeerbase)
411 410 class peer:
412 411 """Base class for peer repositories."""
413 412
414 413 limitedarguments = False
415 414
416 415 def __init__(self, ui, path=None, remotehidden=False):
417 416 self.ui = ui
418 417 self.path = path
419 418
420 419 def capable(self, name):
421 420 # TODO: this class should maybe subclass ipeercommands too, otherwise it
422 421 # is assuming whatever uses this as a mixin also has this interface.
423 422 caps = self.capabilities() # pytype: disable=attribute-error
424 423 if name in caps:
425 424 return True
426 425
427 426 name = b'%s=' % name
428 427 for cap in caps:
429 428 if cap.startswith(name):
430 429 return cap[len(name) :]
431 430
432 431 return False
433 432
434 433 def requirecap(self, name, purpose):
435 434 if self.capable(name):
436 435 return
437 436
438 437 raise error.CapabilityError(
439 438 _(
440 439 b'cannot %s; remote repository does not support the '
441 440 b'\'%s\' capability'
442 441 )
443 442 % (purpose, name)
444 443 )
445 444
446 445
447 446 class iverifyproblem(Protocol):
448 447 """Represents a problem with the integrity of the repository.
449 448
450 449 Instances of this interface are emitted to describe an integrity issue
451 450 with a repository (e.g. corrupt storage, missing data, etc).
452 451
453 452 Instances are essentially messages associated with severity.
454 453 """
455 454
456 455 warning = interfaceutil.Attribute(
457 456 """Message indicating a non-fatal problem."""
458 457 )
459 458
460 459 error = interfaceutil.Attribute("""Message indicating a fatal problem.""")
461 460
462 461 node = interfaceutil.Attribute(
463 462 """Revision encountering the problem.
464 463
465 464 ``None`` means the problem doesn't apply to a single revision.
466 465 """
467 466 )
468 467
469 468
470 469 class irevisiondelta(Protocol):
471 470 """Represents a delta between one revision and another.
472 471
473 472 Instances convey enough information to allow a revision to be exchanged
474 473 with another repository.
475 474
476 475 Instances represent the fulltext revision data or a delta against
477 476 another revision. Therefore the ``revision`` and ``delta`` attributes
478 477 are mutually exclusive.
479 478
480 479 Typically used for changegroup generation.
481 480 """
482 481
483 482 node = interfaceutil.Attribute("""20 byte node of this revision.""")
484 483
485 484 p1node = interfaceutil.Attribute(
486 485 """20 byte node of 1st parent of this revision."""
487 486 )
488 487
489 488 p2node = interfaceutil.Attribute(
490 489 """20 byte node of 2nd parent of this revision."""
491 490 )
492 491
493 492 linknode = interfaceutil.Attribute(
494 493 """20 byte node of the changelog revision this node is linked to."""
495 494 )
496 495
497 496 flags = interfaceutil.Attribute(
498 497 """2 bytes of integer flags that apply to this revision.
499 498
500 499 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
501 500 """
502 501 )
503 502
504 503 basenode = interfaceutil.Attribute(
505 504 """20 byte node of the revision this data is a delta against.
506 505
507 506 ``nullid`` indicates that the revision is a full revision and not
508 507 a delta.
509 508 """
510 509 )
511 510
512 511 baserevisionsize = interfaceutil.Attribute(
513 512 """Size of base revision this delta is against.
514 513
515 514 May be ``None`` if ``basenode`` is ``nullid``.
516 515 """
517 516 )
518 517
519 518 revision = interfaceutil.Attribute(
520 519 """Raw fulltext of revision data for this node."""
521 520 )
522 521
523 522 delta = interfaceutil.Attribute(
524 523 """Delta between ``basenode`` and ``node``.
525 524
526 525 Stored in the bdiff delta format.
527 526 """
528 527 )
529 528
530 529 sidedata = interfaceutil.Attribute(
531 530 """Raw sidedata bytes for the given revision."""
532 531 )
533 532
534 533 protocol_flags = interfaceutil.Attribute(
535 534 """Single byte of integer flags that can influence the protocol.
536 535
537 536 This is a bitwise composition of the ``storageutil.CG_FLAG*`` constants.
538 537 """
539 538 )
540 539
541 540
542 541 class ifilerevisionssequence(Protocol):
543 542 """Contains index data for all revisions of a file.
544 543
545 544 Types implementing this behave like lists of tuples. The index
546 545 in the list corresponds to the revision number. The values contain
547 546 index metadata.
548 547
549 548 The *null* revision (revision number -1) is always the last item
550 549 in the index.
551 550 """
552 551
553 552 def __len__(self):
554 553 """The total number of revisions."""
555 554
556 555 def __getitem__(self, rev):
557 556 """Returns the object having a specific revision number.
558 557
559 558 Returns an 8-tuple with the following fields:
560 559
561 560 offset+flags
562 561 Contains the offset and flags for the revision. 64-bit unsigned
563 562 integer where first 6 bytes are the offset and the next 2 bytes
564 563 are flags. The offset can be 0 if it is not used by the store.
565 564 compressed size
566 565 Size of the revision data in the store. It can be 0 if it isn't
567 566 needed by the store.
568 567 uncompressed size
569 568 Fulltext size. It can be 0 if it isn't needed by the store.
570 569 base revision
571 570 Revision number of revision the delta for storage is encoded
572 571 against. -1 indicates not encoded against a base revision.
573 572 link revision
574 573 Revision number of changelog revision this entry is related to.
575 574 p1 revision
576 575 Revision number of 1st parent. -1 if no 1st parent.
577 576 p2 revision
578 577 Revision number of 2nd parent. -1 if no 1st parent.
579 578 node
580 579 Binary node value for this revision number.
581 580
582 581 Negative values should index off the end of the sequence. ``-1``
583 582 should return the null revision. ``-2`` should return the most
584 583 recent revision.
585 584 """
586 585
587 586 def __contains__(self, rev):
588 587 """Whether a revision number exists."""
589 588
590 589 def insert(self, i, entry):
591 590 """Add an item to the index at specific revision."""
592 591
593 592
594 593 class ifileindex(Protocol):
595 594 """Storage interface for index data of a single file.
596 595
597 596 File storage data is divided into index metadata and data storage.
598 597 This interface defines the index portion of the interface.
599 598
600 599 The index logically consists of:
601 600
602 601 * A mapping between revision numbers and nodes.
603 602 * DAG data (storing and querying the relationship between nodes).
604 603 * Metadata to facilitate storage.
605 604 """
606 605
607 606 nullid = interfaceutil.Attribute(
608 607 """node for the null revision for use as delta base."""
609 608 )
610 609
611 610 def __len__(self):
612 611 """Obtain the number of revisions stored for this file."""
613 612
614 613 def __iter__(self):
615 614 """Iterate over revision numbers for this file."""
616 615
617 616 def hasnode(self, node):
618 617 """Returns a bool indicating if a node is known to this store.
619 618
620 619 Implementations must only return True for full, binary node values:
621 620 hex nodes, revision numbers, and partial node matches must be
622 621 rejected.
623 622
624 623 The null node is never present.
625 624 """
626 625
627 626 def revs(self, start=0, stop=None):
628 627 """Iterate over revision numbers for this file, with control."""
629 628
630 629 def parents(self, node):
631 630 """Returns a 2-tuple of parent nodes for a revision.
632 631
633 632 Values will be ``nullid`` if the parent is empty.
634 633 """
635 634
636 635 def parentrevs(self, rev):
637 636 """Like parents() but operates on revision numbers."""
638 637
639 638 def rev(self, node):
640 639 """Obtain the revision number given a node.
641 640
642 641 Raises ``error.LookupError`` if the node is not known.
643 642 """
644 643
645 644 def node(self, rev):
646 645 """Obtain the node value given a revision number.
647 646
648 647 Raises ``IndexError`` if the node is not known.
649 648 """
650 649
651 650 def lookup(self, node):
652 651 """Attempt to resolve a value to a node.
653 652
654 653 Value can be a binary node, hex node, revision number, or a string
655 654 that can be converted to an integer.
656 655
657 656 Raises ``error.LookupError`` if a node could not be resolved.
658 657 """
659 658
660 659 def linkrev(self, rev):
661 660 """Obtain the changeset revision number a revision is linked to."""
662 661
663 662 def iscensored(self, rev):
664 663 """Return whether a revision's content has been censored."""
665 664
666 665 def commonancestorsheads(self, node1, node2):
667 666 """Obtain an iterable of nodes containing heads of common ancestors.
668 667
669 668 See ``ancestor.commonancestorsheads()``.
670 669 """
671 670
672 671 def descendants(self, revs):
673 672 """Obtain descendant revision numbers for a set of revision numbers.
674 673
675 674 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
676 675 """
677 676
678 677 def heads(self, start=None, stop=None):
679 678 """Obtain a list of nodes that are DAG heads, with control.
680 679
681 680 The set of revisions examined can be limited by specifying
682 681 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
683 682 iterable of nodes. DAG traversal starts at earlier revision
684 683 ``start`` and iterates forward until any node in ``stop`` is
685 684 encountered.
686 685 """
687 686
688 687 def children(self, node):
689 688 """Obtain nodes that are children of a node.
690 689
691 690 Returns a list of nodes.
692 691 """
693 692
694 693
695 694 class ifiledata(Protocol):
696 695 """Storage interface for data storage of a specific file.
697 696
698 697 This complements ``ifileindex`` and provides an interface for accessing
699 698 data for a tracked file.
700 699 """
701 700
702 701 def size(self, rev):
703 702 """Obtain the fulltext size of file data.
704 703
705 704 Any metadata is excluded from size measurements.
706 705 """
707 706
708 707 def revision(self, node):
709 708 """Obtain fulltext data for a node.
710 709
711 710 By default, any storage transformations are applied before the data
712 711 is returned. If ``raw`` is True, non-raw storage transformations
713 712 are not applied.
714 713
715 714 The fulltext data may contain a header containing metadata. Most
716 715 consumers should use ``read()`` to obtain the actual file data.
717 716 """
718 717
719 718 def rawdata(self, node):
720 719 """Obtain raw data for a node."""
721 720
722 721 def read(self, node):
723 722 """Resolve file fulltext data.
724 723
725 724 This is similar to ``revision()`` except any metadata in the data
726 725 headers is stripped.
727 726 """
728 727
729 728 def renamed(self, node):
730 729 """Obtain copy metadata for a node.
731 730
732 731 Returns ``False`` if no copy metadata is stored or a 2-tuple of
733 732 (path, node) from which this revision was copied.
734 733 """
735 734
736 735 def cmp(self, node, fulltext):
737 736 """Compare fulltext to another revision.
738 737
739 738 Returns True if the fulltext is different from what is stored.
740 739
741 740 This takes copy metadata into account.
742 741
743 742 TODO better document the copy metadata and censoring logic.
744 743 """
745 744
746 745 def emitrevisions(
747 746 self,
748 747 nodes,
749 748 nodesorder=None,
750 749 revisiondata=False,
751 750 assumehaveparentrevisions=False,
752 751 deltamode=CG_DELTAMODE_STD,
753 752 ):
754 753 """Produce ``irevisiondelta`` for revisions.
755 754
756 755 Given an iterable of nodes, emits objects conforming to the
757 756 ``irevisiondelta`` interface that describe revisions in storage.
758 757
759 758 This method is a generator.
760 759
761 760 The input nodes may be unordered. Implementations must ensure that a
762 761 node's parents are emitted before the node itself. Transitively, this
763 762 means that a node may only be emitted once all its ancestors in
764 763 ``nodes`` have also been emitted.
765 764
766 765 By default, emits "index" data (the ``node``, ``p1node``, and
767 766 ``p2node`` attributes). If ``revisiondata`` is set, revision data
768 767 will also be present on the emitted objects.
769 768
770 769 With default argument values, implementations can choose to emit
771 770 either fulltext revision data or a delta. When emitting deltas,
772 771 implementations must consider whether the delta's base revision
773 772 fulltext is available to the receiver.
774 773
775 774 The base revision fulltext is guaranteed to be available if any of
776 775 the following are met:
777 776
778 777 * Its fulltext revision was emitted by this method call.
779 778 * A delta for that revision was emitted by this method call.
780 779 * ``assumehaveparentrevisions`` is True and the base revision is a
781 780 parent of the node.
782 781
783 782 ``nodesorder`` can be used to control the order that revisions are
784 783 emitted. By default, revisions can be reordered as long as they are
785 784 in DAG topological order (see above). If the value is ``nodes``,
786 785 the iteration order from ``nodes`` should be used. If the value is
787 786 ``storage``, then the native order from the backing storage layer
788 787 is used. (Not all storage layers will have strong ordering and behavior
789 788 of this mode is storage-dependent.) ``nodes`` ordering can force
790 789 revisions to be emitted before their ancestors, so consumers should
791 790 use it with care.
792 791
793 792 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
794 793 be set and it is the caller's responsibility to resolve it, if needed.
795 794
796 795 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
797 796 all revision data should be emitted as deltas against the revision
798 797 emitted just prior. The initial revision should be a delta against its
799 798 1st parent.
800 799 """
801 800
802 801
803 802 class ifilemutation(Protocol):
804 803 """Storage interface for mutation events of a tracked file."""
805 804
806 805 def add(self, filedata, meta, transaction, linkrev, p1, p2):
807 806 """Add a new revision to the store.
808 807
809 808 Takes file data, dictionary of metadata, a transaction, linkrev,
810 809 and parent nodes.
811 810
812 811 Returns the node that was added.
813 812
814 813 May no-op if a revision matching the supplied data is already stored.
815 814 """
816 815
817 816 def addrevision(
818 817 self,
819 818 revisiondata,
820 819 transaction,
821 820 linkrev,
822 821 p1,
823 822 p2,
824 823 node=None,
825 824 flags=0,
826 825 cachedelta=None,
827 826 ):
828 827 """Add a new revision to the store and return its number.
829 828
830 829 This is similar to ``add()`` except it operates at a lower level.
831 830
832 831 The data passed in already contains a metadata header, if any.
833 832
834 833 ``node`` and ``flags`` can be used to define the expected node and
835 834 the flags to use with storage. ``flags`` is a bitwise value composed
836 835 of the various ``REVISION_FLAG_*`` constants.
837 836
838 837 ``add()`` is usually called when adding files from e.g. the working
839 838 directory. ``addrevision()`` is often called by ``add()`` and for
840 839 scenarios where revision data has already been computed, such as when
841 840 applying raw data from a peer repo.
842 841 """
843 842
844 843 def addgroup(
845 844 self,
846 845 deltas,
847 846 linkmapper,
848 847 transaction,
849 848 addrevisioncb=None,
850 849 duplicaterevisioncb=None,
851 850 maybemissingparents=False,
852 851 ):
853 852 """Process a series of deltas for storage.
854 853
855 854 ``deltas`` is an iterable of 7-tuples of
856 855 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
857 856 to add.
858 857
859 858 The ``delta`` field contains ``mpatch`` data to apply to a base
860 859 revision, identified by ``deltabase``. The base node can be
861 860 ``nullid``, in which case the header from the delta can be ignored
862 861 and the delta used as the fulltext.
863 862
864 863 ``alwayscache`` instructs the lower layers to cache the content of the
865 864 newly added revision, even if it needs to be explicitly computed.
866 865 This used to be the default when ``addrevisioncb`` was provided up to
867 866 Mercurial 5.8.
868 867
869 868 ``addrevisioncb`` should be called for each new rev as it is committed.
870 869 ``duplicaterevisioncb`` should be called for all revs with a
871 870 pre-existing node.
872 871
873 872 ``maybemissingparents`` is a bool indicating whether the incoming
874 873 data may reference parents/ancestor revisions that aren't present.
875 874 This flag is set when receiving data into a "shallow" store that
876 875 doesn't hold all history.
877 876
878 877 Returns a list of nodes that were processed. A node will be in the list
879 878 even if it existed in the store previously.
880 879 """
881 880
882 881 def censorrevision(self, tr, node, tombstone=b''):
883 882 """Remove the content of a single revision.
884 883
885 884 The specified ``node`` will have its content purged from storage.
886 885 Future attempts to access the revision data for this node will
887 886 result in failure.
888 887
889 888 A ``tombstone`` message can optionally be stored. This message may be
890 889 displayed to users when they attempt to access the missing revision
891 890 data.
892 891
893 892 Storage backends may have stored deltas against the previous content
894 893 in this revision. As part of censoring a revision, these storage
895 894 backends are expected to rewrite any internally stored deltas such
896 895 that they no longer reference the deleted content.
897 896 """
898 897
899 898 def getstrippoint(self, minlink):
900 899 """Find the minimum revision that must be stripped to strip a linkrev.
901 900
902 901 Returns a 2-tuple containing the minimum revision number and a set
903 902 of all revisions numbers that would be broken by this strip.
904 903
905 904 TODO this is highly revlog centric and should be abstracted into
906 905 a higher-level deletion API. ``repair.strip()`` relies on this.
907 906 """
908 907
909 908 def strip(self, minlink, transaction):
910 909 """Remove storage of items starting at a linkrev.
911 910
912 911 This uses ``getstrippoint()`` to determine the first node to remove.
913 912 Then it effectively truncates storage for all revisions after that.
914 913
915 914 TODO this is highly revlog centric and should be abstracted into a
916 915 higher-level deletion API.
917 916 """
918 917
919 918
920 919 class ifilestorage(ifileindex, ifiledata, ifilemutation):
921 920 """Complete storage interface for a single tracked file."""
922 921
923 922 def files(self):
924 923 """Obtain paths that are backing storage for this file.
925 924
926 925 TODO this is used heavily by verify code and there should probably
927 926 be a better API for that.
928 927 """
929 928
930 929 def storageinfo(
931 930 self,
932 931 exclusivefiles=False,
933 932 sharedfiles=False,
934 933 revisionscount=False,
935 934 trackedsize=False,
936 935 storedsize=False,
937 936 ):
938 937 """Obtain information about storage for this file's data.
939 938
940 939 Returns a dict describing storage for this tracked path. The keys
941 940 in the dict map to arguments of the same. The arguments are bools
942 941 indicating whether to calculate and obtain that data.
943 942
944 943 exclusivefiles
945 944 Iterable of (vfs, path) describing files that are exclusively
946 945 used to back storage for this tracked path.
947 946
948 947 sharedfiles
949 948 Iterable of (vfs, path) describing files that are used to back
950 949 storage for this tracked path. Those files may also provide storage
951 950 for other stored entities.
952 951
953 952 revisionscount
954 953 Number of revisions available for retrieval.
955 954
956 955 trackedsize
957 956 Total size in bytes of all tracked revisions. This is a sum of the
958 957 length of the fulltext of all revisions.
959 958
960 959 storedsize
961 960 Total size in bytes used to store data for all tracked revisions.
962 961 This is commonly less than ``trackedsize`` due to internal usage
963 962 of deltas rather than fulltext revisions.
964 963
965 964 Not all storage backends may support all queries are have a reasonable
966 965 value to use. In that case, the value should be set to ``None`` and
967 966 callers are expected to handle this special value.
968 967 """
969 968
970 969 def verifyintegrity(self, state):
971 970 """Verifies the integrity of file storage.
972 971
973 972 ``state`` is a dict holding state of the verifier process. It can be
974 973 used to communicate data between invocations of multiple storage
975 974 primitives.
976 975
977 976 If individual revisions cannot have their revision content resolved,
978 977 the method is expected to set the ``skipread`` key to a set of nodes
979 978 that encountered problems. If set, the method can also add the node(s)
980 979 to ``safe_renamed`` in order to indicate nodes that may perform the
981 980 rename checks with currently accessible data.
982 981
983 982 The method yields objects conforming to the ``iverifyproblem``
984 983 interface.
985 984 """
986 985
987 986
988 987 class idirs(Protocol):
989 988 """Interface representing a collection of directories from paths.
990 989
991 990 This interface is essentially a derived data structure representing
992 991 directories from a collection of paths.
993 992 """
994 993
995 994 def addpath(self, path):
996 995 """Add a path to the collection.
997 996
998 997 All directories in the path will be added to the collection.
999 998 """
1000 999
1001 1000 def delpath(self, path):
1002 1001 """Remove a path from the collection.
1003 1002
1004 1003 If the removal was the last path in a particular directory, the
1005 1004 directory is removed from the collection.
1006 1005 """
1007 1006
1008 1007 def __iter__(self):
1009 1008 """Iterate over the directories in this collection of paths."""
1010 1009
1011 1010 def __contains__(self, path):
1012 1011 """Whether a specific directory is in this collection."""
1013 1012
1014 1013
1015 1014 class imanifestdict(Protocol):
1016 1015 """Interface representing a manifest data structure.
1017 1016
1018 1017 A manifest is effectively a dict mapping paths to entries. Each entry
1019 1018 consists of a binary node and extra flags affecting that entry.
1020 1019 """
1021 1020
1022 1021 def __getitem__(self, path):
1023 1022 """Returns the binary node value for a path in the manifest.
1024 1023
1025 1024 Raises ``KeyError`` if the path does not exist in the manifest.
1026 1025
1027 1026 Equivalent to ``self.find(path)[0]``.
1028 1027 """
1029 1028
1030 1029 def find(self, path):
1031 1030 """Returns the entry for a path in the manifest.
1032 1031
1033 1032 Returns a 2-tuple of (node, flags).
1034 1033
1035 1034 Raises ``KeyError`` if the path does not exist in the manifest.
1036 1035 """
1037 1036
1038 1037 def __len__(self):
1039 1038 """Return the number of entries in the manifest."""
1040 1039
1041 1040 def __nonzero__(self):
1042 1041 """Returns True if the manifest has entries, False otherwise."""
1043 1042
1044 1043 __bool__ = __nonzero__
1045 1044
1046 1045 def set(self, path, node, flags):
1047 1046 """Define the node value and flags for a path in the manifest.
1048 1047
1049 1048 Equivalent to __setitem__ followed by setflag, but can be more efficient.
1050 1049 """
1051 1050
1052 1051 def __setitem__(self, path, node):
1053 1052 """Define the node value for a path in the manifest.
1054 1053
1055 1054 If the path is already in the manifest, its flags will be copied to
1056 1055 the new entry.
1057 1056 """
1058 1057
1059 1058 def __contains__(self, path):
1060 1059 """Whether a path exists in the manifest."""
1061 1060
1062 1061 def __delitem__(self, path):
1063 1062 """Remove a path from the manifest.
1064 1063
1065 1064 Raises ``KeyError`` if the path is not in the manifest.
1066 1065 """
1067 1066
1068 1067 def __iter__(self):
1069 1068 """Iterate over paths in the manifest."""
1070 1069
1071 1070 def iterkeys(self):
1072 1071 """Iterate over paths in the manifest."""
1073 1072
1074 1073 def keys(self):
1075 1074 """Obtain a list of paths in the manifest."""
1076 1075
1077 1076 def filesnotin(self, other, match=None):
1078 1077 """Obtain the set of paths in this manifest but not in another.
1079 1078
1080 1079 ``match`` is an optional matcher function to be applied to both
1081 1080 manifests.
1082 1081
1083 1082 Returns a set of paths.
1084 1083 """
1085 1084
1086 1085 def dirs(self):
1087 1086 """Returns an object implementing the ``idirs`` interface."""
1088 1087
1089 1088 def hasdir(self, dir):
1090 1089 """Returns a bool indicating if a directory is in this manifest."""
1091 1090
1092 1091 def walk(self, match):
1093 1092 """Generator of paths in manifest satisfying a matcher.
1094 1093
1095 1094 If the matcher has explicit files listed and they don't exist in
1096 1095 the manifest, ``match.bad()`` is called for each missing file.
1097 1096 """
1098 1097
1099 1098 def diff(self, other, match=None, clean=False):
1100 1099 """Find differences between this manifest and another.
1101 1100
1102 1101 This manifest is compared to ``other``.
1103 1102
1104 1103 If ``match`` is provided, the two manifests are filtered against this
1105 1104 matcher and only entries satisfying the matcher are compared.
1106 1105
1107 1106 If ``clean`` is True, unchanged files are included in the returned
1108 1107 object.
1109 1108
1110 1109 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1111 1110 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1112 1111 represents the node and flags for this manifest and ``(node2, flag2)``
1113 1112 are the same for the other manifest.
1114 1113 """
1115 1114
1116 1115 def setflag(self, path, flag):
1117 1116 """Set the flag value for a given path.
1118 1117
1119 1118 Raises ``KeyError`` if the path is not already in the manifest.
1120 1119 """
1121 1120
1122 1121 def get(self, path, default=None):
1123 1122 """Obtain the node value for a path or a default value if missing."""
1124 1123
1125 1124 def flags(self, path):
1126 1125 """Return the flags value for a path (default: empty bytestring)."""
1127 1126
1128 1127 def copy(self):
1129 1128 """Return a copy of this manifest."""
1130 1129
1131 1130 def items(self):
1132 1131 """Returns an iterable of (path, node) for items in this manifest."""
1133 1132
1134 1133 def iteritems(self):
1135 1134 """Identical to items()."""
1136 1135
1137 1136 def iterentries(self):
1138 1137 """Returns an iterable of (path, node, flags) for this manifest.
1139 1138
1140 1139 Similar to ``iteritems()`` except items are a 3-tuple and include
1141 1140 flags.
1142 1141 """
1143 1142
1144 1143 def text(self):
1145 1144 """Obtain the raw data representation for this manifest.
1146 1145
1147 1146 Result is used to create a manifest revision.
1148 1147 """
1149 1148
1150 1149 def fastdelta(self, base, changes):
1151 1150 """Obtain a delta between this manifest and another given changes.
1152 1151
1153 1152 ``base`` in the raw data representation for another manifest.
1154 1153
1155 1154 ``changes`` is an iterable of ``(path, to_delete)``.
1156 1155
1157 1156 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1158 1157 delta between ``base`` and this manifest.
1159 1158
1160 1159 If this manifest implementation can't support ``fastdelta()``,
1161 1160 raise ``mercurial.manifest.FastdeltaUnavailable``.
1162 1161 """
1163 1162
1164 1163
1165 1164 class imanifestrevisionbase(Protocol):
1166 1165 """Base interface representing a single revision of a manifest.
1167 1166
1168 1167 Should not be used as a primary interface: should always be inherited
1169 1168 as part of a larger interface.
1170 1169 """
1171 1170
1172 1171 def copy(self):
1173 1172 """Obtain a copy of this manifest instance.
1174 1173
1175 1174 Returns an object conforming to the ``imanifestrevisionwritable``
1176 1175 interface. The instance will be associated with the same
1177 1176 ``imanifestlog`` collection as this instance.
1178 1177 """
1179 1178
1180 1179 def read(self):
1181 1180 """Obtain the parsed manifest data structure.
1182 1181
1183 1182 The returned object conforms to the ``imanifestdict`` interface.
1184 1183 """
1185 1184
1186 1185
1187 1186 class imanifestrevisionstored(imanifestrevisionbase):
1188 1187 """Interface representing a manifest revision committed to storage."""
1189 1188
1190 1189 def node(self):
1191 1190 """The binary node for this manifest."""
1192 1191
1193 1192 parents = interfaceutil.Attribute(
1194 1193 """List of binary nodes that are parents for this manifest revision."""
1195 1194 )
1196 1195
1197 1196 def readdelta(self, shallow=False):
1198 1197 """Obtain the manifest data structure representing changes from parent.
1199 1198
1200 1199 This manifest is compared to its 1st parent. A new manifest
1201 1200 representing those differences is constructed.
1202 1201
1203 1202 If `shallow` is True, this will read the delta for this directory,
1204 1203 without recursively reading subdirectory manifests. Instead, any
1205 1204 subdirectory entry will be reported as it appears in the manifest, i.e.
1206 1205 the subdirectory will be reported among files and distinguished only by
1207 1206 its 't' flag. This only apply if the underlying manifest support it.
1208 1207
1209 1208 The returned object conforms to the ``imanifestdict`` interface.
1210 1209 """
1211 1210
1212 1211 def read_any_fast_delta(self, valid_bases=None, *, shallow=False):
1213 1212 """read some manifest information as fast if possible
1214 1213
1215 1214 This might return a "delta", a manifest object containing only file
1216 1215 changed compared to another revisions. The `valid_bases` argument
1217 1216 control the set of revision that might be used as a base.
1218 1217
1219 1218 If no delta can be retrieved quickly, a full read of the manifest will
1220 1219 be performed instead.
1221 1220
1222 1221 The function return a tuple with two elements. The first one is the
1223 1222 delta base used (or None if we did a full read), the second one is the
1224 1223 manifest information.
1225 1224
1226 1225 If `shallow` is True, this will read the delta for this directory,
1227 1226 without recursively reading subdirectory manifests. Instead, any
1228 1227 subdirectory entry will be reported as it appears in the manifest, i.e.
1229 1228 the subdirectory will be reported among files and distinguished only by
1230 1229 its 't' flag. This only apply if the underlying manifest support it.
1231 1230
1232 1231 The returned object conforms to the ``imanifestdict`` interface.
1233 1232 """
1234 1233
1235 1234 def read_delta_parents(self, *, shallow=False, exact=True):
1236 1235 """return a diff from this revision against both parents.
1237 1236
1238 1237 If `exact` is False, this might return a superset of the diff, containing
1239 1238 files that are actually present as is in one of the parents.
1240 1239
1241 1240 If `shallow` is True, this will read the delta for this directory,
1242 1241 without recursively reading subdirectory manifests. Instead, any
1243 1242 subdirectory entry will be reported as it appears in the manifest, i.e.
1244 1243 the subdirectory will be reported among files and distinguished only by
1245 1244 its 't' flag. This only apply if the underlying manifest support it.
1246 1245
1247 1246 The returned object conforms to the ``imanifestdict`` interface."""
1248 1247
1249 1248 def read_delta_new_entries(self, *, shallow=False):
1250 1249 """Return a manifest containing just the entries that might be new to
1251 1250 the repository.
1252 1251
1253 1252 This is often equivalent to a diff against both parents, but without
1254 1253 garantee. For performance reason, It might contains more files in some cases.
1255 1254
1256 1255 If `shallow` is True, this will read the delta for this directory,
1257 1256 without recursively reading subdirectory manifests. Instead, any
1258 1257 subdirectory entry will be reported as it appears in the manifest, i.e.
1259 1258 the subdirectory will be reported among files and distinguished only by
1260 1259 its 't' flag. This only apply if the underlying manifest support it.
1261 1260
1262 1261 The returned object conforms to the ``imanifestdict`` interface."""
1263 1262
1264 1263 def readfast(self, shallow=False):
1265 1264 """Calls either ``read()`` or ``readdelta()``.
1266 1265
1267 1266 The faster of the two options is called.
1268 1267 """
1269 1268
1270 1269 def find(self, key):
1271 1270 """Calls self.read().find(key)``.
1272 1271
1273 1272 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1274 1273 """
1275 1274
1276 1275
1277 1276 class imanifestrevisionwritable(imanifestrevisionbase):
1278 1277 """Interface representing a manifest revision that can be committed."""
1279 1278
1280 1279 def write(
1281 1280 self, transaction, linkrev, p1node, p2node, added, removed, match=None
1282 1281 ):
1283 1282 """Add this revision to storage.
1284 1283
1285 1284 Takes a transaction object, the changeset revision number it will
1286 1285 be associated with, its parent nodes, and lists of added and
1287 1286 removed paths.
1288 1287
1289 1288 If match is provided, storage can choose not to inspect or write out
1290 1289 items that do not match. Storage is still required to be able to provide
1291 1290 the full manifest in the future for any directories written (these
1292 1291 manifests should not be "narrowed on disk").
1293 1292
1294 1293 Returns the binary node of the created revision.
1295 1294 """
1296 1295
1297 1296
1298 1297 class imanifeststorage(Protocol):
1299 1298 """Storage interface for manifest data."""
1300 1299
1301 1300 nodeconstants = interfaceutil.Attribute(
1302 1301 """nodeconstants used by the current repository."""
1303 1302 )
1304 1303
1305 1304 tree = interfaceutil.Attribute(
1306 1305 """The path to the directory this manifest tracks.
1307 1306
1308 1307 The empty bytestring represents the root manifest.
1309 1308 """
1310 1309 )
1311 1310
1312 1311 index = interfaceutil.Attribute(
1313 1312 """An ``ifilerevisionssequence`` instance."""
1314 1313 )
1315 1314
1316 1315 opener = interfaceutil.Attribute(
1317 1316 """VFS opener to use to access underlying files used for storage.
1318 1317
1319 1318 TODO this is revlog specific and should not be exposed.
1320 1319 """
1321 1320 )
1322 1321
1323 1322 fulltextcache = interfaceutil.Attribute(
1324 1323 """Dict with cache of fulltexts.
1325 1324
1326 1325 TODO this doesn't feel appropriate for the storage interface.
1327 1326 """
1328 1327 )
1329 1328
1330 1329 def __len__(self):
1331 1330 """Obtain the number of revisions stored for this manifest."""
1332 1331
1333 1332 def __iter__(self):
1334 1333 """Iterate over revision numbers for this manifest."""
1335 1334
1336 1335 def rev(self, node):
1337 1336 """Obtain the revision number given a binary node.
1338 1337
1339 1338 Raises ``error.LookupError`` if the node is not known.
1340 1339 """
1341 1340
1342 1341 def node(self, rev):
1343 1342 """Obtain the node value given a revision number.
1344 1343
1345 1344 Raises ``error.LookupError`` if the revision is not known.
1346 1345 """
1347 1346
1348 1347 def lookup(self, value):
1349 1348 """Attempt to resolve a value to a node.
1350 1349
1351 1350 Value can be a binary node, hex node, revision number, or a bytes
1352 1351 that can be converted to an integer.
1353 1352
1354 1353 Raises ``error.LookupError`` if a ndoe could not be resolved.
1355 1354 """
1356 1355
1357 1356 def parents(self, node):
1358 1357 """Returns a 2-tuple of parent nodes for a node.
1359 1358
1360 1359 Values will be ``nullid`` if the parent is empty.
1361 1360 """
1362 1361
1363 1362 def parentrevs(self, rev):
1364 1363 """Like parents() but operates on revision numbers."""
1365 1364
1366 1365 def linkrev(self, rev):
1367 1366 """Obtain the changeset revision number a revision is linked to."""
1368 1367
1369 1368 def revision(self, node):
1370 1369 """Obtain fulltext data for a node."""
1371 1370
1372 1371 def rawdata(self, node):
1373 1372 """Obtain raw data for a node."""
1374 1373
1375 1374 def revdiff(self, rev1, rev2):
1376 1375 """Obtain a delta between two revision numbers.
1377 1376
1378 1377 The returned data is the result of ``bdiff.bdiff()`` on the raw
1379 1378 revision data.
1380 1379 """
1381 1380
1382 1381 def cmp(self, node, fulltext):
1383 1382 """Compare fulltext to another revision.
1384 1383
1385 1384 Returns True if the fulltext is different from what is stored.
1386 1385 """
1387 1386
1388 1387 def emitrevisions(
1389 1388 self,
1390 1389 nodes,
1391 1390 nodesorder=None,
1392 1391 revisiondata=False,
1393 1392 assumehaveparentrevisions=False,
1394 1393 ):
1395 1394 """Produce ``irevisiondelta`` describing revisions.
1396 1395
1397 1396 See the documentation for ``ifiledata`` for more.
1398 1397 """
1399 1398
1400 1399 def addgroup(
1401 1400 self,
1402 1401 deltas,
1403 1402 linkmapper,
1404 1403 transaction,
1405 1404 addrevisioncb=None,
1406 1405 duplicaterevisioncb=None,
1407 1406 ):
1408 1407 """Process a series of deltas for storage.
1409 1408
1410 1409 See the documentation in ``ifilemutation`` for more.
1411 1410 """
1412 1411
1413 1412 def rawsize(self, rev):
1414 1413 """Obtain the size of tracked data.
1415 1414
1416 1415 Is equivalent to ``len(m.rawdata(node))``.
1417 1416
1418 1417 TODO this method is only used by upgrade code and may be removed.
1419 1418 """
1420 1419
1421 1420 def getstrippoint(self, minlink):
1422 1421 """Find minimum revision that must be stripped to strip a linkrev.
1423 1422
1424 1423 See the documentation in ``ifilemutation`` for more.
1425 1424 """
1426 1425
1427 1426 def strip(self, minlink, transaction):
1428 1427 """Remove storage of items starting at a linkrev.
1429 1428
1430 1429 See the documentation in ``ifilemutation`` for more.
1431 1430 """
1432 1431
1433 1432 def checksize(self):
1434 1433 """Obtain the expected sizes of backing files.
1435 1434
1436 1435 TODO this is used by verify and it should not be part of the interface.
1437 1436 """
1438 1437
1439 1438 def files(self):
1440 1439 """Obtain paths that are backing storage for this manifest.
1441 1440
1442 1441 TODO this is used by verify and there should probably be a better API
1443 1442 for this functionality.
1444 1443 """
1445 1444
1446 1445 def deltaparent(self, rev):
1447 1446 """Obtain the revision that a revision is delta'd against.
1448 1447
1449 1448 TODO delta encoding is an implementation detail of storage and should
1450 1449 not be exposed to the storage interface.
1451 1450 """
1452 1451
1453 1452 def clone(self, tr, dest, **kwargs):
1454 1453 """Clone this instance to another."""
1455 1454
1456 1455 def clearcaches(self, clear_persisted_data=False):
1457 1456 """Clear any caches associated with this instance."""
1458 1457
1459 1458 def dirlog(self, d):
1460 1459 """Obtain a manifest storage instance for a tree."""
1461 1460
1462 1461 def add(
1463 1462 self,
1464 1463 m,
1465 1464 transaction,
1466 1465 link,
1467 1466 p1,
1468 1467 p2,
1469 1468 added,
1470 1469 removed,
1471 1470 readtree=None,
1472 1471 match=None,
1473 1472 ):
1474 1473 """Add a revision to storage.
1475 1474
1476 1475 ``m`` is an object conforming to ``imanifestdict``.
1477 1476
1478 1477 ``link`` is the linkrev revision number.
1479 1478
1480 1479 ``p1`` and ``p2`` are the parent revision numbers.
1481 1480
1482 1481 ``added`` and ``removed`` are iterables of added and removed paths,
1483 1482 respectively.
1484 1483
1485 1484 ``readtree`` is a function that can be used to read the child tree(s)
1486 1485 when recursively writing the full tree structure when using
1487 1486 treemanifets.
1488 1487
1489 1488 ``match`` is a matcher that can be used to hint to storage that not all
1490 1489 paths must be inspected; this is an optimization and can be safely
1491 1490 ignored. Note that the storage must still be able to reproduce a full
1492 1491 manifest including files that did not match.
1493 1492 """
1494 1493
1495 1494 def storageinfo(
1496 1495 self,
1497 1496 exclusivefiles=False,
1498 1497 sharedfiles=False,
1499 1498 revisionscount=False,
1500 1499 trackedsize=False,
1501 1500 storedsize=False,
1502 1501 ):
1503 1502 """Obtain information about storage for this manifest's data.
1504 1503
1505 1504 See ``ifilestorage.storageinfo()`` for a description of this method.
1506 1505 This one behaves the same way, except for manifest data.
1507 1506 """
1508 1507
1509 1508 def get_revlog(self):
1510 1509 """return an actual revlog instance if any
1511 1510
1512 1511 This exist because a lot of code leverage the fact the underlying
1513 1512 storage is a revlog for optimization, so giving simple way to access
1514 1513 the revlog instance helps such code.
1515 1514 """
1516 1515
1517 1516
1518 1517 class imanifestlog(Protocol):
1519 1518 """Interface representing a collection of manifest snapshots.
1520 1519
1521 1520 Represents the root manifest in a repository.
1522 1521
1523 1522 Also serves as a means to access nested tree manifests and to cache
1524 1523 tree manifests.
1525 1524 """
1526 1525
1527 1526 nodeconstants = interfaceutil.Attribute(
1528 1527 """nodeconstants used by the current repository."""
1529 1528 )
1530 1529
1531 1530 narrowed = interfaceutil.Attribute(
1532 1531 """True, is the manifest is narrowed by a matcher"""
1533 1532 )
1534 1533
1535 1534 def __getitem__(self, node):
1536 1535 """Obtain a manifest instance for a given binary node.
1537 1536
1538 1537 Equivalent to calling ``self.get('', node)``.
1539 1538
1540 1539 The returned object conforms to the ``imanifestrevisionstored``
1541 1540 interface.
1542 1541 """
1543 1542
1544 1543 def get(self, tree, node, verify=True):
1545 1544 """Retrieve the manifest instance for a given directory and binary node.
1546 1545
1547 1546 ``node`` always refers to the node of the root manifest (which will be
1548 1547 the only manifest if flat manifests are being used).
1549 1548
1550 1549 If ``tree`` is the empty string, the root manifest is returned.
1551 1550 Otherwise the manifest for the specified directory will be returned
1552 1551 (requires tree manifests).
1553 1552
1554 1553 If ``verify`` is True, ``LookupError`` is raised if the node is not
1555 1554 known.
1556 1555
1557 1556 The returned object conforms to the ``imanifestrevisionstored``
1558 1557 interface.
1559 1558 """
1560 1559
1561 1560 def getstorage(self, tree):
1562 1561 """Retrieve an interface to storage for a particular tree.
1563 1562
1564 1563 If ``tree`` is the empty bytestring, storage for the root manifest will
1565 1564 be returned. Otherwise storage for a tree manifest is returned.
1566 1565
1567 1566 TODO formalize interface for returned object.
1568 1567 """
1569 1568
1570 1569 def clearcaches(self, clear_persisted_data: bool = False) -> None:
1571 1570 """Clear caches associated with this collection."""
1572 1571
1573 1572 def rev(self, node):
1574 1573 """Obtain the revision number for a binary node.
1575 1574
1576 1575 Raises ``error.LookupError`` if the node is not known.
1577 1576 """
1578 1577
1579 1578 def update_caches(self, transaction):
1580 1579 """update whatever cache are relevant for the used storage."""
1581 1580
1582 1581
1583 1582 class ilocalrepositoryfilestorage(Protocol):
1584 1583 """Local repository sub-interface providing access to tracked file storage.
1585 1584
1586 1585 This interface defines how a repository accesses storage for a single
1587 1586 tracked file path.
1588 1587 """
1589 1588
1590 1589 def file(self, f):
1591 1590 """Obtain a filelog for a tracked path.
1592 1591
1593 1592 The returned type conforms to the ``ifilestorage`` interface.
1594 1593 """
1595 1594
1596 1595
1597 1596 class ilocalrepositorymain(Protocol):
1598 1597 """Main interface for local repositories.
1599 1598
1600 1599 This currently captures the reality of things - not how things should be.
1601 1600 """
1602 1601
1603 1602 nodeconstants = interfaceutil.Attribute(
1604 1603 """Constant nodes matching the hash function used by the repository."""
1605 1604 )
1606 1605 nullid = interfaceutil.Attribute(
1607 1606 """null revision for the hash function used by the repository."""
1608 1607 )
1609 1608
1610 1609 supported = interfaceutil.Attribute(
1611 1610 """Set of requirements that this repo is capable of opening."""
1612 1611 )
1613 1612
1614 1613 requirements = interfaceutil.Attribute(
1615 1614 """Set of requirements this repo uses."""
1616 1615 )
1617 1616
1618 1617 features = interfaceutil.Attribute(
1619 1618 """Set of "features" this repository supports.
1620 1619
1621 1620 A "feature" is a loosely-defined term. It can refer to a feature
1622 1621 in the classical sense or can describe an implementation detail
1623 1622 of the repository. For example, a ``readonly`` feature may denote
1624 1623 the repository as read-only. Or a ``revlogfilestore`` feature may
1625 1624 denote that the repository is using revlogs for file storage.
1626 1625
1627 1626 The intent of features is to provide a machine-queryable mechanism
1628 1627 for repo consumers to test for various repository characteristics.
1629 1628
1630 1629 Features are similar to ``requirements``. The main difference is that
1631 1630 requirements are stored on-disk and represent requirements to open the
1632 1631 repository. Features are more run-time capabilities of the repository
1633 1632 and more granular capabilities (which may be derived from requirements).
1634 1633 """
1635 1634 )
1636 1635
1637 1636 filtername = interfaceutil.Attribute(
1638 1637 """Name of the repoview that is active on this repo."""
1639 1638 )
1640 1639
1641 1640 vfs_map = interfaceutil.Attribute(
1642 1641 """a bytes-key β†’ vfs mapping used by transaction and others"""
1643 1642 )
1644 1643
1645 1644 wvfs = interfaceutil.Attribute(
1646 1645 """VFS used to access the working directory."""
1647 1646 )
1648 1647
1649 1648 vfs = interfaceutil.Attribute(
1650 1649 """VFS rooted at the .hg directory.
1651 1650
1652 1651 Used to access repository data not in the store.
1653 1652 """
1654 1653 )
1655 1654
1656 1655 svfs = interfaceutil.Attribute(
1657 1656 """VFS rooted at the store.
1658 1657
1659 1658 Used to access repository data in the store. Typically .hg/store.
1660 1659 But can point elsewhere if the store is shared.
1661 1660 """
1662 1661 )
1663 1662
1664 1663 root = interfaceutil.Attribute(
1665 1664 """Path to the root of the working directory."""
1666 1665 )
1667 1666
1668 1667 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1669 1668
1670 1669 origroot = interfaceutil.Attribute(
1671 1670 """The filesystem path that was used to construct the repo."""
1672 1671 )
1673 1672
1674 1673 auditor = interfaceutil.Attribute(
1675 1674 """A pathauditor for the working directory.
1676 1675
1677 1676 This checks if a path refers to a nested repository.
1678 1677
1679 1678 Operates on the filesystem.
1680 1679 """
1681 1680 )
1682 1681
1683 1682 nofsauditor = interfaceutil.Attribute(
1684 1683 """A pathauditor for the working directory.
1685 1684
1686 1685 This is like ``auditor`` except it doesn't do filesystem checks.
1687 1686 """
1688 1687 )
1689 1688
1690 1689 baseui = interfaceutil.Attribute(
1691 1690 """Original ui instance passed into constructor."""
1692 1691 )
1693 1692
1694 1693 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1695 1694
1696 1695 sharedpath = interfaceutil.Attribute(
1697 1696 """Path to the .hg directory of the repo this repo was shared from."""
1698 1697 )
1699 1698
1700 1699 store = interfaceutil.Attribute("""A store instance.""")
1701 1700
1702 1701 spath = interfaceutil.Attribute("""Path to the store.""")
1703 1702
1704 1703 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1705 1704
1706 1705 cachevfs = interfaceutil.Attribute(
1707 1706 """A VFS used to access the cache directory.
1708 1707
1709 1708 Typically .hg/cache.
1710 1709 """
1711 1710 )
1712 1711
1713 1712 wcachevfs = interfaceutil.Attribute(
1714 1713 """A VFS used to access the cache directory dedicated to working copy
1715 1714
1716 1715 Typically .hg/wcache.
1717 1716 """
1718 1717 )
1719 1718
1720 1719 filteredrevcache = interfaceutil.Attribute(
1721 1720 """Holds sets of revisions to be filtered."""
1722 1721 )
1723 1722
1724 1723 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1725 1724
1726 1725 filecopiesmode = interfaceutil.Attribute(
1727 1726 """The way files copies should be dealt with in this repo."""
1728 1727 )
1729 1728
1730 1729 def close(self):
1731 1730 """Close the handle on this repository."""
1732 1731
1733 1732 def peer(self, path=None):
1734 1733 """Obtain an object conforming to the ``peer`` interface."""
1735 1734
1736 1735 def unfiltered(self):
1737 1736 """Obtain an unfiltered/raw view of this repo."""
1738 1737
1739 1738 def filtered(self, name, visibilityexceptions=None):
1740 1739 """Obtain a named view of this repository."""
1741 1740
1742 1741 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1743 1742
1744 1743 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1745 1744
1746 1745 manifestlog = interfaceutil.Attribute(
1747 1746 """An instance conforming to the ``imanifestlog`` interface.
1748 1747
1749 1748 Provides access to manifests for the repository.
1750 1749 """
1751 1750 )
1752 1751
1753 1752 dirstate = interfaceutil.Attribute("""Working directory state.""")
1754 1753
1755 1754 narrowpats = interfaceutil.Attribute(
1756 1755 """Matcher patterns for this repository's narrowspec."""
1757 1756 )
1758 1757
1759 1758 def narrowmatch(self, match=None, includeexact=False):
1760 1759 """Obtain a matcher for the narrowspec."""
1761 1760
1762 1761 def setnarrowpats(self, newincludes, newexcludes):
1763 1762 """Define the narrowspec for this repository."""
1764 1763
1765 1764 def __getitem__(self, changeid):
1766 1765 """Try to resolve a changectx."""
1767 1766
1768 1767 def __contains__(self, changeid):
1769 1768 """Whether a changeset exists."""
1770 1769
1771 1770 def __nonzero__(self):
1772 1771 """Always returns True."""
1773 1772 return True
1774 1773
1775 1774 __bool__ = __nonzero__
1776 1775
1777 1776 def __len__(self):
1778 1777 """Returns the number of changesets in the repo."""
1779 1778
1780 1779 def __iter__(self):
1781 1780 """Iterate over revisions in the changelog."""
1782 1781
1783 1782 def revs(self, expr, *args):
1784 1783 """Evaluate a revset.
1785 1784
1786 1785 Emits revisions.
1787 1786 """
1788 1787
1789 1788 def set(self, expr, *args):
1790 1789 """Evaluate a revset.
1791 1790
1792 1791 Emits changectx instances.
1793 1792 """
1794 1793
1795 1794 def anyrevs(self, specs, user=False, localalias=None):
1796 1795 """Find revisions matching one of the given revsets."""
1797 1796
1798 1797 def url(self):
1799 1798 """Returns a string representing the location of this repo."""
1800 1799
1801 1800 def hook(self, name, throw=False, **args):
1802 1801 """Call a hook."""
1803 1802
1804 1803 def tags(self):
1805 1804 """Return a mapping of tag to node."""
1806 1805
1807 1806 def tagtype(self, tagname):
1808 1807 """Return the type of a given tag."""
1809 1808
1810 1809 def tagslist(self):
1811 1810 """Return a list of tags ordered by revision."""
1812 1811
1813 1812 def nodetags(self, node):
1814 1813 """Return the tags associated with a node."""
1815 1814
1816 1815 def nodebookmarks(self, node):
1817 1816 """Return the list of bookmarks pointing to the specified node."""
1818 1817
1819 1818 def branchmap(self):
1820 1819 """Return a mapping of branch to heads in that branch."""
1821 1820
1822 1821 def revbranchcache(self):
1823 1822 pass
1824 1823
1825 1824 def register_changeset(self, rev, changelogrevision):
1826 1825 """Extension point for caches for new nodes.
1827 1826
1828 1827 Multiple consumers are expected to need parts of the changelogrevision,
1829 1828 so it is provided as optimization to avoid duplicate lookups. A simple
1830 1829 cache would be fragile when other revisions are accessed, too."""
1831 1830 pass
1832 1831
1833 1832 def branchtip(self, branchtip, ignoremissing=False):
1834 1833 """Return the tip node for a given branch."""
1835 1834
1836 1835 def lookup(self, key):
1837 1836 """Resolve the node for a revision."""
1838 1837
1839 1838 def lookupbranch(self, key):
1840 1839 """Look up the branch name of the given revision or branch name."""
1841 1840
1842 1841 def known(self, nodes):
1843 1842 """Determine whether a series of nodes is known.
1844 1843
1845 1844 Returns a list of bools.
1846 1845 """
1847 1846
1848 1847 def local(self):
1849 1848 """Whether the repository is local."""
1850 1849 return True
1851 1850
1852 1851 def publishing(self):
1853 1852 """Whether the repository is a publishing repository."""
1854 1853
1855 1854 def cancopy(self):
1856 1855 pass
1857 1856
1858 1857 def shared(self):
1859 1858 """The type of shared repository or None."""
1860 1859
1861 1860 def wjoin(self, f, *insidef):
1862 1861 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1863 1862
1864 1863 def setparents(self, p1, p2):
1865 1864 """Set the parent nodes of the working directory."""
1866 1865
1867 1866 def filectx(self, path, changeid=None, fileid=None):
1868 1867 """Obtain a filectx for the given file revision."""
1869 1868
1870 1869 def getcwd(self):
1871 1870 """Obtain the current working directory from the dirstate."""
1872 1871
1873 1872 def pathto(self, f, cwd=None):
1874 1873 """Obtain the relative path to a file."""
1875 1874
1876 1875 def adddatafilter(self, name, fltr):
1877 1876 pass
1878 1877
1879 1878 def wread(self, filename):
1880 1879 """Read a file from wvfs, using data filters."""
1881 1880
1882 1881 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1883 1882 """Write data to a file in the wvfs, using data filters."""
1884 1883
1885 1884 def wwritedata(self, filename, data):
1886 1885 """Resolve data for writing to the wvfs, using data filters."""
1887 1886
1888 1887 def currenttransaction(self):
1889 1888 """Obtain the current transaction instance or None."""
1890 1889
1891 1890 def transaction(self, desc, report=None):
1892 1891 """Open a new transaction to write to the repository."""
1893 1892
1894 1893 def undofiles(self):
1895 1894 """Returns a list of (vfs, path) for files to undo transactions."""
1896 1895
1897 1896 def recover(self):
1898 1897 """Roll back an interrupted transaction."""
1899 1898
1900 1899 def rollback(self, dryrun=False, force=False):
1901 1900 """Undo the last transaction.
1902 1901
1903 1902 DANGEROUS.
1904 1903 """
1905 1904
1906 1905 def updatecaches(self, tr=None, full=False, caches=None):
1907 1906 """Warm repo caches."""
1908 1907
1909 1908 def invalidatecaches(self):
1910 1909 """Invalidate cached data due to the repository mutating."""
1911 1910
1912 1911 def invalidatevolatilesets(self):
1913 1912 pass
1914 1913
1915 1914 def invalidatedirstate(self):
1916 1915 """Invalidate the dirstate."""
1917 1916
1918 1917 def invalidate(self, clearfilecache=False):
1919 1918 pass
1920 1919
1921 1920 def invalidateall(self):
1922 1921 pass
1923 1922
1924 1923 def lock(self, wait=True):
1925 1924 """Lock the repository store and return a lock instance."""
1926 1925
1927 1926 def currentlock(self):
1928 1927 """Return the lock if it's held or None."""
1929 1928
1930 1929 def wlock(self, wait=True):
1931 1930 """Lock the non-store parts of the repository."""
1932 1931
1933 1932 def currentwlock(self):
1934 1933 """Return the wlock if it's held or None."""
1935 1934
1936 1935 def checkcommitpatterns(self, wctx, match, status, fail):
1937 1936 pass
1938 1937
1939 1938 def commit(
1940 1939 self,
1941 1940 text=b'',
1942 1941 user=None,
1943 1942 date=None,
1944 1943 match=None,
1945 1944 force=False,
1946 1945 editor=False,
1947 1946 extra=None,
1948 1947 ):
1949 1948 """Add a new revision to the repository."""
1950 1949
1951 1950 def commitctx(self, ctx, error=False, origctx=None):
1952 1951 """Commit a commitctx instance to the repository."""
1953 1952
1954 1953 def destroying(self):
1955 1954 """Inform the repository that nodes are about to be destroyed."""
1956 1955
1957 1956 def destroyed(self):
1958 1957 """Inform the repository that nodes have been destroyed."""
1959 1958
1960 1959 def status(
1961 1960 self,
1962 1961 node1=b'.',
1963 1962 node2=None,
1964 1963 match=None,
1965 1964 ignored=False,
1966 1965 clean=False,
1967 1966 unknown=False,
1968 1967 listsubrepos=False,
1969 1968 ):
1970 1969 """Convenience method to call repo[x].status()."""
1971 1970
1972 1971 def addpostdsstatus(self, ps):
1973 1972 pass
1974 1973
1975 1974 def postdsstatus(self):
1976 1975 pass
1977 1976
1978 1977 def clearpostdsstatus(self):
1979 1978 pass
1980 1979
1981 1980 def heads(self, start=None):
1982 1981 """Obtain list of nodes that are DAG heads."""
1983 1982
1984 1983 def branchheads(self, branch=None, start=None, closed=False):
1985 1984 pass
1986 1985
1987 1986 def branches(self, nodes):
1988 1987 pass
1989 1988
1990 1989 def between(self, pairs):
1991 1990 pass
1992 1991
1993 1992 def checkpush(self, pushop):
1994 1993 pass
1995 1994
1996 1995 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1997 1996
1998 1997 def pushkey(self, namespace, key, old, new):
1999 1998 pass
2000 1999
2001 2000 def listkeys(self, namespace):
2002 2001 pass
2003 2002
2004 2003 def debugwireargs(self, one, two, three=None, four=None, five=None):
2005 2004 pass
2006 2005
2007 2006 def savecommitmessage(self, text):
2008 2007 pass
2009 2008
2010 2009 def register_sidedata_computer(
2011 2010 self, kind, category, keys, computer, flags, replace=False
2012 2011 ):
2013 2012 pass
2014 2013
2015 2014 def register_wanted_sidedata(self, category):
2016 2015 pass
2017 2016
2018 2017
2019 2018 class completelocalrepository(
2020 2019 ilocalrepositorymain, ilocalrepositoryfilestorage
2021 2020 ):
2022 2021 """Complete interface for a local repository."""
2023 2022
2024 2023
2025 2024 class iwireprotocolcommandcacher(Protocol):
2026 2025 """Represents a caching backend for wire protocol commands.
2027 2026
2028 2027 Wire protocol version 2 supports transparent caching of many commands.
2029 2028 To leverage this caching, servers can activate objects that cache
2030 2029 command responses. Objects handle both cache writing and reading.
2031 2030 This interface defines how that response caching mechanism works.
2032 2031
2033 2032 Wire protocol version 2 commands emit a series of objects that are
2034 2033 serialized and sent to the client. The caching layer exists between
2035 2034 the invocation of the command function and the sending of its output
2036 2035 objects to an output layer.
2037 2036
2038 2037 Instances of this interface represent a binding to a cache that
2039 2038 can serve a response (in place of calling a command function) and/or
2040 2039 write responses to a cache for subsequent use.
2041 2040
2042 2041 When a command request arrives, the following happens with regards
2043 2042 to this interface:
2044 2043
2045 2044 1. The server determines whether the command request is cacheable.
2046 2045 2. If it is, an instance of this interface is spawned.
2047 2046 3. The cacher is activated in a context manager (``__enter__`` is called).
2048 2047 4. A cache *key* for that request is derived. This will call the
2049 2048 instance's ``adjustcachekeystate()`` method so the derivation
2050 2049 can be influenced.
2051 2050 5. The cacher is informed of the derived cache key via a call to
2052 2051 ``setcachekey()``.
2053 2052 6. The cacher's ``lookup()`` method is called to test for presence of
2054 2053 the derived key in the cache.
2055 2054 7. If ``lookup()`` returns a hit, that cached result is used in place
2056 2055 of invoking the command function. ``__exit__`` is called and the instance
2057 2056 is discarded.
2058 2057 8. The command function is invoked.
2059 2058 9. ``onobject()`` is called for each object emitted by the command
2060 2059 function.
2061 2060 10. After the final object is seen, ``onfinished()`` is called.
2062 2061 11. ``__exit__`` is called to signal the end of use of the instance.
2063 2062
2064 2063 Cache *key* derivation can be influenced by the instance.
2065 2064
2066 2065 Cache keys are initially derived by a deterministic representation of
2067 2066 the command request. This includes the command name, arguments, protocol
2068 2067 version, etc. This initial key derivation is performed by CBOR-encoding a
2069 2068 data structure and feeding that output into a hasher.
2070 2069
2071 2070 Instances of this interface can influence this initial key derivation
2072 2071 via ``adjustcachekeystate()``.
2073 2072
2074 2073 The instance is informed of the derived cache key via a call to
2075 2074 ``setcachekey()``. The instance must store the key locally so it can
2076 2075 be consulted on subsequent operations that may require it.
2077 2076
2078 2077 When constructed, the instance has access to a callable that can be used
2079 2078 for encoding response objects. This callable receives as its single
2080 2079 argument an object emitted by a command function. It returns an iterable
2081 2080 of bytes chunks representing the encoded object. Unless the cacher is
2082 2081 caching native Python objects in memory or has a way of reconstructing
2083 2082 the original Python objects, implementations typically call this function
2084 2083 to produce bytes from the output objects and then store those bytes in
2085 2084 the cache. When it comes time to re-emit those bytes, they are wrapped
2086 2085 in a ``wireprototypes.encodedresponse`` instance to tell the output
2087 2086 layer that they are pre-encoded.
2088 2087
2089 2088 When receiving the objects emitted by the command function, instances
2090 2089 can choose what to do with those objects. The simplest thing to do is
2091 2090 re-emit the original objects. They will be forwarded to the output
2092 2091 layer and will be processed as if the cacher did not exist.
2093 2092
2094 2093 Implementations could also choose to not emit objects - instead locally
2095 2094 buffering objects or their encoded representation. They could then emit
2096 2095 a single "coalesced" object when ``onfinished()`` is called. In
2097 2096 this way, the implementation would function as a filtering layer of
2098 2097 sorts.
2099 2098
2100 2099 When caching objects, typically the encoded form of the object will
2101 2100 be stored. Keep in mind that if the original object is forwarded to
2102 2101 the output layer, it will need to be encoded there as well. For large
2103 2102 output, this redundant encoding could add overhead. Implementations
2104 2103 could wrap the encoded object data in ``wireprototypes.encodedresponse``
2105 2104 instances to avoid this overhead.
2106 2105 """
2107 2106
2108 2107 def __enter__(self):
2109 2108 """Marks the instance as active.
2110 2109
2111 2110 Should return self.
2112 2111 """
2113 2112
2114 2113 def __exit__(self, exctype, excvalue, exctb):
2115 2114 """Called when cacher is no longer used.
2116 2115
2117 2116 This can be used by implementations to perform cleanup actions (e.g.
2118 2117 disconnecting network sockets, aborting a partially cached response.
2119 2118 """
2120 2119
2121 2120 def adjustcachekeystate(self, state):
2122 2121 """Influences cache key derivation by adjusting state to derive key.
2123 2122
2124 2123 A dict defining the state used to derive the cache key is passed.
2125 2124
2126 2125 Implementations can modify this dict to record additional state that
2127 2126 is wanted to influence key derivation.
2128 2127
2129 2128 Implementations are *highly* encouraged to not modify or delete
2130 2129 existing keys.
2131 2130 """
2132 2131
2133 2132 def setcachekey(self, key):
2134 2133 """Record the derived cache key for this request.
2135 2134
2136 2135 Instances may mutate the key for internal usage, as desired. e.g.
2137 2136 instances may wish to prepend the repo name, introduce path
2138 2137 components for filesystem or URL addressing, etc. Behavior is up to
2139 2138 the cache.
2140 2139
2141 2140 Returns a bool indicating if the request is cacheable by this
2142 2141 instance.
2143 2142 """
2144 2143
2145 2144 def lookup(self):
2146 2145 """Attempt to resolve an entry in the cache.
2147 2146
2148 2147 The instance is instructed to look for the cache key that it was
2149 2148 informed about via the call to ``setcachekey()``.
2150 2149
2151 2150 If there's no cache hit or the cacher doesn't wish to use the cached
2152 2151 entry, ``None`` should be returned.
2153 2152
2154 2153 Else, a dict defining the cached result should be returned. The
2155 2154 dict may have the following keys:
2156 2155
2157 2156 objs
2158 2157 An iterable of objects that should be sent to the client. That
2159 2158 iterable of objects is expected to be what the command function
2160 2159 would return if invoked or an equivalent representation thereof.
2161 2160 """
2162 2161
2163 2162 def onobject(self, obj):
2164 2163 """Called when a new object is emitted from the command function.
2165 2164
2166 2165 Receives as its argument the object that was emitted from the
2167 2166 command function.
2168 2167
2169 2168 This method returns an iterator of objects to forward to the output
2170 2169 layer. The easiest implementation is a generator that just
2171 2170 ``yield obj``.
2172 2171 """
2173 2172
2174 2173 def onfinished(self):
2175 2174 """Called after all objects have been emitted from the command function.
2176 2175
2177 2176 Implementations should return an iterator of objects to forward to
2178 2177 the output layer.
2179 2178
2180 2179 This method can be a generator.
2181 2180 """
General Comments 0
You need to be logged in to leave comments. Login now