##// END OF EJS Templates
nodemap: make sure hooks have access to an up-to-date version...
marmoute -
r45003:64e2f603 default
parent child Browse files
Show More
@@ -1,1579 +1,1578
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
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 functools
11 11 import re
12 12
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 )
17 17
18 18
19 19 def loadconfigtable(ui, extname, configtable):
20 20 """update config item known to the ui with the extension ones"""
21 21 for section, items in sorted(configtable.items()):
22 22 knownitems = ui._knownconfig.setdefault(section, itemregister())
23 23 knownkeys = set(knownitems)
24 24 newkeys = set(items)
25 25 for key in sorted(knownkeys & newkeys):
26 26 msg = b"extension '%s' overwrite config item '%s.%s'"
27 27 msg %= (extname, section, key)
28 28 ui.develwarn(msg, config=b'warn-config')
29 29
30 30 knownitems.update(items)
31 31
32 32
33 33 class configitem(object):
34 34 """represent a known config item
35 35
36 36 :section: the official config section where to find this item,
37 37 :name: the official name within the section,
38 38 :default: default value for this item,
39 39 :alias: optional list of tuples as alternatives,
40 40 :generic: this is a generic definition, match name using regular expression.
41 41 """
42 42
43 43 def __init__(
44 44 self,
45 45 section,
46 46 name,
47 47 default=None,
48 48 alias=(),
49 49 generic=False,
50 50 priority=0,
51 51 experimental=False,
52 52 ):
53 53 self.section = section
54 54 self.name = name
55 55 self.default = default
56 56 self.alias = list(alias)
57 57 self.generic = generic
58 58 self.priority = priority
59 59 self.experimental = experimental
60 60 self._re = None
61 61 if generic:
62 62 self._re = re.compile(self.name)
63 63
64 64
65 65 class itemregister(dict):
66 66 """A specialized dictionary that can handle wild-card selection"""
67 67
68 68 def __init__(self):
69 69 super(itemregister, self).__init__()
70 70 self._generics = set()
71 71
72 72 def update(self, other):
73 73 super(itemregister, self).update(other)
74 74 self._generics.update(other._generics)
75 75
76 76 def __setitem__(self, key, item):
77 77 super(itemregister, self).__setitem__(key, item)
78 78 if item.generic:
79 79 self._generics.add(item)
80 80
81 81 def get(self, key):
82 82 baseitem = super(itemregister, self).get(key)
83 83 if baseitem is not None and not baseitem.generic:
84 84 return baseitem
85 85
86 86 # search for a matching generic item
87 87 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
88 88 for item in generics:
89 89 # we use 'match' instead of 'search' to make the matching simpler
90 90 # for people unfamiliar with regular expression. Having the match
91 91 # rooted to the start of the string will produce less surprising
92 92 # result for user writing simple regex for sub-attribute.
93 93 #
94 94 # For example using "color\..*" match produces an unsurprising
95 95 # result, while using search could suddenly match apparently
96 96 # unrelated configuration that happens to contains "color."
97 97 # anywhere. This is a tradeoff where we favor requiring ".*" on
98 98 # some match to avoid the need to prefix most pattern with "^".
99 99 # The "^" seems more error prone.
100 100 if item._re.match(key):
101 101 return item
102 102
103 103 return None
104 104
105 105
106 106 coreitems = {}
107 107
108 108
109 109 def _register(configtable, *args, **kwargs):
110 110 item = configitem(*args, **kwargs)
111 111 section = configtable.setdefault(item.section, itemregister())
112 112 if item.name in section:
113 113 msg = b"duplicated config item registration for '%s.%s'"
114 114 raise error.ProgrammingError(msg % (item.section, item.name))
115 115 section[item.name] = item
116 116
117 117
118 118 # special value for case where the default is derived from other values
119 119 dynamicdefault = object()
120 120
121 121 # Registering actual config items
122 122
123 123
124 124 def getitemregister(configtable):
125 125 f = functools.partial(_register, configtable)
126 126 # export pseudo enum as configitem.*
127 127 f.dynamicdefault = dynamicdefault
128 128 return f
129 129
130 130
131 131 coreconfigitem = getitemregister(coreitems)
132 132
133 133
134 134 def _registerdiffopts(section, configprefix=b''):
135 135 coreconfigitem(
136 136 section, configprefix + b'nodates', default=False,
137 137 )
138 138 coreconfigitem(
139 139 section, configprefix + b'showfunc', default=False,
140 140 )
141 141 coreconfigitem(
142 142 section, configprefix + b'unified', default=None,
143 143 )
144 144 coreconfigitem(
145 145 section, configprefix + b'git', default=False,
146 146 )
147 147 coreconfigitem(
148 148 section, configprefix + b'ignorews', default=False,
149 149 )
150 150 coreconfigitem(
151 151 section, configprefix + b'ignorewsamount', default=False,
152 152 )
153 153 coreconfigitem(
154 154 section, configprefix + b'ignoreblanklines', default=False,
155 155 )
156 156 coreconfigitem(
157 157 section, configprefix + b'ignorewseol', default=False,
158 158 )
159 159 coreconfigitem(
160 160 section, configprefix + b'nobinary', default=False,
161 161 )
162 162 coreconfigitem(
163 163 section, configprefix + b'noprefix', default=False,
164 164 )
165 165 coreconfigitem(
166 166 section, configprefix + b'word-diff', default=False,
167 167 )
168 168
169 169
170 170 coreconfigitem(
171 171 b'alias', b'.*', default=dynamicdefault, generic=True,
172 172 )
173 173 coreconfigitem(
174 174 b'auth', b'cookiefile', default=None,
175 175 )
176 176 _registerdiffopts(section=b'annotate')
177 177 # bookmarks.pushing: internal hack for discovery
178 178 coreconfigitem(
179 179 b'bookmarks', b'pushing', default=list,
180 180 )
181 181 # bundle.mainreporoot: internal hack for bundlerepo
182 182 coreconfigitem(
183 183 b'bundle', b'mainreporoot', default=b'',
184 184 )
185 185 coreconfigitem(
186 186 b'censor', b'policy', default=b'abort', experimental=True,
187 187 )
188 188 coreconfigitem(
189 189 b'chgserver', b'idletimeout', default=3600,
190 190 )
191 191 coreconfigitem(
192 192 b'chgserver', b'skiphash', default=False,
193 193 )
194 194 coreconfigitem(
195 195 b'cmdserver', b'log', default=None,
196 196 )
197 197 coreconfigitem(
198 198 b'cmdserver', b'max-log-files', default=7,
199 199 )
200 200 coreconfigitem(
201 201 b'cmdserver', b'max-log-size', default=b'1 MB',
202 202 )
203 203 coreconfigitem(
204 204 b'cmdserver', b'max-repo-cache', default=0, experimental=True,
205 205 )
206 206 coreconfigitem(
207 207 b'cmdserver', b'message-encodings', default=list, experimental=True,
208 208 )
209 209 coreconfigitem(
210 210 b'cmdserver',
211 211 b'track-log',
212 212 default=lambda: [b'chgserver', b'cmdserver', b'repocache'],
213 213 )
214 214 coreconfigitem(
215 215 b'color', b'.*', default=None, generic=True,
216 216 )
217 217 coreconfigitem(
218 218 b'color', b'mode', default=b'auto',
219 219 )
220 220 coreconfigitem(
221 221 b'color', b'pagermode', default=dynamicdefault,
222 222 )
223 223 _registerdiffopts(section=b'commands', configprefix=b'commit.interactive.')
224 224 coreconfigitem(
225 225 b'commands', b'commit.post-status', default=False,
226 226 )
227 227 coreconfigitem(
228 228 b'commands', b'grep.all-files', default=False, experimental=True,
229 229 )
230 230 coreconfigitem(
231 231 b'commands', b'merge.require-rev', default=False,
232 232 )
233 233 coreconfigitem(
234 234 b'commands', b'push.require-revs', default=False,
235 235 )
236 236 coreconfigitem(
237 237 b'commands', b'resolve.confirm', default=False,
238 238 )
239 239 coreconfigitem(
240 240 b'commands', b'resolve.explicit-re-merge', default=False,
241 241 )
242 242 coreconfigitem(
243 243 b'commands', b'resolve.mark-check', default=b'none',
244 244 )
245 245 _registerdiffopts(section=b'commands', configprefix=b'revert.interactive.')
246 246 coreconfigitem(
247 247 b'commands', b'show.aliasprefix', default=list,
248 248 )
249 249 coreconfigitem(
250 250 b'commands', b'status.relative', default=False,
251 251 )
252 252 coreconfigitem(
253 253 b'commands', b'status.skipstates', default=[], experimental=True,
254 254 )
255 255 coreconfigitem(
256 256 b'commands', b'status.terse', default=b'',
257 257 )
258 258 coreconfigitem(
259 259 b'commands', b'status.verbose', default=False,
260 260 )
261 261 coreconfigitem(
262 262 b'commands', b'update.check', default=None,
263 263 )
264 264 coreconfigitem(
265 265 b'commands', b'update.requiredest', default=False,
266 266 )
267 267 coreconfigitem(
268 268 b'committemplate', b'.*', default=None, generic=True,
269 269 )
270 270 coreconfigitem(
271 271 b'convert', b'bzr.saverev', default=True,
272 272 )
273 273 coreconfigitem(
274 274 b'convert', b'cvsps.cache', default=True,
275 275 )
276 276 coreconfigitem(
277 277 b'convert', b'cvsps.fuzz', default=60,
278 278 )
279 279 coreconfigitem(
280 280 b'convert', b'cvsps.logencoding', default=None,
281 281 )
282 282 coreconfigitem(
283 283 b'convert', b'cvsps.mergefrom', default=None,
284 284 )
285 285 coreconfigitem(
286 286 b'convert', b'cvsps.mergeto', default=None,
287 287 )
288 288 coreconfigitem(
289 289 b'convert', b'git.committeractions', default=lambda: [b'messagedifferent'],
290 290 )
291 291 coreconfigitem(
292 292 b'convert', b'git.extrakeys', default=list,
293 293 )
294 294 coreconfigitem(
295 295 b'convert', b'git.findcopiesharder', default=False,
296 296 )
297 297 coreconfigitem(
298 298 b'convert', b'git.remoteprefix', default=b'remote',
299 299 )
300 300 coreconfigitem(
301 301 b'convert', b'git.renamelimit', default=400,
302 302 )
303 303 coreconfigitem(
304 304 b'convert', b'git.saverev', default=True,
305 305 )
306 306 coreconfigitem(
307 307 b'convert', b'git.similarity', default=50,
308 308 )
309 309 coreconfigitem(
310 310 b'convert', b'git.skipsubmodules', default=False,
311 311 )
312 312 coreconfigitem(
313 313 b'convert', b'hg.clonebranches', default=False,
314 314 )
315 315 coreconfigitem(
316 316 b'convert', b'hg.ignoreerrors', default=False,
317 317 )
318 318 coreconfigitem(
319 319 b'convert', b'hg.preserve-hash', default=False,
320 320 )
321 321 coreconfigitem(
322 322 b'convert', b'hg.revs', default=None,
323 323 )
324 324 coreconfigitem(
325 325 b'convert', b'hg.saverev', default=False,
326 326 )
327 327 coreconfigitem(
328 328 b'convert', b'hg.sourcename', default=None,
329 329 )
330 330 coreconfigitem(
331 331 b'convert', b'hg.startrev', default=None,
332 332 )
333 333 coreconfigitem(
334 334 b'convert', b'hg.tagsbranch', default=b'default',
335 335 )
336 336 coreconfigitem(
337 337 b'convert', b'hg.usebranchnames', default=True,
338 338 )
339 339 coreconfigitem(
340 340 b'convert', b'ignoreancestorcheck', default=False, experimental=True,
341 341 )
342 342 coreconfigitem(
343 343 b'convert', b'localtimezone', default=False,
344 344 )
345 345 coreconfigitem(
346 346 b'convert', b'p4.encoding', default=dynamicdefault,
347 347 )
348 348 coreconfigitem(
349 349 b'convert', b'p4.startrev', default=0,
350 350 )
351 351 coreconfigitem(
352 352 b'convert', b'skiptags', default=False,
353 353 )
354 354 coreconfigitem(
355 355 b'convert', b'svn.debugsvnlog', default=True,
356 356 )
357 357 coreconfigitem(
358 358 b'convert', b'svn.trunk', default=None,
359 359 )
360 360 coreconfigitem(
361 361 b'convert', b'svn.tags', default=None,
362 362 )
363 363 coreconfigitem(
364 364 b'convert', b'svn.branches', default=None,
365 365 )
366 366 coreconfigitem(
367 367 b'convert', b'svn.startrev', default=0,
368 368 )
369 369 coreconfigitem(
370 370 b'debug', b'dirstate.delaywrite', default=0,
371 371 )
372 372 coreconfigitem(
373 373 b'defaults', b'.*', default=None, generic=True,
374 374 )
375 375 coreconfigitem(
376 376 b'devel', b'all-warnings', default=False,
377 377 )
378 378 coreconfigitem(
379 379 b'devel', b'bundle2.debug', default=False,
380 380 )
381 381 coreconfigitem(
382 382 b'devel', b'bundle.delta', default=b'',
383 383 )
384 384 coreconfigitem(
385 385 b'devel', b'cache-vfs', default=None,
386 386 )
387 387 coreconfigitem(
388 388 b'devel', b'check-locks', default=False,
389 389 )
390 390 coreconfigitem(
391 391 b'devel', b'check-relroot', default=False,
392 392 )
393 393 coreconfigitem(
394 394 b'devel', b'default-date', default=None,
395 395 )
396 396 coreconfigitem(
397 397 b'devel', b'deprec-warn', default=False,
398 398 )
399 399 coreconfigitem(
400 400 b'devel', b'disableloaddefaultcerts', default=False,
401 401 )
402 402 coreconfigitem(
403 403 b'devel', b'warn-empty-changegroup', default=False,
404 404 )
405 405 coreconfigitem(
406 406 b'devel', b'legacy.exchange', default=list,
407 407 )
408 408 # TODO before getting `persistent-nodemap` out of experimental
409 409 #
410 410 # * code/tests around aborted transaction
411 # * code/tests around pending data for hooks
412 411 # * regenerate a new nodemap when the unused/total ration is to high
413 412 # * decide for a "status" of the persistent nodemap and associated location
414 413 # - part of the store next the revlog itself (new requirements)
415 414 # - part of the cache directory
416 415 # - part of an `index` directory
417 416 # (https://www.mercurial-scm.org/wiki/ComputedIndexPlan)
418 417 # * do we want to use this for more than just changelog? if so we need:
419 418 # - simpler "pending" logic for them
420 419 # - double check the memory story (we dont want to keep all revlog in memory)
421 420 # - think about the naming scheme if we are in "cache"
422 421 # * increment the version format to "1" and freeze it.
423 422 coreconfigitem(
424 423 b'devel', b'persistent-nodemap', default=False,
425 424 )
426 425 coreconfigitem(
427 426 b'devel', b'servercafile', default=b'',
428 427 )
429 428 coreconfigitem(
430 429 b'devel', b'serverexactprotocol', default=b'',
431 430 )
432 431 coreconfigitem(
433 432 b'devel', b'serverrequirecert', default=False,
434 433 )
435 434 coreconfigitem(
436 435 b'devel', b'strip-obsmarkers', default=True,
437 436 )
438 437 coreconfigitem(
439 438 b'devel', b'warn-config', default=None,
440 439 )
441 440 coreconfigitem(
442 441 b'devel', b'warn-config-default', default=None,
443 442 )
444 443 coreconfigitem(
445 444 b'devel', b'user.obsmarker', default=None,
446 445 )
447 446 coreconfigitem(
448 447 b'devel', b'warn-config-unknown', default=None,
449 448 )
450 449 coreconfigitem(
451 450 b'devel', b'debug.copies', default=False,
452 451 )
453 452 coreconfigitem(
454 453 b'devel', b'debug.extensions', default=False,
455 454 )
456 455 coreconfigitem(
457 456 b'devel', b'debug.repo-filters', default=False,
458 457 )
459 458 coreconfigitem(
460 459 b'devel', b'debug.peer-request', default=False,
461 460 )
462 461 coreconfigitem(
463 462 b'devel', b'discovery.randomize', default=True,
464 463 )
465 464 _registerdiffopts(section=b'diff')
466 465 coreconfigitem(
467 466 b'email', b'bcc', default=None,
468 467 )
469 468 coreconfigitem(
470 469 b'email', b'cc', default=None,
471 470 )
472 471 coreconfigitem(
473 472 b'email', b'charsets', default=list,
474 473 )
475 474 coreconfigitem(
476 475 b'email', b'from', default=None,
477 476 )
478 477 coreconfigitem(
479 478 b'email', b'method', default=b'smtp',
480 479 )
481 480 coreconfigitem(
482 481 b'email', b'reply-to', default=None,
483 482 )
484 483 coreconfigitem(
485 484 b'email', b'to', default=None,
486 485 )
487 486 coreconfigitem(
488 487 b'experimental', b'archivemetatemplate', default=dynamicdefault,
489 488 )
490 489 coreconfigitem(
491 490 b'experimental', b'auto-publish', default=b'publish',
492 491 )
493 492 coreconfigitem(
494 493 b'experimental', b'bundle-phases', default=False,
495 494 )
496 495 coreconfigitem(
497 496 b'experimental', b'bundle2-advertise', default=True,
498 497 )
499 498 coreconfigitem(
500 499 b'experimental', b'bundle2-output-capture', default=False,
501 500 )
502 501 coreconfigitem(
503 502 b'experimental', b'bundle2.pushback', default=False,
504 503 )
505 504 coreconfigitem(
506 505 b'experimental', b'bundle2lazylocking', default=False,
507 506 )
508 507 coreconfigitem(
509 508 b'experimental', b'bundlecomplevel', default=None,
510 509 )
511 510 coreconfigitem(
512 511 b'experimental', b'bundlecomplevel.bzip2', default=None,
513 512 )
514 513 coreconfigitem(
515 514 b'experimental', b'bundlecomplevel.gzip', default=None,
516 515 )
517 516 coreconfigitem(
518 517 b'experimental', b'bundlecomplevel.none', default=None,
519 518 )
520 519 coreconfigitem(
521 520 b'experimental', b'bundlecomplevel.zstd', default=None,
522 521 )
523 522 coreconfigitem(
524 523 b'experimental', b'changegroup3', default=False,
525 524 )
526 525 coreconfigitem(
527 526 b'experimental', b'cleanup-as-archived', default=False,
528 527 )
529 528 coreconfigitem(
530 529 b'experimental', b'clientcompressionengines', default=list,
531 530 )
532 531 coreconfigitem(
533 532 b'experimental', b'copytrace', default=b'on',
534 533 )
535 534 coreconfigitem(
536 535 b'experimental', b'copytrace.movecandidateslimit', default=100,
537 536 )
538 537 coreconfigitem(
539 538 b'experimental', b'copytrace.sourcecommitlimit', default=100,
540 539 )
541 540 coreconfigitem(
542 541 b'experimental', b'copies.read-from', default=b"filelog-only",
543 542 )
544 543 coreconfigitem(
545 544 b'experimental', b'copies.write-to', default=b'filelog-only',
546 545 )
547 546 coreconfigitem(
548 547 b'experimental', b'crecordtest', default=None,
549 548 )
550 549 coreconfigitem(
551 550 b'experimental', b'directaccess', default=False,
552 551 )
553 552 coreconfigitem(
554 553 b'experimental', b'directaccess.revnums', default=False,
555 554 )
556 555 coreconfigitem(
557 556 b'experimental', b'editortmpinhg', default=False,
558 557 )
559 558 coreconfigitem(
560 559 b'experimental', b'evolution', default=list,
561 560 )
562 561 coreconfigitem(
563 562 b'experimental',
564 563 b'evolution.allowdivergence',
565 564 default=False,
566 565 alias=[(b'experimental', b'allowdivergence')],
567 566 )
568 567 coreconfigitem(
569 568 b'experimental', b'evolution.allowunstable', default=None,
570 569 )
571 570 coreconfigitem(
572 571 b'experimental', b'evolution.createmarkers', default=None,
573 572 )
574 573 coreconfigitem(
575 574 b'experimental',
576 575 b'evolution.effect-flags',
577 576 default=True,
578 577 alias=[(b'experimental', b'effect-flags')],
579 578 )
580 579 coreconfigitem(
581 580 b'experimental', b'evolution.exchange', default=None,
582 581 )
583 582 coreconfigitem(
584 583 b'experimental', b'evolution.bundle-obsmarker', default=False,
585 584 )
586 585 coreconfigitem(
587 586 b'experimental', b'log.topo', default=False,
588 587 )
589 588 coreconfigitem(
590 589 b'experimental', b'evolution.report-instabilities', default=True,
591 590 )
592 591 coreconfigitem(
593 592 b'experimental', b'evolution.track-operation', default=True,
594 593 )
595 594 # repo-level config to exclude a revset visibility
596 595 #
597 596 # The target use case is to use `share` to expose different subset of the same
598 597 # repository, especially server side. See also `server.view`.
599 598 coreconfigitem(
600 599 b'experimental', b'extra-filter-revs', default=None,
601 600 )
602 601 coreconfigitem(
603 602 b'experimental', b'maxdeltachainspan', default=-1,
604 603 )
605 604 coreconfigitem(
606 605 b'experimental', b'mergetempdirprefix', default=None,
607 606 )
608 607 coreconfigitem(
609 608 b'experimental', b'mmapindexthreshold', default=None,
610 609 )
611 610 coreconfigitem(
612 611 b'experimental', b'narrow', default=False,
613 612 )
614 613 coreconfigitem(
615 614 b'experimental', b'nonnormalparanoidcheck', default=False,
616 615 )
617 616 coreconfigitem(
618 617 b'experimental', b'exportableenviron', default=list,
619 618 )
620 619 coreconfigitem(
621 620 b'experimental', b'extendedheader.index', default=None,
622 621 )
623 622 coreconfigitem(
624 623 b'experimental', b'extendedheader.similarity', default=False,
625 624 )
626 625 coreconfigitem(
627 626 b'experimental', b'graphshorten', default=False,
628 627 )
629 628 coreconfigitem(
630 629 b'experimental', b'graphstyle.parent', default=dynamicdefault,
631 630 )
632 631 coreconfigitem(
633 632 b'experimental', b'graphstyle.missing', default=dynamicdefault,
634 633 )
635 634 coreconfigitem(
636 635 b'experimental', b'graphstyle.grandparent', default=dynamicdefault,
637 636 )
638 637 coreconfigitem(
639 638 b'experimental', b'hook-track-tags', default=False,
640 639 )
641 640 coreconfigitem(
642 641 b'experimental', b'httppeer.advertise-v2', default=False,
643 642 )
644 643 coreconfigitem(
645 644 b'experimental', b'httppeer.v2-encoder-order', default=None,
646 645 )
647 646 coreconfigitem(
648 647 b'experimental', b'httppostargs', default=False,
649 648 )
650 649 coreconfigitem(
651 650 b'experimental', b'mergedriver', default=None,
652 651 )
653 652 coreconfigitem(b'experimental', b'nointerrupt', default=False)
654 653 coreconfigitem(b'experimental', b'nointerrupt-interactiveonly', default=True)
655 654
656 655 coreconfigitem(
657 656 b'experimental', b'obsmarkers-exchange-debug', default=False,
658 657 )
659 658 coreconfigitem(
660 659 b'experimental', b'remotenames', default=False,
661 660 )
662 661 coreconfigitem(
663 662 b'experimental', b'removeemptydirs', default=True,
664 663 )
665 664 coreconfigitem(
666 665 b'experimental', b'revert.interactive.select-to-keep', default=False,
667 666 )
668 667 coreconfigitem(
669 668 b'experimental', b'revisions.prefixhexnode', default=False,
670 669 )
671 670 coreconfigitem(
672 671 b'experimental', b'revlogv2', default=None,
673 672 )
674 673 coreconfigitem(
675 674 b'experimental', b'revisions.disambiguatewithin', default=None,
676 675 )
677 676 coreconfigitem(
678 677 b'experimental', b'rust.index', default=False,
679 678 )
680 679 coreconfigitem(
681 680 b'experimental', b'exp-persistent-nodemap', default=False,
682 681 )
683 682 coreconfigitem(
684 683 b'experimental', b'exp-persistent-nodemap.mmap', default=True,
685 684 )
686 685 coreconfigitem(
687 686 b'experimental', b'server.filesdata.recommended-batch-size', default=50000,
688 687 )
689 688 coreconfigitem(
690 689 b'experimental',
691 690 b'server.manifestdata.recommended-batch-size',
692 691 default=100000,
693 692 )
694 693 coreconfigitem(
695 694 b'experimental', b'server.stream-narrow-clones', default=False,
696 695 )
697 696 coreconfigitem(
698 697 b'experimental', b'single-head-per-branch', default=False,
699 698 )
700 699 coreconfigitem(
701 700 b'experimental',
702 701 b'single-head-per-branch:account-closed-heads',
703 702 default=False,
704 703 )
705 704 coreconfigitem(
706 705 b'experimental', b'sshserver.support-v2', default=False,
707 706 )
708 707 coreconfigitem(
709 708 b'experimental', b'sparse-read', default=False,
710 709 )
711 710 coreconfigitem(
712 711 b'experimental', b'sparse-read.density-threshold', default=0.50,
713 712 )
714 713 coreconfigitem(
715 714 b'experimental', b'sparse-read.min-gap-size', default=b'65K',
716 715 )
717 716 coreconfigitem(
718 717 b'experimental', b'treemanifest', default=False,
719 718 )
720 719 coreconfigitem(
721 720 b'experimental', b'update.atomic-file', default=False,
722 721 )
723 722 coreconfigitem(
724 723 b'experimental', b'sshpeer.advertise-v2', default=False,
725 724 )
726 725 coreconfigitem(
727 726 b'experimental', b'web.apiserver', default=False,
728 727 )
729 728 coreconfigitem(
730 729 b'experimental', b'web.api.http-v2', default=False,
731 730 )
732 731 coreconfigitem(
733 732 b'experimental', b'web.api.debugreflect', default=False,
734 733 )
735 734 coreconfigitem(
736 735 b'experimental', b'worker.wdir-get-thread-safe', default=False,
737 736 )
738 737 coreconfigitem(
739 738 b'experimental', b'worker.repository-upgrade', default=False,
740 739 )
741 740 coreconfigitem(
742 741 b'experimental', b'xdiff', default=False,
743 742 )
744 743 coreconfigitem(
745 744 b'extensions', b'.*', default=None, generic=True,
746 745 )
747 746 coreconfigitem(
748 747 b'extdata', b'.*', default=None, generic=True,
749 748 )
750 749 coreconfigitem(
751 750 b'format', b'bookmarks-in-store', default=False,
752 751 )
753 752 coreconfigitem(
754 753 b'format', b'chunkcachesize', default=None, experimental=True,
755 754 )
756 755 coreconfigitem(
757 756 b'format', b'dotencode', default=True,
758 757 )
759 758 coreconfigitem(
760 759 b'format', b'generaldelta', default=False, experimental=True,
761 760 )
762 761 coreconfigitem(
763 762 b'format', b'manifestcachesize', default=None, experimental=True,
764 763 )
765 764 coreconfigitem(
766 765 b'format', b'maxchainlen', default=dynamicdefault, experimental=True,
767 766 )
768 767 coreconfigitem(
769 768 b'format', b'obsstore-version', default=None,
770 769 )
771 770 coreconfigitem(
772 771 b'format', b'sparse-revlog', default=True,
773 772 )
774 773 coreconfigitem(
775 774 b'format',
776 775 b'revlog-compression',
777 776 default=lambda: [b'zlib'],
778 777 alias=[(b'experimental', b'format.compression')],
779 778 )
780 779 coreconfigitem(
781 780 b'format', b'usefncache', default=True,
782 781 )
783 782 coreconfigitem(
784 783 b'format', b'usegeneraldelta', default=True,
785 784 )
786 785 coreconfigitem(
787 786 b'format', b'usestore', default=True,
788 787 )
789 788 coreconfigitem(
790 789 b'format',
791 790 b'exp-use-copies-side-data-changeset',
792 791 default=False,
793 792 experimental=True,
794 793 )
795 794 coreconfigitem(
796 795 b'format', b'exp-use-side-data', default=False, experimental=True,
797 796 )
798 797 coreconfigitem(
799 798 b'format', b'internal-phase', default=False, experimental=True,
800 799 )
801 800 coreconfigitem(
802 801 b'fsmonitor', b'warn_when_unused', default=True,
803 802 )
804 803 coreconfigitem(
805 804 b'fsmonitor', b'warn_update_file_count', default=50000,
806 805 )
807 806 coreconfigitem(
808 807 b'help', br'hidden-command\..*', default=False, generic=True,
809 808 )
810 809 coreconfigitem(
811 810 b'help', br'hidden-topic\..*', default=False, generic=True,
812 811 )
813 812 coreconfigitem(
814 813 b'hooks', b'.*', default=dynamicdefault, generic=True,
815 814 )
816 815 coreconfigitem(
817 816 b'hgweb-paths', b'.*', default=list, generic=True,
818 817 )
819 818 coreconfigitem(
820 819 b'hostfingerprints', b'.*', default=list, generic=True,
821 820 )
822 821 coreconfigitem(
823 822 b'hostsecurity', b'ciphers', default=None,
824 823 )
825 824 coreconfigitem(
826 825 b'hostsecurity', b'disabletls10warning', default=False,
827 826 )
828 827 coreconfigitem(
829 828 b'hostsecurity', b'minimumprotocol', default=dynamicdefault,
830 829 )
831 830 coreconfigitem(
832 831 b'hostsecurity',
833 832 b'.*:minimumprotocol$',
834 833 default=dynamicdefault,
835 834 generic=True,
836 835 )
837 836 coreconfigitem(
838 837 b'hostsecurity', b'.*:ciphers$', default=dynamicdefault, generic=True,
839 838 )
840 839 coreconfigitem(
841 840 b'hostsecurity', b'.*:fingerprints$', default=list, generic=True,
842 841 )
843 842 coreconfigitem(
844 843 b'hostsecurity', b'.*:verifycertsfile$', default=None, generic=True,
845 844 )
846 845
847 846 coreconfigitem(
848 847 b'http_proxy', b'always', default=False,
849 848 )
850 849 coreconfigitem(
851 850 b'http_proxy', b'host', default=None,
852 851 )
853 852 coreconfigitem(
854 853 b'http_proxy', b'no', default=list,
855 854 )
856 855 coreconfigitem(
857 856 b'http_proxy', b'passwd', default=None,
858 857 )
859 858 coreconfigitem(
860 859 b'http_proxy', b'user', default=None,
861 860 )
862 861
863 862 coreconfigitem(
864 863 b'http', b'timeout', default=None,
865 864 )
866 865
867 866 coreconfigitem(
868 867 b'logtoprocess', b'commandexception', default=None,
869 868 )
870 869 coreconfigitem(
871 870 b'logtoprocess', b'commandfinish', default=None,
872 871 )
873 872 coreconfigitem(
874 873 b'logtoprocess', b'command', default=None,
875 874 )
876 875 coreconfigitem(
877 876 b'logtoprocess', b'develwarn', default=None,
878 877 )
879 878 coreconfigitem(
880 879 b'logtoprocess', b'uiblocked', default=None,
881 880 )
882 881 coreconfigitem(
883 882 b'merge', b'checkunknown', default=b'abort',
884 883 )
885 884 coreconfigitem(
886 885 b'merge', b'checkignored', default=b'abort',
887 886 )
888 887 coreconfigitem(
889 888 b'experimental', b'merge.checkpathconflicts', default=False,
890 889 )
891 890 coreconfigitem(
892 891 b'merge', b'followcopies', default=True,
893 892 )
894 893 coreconfigitem(
895 894 b'merge', b'on-failure', default=b'continue',
896 895 )
897 896 coreconfigitem(
898 897 b'merge', b'preferancestor', default=lambda: [b'*'], experimental=True,
899 898 )
900 899 coreconfigitem(
901 900 b'merge', b'strict-capability-check', default=False,
902 901 )
903 902 coreconfigitem(
904 903 b'merge-tools', b'.*', default=None, generic=True,
905 904 )
906 905 coreconfigitem(
907 906 b'merge-tools',
908 907 br'.*\.args$',
909 908 default=b"$local $base $other",
910 909 generic=True,
911 910 priority=-1,
912 911 )
913 912 coreconfigitem(
914 913 b'merge-tools', br'.*\.binary$', default=False, generic=True, priority=-1,
915 914 )
916 915 coreconfigitem(
917 916 b'merge-tools', br'.*\.check$', default=list, generic=True, priority=-1,
918 917 )
919 918 coreconfigitem(
920 919 b'merge-tools',
921 920 br'.*\.checkchanged$',
922 921 default=False,
923 922 generic=True,
924 923 priority=-1,
925 924 )
926 925 coreconfigitem(
927 926 b'merge-tools',
928 927 br'.*\.executable$',
929 928 default=dynamicdefault,
930 929 generic=True,
931 930 priority=-1,
932 931 )
933 932 coreconfigitem(
934 933 b'merge-tools', br'.*\.fixeol$', default=False, generic=True, priority=-1,
935 934 )
936 935 coreconfigitem(
937 936 b'merge-tools', br'.*\.gui$', default=False, generic=True, priority=-1,
938 937 )
939 938 coreconfigitem(
940 939 b'merge-tools',
941 940 br'.*\.mergemarkers$',
942 941 default=b'basic',
943 942 generic=True,
944 943 priority=-1,
945 944 )
946 945 coreconfigitem(
947 946 b'merge-tools',
948 947 br'.*\.mergemarkertemplate$',
949 948 default=dynamicdefault, # take from ui.mergemarkertemplate
950 949 generic=True,
951 950 priority=-1,
952 951 )
953 952 coreconfigitem(
954 953 b'merge-tools', br'.*\.priority$', default=0, generic=True, priority=-1,
955 954 )
956 955 coreconfigitem(
957 956 b'merge-tools',
958 957 br'.*\.premerge$',
959 958 default=dynamicdefault,
960 959 generic=True,
961 960 priority=-1,
962 961 )
963 962 coreconfigitem(
964 963 b'merge-tools', br'.*\.symlink$', default=False, generic=True, priority=-1,
965 964 )
966 965 coreconfigitem(
967 966 b'pager', b'attend-.*', default=dynamicdefault, generic=True,
968 967 )
969 968 coreconfigitem(
970 969 b'pager', b'ignore', default=list,
971 970 )
972 971 coreconfigitem(
973 972 b'pager', b'pager', default=dynamicdefault,
974 973 )
975 974 coreconfigitem(
976 975 b'patch', b'eol', default=b'strict',
977 976 )
978 977 coreconfigitem(
979 978 b'patch', b'fuzz', default=2,
980 979 )
981 980 coreconfigitem(
982 981 b'paths', b'default', default=None,
983 982 )
984 983 coreconfigitem(
985 984 b'paths', b'default-push', default=None,
986 985 )
987 986 coreconfigitem(
988 987 b'paths', b'.*', default=None, generic=True,
989 988 )
990 989 coreconfigitem(
991 990 b'phases', b'checksubrepos', default=b'follow',
992 991 )
993 992 coreconfigitem(
994 993 b'phases', b'new-commit', default=b'draft',
995 994 )
996 995 coreconfigitem(
997 996 b'phases', b'publish', default=True,
998 997 )
999 998 coreconfigitem(
1000 999 b'profiling', b'enabled', default=False,
1001 1000 )
1002 1001 coreconfigitem(
1003 1002 b'profiling', b'format', default=b'text',
1004 1003 )
1005 1004 coreconfigitem(
1006 1005 b'profiling', b'freq', default=1000,
1007 1006 )
1008 1007 coreconfigitem(
1009 1008 b'profiling', b'limit', default=30,
1010 1009 )
1011 1010 coreconfigitem(
1012 1011 b'profiling', b'nested', default=0,
1013 1012 )
1014 1013 coreconfigitem(
1015 1014 b'profiling', b'output', default=None,
1016 1015 )
1017 1016 coreconfigitem(
1018 1017 b'profiling', b'showmax', default=0.999,
1019 1018 )
1020 1019 coreconfigitem(
1021 1020 b'profiling', b'showmin', default=dynamicdefault,
1022 1021 )
1023 1022 coreconfigitem(
1024 1023 b'profiling', b'showtime', default=True,
1025 1024 )
1026 1025 coreconfigitem(
1027 1026 b'profiling', b'sort', default=b'inlinetime',
1028 1027 )
1029 1028 coreconfigitem(
1030 1029 b'profiling', b'statformat', default=b'hotpath',
1031 1030 )
1032 1031 coreconfigitem(
1033 1032 b'profiling', b'time-track', default=dynamicdefault,
1034 1033 )
1035 1034 coreconfigitem(
1036 1035 b'profiling', b'type', default=b'stat',
1037 1036 )
1038 1037 coreconfigitem(
1039 1038 b'progress', b'assume-tty', default=False,
1040 1039 )
1041 1040 coreconfigitem(
1042 1041 b'progress', b'changedelay', default=1,
1043 1042 )
1044 1043 coreconfigitem(
1045 1044 b'progress', b'clear-complete', default=True,
1046 1045 )
1047 1046 coreconfigitem(
1048 1047 b'progress', b'debug', default=False,
1049 1048 )
1050 1049 coreconfigitem(
1051 1050 b'progress', b'delay', default=3,
1052 1051 )
1053 1052 coreconfigitem(
1054 1053 b'progress', b'disable', default=False,
1055 1054 )
1056 1055 coreconfigitem(
1057 1056 b'progress', b'estimateinterval', default=60.0,
1058 1057 )
1059 1058 coreconfigitem(
1060 1059 b'progress',
1061 1060 b'format',
1062 1061 default=lambda: [b'topic', b'bar', b'number', b'estimate'],
1063 1062 )
1064 1063 coreconfigitem(
1065 1064 b'progress', b'refresh', default=0.1,
1066 1065 )
1067 1066 coreconfigitem(
1068 1067 b'progress', b'width', default=dynamicdefault,
1069 1068 )
1070 1069 coreconfigitem(
1071 1070 b'push', b'pushvars.server', default=False,
1072 1071 )
1073 1072 coreconfigitem(
1074 1073 b'rewrite',
1075 1074 b'backup-bundle',
1076 1075 default=True,
1077 1076 alias=[(b'ui', b'history-editing-backup')],
1078 1077 )
1079 1078 coreconfigitem(
1080 1079 b'rewrite', b'update-timestamp', default=False,
1081 1080 )
1082 1081 coreconfigitem(
1083 1082 b'storage', b'new-repo-backend', default=b'revlogv1', experimental=True,
1084 1083 )
1085 1084 coreconfigitem(
1086 1085 b'storage',
1087 1086 b'revlog.optimize-delta-parent-choice',
1088 1087 default=True,
1089 1088 alias=[(b'format', b'aggressivemergedeltas')],
1090 1089 )
1091 1090 coreconfigitem(
1092 1091 b'storage', b'revlog.reuse-external-delta', default=True,
1093 1092 )
1094 1093 coreconfigitem(
1095 1094 b'storage', b'revlog.reuse-external-delta-parent', default=None,
1096 1095 )
1097 1096 coreconfigitem(
1098 1097 b'storage', b'revlog.zlib.level', default=None,
1099 1098 )
1100 1099 coreconfigitem(
1101 1100 b'storage', b'revlog.zstd.level', default=None,
1102 1101 )
1103 1102 coreconfigitem(
1104 1103 b'server', b'bookmarks-pushkey-compat', default=True,
1105 1104 )
1106 1105 coreconfigitem(
1107 1106 b'server', b'bundle1', default=True,
1108 1107 )
1109 1108 coreconfigitem(
1110 1109 b'server', b'bundle1gd', default=None,
1111 1110 )
1112 1111 coreconfigitem(
1113 1112 b'server', b'bundle1.pull', default=None,
1114 1113 )
1115 1114 coreconfigitem(
1116 1115 b'server', b'bundle1gd.pull', default=None,
1117 1116 )
1118 1117 coreconfigitem(
1119 1118 b'server', b'bundle1.push', default=None,
1120 1119 )
1121 1120 coreconfigitem(
1122 1121 b'server', b'bundle1gd.push', default=None,
1123 1122 )
1124 1123 coreconfigitem(
1125 1124 b'server',
1126 1125 b'bundle2.stream',
1127 1126 default=True,
1128 1127 alias=[(b'experimental', b'bundle2.stream')],
1129 1128 )
1130 1129 coreconfigitem(
1131 1130 b'server', b'compressionengines', default=list,
1132 1131 )
1133 1132 coreconfigitem(
1134 1133 b'server', b'concurrent-push-mode', default=b'check-related',
1135 1134 )
1136 1135 coreconfigitem(
1137 1136 b'server', b'disablefullbundle', default=False,
1138 1137 )
1139 1138 coreconfigitem(
1140 1139 b'server', b'maxhttpheaderlen', default=1024,
1141 1140 )
1142 1141 coreconfigitem(
1143 1142 b'server', b'pullbundle', default=False,
1144 1143 )
1145 1144 coreconfigitem(
1146 1145 b'server', b'preferuncompressed', default=False,
1147 1146 )
1148 1147 coreconfigitem(
1149 1148 b'server', b'streamunbundle', default=False,
1150 1149 )
1151 1150 coreconfigitem(
1152 1151 b'server', b'uncompressed', default=True,
1153 1152 )
1154 1153 coreconfigitem(
1155 1154 b'server', b'uncompressedallowsecret', default=False,
1156 1155 )
1157 1156 coreconfigitem(
1158 1157 b'server', b'view', default=b'served',
1159 1158 )
1160 1159 coreconfigitem(
1161 1160 b'server', b'validate', default=False,
1162 1161 )
1163 1162 coreconfigitem(
1164 1163 b'server', b'zliblevel', default=-1,
1165 1164 )
1166 1165 coreconfigitem(
1167 1166 b'server', b'zstdlevel', default=3,
1168 1167 )
1169 1168 coreconfigitem(
1170 1169 b'share', b'pool', default=None,
1171 1170 )
1172 1171 coreconfigitem(
1173 1172 b'share', b'poolnaming', default=b'identity',
1174 1173 )
1175 1174 coreconfigitem(
1176 1175 b'shelve', b'maxbackups', default=10,
1177 1176 )
1178 1177 coreconfigitem(
1179 1178 b'smtp', b'host', default=None,
1180 1179 )
1181 1180 coreconfigitem(
1182 1181 b'smtp', b'local_hostname', default=None,
1183 1182 )
1184 1183 coreconfigitem(
1185 1184 b'smtp', b'password', default=None,
1186 1185 )
1187 1186 coreconfigitem(
1188 1187 b'smtp', b'port', default=dynamicdefault,
1189 1188 )
1190 1189 coreconfigitem(
1191 1190 b'smtp', b'tls', default=b'none',
1192 1191 )
1193 1192 coreconfigitem(
1194 1193 b'smtp', b'username', default=None,
1195 1194 )
1196 1195 coreconfigitem(
1197 1196 b'sparse', b'missingwarning', default=True, experimental=True,
1198 1197 )
1199 1198 coreconfigitem(
1200 1199 b'subrepos',
1201 1200 b'allowed',
1202 1201 default=dynamicdefault, # to make backporting simpler
1203 1202 )
1204 1203 coreconfigitem(
1205 1204 b'subrepos', b'hg:allowed', default=dynamicdefault,
1206 1205 )
1207 1206 coreconfigitem(
1208 1207 b'subrepos', b'git:allowed', default=dynamicdefault,
1209 1208 )
1210 1209 coreconfigitem(
1211 1210 b'subrepos', b'svn:allowed', default=dynamicdefault,
1212 1211 )
1213 1212 coreconfigitem(
1214 1213 b'templates', b'.*', default=None, generic=True,
1215 1214 )
1216 1215 coreconfigitem(
1217 1216 b'templateconfig', b'.*', default=dynamicdefault, generic=True,
1218 1217 )
1219 1218 coreconfigitem(
1220 1219 b'trusted', b'groups', default=list,
1221 1220 )
1222 1221 coreconfigitem(
1223 1222 b'trusted', b'users', default=list,
1224 1223 )
1225 1224 coreconfigitem(
1226 1225 b'ui', b'_usedassubrepo', default=False,
1227 1226 )
1228 1227 coreconfigitem(
1229 1228 b'ui', b'allowemptycommit', default=False,
1230 1229 )
1231 1230 coreconfigitem(
1232 1231 b'ui', b'archivemeta', default=True,
1233 1232 )
1234 1233 coreconfigitem(
1235 1234 b'ui', b'askusername', default=False,
1236 1235 )
1237 1236 coreconfigitem(
1238 1237 b'ui', b'clonebundlefallback', default=False,
1239 1238 )
1240 1239 coreconfigitem(
1241 1240 b'ui', b'clonebundleprefers', default=list,
1242 1241 )
1243 1242 coreconfigitem(
1244 1243 b'ui', b'clonebundles', default=True,
1245 1244 )
1246 1245 coreconfigitem(
1247 1246 b'ui', b'color', default=b'auto',
1248 1247 )
1249 1248 coreconfigitem(
1250 1249 b'ui', b'commitsubrepos', default=False,
1251 1250 )
1252 1251 coreconfigitem(
1253 1252 b'ui', b'debug', default=False,
1254 1253 )
1255 1254 coreconfigitem(
1256 1255 b'ui', b'debugger', default=None,
1257 1256 )
1258 1257 coreconfigitem(
1259 1258 b'ui', b'editor', default=dynamicdefault,
1260 1259 )
1261 1260 coreconfigitem(
1262 1261 b'ui', b'fallbackencoding', default=None,
1263 1262 )
1264 1263 coreconfigitem(
1265 1264 b'ui', b'forcecwd', default=None,
1266 1265 )
1267 1266 coreconfigitem(
1268 1267 b'ui', b'forcemerge', default=None,
1269 1268 )
1270 1269 coreconfigitem(
1271 1270 b'ui', b'formatdebug', default=False,
1272 1271 )
1273 1272 coreconfigitem(
1274 1273 b'ui', b'formatjson', default=False,
1275 1274 )
1276 1275 coreconfigitem(
1277 1276 b'ui', b'formatted', default=None,
1278 1277 )
1279 1278 coreconfigitem(
1280 1279 b'ui', b'graphnodetemplate', default=None,
1281 1280 )
1282 1281 coreconfigitem(
1283 1282 b'ui', b'interactive', default=None,
1284 1283 )
1285 1284 coreconfigitem(
1286 1285 b'ui', b'interface', default=None,
1287 1286 )
1288 1287 coreconfigitem(
1289 1288 b'ui', b'interface.chunkselector', default=None,
1290 1289 )
1291 1290 coreconfigitem(
1292 1291 b'ui', b'large-file-limit', default=10000000,
1293 1292 )
1294 1293 coreconfigitem(
1295 1294 b'ui', b'logblockedtimes', default=False,
1296 1295 )
1297 1296 coreconfigitem(
1298 1297 b'ui', b'logtemplate', default=None,
1299 1298 )
1300 1299 coreconfigitem(
1301 1300 b'ui', b'merge', default=None,
1302 1301 )
1303 1302 coreconfigitem(
1304 1303 b'ui', b'mergemarkers', default=b'basic',
1305 1304 )
1306 1305 coreconfigitem(
1307 1306 b'ui',
1308 1307 b'mergemarkertemplate',
1309 1308 default=(
1310 1309 b'{node|short} '
1311 1310 b'{ifeq(tags, "tip", "", '
1312 1311 b'ifeq(tags, "", "", "{tags} "))}'
1313 1312 b'{if(bookmarks, "{bookmarks} ")}'
1314 1313 b'{ifeq(branch, "default", "", "{branch} ")}'
1315 1314 b'- {author|user}: {desc|firstline}'
1316 1315 ),
1317 1316 )
1318 1317 coreconfigitem(
1319 1318 b'ui', b'message-output', default=b'stdio',
1320 1319 )
1321 1320 coreconfigitem(
1322 1321 b'ui', b'nontty', default=False,
1323 1322 )
1324 1323 coreconfigitem(
1325 1324 b'ui', b'origbackuppath', default=None,
1326 1325 )
1327 1326 coreconfigitem(
1328 1327 b'ui', b'paginate', default=True,
1329 1328 )
1330 1329 coreconfigitem(
1331 1330 b'ui', b'patch', default=None,
1332 1331 )
1333 1332 coreconfigitem(
1334 1333 b'ui', b'pre-merge-tool-output-template', default=None,
1335 1334 )
1336 1335 coreconfigitem(
1337 1336 b'ui', b'portablefilenames', default=b'warn',
1338 1337 )
1339 1338 coreconfigitem(
1340 1339 b'ui', b'promptecho', default=False,
1341 1340 )
1342 1341 coreconfigitem(
1343 1342 b'ui', b'quiet', default=False,
1344 1343 )
1345 1344 coreconfigitem(
1346 1345 b'ui', b'quietbookmarkmove', default=False,
1347 1346 )
1348 1347 coreconfigitem(
1349 1348 b'ui', b'relative-paths', default=b'legacy',
1350 1349 )
1351 1350 coreconfigitem(
1352 1351 b'ui', b'remotecmd', default=b'hg',
1353 1352 )
1354 1353 coreconfigitem(
1355 1354 b'ui', b'report_untrusted', default=True,
1356 1355 )
1357 1356 coreconfigitem(
1358 1357 b'ui', b'rollback', default=True,
1359 1358 )
1360 1359 coreconfigitem(
1361 1360 b'ui', b'signal-safe-lock', default=True,
1362 1361 )
1363 1362 coreconfigitem(
1364 1363 b'ui', b'slash', default=False,
1365 1364 )
1366 1365 coreconfigitem(
1367 1366 b'ui', b'ssh', default=b'ssh',
1368 1367 )
1369 1368 coreconfigitem(
1370 1369 b'ui', b'ssherrorhint', default=None,
1371 1370 )
1372 1371 coreconfigitem(
1373 1372 b'ui', b'statuscopies', default=False,
1374 1373 )
1375 1374 coreconfigitem(
1376 1375 b'ui', b'strict', default=False,
1377 1376 )
1378 1377 coreconfigitem(
1379 1378 b'ui', b'style', default=b'',
1380 1379 )
1381 1380 coreconfigitem(
1382 1381 b'ui', b'supportcontact', default=None,
1383 1382 )
1384 1383 coreconfigitem(
1385 1384 b'ui', b'textwidth', default=78,
1386 1385 )
1387 1386 coreconfigitem(
1388 1387 b'ui', b'timeout', default=b'600',
1389 1388 )
1390 1389 coreconfigitem(
1391 1390 b'ui', b'timeout.warn', default=0,
1392 1391 )
1393 1392 coreconfigitem(
1394 1393 b'ui', b'traceback', default=False,
1395 1394 )
1396 1395 coreconfigitem(
1397 1396 b'ui', b'tweakdefaults', default=False,
1398 1397 )
1399 1398 coreconfigitem(b'ui', b'username', alias=[(b'ui', b'user')])
1400 1399 coreconfigitem(
1401 1400 b'ui', b'verbose', default=False,
1402 1401 )
1403 1402 coreconfigitem(
1404 1403 b'verify', b'skipflags', default=None,
1405 1404 )
1406 1405 coreconfigitem(
1407 1406 b'web', b'allowbz2', default=False,
1408 1407 )
1409 1408 coreconfigitem(
1410 1409 b'web', b'allowgz', default=False,
1411 1410 )
1412 1411 coreconfigitem(
1413 1412 b'web', b'allow-pull', alias=[(b'web', b'allowpull')], default=True,
1414 1413 )
1415 1414 coreconfigitem(
1416 1415 b'web', b'allow-push', alias=[(b'web', b'allow_push')], default=list,
1417 1416 )
1418 1417 coreconfigitem(
1419 1418 b'web', b'allowzip', default=False,
1420 1419 )
1421 1420 coreconfigitem(
1422 1421 b'web', b'archivesubrepos', default=False,
1423 1422 )
1424 1423 coreconfigitem(
1425 1424 b'web', b'cache', default=True,
1426 1425 )
1427 1426 coreconfigitem(
1428 1427 b'web', b'comparisoncontext', default=5,
1429 1428 )
1430 1429 coreconfigitem(
1431 1430 b'web', b'contact', default=None,
1432 1431 )
1433 1432 coreconfigitem(
1434 1433 b'web', b'deny_push', default=list,
1435 1434 )
1436 1435 coreconfigitem(
1437 1436 b'web', b'guessmime', default=False,
1438 1437 )
1439 1438 coreconfigitem(
1440 1439 b'web', b'hidden', default=False,
1441 1440 )
1442 1441 coreconfigitem(
1443 1442 b'web', b'labels', default=list,
1444 1443 )
1445 1444 coreconfigitem(
1446 1445 b'web', b'logoimg', default=b'hglogo.png',
1447 1446 )
1448 1447 coreconfigitem(
1449 1448 b'web', b'logourl', default=b'https://mercurial-scm.org/',
1450 1449 )
1451 1450 coreconfigitem(
1452 1451 b'web', b'accesslog', default=b'-',
1453 1452 )
1454 1453 coreconfigitem(
1455 1454 b'web', b'address', default=b'',
1456 1455 )
1457 1456 coreconfigitem(
1458 1457 b'web', b'allow-archive', alias=[(b'web', b'allow_archive')], default=list,
1459 1458 )
1460 1459 coreconfigitem(
1461 1460 b'web', b'allow_read', default=list,
1462 1461 )
1463 1462 coreconfigitem(
1464 1463 b'web', b'baseurl', default=None,
1465 1464 )
1466 1465 coreconfigitem(
1467 1466 b'web', b'cacerts', default=None,
1468 1467 )
1469 1468 coreconfigitem(
1470 1469 b'web', b'certificate', default=None,
1471 1470 )
1472 1471 coreconfigitem(
1473 1472 b'web', b'collapse', default=False,
1474 1473 )
1475 1474 coreconfigitem(
1476 1475 b'web', b'csp', default=None,
1477 1476 )
1478 1477 coreconfigitem(
1479 1478 b'web', b'deny_read', default=list,
1480 1479 )
1481 1480 coreconfigitem(
1482 1481 b'web', b'descend', default=True,
1483 1482 )
1484 1483 coreconfigitem(
1485 1484 b'web', b'description', default=b"",
1486 1485 )
1487 1486 coreconfigitem(
1488 1487 b'web', b'encoding', default=lambda: encoding.encoding,
1489 1488 )
1490 1489 coreconfigitem(
1491 1490 b'web', b'errorlog', default=b'-',
1492 1491 )
1493 1492 coreconfigitem(
1494 1493 b'web', b'ipv6', default=False,
1495 1494 )
1496 1495 coreconfigitem(
1497 1496 b'web', b'maxchanges', default=10,
1498 1497 )
1499 1498 coreconfigitem(
1500 1499 b'web', b'maxfiles', default=10,
1501 1500 )
1502 1501 coreconfigitem(
1503 1502 b'web', b'maxshortchanges', default=60,
1504 1503 )
1505 1504 coreconfigitem(
1506 1505 b'web', b'motd', default=b'',
1507 1506 )
1508 1507 coreconfigitem(
1509 1508 b'web', b'name', default=dynamicdefault,
1510 1509 )
1511 1510 coreconfigitem(
1512 1511 b'web', b'port', default=8000,
1513 1512 )
1514 1513 coreconfigitem(
1515 1514 b'web', b'prefix', default=b'',
1516 1515 )
1517 1516 coreconfigitem(
1518 1517 b'web', b'push_ssl', default=True,
1519 1518 )
1520 1519 coreconfigitem(
1521 1520 b'web', b'refreshinterval', default=20,
1522 1521 )
1523 1522 coreconfigitem(
1524 1523 b'web', b'server-header', default=None,
1525 1524 )
1526 1525 coreconfigitem(
1527 1526 b'web', b'static', default=None,
1528 1527 )
1529 1528 coreconfigitem(
1530 1529 b'web', b'staticurl', default=None,
1531 1530 )
1532 1531 coreconfigitem(
1533 1532 b'web', b'stripes', default=1,
1534 1533 )
1535 1534 coreconfigitem(
1536 1535 b'web', b'style', default=b'paper',
1537 1536 )
1538 1537 coreconfigitem(
1539 1538 b'web', b'templates', default=None,
1540 1539 )
1541 1540 coreconfigitem(
1542 1541 b'web', b'view', default=b'served', experimental=True,
1543 1542 )
1544 1543 coreconfigitem(
1545 1544 b'worker', b'backgroundclose', default=dynamicdefault,
1546 1545 )
1547 1546 # Windows defaults to a limit of 512 open files. A buffer of 128
1548 1547 # should give us enough headway.
1549 1548 coreconfigitem(
1550 1549 b'worker', b'backgroundclosemaxqueue', default=384,
1551 1550 )
1552 1551 coreconfigitem(
1553 1552 b'worker', b'backgroundcloseminfilecount', default=2048,
1554 1553 )
1555 1554 coreconfigitem(
1556 1555 b'worker', b'backgroundclosethreadcount', default=4,
1557 1556 )
1558 1557 coreconfigitem(
1559 1558 b'worker', b'enabled', default=True,
1560 1559 )
1561 1560 coreconfigitem(
1562 1561 b'worker', b'numcpus', default=None,
1563 1562 )
1564 1563
1565 1564 # Rebase related configuration moved to core because other extension are doing
1566 1565 # strange things. For example, shelve import the extensions to reuse some bit
1567 1566 # without formally loading it.
1568 1567 coreconfigitem(
1569 1568 b'commands', b'rebase.requiredest', default=False,
1570 1569 )
1571 1570 coreconfigitem(
1572 1571 b'experimental', b'rebaseskipobsolete', default=True,
1573 1572 )
1574 1573 coreconfigitem(
1575 1574 b'rebase', b'singletransaction', default=False,
1576 1575 )
1577 1576 coreconfigitem(
1578 1577 b'rebase', b'experimental.inmemory', default=False,
1579 1578 )
@@ -1,3058 +1,3065
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 io
20 20 import os
21 21 import struct
22 22 import zlib
23 23
24 24 # import stuff from node for others to import from revlog
25 25 from .node import (
26 26 bin,
27 27 hex,
28 28 nullhex,
29 29 nullid,
30 30 nullrev,
31 31 short,
32 32 wdirfilenodeids,
33 33 wdirhex,
34 34 wdirid,
35 35 wdirrev,
36 36 )
37 37 from .i18n import _
38 38 from .pycompat import getattr
39 39 from .revlogutils.constants import (
40 40 FLAG_GENERALDELTA,
41 41 FLAG_INLINE_DATA,
42 42 REVLOGV0,
43 43 REVLOGV1,
44 44 REVLOGV1_FLAGS,
45 45 REVLOGV2,
46 46 REVLOGV2_FLAGS,
47 47 REVLOG_DEFAULT_FLAGS,
48 48 REVLOG_DEFAULT_FORMAT,
49 49 REVLOG_DEFAULT_VERSION,
50 50 )
51 51 from .revlogutils.flagutil import (
52 52 REVIDX_DEFAULT_FLAGS,
53 53 REVIDX_ELLIPSIS,
54 54 REVIDX_EXTSTORED,
55 55 REVIDX_FLAGS_ORDER,
56 56 REVIDX_ISCENSORED,
57 57 REVIDX_RAWTEXT_CHANGING_FLAGS,
58 58 REVIDX_SIDEDATA,
59 59 )
60 60 from .thirdparty import attr
61 61 from . import (
62 62 ancestor,
63 63 dagop,
64 64 error,
65 65 mdiff,
66 66 policy,
67 67 pycompat,
68 68 templatefilters,
69 69 util,
70 70 )
71 71 from .interfaces import (
72 72 repository,
73 73 util as interfaceutil,
74 74 )
75 75 from .revlogutils import (
76 76 deltas as deltautil,
77 77 flagutil,
78 78 nodemap as nodemaputil,
79 79 sidedata as sidedatautil,
80 80 )
81 81 from .utils import (
82 82 storageutil,
83 83 stringutil,
84 84 )
85 85
86 86 # blanked usage of all the name to prevent pyflakes constraints
87 87 # We need these name available in the module for extensions.
88 88 REVLOGV0
89 89 REVLOGV1
90 90 REVLOGV2
91 91 FLAG_INLINE_DATA
92 92 FLAG_GENERALDELTA
93 93 REVLOG_DEFAULT_FLAGS
94 94 REVLOG_DEFAULT_FORMAT
95 95 REVLOG_DEFAULT_VERSION
96 96 REVLOGV1_FLAGS
97 97 REVLOGV2_FLAGS
98 98 REVIDX_ISCENSORED
99 99 REVIDX_ELLIPSIS
100 100 REVIDX_SIDEDATA
101 101 REVIDX_EXTSTORED
102 102 REVIDX_DEFAULT_FLAGS
103 103 REVIDX_FLAGS_ORDER
104 104 REVIDX_RAWTEXT_CHANGING_FLAGS
105 105
106 106 parsers = policy.importmod('parsers')
107 107 rustancestor = policy.importrust('ancestor')
108 108 rustdagop = policy.importrust('dagop')
109 109 rustrevlog = policy.importrust('revlog')
110 110
111 111 # Aliased for performance.
112 112 _zlibdecompress = zlib.decompress
113 113
114 114 # max size of revlog with inline data
115 115 _maxinline = 131072
116 116 _chunksize = 1048576
117 117
118 118 # Flag processors for REVIDX_ELLIPSIS.
119 119 def ellipsisreadprocessor(rl, text):
120 120 return text, False, {}
121 121
122 122
123 123 def ellipsiswriteprocessor(rl, text, sidedata):
124 124 return text, False
125 125
126 126
127 127 def ellipsisrawprocessor(rl, text):
128 128 return False
129 129
130 130
131 131 ellipsisprocessor = (
132 132 ellipsisreadprocessor,
133 133 ellipsiswriteprocessor,
134 134 ellipsisrawprocessor,
135 135 )
136 136
137 137
138 138 def getoffset(q):
139 139 return int(q >> 16)
140 140
141 141
142 142 def gettype(q):
143 143 return int(q & 0xFFFF)
144 144
145 145
146 146 def offset_type(offset, type):
147 147 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
148 148 raise ValueError(b'unknown revlog index flags')
149 149 return int(int(offset) << 16 | type)
150 150
151 151
152 152 def _verify_revision(rl, skipflags, state, node):
153 153 """Verify the integrity of the given revlog ``node`` while providing a hook
154 154 point for extensions to influence the operation."""
155 155 if skipflags:
156 156 state[b'skipread'].add(node)
157 157 else:
158 158 # Side-effect: read content and verify hash.
159 159 rl.revision(node)
160 160
161 161
162 162 @attr.s(slots=True, frozen=True)
163 163 class _revisioninfo(object):
164 164 """Information about a revision that allows building its fulltext
165 165 node: expected hash of the revision
166 166 p1, p2: parent revs of the revision
167 167 btext: built text cache consisting of a one-element list
168 168 cachedelta: (baserev, uncompressed_delta) or None
169 169 flags: flags associated to the revision storage
170 170
171 171 One of btext[0] or cachedelta must be set.
172 172 """
173 173
174 174 node = attr.ib()
175 175 p1 = attr.ib()
176 176 p2 = attr.ib()
177 177 btext = attr.ib()
178 178 textlen = attr.ib()
179 179 cachedelta = attr.ib()
180 180 flags = attr.ib()
181 181
182 182
183 183 @interfaceutil.implementer(repository.irevisiondelta)
184 184 @attr.s(slots=True)
185 185 class revlogrevisiondelta(object):
186 186 node = attr.ib()
187 187 p1node = attr.ib()
188 188 p2node = attr.ib()
189 189 basenode = attr.ib()
190 190 flags = attr.ib()
191 191 baserevisionsize = attr.ib()
192 192 revision = attr.ib()
193 193 delta = attr.ib()
194 194 linknode = attr.ib(default=None)
195 195
196 196
197 197 @interfaceutil.implementer(repository.iverifyproblem)
198 198 @attr.s(frozen=True)
199 199 class revlogproblem(object):
200 200 warning = attr.ib(default=None)
201 201 error = attr.ib(default=None)
202 202 node = attr.ib(default=None)
203 203
204 204
205 205 # index v0:
206 206 # 4 bytes: offset
207 207 # 4 bytes: compressed length
208 208 # 4 bytes: base rev
209 209 # 4 bytes: link rev
210 210 # 20 bytes: parent 1 nodeid
211 211 # 20 bytes: parent 2 nodeid
212 212 # 20 bytes: nodeid
213 213 indexformatv0 = struct.Struct(b">4l20s20s20s")
214 214 indexformatv0_pack = indexformatv0.pack
215 215 indexformatv0_unpack = indexformatv0.unpack
216 216
217 217
218 218 class revlogoldindex(list):
219 219 @property
220 220 def nodemap(self):
221 221 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
222 222 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
223 223 return self._nodemap
224 224
225 225 @util.propertycache
226 226 def _nodemap(self):
227 227 nodemap = nodemaputil.NodeMap({nullid: nullrev})
228 228 for r in range(0, len(self)):
229 229 n = self[r][7]
230 230 nodemap[n] = r
231 231 return nodemap
232 232
233 233 def has_node(self, node):
234 234 """return True if the node exist in the index"""
235 235 return node in self._nodemap
236 236
237 237 def rev(self, node):
238 238 """return a revision for a node
239 239
240 240 If the node is unknown, raise a RevlogError"""
241 241 return self._nodemap[node]
242 242
243 243 def get_rev(self, node):
244 244 """return a revision for a node
245 245
246 246 If the node is unknown, return None"""
247 247 return self._nodemap.get(node)
248 248
249 249 def append(self, tup):
250 250 self._nodemap[tup[7]] = len(self)
251 251 super(revlogoldindex, self).append(tup)
252 252
253 253 def __delitem__(self, i):
254 254 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
255 255 raise ValueError(b"deleting slices only supports a:-1 with step 1")
256 256 for r in pycompat.xrange(i.start, len(self)):
257 257 del self._nodemap[self[r][7]]
258 258 super(revlogoldindex, self).__delitem__(i)
259 259
260 260 def clearcaches(self):
261 261 self.__dict__.pop('_nodemap', None)
262 262
263 263 def __getitem__(self, i):
264 264 if i == -1:
265 265 return (0, 0, 0, -1, -1, -1, -1, nullid)
266 266 return list.__getitem__(self, i)
267 267
268 268
269 269 class revlogoldio(object):
270 270 def __init__(self):
271 271 self.size = indexformatv0.size
272 272
273 273 def parseindex(self, data, inline):
274 274 s = self.size
275 275 index = []
276 276 nodemap = nodemaputil.NodeMap({nullid: nullrev})
277 277 n = off = 0
278 278 l = len(data)
279 279 while off + s <= l:
280 280 cur = data[off : off + s]
281 281 off += s
282 282 e = indexformatv0_unpack(cur)
283 283 # transform to revlogv1 format
284 284 e2 = (
285 285 offset_type(e[0], 0),
286 286 e[1],
287 287 -1,
288 288 e[2],
289 289 e[3],
290 290 nodemap.get(e[4], nullrev),
291 291 nodemap.get(e[5], nullrev),
292 292 e[6],
293 293 )
294 294 index.append(e2)
295 295 nodemap[e[6]] = n
296 296 n += 1
297 297
298 298 index = revlogoldindex(index)
299 299 return index, None
300 300
301 301 def packentry(self, entry, node, version, rev):
302 302 if gettype(entry[0]):
303 303 raise error.RevlogError(
304 304 _(b'index entry flags need revlog version 1')
305 305 )
306 306 e2 = (
307 307 getoffset(entry[0]),
308 308 entry[1],
309 309 entry[3],
310 310 entry[4],
311 311 node(entry[5]),
312 312 node(entry[6]),
313 313 entry[7],
314 314 )
315 315 return indexformatv0_pack(*e2)
316 316
317 317
318 318 # index ng:
319 319 # 6 bytes: offset
320 320 # 2 bytes: flags
321 321 # 4 bytes: compressed length
322 322 # 4 bytes: uncompressed length
323 323 # 4 bytes: base rev
324 324 # 4 bytes: link rev
325 325 # 4 bytes: parent 1 rev
326 326 # 4 bytes: parent 2 rev
327 327 # 32 bytes: nodeid
328 328 indexformatng = struct.Struct(b">Qiiiiii20s12x")
329 329 indexformatng_pack = indexformatng.pack
330 330 versionformat = struct.Struct(b">I")
331 331 versionformat_pack = versionformat.pack
332 332 versionformat_unpack = versionformat.unpack
333 333
334 334 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
335 335 # signed integer)
336 336 _maxentrysize = 0x7FFFFFFF
337 337
338 338
339 339 class revlogio(object):
340 340 def __init__(self):
341 341 self.size = indexformatng.size
342 342
343 343 def parseindex(self, data, inline):
344 344 # call the C implementation to parse the index data
345 345 index, cache = parsers.parse_index2(data, inline)
346 346 return index, cache
347 347
348 348 def packentry(self, entry, node, version, rev):
349 349 p = indexformatng_pack(*entry)
350 350 if rev == 0:
351 351 p = versionformat_pack(version) + p[4:]
352 352 return p
353 353
354 354
355 355 NodemapRevlogIO = None
356 356
357 357 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
358 358
359 359 class NodemapRevlogIO(revlogio):
360 360 """A debug oriented IO class that return a PersistentNodeMapIndexObject
361 361
362 362 The PersistentNodeMapIndexObject object is meant to test the persistent nodemap feature.
363 363 """
364 364
365 365 def parseindex(self, data, inline):
366 366 index, cache = parsers.parse_index_devel_nodemap(data, inline)
367 367 return index, cache
368 368
369 369
370 370 class rustrevlogio(revlogio):
371 371 def parseindex(self, data, inline):
372 372 index, cache = super(rustrevlogio, self).parseindex(data, inline)
373 373 return rustrevlog.MixedIndex(index), cache
374 374
375 375
376 376 class revlog(object):
377 377 """
378 378 the underlying revision storage object
379 379
380 380 A revlog consists of two parts, an index and the revision data.
381 381
382 382 The index is a file with a fixed record size containing
383 383 information on each revision, including its nodeid (hash), the
384 384 nodeids of its parents, the position and offset of its data within
385 385 the data file, and the revision it's based on. Finally, each entry
386 386 contains a linkrev entry that can serve as a pointer to external
387 387 data.
388 388
389 389 The revision data itself is a linear collection of data chunks.
390 390 Each chunk represents a revision and is usually represented as a
391 391 delta against the previous chunk. To bound lookup time, runs of
392 392 deltas are limited to about 2 times the length of the original
393 393 version data. This makes retrieval of a version proportional to
394 394 its size, or O(1) relative to the number of revisions.
395 395
396 396 Both pieces of the revlog are written to in an append-only
397 397 fashion, which means we never need to rewrite a file to insert or
398 398 remove data, and can use some simple techniques to avoid the need
399 399 for locking while reading.
400 400
401 401 If checkambig, indexfile is opened with checkambig=True at
402 402 writing, to avoid file stat ambiguity.
403 403
404 404 If mmaplargeindex is True, and an mmapindexthreshold is set, the
405 405 index will be mmapped rather than read if it is larger than the
406 406 configured threshold.
407 407
408 408 If censorable is True, the revlog can have censored revisions.
409 409
410 410 If `upperboundcomp` is not None, this is the expected maximal gain from
411 411 compression for the data content.
412 412 """
413 413
414 414 _flagserrorclass = error.RevlogError
415 415
416 416 def __init__(
417 417 self,
418 418 opener,
419 419 indexfile,
420 420 datafile=None,
421 421 checkambig=False,
422 422 mmaplargeindex=False,
423 423 censorable=False,
424 424 upperboundcomp=None,
425 425 persistentnodemap=False,
426 426 ):
427 427 """
428 428 create a revlog object
429 429
430 430 opener is a function that abstracts the file opening operation
431 431 and can be used to implement COW semantics or the like.
432 432
433 433 """
434 434 self.upperboundcomp = upperboundcomp
435 435 self.indexfile = indexfile
436 436 self.datafile = datafile or (indexfile[:-2] + b".d")
437 437 self.nodemap_file = None
438 438 if persistentnodemap:
439 self.nodemap_file = indexfile[:-2] + b".n"
439 if indexfile.endswith(b'.a'):
440 pending_path = indexfile[:-4] + b".n.a"
441 if opener.exists(pending_path):
442 self.nodemap_file = pending_path
443 else:
444 self.nodemap_file = indexfile[:-4] + b".n"
445 else:
446 self.nodemap_file = indexfile[:-2] + b".n"
440 447
441 448 self.opener = opener
442 449 # When True, indexfile is opened with checkambig=True at writing, to
443 450 # avoid file stat ambiguity.
444 451 self._checkambig = checkambig
445 452 self._mmaplargeindex = mmaplargeindex
446 453 self._censorable = censorable
447 454 # 3-tuple of (node, rev, text) for a raw revision.
448 455 self._revisioncache = None
449 456 # Maps rev to chain base rev.
450 457 self._chainbasecache = util.lrucachedict(100)
451 458 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
452 459 self._chunkcache = (0, b'')
453 460 # How much data to read and cache into the raw revlog data cache.
454 461 self._chunkcachesize = 65536
455 462 self._maxchainlen = None
456 463 self._deltabothparents = True
457 464 self.index = None
458 465 self._nodemap_docket = None
459 466 # Mapping of partial identifiers to full nodes.
460 467 self._pcache = {}
461 468 # Mapping of revision integer to full node.
462 469 self._compengine = b'zlib'
463 470 self._compengineopts = {}
464 471 self._maxdeltachainspan = -1
465 472 self._withsparseread = False
466 473 self._sparserevlog = False
467 474 self._srdensitythreshold = 0.50
468 475 self._srmingapsize = 262144
469 476
470 477 # Make copy of flag processors so each revlog instance can support
471 478 # custom flags.
472 479 self._flagprocessors = dict(flagutil.flagprocessors)
473 480
474 481 # 2-tuple of file handles being used for active writing.
475 482 self._writinghandles = None
476 483
477 484 self._loadindex()
478 485
479 486 def _loadindex(self):
480 487 mmapindexthreshold = None
481 488 opts = self.opener.options
482 489
483 490 if b'revlogv2' in opts:
484 491 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
485 492 elif b'revlogv1' in opts:
486 493 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
487 494 if b'generaldelta' in opts:
488 495 newversionflags |= FLAG_GENERALDELTA
489 496 elif b'revlogv0' in self.opener.options:
490 497 newversionflags = REVLOGV0
491 498 else:
492 499 newversionflags = REVLOG_DEFAULT_VERSION
493 500
494 501 if b'chunkcachesize' in opts:
495 502 self._chunkcachesize = opts[b'chunkcachesize']
496 503 if b'maxchainlen' in opts:
497 504 self._maxchainlen = opts[b'maxchainlen']
498 505 if b'deltabothparents' in opts:
499 506 self._deltabothparents = opts[b'deltabothparents']
500 507 self._lazydelta = bool(opts.get(b'lazydelta', True))
501 508 self._lazydeltabase = False
502 509 if self._lazydelta:
503 510 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
504 511 if b'compengine' in opts:
505 512 self._compengine = opts[b'compengine']
506 513 if b'zlib.level' in opts:
507 514 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
508 515 if b'zstd.level' in opts:
509 516 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
510 517 if b'maxdeltachainspan' in opts:
511 518 self._maxdeltachainspan = opts[b'maxdeltachainspan']
512 519 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
513 520 mmapindexthreshold = opts[b'mmapindexthreshold']
514 521 self.hassidedata = bool(opts.get(b'side-data', False))
515 522 if self.hassidedata:
516 523 self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors
517 524 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
518 525 withsparseread = bool(opts.get(b'with-sparse-read', False))
519 526 # sparse-revlog forces sparse-read
520 527 self._withsparseread = self._sparserevlog or withsparseread
521 528 if b'sparse-read-density-threshold' in opts:
522 529 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
523 530 if b'sparse-read-min-gap-size' in opts:
524 531 self._srmingapsize = opts[b'sparse-read-min-gap-size']
525 532 if opts.get(b'enableellipsis'):
526 533 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
527 534
528 535 # revlog v0 doesn't have flag processors
529 536 for flag, processor in pycompat.iteritems(
530 537 opts.get(b'flagprocessors', {})
531 538 ):
532 539 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
533 540
534 541 if self._chunkcachesize <= 0:
535 542 raise error.RevlogError(
536 543 _(b'revlog chunk cache size %r is not greater than 0')
537 544 % self._chunkcachesize
538 545 )
539 546 elif self._chunkcachesize & (self._chunkcachesize - 1):
540 547 raise error.RevlogError(
541 548 _(b'revlog chunk cache size %r is not a power of 2')
542 549 % self._chunkcachesize
543 550 )
544 551
545 552 indexdata = b''
546 553 self._initempty = True
547 554 try:
548 555 with self._indexfp() as f:
549 556 if (
550 557 mmapindexthreshold is not None
551 558 and self.opener.fstat(f).st_size >= mmapindexthreshold
552 559 ):
553 560 # TODO: should .close() to release resources without
554 561 # relying on Python GC
555 562 indexdata = util.buffer(util.mmapread(f))
556 563 else:
557 564 indexdata = f.read()
558 565 if len(indexdata) > 0:
559 566 versionflags = versionformat_unpack(indexdata[:4])[0]
560 567 self._initempty = False
561 568 else:
562 569 versionflags = newversionflags
563 570 except IOError as inst:
564 571 if inst.errno != errno.ENOENT:
565 572 raise
566 573
567 574 versionflags = newversionflags
568 575
569 576 self.version = versionflags
570 577
571 578 flags = versionflags & ~0xFFFF
572 579 fmt = versionflags & 0xFFFF
573 580
574 581 if fmt == REVLOGV0:
575 582 if flags:
576 583 raise error.RevlogError(
577 584 _(b'unknown flags (%#04x) in version %d revlog %s')
578 585 % (flags >> 16, fmt, self.indexfile)
579 586 )
580 587
581 588 self._inline = False
582 589 self._generaldelta = False
583 590
584 591 elif fmt == REVLOGV1:
585 592 if flags & ~REVLOGV1_FLAGS:
586 593 raise error.RevlogError(
587 594 _(b'unknown flags (%#04x) in version %d revlog %s')
588 595 % (flags >> 16, fmt, self.indexfile)
589 596 )
590 597
591 598 self._inline = versionflags & FLAG_INLINE_DATA
592 599 self._generaldelta = versionflags & FLAG_GENERALDELTA
593 600
594 601 elif fmt == REVLOGV2:
595 602 if flags & ~REVLOGV2_FLAGS:
596 603 raise error.RevlogError(
597 604 _(b'unknown flags (%#04x) in version %d revlog %s')
598 605 % (flags >> 16, fmt, self.indexfile)
599 606 )
600 607
601 608 self._inline = versionflags & FLAG_INLINE_DATA
602 609 # generaldelta implied by version 2 revlogs.
603 610 self._generaldelta = True
604 611
605 612 else:
606 613 raise error.RevlogError(
607 614 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
608 615 )
609 616 # sparse-revlog can't be on without general-delta (issue6056)
610 617 if not self._generaldelta:
611 618 self._sparserevlog = False
612 619
613 620 self._storedeltachains = True
614 621
615 622 devel_nodemap = (
616 623 self.nodemap_file
617 624 and opts.get(b'devel-force-nodemap', False)
618 625 and NodemapRevlogIO is not None
619 626 )
620 627
621 628 use_rust_index = False
622 629 if rustrevlog is not None:
623 630 if self.nodemap_file is not None:
624 631 use_rust_index = True
625 632 else:
626 633 use_rust_index = self.opener.options.get(b'rust.index')
627 634
628 635 self._io = revlogio()
629 636 if self.version == REVLOGV0:
630 637 self._io = revlogoldio()
631 638 elif devel_nodemap:
632 639 self._io = NodemapRevlogIO()
633 640 elif use_rust_index:
634 641 self._io = rustrevlogio()
635 642 try:
636 643 d = self._io.parseindex(indexdata, self._inline)
637 644 index, _chunkcache = d
638 645 use_nodemap = (
639 646 not self._inline
640 647 and self.nodemap_file is not None
641 648 and util.safehasattr(index, 'update_nodemap_data')
642 649 )
643 650 if use_nodemap:
644 651 nodemap_data = nodemaputil.persisted_data(self)
645 652 if nodemap_data is not None:
646 653 docket = nodemap_data[0]
647 654 if d[0][docket.tip_rev][7] == docket.tip_node:
648 655 # no changelog tampering
649 656 self._nodemap_docket = docket
650 657 index.update_nodemap_data(*nodemap_data)
651 658 except (ValueError, IndexError):
652 659 raise error.RevlogError(
653 660 _(b"index %s is corrupted") % self.indexfile
654 661 )
655 662 self.index, self._chunkcache = d
656 663 if not self._chunkcache:
657 664 self._chunkclear()
658 665 # revnum -> (chain-length, sum-delta-length)
659 666 self._chaininfocache = {}
660 667 # revlog header -> revlog compressor
661 668 self._decompressors = {}
662 669
663 670 @util.propertycache
664 671 def _compressor(self):
665 672 engine = util.compengines[self._compengine]
666 673 return engine.revlogcompressor(self._compengineopts)
667 674
668 675 def _indexfp(self, mode=b'r'):
669 676 """file object for the revlog's index file"""
670 677 args = {'mode': mode}
671 678 if mode != b'r':
672 679 args['checkambig'] = self._checkambig
673 680 if mode == b'w':
674 681 args['atomictemp'] = True
675 682 return self.opener(self.indexfile, **args)
676 683
677 684 def _datafp(self, mode=b'r'):
678 685 """file object for the revlog's data file"""
679 686 return self.opener(self.datafile, mode=mode)
680 687
681 688 @contextlib.contextmanager
682 689 def _datareadfp(self, existingfp=None):
683 690 """file object suitable to read data"""
684 691 # Use explicit file handle, if given.
685 692 if existingfp is not None:
686 693 yield existingfp
687 694
688 695 # Use a file handle being actively used for writes, if available.
689 696 # There is some danger to doing this because reads will seek the
690 697 # file. However, _writeentry() performs a SEEK_END before all writes,
691 698 # so we should be safe.
692 699 elif self._writinghandles:
693 700 if self._inline:
694 701 yield self._writinghandles[0]
695 702 else:
696 703 yield self._writinghandles[1]
697 704
698 705 # Otherwise open a new file handle.
699 706 else:
700 707 if self._inline:
701 708 func = self._indexfp
702 709 else:
703 710 func = self._datafp
704 711 with func() as fp:
705 712 yield fp
706 713
707 714 def tiprev(self):
708 715 return len(self.index) - 1
709 716
710 717 def tip(self):
711 718 return self.node(self.tiprev())
712 719
713 720 def __contains__(self, rev):
714 721 return 0 <= rev < len(self)
715 722
716 723 def __len__(self):
717 724 return len(self.index)
718 725
719 726 def __iter__(self):
720 727 return iter(pycompat.xrange(len(self)))
721 728
722 729 def revs(self, start=0, stop=None):
723 730 """iterate over all rev in this revlog (from start to stop)"""
724 731 return storageutil.iterrevs(len(self), start=start, stop=stop)
725 732
726 733 @property
727 734 def nodemap(self):
728 735 msg = (
729 736 b"revlog.nodemap is deprecated, "
730 737 b"use revlog.index.[has_node|rev|get_rev]"
731 738 )
732 739 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
733 740 return self.index.nodemap
734 741
735 742 @property
736 743 def _nodecache(self):
737 744 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
738 745 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
739 746 return self.index.nodemap
740 747
741 748 def hasnode(self, node):
742 749 try:
743 750 self.rev(node)
744 751 return True
745 752 except KeyError:
746 753 return False
747 754
748 755 def candelta(self, baserev, rev):
749 756 """whether two revisions (baserev, rev) can be delta-ed or not"""
750 757 # Disable delta if either rev requires a content-changing flag
751 758 # processor (ex. LFS). This is because such flag processor can alter
752 759 # the rawtext content that the delta will be based on, and two clients
753 760 # could have a same revlog node with different flags (i.e. different
754 761 # rawtext contents) and the delta could be incompatible.
755 762 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
756 763 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
757 764 ):
758 765 return False
759 766 return True
760 767
761 768 def update_caches(self, transaction):
762 769 if self.nodemap_file is not None:
763 770 if transaction is None:
764 771 nodemaputil.update_persistent_nodemap(self)
765 772 else:
766 773 nodemaputil.setup_persistent_nodemap(transaction, self)
767 774
768 775 def clearcaches(self):
769 776 self._revisioncache = None
770 777 self._chainbasecache.clear()
771 778 self._chunkcache = (0, b'')
772 779 self._pcache = {}
773 780 self._nodemap_docket = None
774 781 self.index.clearcaches()
775 782 # The python code is the one responsible for validating the docket, we
776 783 # end up having to refresh it here.
777 784 use_nodemap = (
778 785 not self._inline
779 786 and self.nodemap_file is not None
780 787 and util.safehasattr(self.index, 'update_nodemap_data')
781 788 )
782 789 if use_nodemap:
783 790 nodemap_data = nodemaputil.persisted_data(self)
784 791 if nodemap_data is not None:
785 792 self._nodemap_docket = nodemap_data[0]
786 793 self.index.update_nodemap_data(*nodemap_data)
787 794
788 795 def rev(self, node):
789 796 try:
790 797 return self.index.rev(node)
791 798 except TypeError:
792 799 raise
793 800 except error.RevlogError:
794 801 # parsers.c radix tree lookup failed
795 802 if node == wdirid or node in wdirfilenodeids:
796 803 raise error.WdirUnsupported
797 804 raise error.LookupError(node, self.indexfile, _(b'no node'))
798 805
799 806 # Accessors for index entries.
800 807
801 808 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
802 809 # are flags.
803 810 def start(self, rev):
804 811 return int(self.index[rev][0] >> 16)
805 812
806 813 def flags(self, rev):
807 814 return self.index[rev][0] & 0xFFFF
808 815
809 816 def length(self, rev):
810 817 return self.index[rev][1]
811 818
812 819 def rawsize(self, rev):
813 820 """return the length of the uncompressed text for a given revision"""
814 821 l = self.index[rev][2]
815 822 if l >= 0:
816 823 return l
817 824
818 825 t = self.rawdata(rev)
819 826 return len(t)
820 827
821 828 def size(self, rev):
822 829 """length of non-raw text (processed by a "read" flag processor)"""
823 830 # fast path: if no "read" flag processor could change the content,
824 831 # size is rawsize. note: ELLIPSIS is known to not change the content.
825 832 flags = self.flags(rev)
826 833 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
827 834 return self.rawsize(rev)
828 835
829 836 return len(self.revision(rev, raw=False))
830 837
831 838 def chainbase(self, rev):
832 839 base = self._chainbasecache.get(rev)
833 840 if base is not None:
834 841 return base
835 842
836 843 index = self.index
837 844 iterrev = rev
838 845 base = index[iterrev][3]
839 846 while base != iterrev:
840 847 iterrev = base
841 848 base = index[iterrev][3]
842 849
843 850 self._chainbasecache[rev] = base
844 851 return base
845 852
846 853 def linkrev(self, rev):
847 854 return self.index[rev][4]
848 855
849 856 def parentrevs(self, rev):
850 857 try:
851 858 entry = self.index[rev]
852 859 except IndexError:
853 860 if rev == wdirrev:
854 861 raise error.WdirUnsupported
855 862 raise
856 863
857 864 return entry[5], entry[6]
858 865
859 866 # fast parentrevs(rev) where rev isn't filtered
860 867 _uncheckedparentrevs = parentrevs
861 868
862 869 def node(self, rev):
863 870 try:
864 871 return self.index[rev][7]
865 872 except IndexError:
866 873 if rev == wdirrev:
867 874 raise error.WdirUnsupported
868 875 raise
869 876
870 877 # Derived from index values.
871 878
872 879 def end(self, rev):
873 880 return self.start(rev) + self.length(rev)
874 881
875 882 def parents(self, node):
876 883 i = self.index
877 884 d = i[self.rev(node)]
878 885 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
879 886
880 887 def chainlen(self, rev):
881 888 return self._chaininfo(rev)[0]
882 889
883 890 def _chaininfo(self, rev):
884 891 chaininfocache = self._chaininfocache
885 892 if rev in chaininfocache:
886 893 return chaininfocache[rev]
887 894 index = self.index
888 895 generaldelta = self._generaldelta
889 896 iterrev = rev
890 897 e = index[iterrev]
891 898 clen = 0
892 899 compresseddeltalen = 0
893 900 while iterrev != e[3]:
894 901 clen += 1
895 902 compresseddeltalen += e[1]
896 903 if generaldelta:
897 904 iterrev = e[3]
898 905 else:
899 906 iterrev -= 1
900 907 if iterrev in chaininfocache:
901 908 t = chaininfocache[iterrev]
902 909 clen += t[0]
903 910 compresseddeltalen += t[1]
904 911 break
905 912 e = index[iterrev]
906 913 else:
907 914 # Add text length of base since decompressing that also takes
908 915 # work. For cache hits the length is already included.
909 916 compresseddeltalen += e[1]
910 917 r = (clen, compresseddeltalen)
911 918 chaininfocache[rev] = r
912 919 return r
913 920
914 921 def _deltachain(self, rev, stoprev=None):
915 922 """Obtain the delta chain for a revision.
916 923
917 924 ``stoprev`` specifies a revision to stop at. If not specified, we
918 925 stop at the base of the chain.
919 926
920 927 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
921 928 revs in ascending order and ``stopped`` is a bool indicating whether
922 929 ``stoprev`` was hit.
923 930 """
924 931 # Try C implementation.
925 932 try:
926 933 return self.index.deltachain(rev, stoprev, self._generaldelta)
927 934 except AttributeError:
928 935 pass
929 936
930 937 chain = []
931 938
932 939 # Alias to prevent attribute lookup in tight loop.
933 940 index = self.index
934 941 generaldelta = self._generaldelta
935 942
936 943 iterrev = rev
937 944 e = index[iterrev]
938 945 while iterrev != e[3] and iterrev != stoprev:
939 946 chain.append(iterrev)
940 947 if generaldelta:
941 948 iterrev = e[3]
942 949 else:
943 950 iterrev -= 1
944 951 e = index[iterrev]
945 952
946 953 if iterrev == stoprev:
947 954 stopped = True
948 955 else:
949 956 chain.append(iterrev)
950 957 stopped = False
951 958
952 959 chain.reverse()
953 960 return chain, stopped
954 961
955 962 def ancestors(self, revs, stoprev=0, inclusive=False):
956 963 """Generate the ancestors of 'revs' in reverse revision order.
957 964 Does not generate revs lower than stoprev.
958 965
959 966 See the documentation for ancestor.lazyancestors for more details."""
960 967
961 968 # first, make sure start revisions aren't filtered
962 969 revs = list(revs)
963 970 checkrev = self.node
964 971 for r in revs:
965 972 checkrev(r)
966 973 # and we're sure ancestors aren't filtered as well
967 974
968 975 if rustancestor is not None:
969 976 lazyancestors = rustancestor.LazyAncestors
970 977 arg = self.index
971 978 else:
972 979 lazyancestors = ancestor.lazyancestors
973 980 arg = self._uncheckedparentrevs
974 981 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
975 982
976 983 def descendants(self, revs):
977 984 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
978 985
979 986 def findcommonmissing(self, common=None, heads=None):
980 987 """Return a tuple of the ancestors of common and the ancestors of heads
981 988 that are not ancestors of common. In revset terminology, we return the
982 989 tuple:
983 990
984 991 ::common, (::heads) - (::common)
985 992
986 993 The list is sorted by revision number, meaning it is
987 994 topologically sorted.
988 995
989 996 'heads' and 'common' are both lists of node IDs. If heads is
990 997 not supplied, uses all of the revlog's heads. If common is not
991 998 supplied, uses nullid."""
992 999 if common is None:
993 1000 common = [nullid]
994 1001 if heads is None:
995 1002 heads = self.heads()
996 1003
997 1004 common = [self.rev(n) for n in common]
998 1005 heads = [self.rev(n) for n in heads]
999 1006
1000 1007 # we want the ancestors, but inclusive
1001 1008 class lazyset(object):
1002 1009 def __init__(self, lazyvalues):
1003 1010 self.addedvalues = set()
1004 1011 self.lazyvalues = lazyvalues
1005 1012
1006 1013 def __contains__(self, value):
1007 1014 return value in self.addedvalues or value in self.lazyvalues
1008 1015
1009 1016 def __iter__(self):
1010 1017 added = self.addedvalues
1011 1018 for r in added:
1012 1019 yield r
1013 1020 for r in self.lazyvalues:
1014 1021 if not r in added:
1015 1022 yield r
1016 1023
1017 1024 def add(self, value):
1018 1025 self.addedvalues.add(value)
1019 1026
1020 1027 def update(self, values):
1021 1028 self.addedvalues.update(values)
1022 1029
1023 1030 has = lazyset(self.ancestors(common))
1024 1031 has.add(nullrev)
1025 1032 has.update(common)
1026 1033
1027 1034 # take all ancestors from heads that aren't in has
1028 1035 missing = set()
1029 1036 visit = collections.deque(r for r in heads if r not in has)
1030 1037 while visit:
1031 1038 r = visit.popleft()
1032 1039 if r in missing:
1033 1040 continue
1034 1041 else:
1035 1042 missing.add(r)
1036 1043 for p in self.parentrevs(r):
1037 1044 if p not in has:
1038 1045 visit.append(p)
1039 1046 missing = list(missing)
1040 1047 missing.sort()
1041 1048 return has, [self.node(miss) for miss in missing]
1042 1049
1043 1050 def incrementalmissingrevs(self, common=None):
1044 1051 """Return an object that can be used to incrementally compute the
1045 1052 revision numbers of the ancestors of arbitrary sets that are not
1046 1053 ancestors of common. This is an ancestor.incrementalmissingancestors
1047 1054 object.
1048 1055
1049 1056 'common' is a list of revision numbers. If common is not supplied, uses
1050 1057 nullrev.
1051 1058 """
1052 1059 if common is None:
1053 1060 common = [nullrev]
1054 1061
1055 1062 if rustancestor is not None:
1056 1063 return rustancestor.MissingAncestors(self.index, common)
1057 1064 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1058 1065
1059 1066 def findmissingrevs(self, common=None, heads=None):
1060 1067 """Return the revision numbers of the ancestors of heads that
1061 1068 are not ancestors of common.
1062 1069
1063 1070 More specifically, return a list of revision numbers corresponding to
1064 1071 nodes N such that every N satisfies the following constraints:
1065 1072
1066 1073 1. N is an ancestor of some node in 'heads'
1067 1074 2. N is not an ancestor of any node in 'common'
1068 1075
1069 1076 The list is sorted by revision number, meaning it is
1070 1077 topologically sorted.
1071 1078
1072 1079 'heads' and 'common' are both lists of revision numbers. If heads is
1073 1080 not supplied, uses all of the revlog's heads. If common is not
1074 1081 supplied, uses nullid."""
1075 1082 if common is None:
1076 1083 common = [nullrev]
1077 1084 if heads is None:
1078 1085 heads = self.headrevs()
1079 1086
1080 1087 inc = self.incrementalmissingrevs(common=common)
1081 1088 return inc.missingancestors(heads)
1082 1089
1083 1090 def findmissing(self, common=None, heads=None):
1084 1091 """Return the ancestors of heads that are not ancestors of common.
1085 1092
1086 1093 More specifically, return a list of nodes N such that every N
1087 1094 satisfies the following constraints:
1088 1095
1089 1096 1. N is an ancestor of some node in 'heads'
1090 1097 2. N is not an ancestor of any node in 'common'
1091 1098
1092 1099 The list is sorted by revision number, meaning it is
1093 1100 topologically sorted.
1094 1101
1095 1102 'heads' and 'common' are both lists of node IDs. If heads is
1096 1103 not supplied, uses all of the revlog's heads. If common is not
1097 1104 supplied, uses nullid."""
1098 1105 if common is None:
1099 1106 common = [nullid]
1100 1107 if heads is None:
1101 1108 heads = self.heads()
1102 1109
1103 1110 common = [self.rev(n) for n in common]
1104 1111 heads = [self.rev(n) for n in heads]
1105 1112
1106 1113 inc = self.incrementalmissingrevs(common=common)
1107 1114 return [self.node(r) for r in inc.missingancestors(heads)]
1108 1115
1109 1116 def nodesbetween(self, roots=None, heads=None):
1110 1117 """Return a topological path from 'roots' to 'heads'.
1111 1118
1112 1119 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1113 1120 topologically sorted list of all nodes N that satisfy both of
1114 1121 these constraints:
1115 1122
1116 1123 1. N is a descendant of some node in 'roots'
1117 1124 2. N is an ancestor of some node in 'heads'
1118 1125
1119 1126 Every node is considered to be both a descendant and an ancestor
1120 1127 of itself, so every reachable node in 'roots' and 'heads' will be
1121 1128 included in 'nodes'.
1122 1129
1123 1130 'outroots' is the list of reachable nodes in 'roots', i.e., the
1124 1131 subset of 'roots' that is returned in 'nodes'. Likewise,
1125 1132 'outheads' is the subset of 'heads' that is also in 'nodes'.
1126 1133
1127 1134 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1128 1135 unspecified, uses nullid as the only root. If 'heads' is
1129 1136 unspecified, uses list of all of the revlog's heads."""
1130 1137 nonodes = ([], [], [])
1131 1138 if roots is not None:
1132 1139 roots = list(roots)
1133 1140 if not roots:
1134 1141 return nonodes
1135 1142 lowestrev = min([self.rev(n) for n in roots])
1136 1143 else:
1137 1144 roots = [nullid] # Everybody's a descendant of nullid
1138 1145 lowestrev = nullrev
1139 1146 if (lowestrev == nullrev) and (heads is None):
1140 1147 # We want _all_ the nodes!
1141 1148 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1142 1149 if heads is None:
1143 1150 # All nodes are ancestors, so the latest ancestor is the last
1144 1151 # node.
1145 1152 highestrev = len(self) - 1
1146 1153 # Set ancestors to None to signal that every node is an ancestor.
1147 1154 ancestors = None
1148 1155 # Set heads to an empty dictionary for later discovery of heads
1149 1156 heads = {}
1150 1157 else:
1151 1158 heads = list(heads)
1152 1159 if not heads:
1153 1160 return nonodes
1154 1161 ancestors = set()
1155 1162 # Turn heads into a dictionary so we can remove 'fake' heads.
1156 1163 # Also, later we will be using it to filter out the heads we can't
1157 1164 # find from roots.
1158 1165 heads = dict.fromkeys(heads, False)
1159 1166 # Start at the top and keep marking parents until we're done.
1160 1167 nodestotag = set(heads)
1161 1168 # Remember where the top was so we can use it as a limit later.
1162 1169 highestrev = max([self.rev(n) for n in nodestotag])
1163 1170 while nodestotag:
1164 1171 # grab a node to tag
1165 1172 n = nodestotag.pop()
1166 1173 # Never tag nullid
1167 1174 if n == nullid:
1168 1175 continue
1169 1176 # A node's revision number represents its place in a
1170 1177 # topologically sorted list of nodes.
1171 1178 r = self.rev(n)
1172 1179 if r >= lowestrev:
1173 1180 if n not in ancestors:
1174 1181 # If we are possibly a descendant of one of the roots
1175 1182 # and we haven't already been marked as an ancestor
1176 1183 ancestors.add(n) # Mark as ancestor
1177 1184 # Add non-nullid parents to list of nodes to tag.
1178 1185 nodestotag.update(
1179 1186 [p for p in self.parents(n) if p != nullid]
1180 1187 )
1181 1188 elif n in heads: # We've seen it before, is it a fake head?
1182 1189 # So it is, real heads should not be the ancestors of
1183 1190 # any other heads.
1184 1191 heads.pop(n)
1185 1192 if not ancestors:
1186 1193 return nonodes
1187 1194 # Now that we have our set of ancestors, we want to remove any
1188 1195 # roots that are not ancestors.
1189 1196
1190 1197 # If one of the roots was nullid, everything is included anyway.
1191 1198 if lowestrev > nullrev:
1192 1199 # But, since we weren't, let's recompute the lowest rev to not
1193 1200 # include roots that aren't ancestors.
1194 1201
1195 1202 # Filter out roots that aren't ancestors of heads
1196 1203 roots = [root for root in roots if root in ancestors]
1197 1204 # Recompute the lowest revision
1198 1205 if roots:
1199 1206 lowestrev = min([self.rev(root) for root in roots])
1200 1207 else:
1201 1208 # No more roots? Return empty list
1202 1209 return nonodes
1203 1210 else:
1204 1211 # We are descending from nullid, and don't need to care about
1205 1212 # any other roots.
1206 1213 lowestrev = nullrev
1207 1214 roots = [nullid]
1208 1215 # Transform our roots list into a set.
1209 1216 descendants = set(roots)
1210 1217 # Also, keep the original roots so we can filter out roots that aren't
1211 1218 # 'real' roots (i.e. are descended from other roots).
1212 1219 roots = descendants.copy()
1213 1220 # Our topologically sorted list of output nodes.
1214 1221 orderedout = []
1215 1222 # Don't start at nullid since we don't want nullid in our output list,
1216 1223 # and if nullid shows up in descendants, empty parents will look like
1217 1224 # they're descendants.
1218 1225 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1219 1226 n = self.node(r)
1220 1227 isdescendant = False
1221 1228 if lowestrev == nullrev: # Everybody is a descendant of nullid
1222 1229 isdescendant = True
1223 1230 elif n in descendants:
1224 1231 # n is already a descendant
1225 1232 isdescendant = True
1226 1233 # This check only needs to be done here because all the roots
1227 1234 # will start being marked is descendants before the loop.
1228 1235 if n in roots:
1229 1236 # If n was a root, check if it's a 'real' root.
1230 1237 p = tuple(self.parents(n))
1231 1238 # If any of its parents are descendants, it's not a root.
1232 1239 if (p[0] in descendants) or (p[1] in descendants):
1233 1240 roots.remove(n)
1234 1241 else:
1235 1242 p = tuple(self.parents(n))
1236 1243 # A node is a descendant if either of its parents are
1237 1244 # descendants. (We seeded the dependents list with the roots
1238 1245 # up there, remember?)
1239 1246 if (p[0] in descendants) or (p[1] in descendants):
1240 1247 descendants.add(n)
1241 1248 isdescendant = True
1242 1249 if isdescendant and ((ancestors is None) or (n in ancestors)):
1243 1250 # Only include nodes that are both descendants and ancestors.
1244 1251 orderedout.append(n)
1245 1252 if (ancestors is not None) and (n in heads):
1246 1253 # We're trying to figure out which heads are reachable
1247 1254 # from roots.
1248 1255 # Mark this head as having been reached
1249 1256 heads[n] = True
1250 1257 elif ancestors is None:
1251 1258 # Otherwise, we're trying to discover the heads.
1252 1259 # Assume this is a head because if it isn't, the next step
1253 1260 # will eventually remove it.
1254 1261 heads[n] = True
1255 1262 # But, obviously its parents aren't.
1256 1263 for p in self.parents(n):
1257 1264 heads.pop(p, None)
1258 1265 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1259 1266 roots = list(roots)
1260 1267 assert orderedout
1261 1268 assert roots
1262 1269 assert heads
1263 1270 return (orderedout, roots, heads)
1264 1271
1265 1272 def headrevs(self, revs=None):
1266 1273 if revs is None:
1267 1274 try:
1268 1275 return self.index.headrevs()
1269 1276 except AttributeError:
1270 1277 return self._headrevs()
1271 1278 if rustdagop is not None:
1272 1279 return rustdagop.headrevs(self.index, revs)
1273 1280 return dagop.headrevs(revs, self._uncheckedparentrevs)
1274 1281
1275 1282 def computephases(self, roots):
1276 1283 return self.index.computephasesmapsets(roots)
1277 1284
1278 1285 def _headrevs(self):
1279 1286 count = len(self)
1280 1287 if not count:
1281 1288 return [nullrev]
1282 1289 # we won't iter over filtered rev so nobody is a head at start
1283 1290 ishead = [0] * (count + 1)
1284 1291 index = self.index
1285 1292 for r in self:
1286 1293 ishead[r] = 1 # I may be an head
1287 1294 e = index[r]
1288 1295 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1289 1296 return [r for r, val in enumerate(ishead) if val]
1290 1297
1291 1298 def heads(self, start=None, stop=None):
1292 1299 """return the list of all nodes that have no children
1293 1300
1294 1301 if start is specified, only heads that are descendants of
1295 1302 start will be returned
1296 1303 if stop is specified, it will consider all the revs from stop
1297 1304 as if they had no children
1298 1305 """
1299 1306 if start is None and stop is None:
1300 1307 if not len(self):
1301 1308 return [nullid]
1302 1309 return [self.node(r) for r in self.headrevs()]
1303 1310
1304 1311 if start is None:
1305 1312 start = nullrev
1306 1313 else:
1307 1314 start = self.rev(start)
1308 1315
1309 1316 stoprevs = {self.rev(n) for n in stop or []}
1310 1317
1311 1318 revs = dagop.headrevssubset(
1312 1319 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1313 1320 )
1314 1321
1315 1322 return [self.node(rev) for rev in revs]
1316 1323
1317 1324 def children(self, node):
1318 1325 """find the children of a given node"""
1319 1326 c = []
1320 1327 p = self.rev(node)
1321 1328 for r in self.revs(start=p + 1):
1322 1329 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1323 1330 if prevs:
1324 1331 for pr in prevs:
1325 1332 if pr == p:
1326 1333 c.append(self.node(r))
1327 1334 elif p == nullrev:
1328 1335 c.append(self.node(r))
1329 1336 return c
1330 1337
1331 1338 def commonancestorsheads(self, a, b):
1332 1339 """calculate all the heads of the common ancestors of nodes a and b"""
1333 1340 a, b = self.rev(a), self.rev(b)
1334 1341 ancs = self._commonancestorsheads(a, b)
1335 1342 return pycompat.maplist(self.node, ancs)
1336 1343
1337 1344 def _commonancestorsheads(self, *revs):
1338 1345 """calculate all the heads of the common ancestors of revs"""
1339 1346 try:
1340 1347 ancs = self.index.commonancestorsheads(*revs)
1341 1348 except (AttributeError, OverflowError): # C implementation failed
1342 1349 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1343 1350 return ancs
1344 1351
1345 1352 def isancestor(self, a, b):
1346 1353 """return True if node a is an ancestor of node b
1347 1354
1348 1355 A revision is considered an ancestor of itself."""
1349 1356 a, b = self.rev(a), self.rev(b)
1350 1357 return self.isancestorrev(a, b)
1351 1358
1352 1359 def isancestorrev(self, a, b):
1353 1360 """return True if revision a is an ancestor of revision b
1354 1361
1355 1362 A revision is considered an ancestor of itself.
1356 1363
1357 1364 The implementation of this is trivial but the use of
1358 1365 reachableroots is not."""
1359 1366 if a == nullrev:
1360 1367 return True
1361 1368 elif a == b:
1362 1369 return True
1363 1370 elif a > b:
1364 1371 return False
1365 1372 return bool(self.reachableroots(a, [b], [a], includepath=False))
1366 1373
1367 1374 def reachableroots(self, minroot, heads, roots, includepath=False):
1368 1375 """return (heads(::(<roots> and <roots>::<heads>)))
1369 1376
1370 1377 If includepath is True, return (<roots>::<heads>)."""
1371 1378 try:
1372 1379 return self.index.reachableroots2(
1373 1380 minroot, heads, roots, includepath
1374 1381 )
1375 1382 except AttributeError:
1376 1383 return dagop._reachablerootspure(
1377 1384 self.parentrevs, minroot, roots, heads, includepath
1378 1385 )
1379 1386
1380 1387 def ancestor(self, a, b):
1381 1388 """calculate the "best" common ancestor of nodes a and b"""
1382 1389
1383 1390 a, b = self.rev(a), self.rev(b)
1384 1391 try:
1385 1392 ancs = self.index.ancestors(a, b)
1386 1393 except (AttributeError, OverflowError):
1387 1394 ancs = ancestor.ancestors(self.parentrevs, a, b)
1388 1395 if ancs:
1389 1396 # choose a consistent winner when there's a tie
1390 1397 return min(map(self.node, ancs))
1391 1398 return nullid
1392 1399
1393 1400 def _match(self, id):
1394 1401 if isinstance(id, int):
1395 1402 # rev
1396 1403 return self.node(id)
1397 1404 if len(id) == 20:
1398 1405 # possibly a binary node
1399 1406 # odds of a binary node being all hex in ASCII are 1 in 10**25
1400 1407 try:
1401 1408 node = id
1402 1409 self.rev(node) # quick search the index
1403 1410 return node
1404 1411 except error.LookupError:
1405 1412 pass # may be partial hex id
1406 1413 try:
1407 1414 # str(rev)
1408 1415 rev = int(id)
1409 1416 if b"%d" % rev != id:
1410 1417 raise ValueError
1411 1418 if rev < 0:
1412 1419 rev = len(self) + rev
1413 1420 if rev < 0 or rev >= len(self):
1414 1421 raise ValueError
1415 1422 return self.node(rev)
1416 1423 except (ValueError, OverflowError):
1417 1424 pass
1418 1425 if len(id) == 40:
1419 1426 try:
1420 1427 # a full hex nodeid?
1421 1428 node = bin(id)
1422 1429 self.rev(node)
1423 1430 return node
1424 1431 except (TypeError, error.LookupError):
1425 1432 pass
1426 1433
1427 1434 def _partialmatch(self, id):
1428 1435 # we don't care wdirfilenodeids as they should be always full hash
1429 1436 maybewdir = wdirhex.startswith(id)
1430 1437 try:
1431 1438 partial = self.index.partialmatch(id)
1432 1439 if partial and self.hasnode(partial):
1433 1440 if maybewdir:
1434 1441 # single 'ff...' match in radix tree, ambiguous with wdir
1435 1442 raise error.RevlogError
1436 1443 return partial
1437 1444 if maybewdir:
1438 1445 # no 'ff...' match in radix tree, wdir identified
1439 1446 raise error.WdirUnsupported
1440 1447 return None
1441 1448 except error.RevlogError:
1442 1449 # parsers.c radix tree lookup gave multiple matches
1443 1450 # fast path: for unfiltered changelog, radix tree is accurate
1444 1451 if not getattr(self, 'filteredrevs', None):
1445 1452 raise error.AmbiguousPrefixLookupError(
1446 1453 id, self.indexfile, _(b'ambiguous identifier')
1447 1454 )
1448 1455 # fall through to slow path that filters hidden revisions
1449 1456 except (AttributeError, ValueError):
1450 1457 # we are pure python, or key was too short to search radix tree
1451 1458 pass
1452 1459
1453 1460 if id in self._pcache:
1454 1461 return self._pcache[id]
1455 1462
1456 1463 if len(id) <= 40:
1457 1464 try:
1458 1465 # hex(node)[:...]
1459 1466 l = len(id) // 2 # grab an even number of digits
1460 1467 prefix = bin(id[: l * 2])
1461 1468 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1462 1469 nl = [
1463 1470 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1464 1471 ]
1465 1472 if nullhex.startswith(id):
1466 1473 nl.append(nullid)
1467 1474 if len(nl) > 0:
1468 1475 if len(nl) == 1 and not maybewdir:
1469 1476 self._pcache[id] = nl[0]
1470 1477 return nl[0]
1471 1478 raise error.AmbiguousPrefixLookupError(
1472 1479 id, self.indexfile, _(b'ambiguous identifier')
1473 1480 )
1474 1481 if maybewdir:
1475 1482 raise error.WdirUnsupported
1476 1483 return None
1477 1484 except TypeError:
1478 1485 pass
1479 1486
1480 1487 def lookup(self, id):
1481 1488 """locate a node based on:
1482 1489 - revision number or str(revision number)
1483 1490 - nodeid or subset of hex nodeid
1484 1491 """
1485 1492 n = self._match(id)
1486 1493 if n is not None:
1487 1494 return n
1488 1495 n = self._partialmatch(id)
1489 1496 if n:
1490 1497 return n
1491 1498
1492 1499 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1493 1500
1494 1501 def shortest(self, node, minlength=1):
1495 1502 """Find the shortest unambiguous prefix that matches node."""
1496 1503
1497 1504 def isvalid(prefix):
1498 1505 try:
1499 1506 matchednode = self._partialmatch(prefix)
1500 1507 except error.AmbiguousPrefixLookupError:
1501 1508 return False
1502 1509 except error.WdirUnsupported:
1503 1510 # single 'ff...' match
1504 1511 return True
1505 1512 if matchednode is None:
1506 1513 raise error.LookupError(node, self.indexfile, _(b'no node'))
1507 1514 return True
1508 1515
1509 1516 def maybewdir(prefix):
1510 1517 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1511 1518
1512 1519 hexnode = hex(node)
1513 1520
1514 1521 def disambiguate(hexnode, minlength):
1515 1522 """Disambiguate against wdirid."""
1516 1523 for length in range(minlength, 41):
1517 1524 prefix = hexnode[:length]
1518 1525 if not maybewdir(prefix):
1519 1526 return prefix
1520 1527
1521 1528 if not getattr(self, 'filteredrevs', None):
1522 1529 try:
1523 1530 length = max(self.index.shortest(node), minlength)
1524 1531 return disambiguate(hexnode, length)
1525 1532 except error.RevlogError:
1526 1533 if node != wdirid:
1527 1534 raise error.LookupError(node, self.indexfile, _(b'no node'))
1528 1535 except AttributeError:
1529 1536 # Fall through to pure code
1530 1537 pass
1531 1538
1532 1539 if node == wdirid:
1533 1540 for length in range(minlength, 41):
1534 1541 prefix = hexnode[:length]
1535 1542 if isvalid(prefix):
1536 1543 return prefix
1537 1544
1538 1545 for length in range(minlength, 41):
1539 1546 prefix = hexnode[:length]
1540 1547 if isvalid(prefix):
1541 1548 return disambiguate(hexnode, length)
1542 1549
1543 1550 def cmp(self, node, text):
1544 1551 """compare text with a given file revision
1545 1552
1546 1553 returns True if text is different than what is stored.
1547 1554 """
1548 1555 p1, p2 = self.parents(node)
1549 1556 return storageutil.hashrevisionsha1(text, p1, p2) != node
1550 1557
1551 1558 def _cachesegment(self, offset, data):
1552 1559 """Add a segment to the revlog cache.
1553 1560
1554 1561 Accepts an absolute offset and the data that is at that location.
1555 1562 """
1556 1563 o, d = self._chunkcache
1557 1564 # try to add to existing cache
1558 1565 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1559 1566 self._chunkcache = o, d + data
1560 1567 else:
1561 1568 self._chunkcache = offset, data
1562 1569
1563 1570 def _readsegment(self, offset, length, df=None):
1564 1571 """Load a segment of raw data from the revlog.
1565 1572
1566 1573 Accepts an absolute offset, length to read, and an optional existing
1567 1574 file handle to read from.
1568 1575
1569 1576 If an existing file handle is passed, it will be seeked and the
1570 1577 original seek position will NOT be restored.
1571 1578
1572 1579 Returns a str or buffer of raw byte data.
1573 1580
1574 1581 Raises if the requested number of bytes could not be read.
1575 1582 """
1576 1583 # Cache data both forward and backward around the requested
1577 1584 # data, in a fixed size window. This helps speed up operations
1578 1585 # involving reading the revlog backwards.
1579 1586 cachesize = self._chunkcachesize
1580 1587 realoffset = offset & ~(cachesize - 1)
1581 1588 reallength = (
1582 1589 (offset + length + cachesize) & ~(cachesize - 1)
1583 1590 ) - realoffset
1584 1591 with self._datareadfp(df) as df:
1585 1592 df.seek(realoffset)
1586 1593 d = df.read(reallength)
1587 1594
1588 1595 self._cachesegment(realoffset, d)
1589 1596 if offset != realoffset or reallength != length:
1590 1597 startoffset = offset - realoffset
1591 1598 if len(d) - startoffset < length:
1592 1599 raise error.RevlogError(
1593 1600 _(
1594 1601 b'partial read of revlog %s; expected %d bytes from '
1595 1602 b'offset %d, got %d'
1596 1603 )
1597 1604 % (
1598 1605 self.indexfile if self._inline else self.datafile,
1599 1606 length,
1600 1607 realoffset,
1601 1608 len(d) - startoffset,
1602 1609 )
1603 1610 )
1604 1611
1605 1612 return util.buffer(d, startoffset, length)
1606 1613
1607 1614 if len(d) < length:
1608 1615 raise error.RevlogError(
1609 1616 _(
1610 1617 b'partial read of revlog %s; expected %d bytes from offset '
1611 1618 b'%d, got %d'
1612 1619 )
1613 1620 % (
1614 1621 self.indexfile if self._inline else self.datafile,
1615 1622 length,
1616 1623 offset,
1617 1624 len(d),
1618 1625 )
1619 1626 )
1620 1627
1621 1628 return d
1622 1629
1623 1630 def _getsegment(self, offset, length, df=None):
1624 1631 """Obtain a segment of raw data from the revlog.
1625 1632
1626 1633 Accepts an absolute offset, length of bytes to obtain, and an
1627 1634 optional file handle to the already-opened revlog. If the file
1628 1635 handle is used, it's original seek position will not be preserved.
1629 1636
1630 1637 Requests for data may be returned from a cache.
1631 1638
1632 1639 Returns a str or a buffer instance of raw byte data.
1633 1640 """
1634 1641 o, d = self._chunkcache
1635 1642 l = len(d)
1636 1643
1637 1644 # is it in the cache?
1638 1645 cachestart = offset - o
1639 1646 cacheend = cachestart + length
1640 1647 if cachestart >= 0 and cacheend <= l:
1641 1648 if cachestart == 0 and cacheend == l:
1642 1649 return d # avoid a copy
1643 1650 return util.buffer(d, cachestart, cacheend - cachestart)
1644 1651
1645 1652 return self._readsegment(offset, length, df=df)
1646 1653
1647 1654 def _getsegmentforrevs(self, startrev, endrev, df=None):
1648 1655 """Obtain a segment of raw data corresponding to a range of revisions.
1649 1656
1650 1657 Accepts the start and end revisions and an optional already-open
1651 1658 file handle to be used for reading. If the file handle is read, its
1652 1659 seek position will not be preserved.
1653 1660
1654 1661 Requests for data may be satisfied by a cache.
1655 1662
1656 1663 Returns a 2-tuple of (offset, data) for the requested range of
1657 1664 revisions. Offset is the integer offset from the beginning of the
1658 1665 revlog and data is a str or buffer of the raw byte data.
1659 1666
1660 1667 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1661 1668 to determine where each revision's data begins and ends.
1662 1669 """
1663 1670 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1664 1671 # (functions are expensive).
1665 1672 index = self.index
1666 1673 istart = index[startrev]
1667 1674 start = int(istart[0] >> 16)
1668 1675 if startrev == endrev:
1669 1676 end = start + istart[1]
1670 1677 else:
1671 1678 iend = index[endrev]
1672 1679 end = int(iend[0] >> 16) + iend[1]
1673 1680
1674 1681 if self._inline:
1675 1682 start += (startrev + 1) * self._io.size
1676 1683 end += (endrev + 1) * self._io.size
1677 1684 length = end - start
1678 1685
1679 1686 return start, self._getsegment(start, length, df=df)
1680 1687
1681 1688 def _chunk(self, rev, df=None):
1682 1689 """Obtain a single decompressed chunk for a revision.
1683 1690
1684 1691 Accepts an integer revision and an optional already-open file handle
1685 1692 to be used for reading. If used, the seek position of the file will not
1686 1693 be preserved.
1687 1694
1688 1695 Returns a str holding uncompressed data for the requested revision.
1689 1696 """
1690 1697 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1691 1698
1692 1699 def _chunks(self, revs, df=None, targetsize=None):
1693 1700 """Obtain decompressed chunks for the specified revisions.
1694 1701
1695 1702 Accepts an iterable of numeric revisions that are assumed to be in
1696 1703 ascending order. Also accepts an optional already-open file handle
1697 1704 to be used for reading. If used, the seek position of the file will
1698 1705 not be preserved.
1699 1706
1700 1707 This function is similar to calling ``self._chunk()`` multiple times,
1701 1708 but is faster.
1702 1709
1703 1710 Returns a list with decompressed data for each requested revision.
1704 1711 """
1705 1712 if not revs:
1706 1713 return []
1707 1714 start = self.start
1708 1715 length = self.length
1709 1716 inline = self._inline
1710 1717 iosize = self._io.size
1711 1718 buffer = util.buffer
1712 1719
1713 1720 l = []
1714 1721 ladd = l.append
1715 1722
1716 1723 if not self._withsparseread:
1717 1724 slicedchunks = (revs,)
1718 1725 else:
1719 1726 slicedchunks = deltautil.slicechunk(
1720 1727 self, revs, targetsize=targetsize
1721 1728 )
1722 1729
1723 1730 for revschunk in slicedchunks:
1724 1731 firstrev = revschunk[0]
1725 1732 # Skip trailing revisions with empty diff
1726 1733 for lastrev in revschunk[::-1]:
1727 1734 if length(lastrev) != 0:
1728 1735 break
1729 1736
1730 1737 try:
1731 1738 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1732 1739 except OverflowError:
1733 1740 # issue4215 - we can't cache a run of chunks greater than
1734 1741 # 2G on Windows
1735 1742 return [self._chunk(rev, df=df) for rev in revschunk]
1736 1743
1737 1744 decomp = self.decompress
1738 1745 for rev in revschunk:
1739 1746 chunkstart = start(rev)
1740 1747 if inline:
1741 1748 chunkstart += (rev + 1) * iosize
1742 1749 chunklength = length(rev)
1743 1750 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1744 1751
1745 1752 return l
1746 1753
1747 1754 def _chunkclear(self):
1748 1755 """Clear the raw chunk cache."""
1749 1756 self._chunkcache = (0, b'')
1750 1757
1751 1758 def deltaparent(self, rev):
1752 1759 """return deltaparent of the given revision"""
1753 1760 base = self.index[rev][3]
1754 1761 if base == rev:
1755 1762 return nullrev
1756 1763 elif self._generaldelta:
1757 1764 return base
1758 1765 else:
1759 1766 return rev - 1
1760 1767
1761 1768 def issnapshot(self, rev):
1762 1769 """tells whether rev is a snapshot
1763 1770 """
1764 1771 if not self._sparserevlog:
1765 1772 return self.deltaparent(rev) == nullrev
1766 1773 elif util.safehasattr(self.index, b'issnapshot'):
1767 1774 # directly assign the method to cache the testing and access
1768 1775 self.issnapshot = self.index.issnapshot
1769 1776 return self.issnapshot(rev)
1770 1777 if rev == nullrev:
1771 1778 return True
1772 1779 entry = self.index[rev]
1773 1780 base = entry[3]
1774 1781 if base == rev:
1775 1782 return True
1776 1783 if base == nullrev:
1777 1784 return True
1778 1785 p1 = entry[5]
1779 1786 p2 = entry[6]
1780 1787 if base == p1 or base == p2:
1781 1788 return False
1782 1789 return self.issnapshot(base)
1783 1790
1784 1791 def snapshotdepth(self, rev):
1785 1792 """number of snapshot in the chain before this one"""
1786 1793 if not self.issnapshot(rev):
1787 1794 raise error.ProgrammingError(b'revision %d not a snapshot')
1788 1795 return len(self._deltachain(rev)[0]) - 1
1789 1796
1790 1797 def revdiff(self, rev1, rev2):
1791 1798 """return or calculate a delta between two revisions
1792 1799
1793 1800 The delta calculated is in binary form and is intended to be written to
1794 1801 revlog data directly. So this function needs raw revision data.
1795 1802 """
1796 1803 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1797 1804 return bytes(self._chunk(rev2))
1798 1805
1799 1806 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1800 1807
1801 1808 def _processflags(self, text, flags, operation, raw=False):
1802 1809 """deprecated entry point to access flag processors"""
1803 1810 msg = b'_processflag(...) use the specialized variant'
1804 1811 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1805 1812 if raw:
1806 1813 return text, flagutil.processflagsraw(self, text, flags)
1807 1814 elif operation == b'read':
1808 1815 return flagutil.processflagsread(self, text, flags)
1809 1816 else: # write operation
1810 1817 return flagutil.processflagswrite(self, text, flags)
1811 1818
1812 1819 def revision(self, nodeorrev, _df=None, raw=False):
1813 1820 """return an uncompressed revision of a given node or revision
1814 1821 number.
1815 1822
1816 1823 _df - an existing file handle to read from. (internal-only)
1817 1824 raw - an optional argument specifying if the revision data is to be
1818 1825 treated as raw data when applying flag transforms. 'raw' should be set
1819 1826 to True when generating changegroups or in debug commands.
1820 1827 """
1821 1828 if raw:
1822 1829 msg = (
1823 1830 b'revlog.revision(..., raw=True) is deprecated, '
1824 1831 b'use revlog.rawdata(...)'
1825 1832 )
1826 1833 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1827 1834 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1828 1835
1829 1836 def sidedata(self, nodeorrev, _df=None):
1830 1837 """a map of extra data related to the changeset but not part of the hash
1831 1838
1832 1839 This function currently return a dictionary. However, more advanced
1833 1840 mapping object will likely be used in the future for a more
1834 1841 efficient/lazy code.
1835 1842 """
1836 1843 return self._revisiondata(nodeorrev, _df)[1]
1837 1844
1838 1845 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1839 1846 # deal with <nodeorrev> argument type
1840 1847 if isinstance(nodeorrev, int):
1841 1848 rev = nodeorrev
1842 1849 node = self.node(rev)
1843 1850 else:
1844 1851 node = nodeorrev
1845 1852 rev = None
1846 1853
1847 1854 # fast path the special `nullid` rev
1848 1855 if node == nullid:
1849 1856 return b"", {}
1850 1857
1851 1858 # ``rawtext`` is the text as stored inside the revlog. Might be the
1852 1859 # revision or might need to be processed to retrieve the revision.
1853 1860 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1854 1861
1855 1862 if raw and validated:
1856 1863 # if we don't want to process the raw text and that raw
1857 1864 # text is cached, we can exit early.
1858 1865 return rawtext, {}
1859 1866 if rev is None:
1860 1867 rev = self.rev(node)
1861 1868 # the revlog's flag for this revision
1862 1869 # (usually alter its state or content)
1863 1870 flags = self.flags(rev)
1864 1871
1865 1872 if validated and flags == REVIDX_DEFAULT_FLAGS:
1866 1873 # no extra flags set, no flag processor runs, text = rawtext
1867 1874 return rawtext, {}
1868 1875
1869 1876 sidedata = {}
1870 1877 if raw:
1871 1878 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1872 1879 text = rawtext
1873 1880 else:
1874 1881 try:
1875 1882 r = flagutil.processflagsread(self, rawtext, flags)
1876 1883 except error.SidedataHashError as exc:
1877 1884 msg = _(b"integrity check failed on %s:%s sidedata key %d")
1878 1885 msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey)
1879 1886 raise error.RevlogError(msg)
1880 1887 text, validatehash, sidedata = r
1881 1888 if validatehash:
1882 1889 self.checkhash(text, node, rev=rev)
1883 1890 if not validated:
1884 1891 self._revisioncache = (node, rev, rawtext)
1885 1892
1886 1893 return text, sidedata
1887 1894
1888 1895 def _rawtext(self, node, rev, _df=None):
1889 1896 """return the possibly unvalidated rawtext for a revision
1890 1897
1891 1898 returns (rev, rawtext, validated)
1892 1899 """
1893 1900
1894 1901 # revision in the cache (could be useful to apply delta)
1895 1902 cachedrev = None
1896 1903 # An intermediate text to apply deltas to
1897 1904 basetext = None
1898 1905
1899 1906 # Check if we have the entry in cache
1900 1907 # The cache entry looks like (node, rev, rawtext)
1901 1908 if self._revisioncache:
1902 1909 if self._revisioncache[0] == node:
1903 1910 return (rev, self._revisioncache[2], True)
1904 1911 cachedrev = self._revisioncache[1]
1905 1912
1906 1913 if rev is None:
1907 1914 rev = self.rev(node)
1908 1915
1909 1916 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1910 1917 if stopped:
1911 1918 basetext = self._revisioncache[2]
1912 1919
1913 1920 # drop cache to save memory, the caller is expected to
1914 1921 # update self._revisioncache after validating the text
1915 1922 self._revisioncache = None
1916 1923
1917 1924 targetsize = None
1918 1925 rawsize = self.index[rev][2]
1919 1926 if 0 <= rawsize:
1920 1927 targetsize = 4 * rawsize
1921 1928
1922 1929 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1923 1930 if basetext is None:
1924 1931 basetext = bytes(bins[0])
1925 1932 bins = bins[1:]
1926 1933
1927 1934 rawtext = mdiff.patches(basetext, bins)
1928 1935 del basetext # let us have a chance to free memory early
1929 1936 return (rev, rawtext, False)
1930 1937
1931 1938 def rawdata(self, nodeorrev, _df=None):
1932 1939 """return an uncompressed raw data of a given node or revision number.
1933 1940
1934 1941 _df - an existing file handle to read from. (internal-only)
1935 1942 """
1936 1943 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1937 1944
1938 1945 def hash(self, text, p1, p2):
1939 1946 """Compute a node hash.
1940 1947
1941 1948 Available as a function so that subclasses can replace the hash
1942 1949 as needed.
1943 1950 """
1944 1951 return storageutil.hashrevisionsha1(text, p1, p2)
1945 1952
1946 1953 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1947 1954 """Check node hash integrity.
1948 1955
1949 1956 Available as a function so that subclasses can extend hash mismatch
1950 1957 behaviors as needed.
1951 1958 """
1952 1959 try:
1953 1960 if p1 is None and p2 is None:
1954 1961 p1, p2 = self.parents(node)
1955 1962 if node != self.hash(text, p1, p2):
1956 1963 # Clear the revision cache on hash failure. The revision cache
1957 1964 # only stores the raw revision and clearing the cache does have
1958 1965 # the side-effect that we won't have a cache hit when the raw
1959 1966 # revision data is accessed. But this case should be rare and
1960 1967 # it is extra work to teach the cache about the hash
1961 1968 # verification state.
1962 1969 if self._revisioncache and self._revisioncache[0] == node:
1963 1970 self._revisioncache = None
1964 1971
1965 1972 revornode = rev
1966 1973 if revornode is None:
1967 1974 revornode = templatefilters.short(hex(node))
1968 1975 raise error.RevlogError(
1969 1976 _(b"integrity check failed on %s:%s")
1970 1977 % (self.indexfile, pycompat.bytestr(revornode))
1971 1978 )
1972 1979 except error.RevlogError:
1973 1980 if self._censorable and storageutil.iscensoredtext(text):
1974 1981 raise error.CensoredNodeError(self.indexfile, node, text)
1975 1982 raise
1976 1983
1977 1984 def _enforceinlinesize(self, tr, fp=None):
1978 1985 """Check if the revlog is too big for inline and convert if so.
1979 1986
1980 1987 This should be called after revisions are added to the revlog. If the
1981 1988 revlog has grown too large to be an inline revlog, it will convert it
1982 1989 to use multiple index and data files.
1983 1990 """
1984 1991 tiprev = len(self) - 1
1985 1992 if (
1986 1993 not self._inline
1987 1994 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
1988 1995 ):
1989 1996 return
1990 1997
1991 1998 trinfo = tr.find(self.indexfile)
1992 1999 if trinfo is None:
1993 2000 raise error.RevlogError(
1994 2001 _(b"%s not found in the transaction") % self.indexfile
1995 2002 )
1996 2003
1997 2004 trindex = trinfo[2]
1998 2005 if trindex is not None:
1999 2006 dataoff = self.start(trindex)
2000 2007 else:
2001 2008 # revlog was stripped at start of transaction, use all leftover data
2002 2009 trindex = len(self) - 1
2003 2010 dataoff = self.end(tiprev)
2004 2011
2005 2012 tr.add(self.datafile, dataoff)
2006 2013
2007 2014 if fp:
2008 2015 fp.flush()
2009 2016 fp.close()
2010 2017 # We can't use the cached file handle after close(). So prevent
2011 2018 # its usage.
2012 2019 self._writinghandles = None
2013 2020
2014 2021 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2015 2022 for r in self:
2016 2023 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2017 2024
2018 2025 with self._indexfp(b'w') as fp:
2019 2026 self.version &= ~FLAG_INLINE_DATA
2020 2027 self._inline = False
2021 2028 io = self._io
2022 2029 for i in self:
2023 2030 e = io.packentry(self.index[i], self.node, self.version, i)
2024 2031 fp.write(e)
2025 2032
2026 2033 # the temp file replace the real index when we exit the context
2027 2034 # manager
2028 2035
2029 2036 tr.replace(self.indexfile, trindex * self._io.size)
2030 2037 nodemaputil.setup_persistent_nodemap(tr, self)
2031 2038 self._chunkclear()
2032 2039
2033 2040 def _nodeduplicatecallback(self, transaction, node):
2034 2041 """called when trying to add a node already stored.
2035 2042 """
2036 2043
2037 2044 def addrevision(
2038 2045 self,
2039 2046 text,
2040 2047 transaction,
2041 2048 link,
2042 2049 p1,
2043 2050 p2,
2044 2051 cachedelta=None,
2045 2052 node=None,
2046 2053 flags=REVIDX_DEFAULT_FLAGS,
2047 2054 deltacomputer=None,
2048 2055 sidedata=None,
2049 2056 ):
2050 2057 """add a revision to the log
2051 2058
2052 2059 text - the revision data to add
2053 2060 transaction - the transaction object used for rollback
2054 2061 link - the linkrev data to add
2055 2062 p1, p2 - the parent nodeids of the revision
2056 2063 cachedelta - an optional precomputed delta
2057 2064 node - nodeid of revision; typically node is not specified, and it is
2058 2065 computed by default as hash(text, p1, p2), however subclasses might
2059 2066 use different hashing method (and override checkhash() in such case)
2060 2067 flags - the known flags to set on the revision
2061 2068 deltacomputer - an optional deltacomputer instance shared between
2062 2069 multiple calls
2063 2070 """
2064 2071 if link == nullrev:
2065 2072 raise error.RevlogError(
2066 2073 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2067 2074 )
2068 2075
2069 2076 if sidedata is None:
2070 2077 sidedata = {}
2071 2078 flags = flags & ~REVIDX_SIDEDATA
2072 2079 elif not self.hassidedata:
2073 2080 raise error.ProgrammingError(
2074 2081 _(b"trying to add sidedata to a revlog who don't support them")
2075 2082 )
2076 2083 else:
2077 2084 flags |= REVIDX_SIDEDATA
2078 2085
2079 2086 if flags:
2080 2087 node = node or self.hash(text, p1, p2)
2081 2088
2082 2089 rawtext, validatehash = flagutil.processflagswrite(
2083 2090 self, text, flags, sidedata=sidedata
2084 2091 )
2085 2092
2086 2093 # If the flag processor modifies the revision data, ignore any provided
2087 2094 # cachedelta.
2088 2095 if rawtext != text:
2089 2096 cachedelta = None
2090 2097
2091 2098 if len(rawtext) > _maxentrysize:
2092 2099 raise error.RevlogError(
2093 2100 _(
2094 2101 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2095 2102 )
2096 2103 % (self.indexfile, len(rawtext))
2097 2104 )
2098 2105
2099 2106 node = node or self.hash(rawtext, p1, p2)
2100 2107 if self.index.has_node(node):
2101 2108 return node
2102 2109
2103 2110 if validatehash:
2104 2111 self.checkhash(rawtext, node, p1=p1, p2=p2)
2105 2112
2106 2113 return self.addrawrevision(
2107 2114 rawtext,
2108 2115 transaction,
2109 2116 link,
2110 2117 p1,
2111 2118 p2,
2112 2119 node,
2113 2120 flags,
2114 2121 cachedelta=cachedelta,
2115 2122 deltacomputer=deltacomputer,
2116 2123 )
2117 2124
2118 2125 def addrawrevision(
2119 2126 self,
2120 2127 rawtext,
2121 2128 transaction,
2122 2129 link,
2123 2130 p1,
2124 2131 p2,
2125 2132 node,
2126 2133 flags,
2127 2134 cachedelta=None,
2128 2135 deltacomputer=None,
2129 2136 ):
2130 2137 """add a raw revision with known flags, node and parents
2131 2138 useful when reusing a revision not stored in this revlog (ex: received
2132 2139 over wire, or read from an external bundle).
2133 2140 """
2134 2141 dfh = None
2135 2142 if not self._inline:
2136 2143 dfh = self._datafp(b"a+")
2137 2144 ifh = self._indexfp(b"a+")
2138 2145 try:
2139 2146 return self._addrevision(
2140 2147 node,
2141 2148 rawtext,
2142 2149 transaction,
2143 2150 link,
2144 2151 p1,
2145 2152 p2,
2146 2153 flags,
2147 2154 cachedelta,
2148 2155 ifh,
2149 2156 dfh,
2150 2157 deltacomputer=deltacomputer,
2151 2158 )
2152 2159 finally:
2153 2160 if dfh:
2154 2161 dfh.close()
2155 2162 ifh.close()
2156 2163
2157 2164 def compress(self, data):
2158 2165 """Generate a possibly-compressed representation of data."""
2159 2166 if not data:
2160 2167 return b'', data
2161 2168
2162 2169 compressed = self._compressor.compress(data)
2163 2170
2164 2171 if compressed:
2165 2172 # The revlog compressor added the header in the returned data.
2166 2173 return b'', compressed
2167 2174
2168 2175 if data[0:1] == b'\0':
2169 2176 return b'', data
2170 2177 return b'u', data
2171 2178
2172 2179 def decompress(self, data):
2173 2180 """Decompress a revlog chunk.
2174 2181
2175 2182 The chunk is expected to begin with a header identifying the
2176 2183 format type so it can be routed to an appropriate decompressor.
2177 2184 """
2178 2185 if not data:
2179 2186 return data
2180 2187
2181 2188 # Revlogs are read much more frequently than they are written and many
2182 2189 # chunks only take microseconds to decompress, so performance is
2183 2190 # important here.
2184 2191 #
2185 2192 # We can make a few assumptions about revlogs:
2186 2193 #
2187 2194 # 1) the majority of chunks will be compressed (as opposed to inline
2188 2195 # raw data).
2189 2196 # 2) decompressing *any* data will likely by at least 10x slower than
2190 2197 # returning raw inline data.
2191 2198 # 3) we want to prioritize common and officially supported compression
2192 2199 # engines
2193 2200 #
2194 2201 # It follows that we want to optimize for "decompress compressed data
2195 2202 # when encoded with common and officially supported compression engines"
2196 2203 # case over "raw data" and "data encoded by less common or non-official
2197 2204 # compression engines." That is why we have the inline lookup first
2198 2205 # followed by the compengines lookup.
2199 2206 #
2200 2207 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2201 2208 # compressed chunks. And this matters for changelog and manifest reads.
2202 2209 t = data[0:1]
2203 2210
2204 2211 if t == b'x':
2205 2212 try:
2206 2213 return _zlibdecompress(data)
2207 2214 except zlib.error as e:
2208 2215 raise error.RevlogError(
2209 2216 _(b'revlog decompress error: %s')
2210 2217 % stringutil.forcebytestr(e)
2211 2218 )
2212 2219 # '\0' is more common than 'u' so it goes first.
2213 2220 elif t == b'\0':
2214 2221 return data
2215 2222 elif t == b'u':
2216 2223 return util.buffer(data, 1)
2217 2224
2218 2225 try:
2219 2226 compressor = self._decompressors[t]
2220 2227 except KeyError:
2221 2228 try:
2222 2229 engine = util.compengines.forrevlogheader(t)
2223 2230 compressor = engine.revlogcompressor(self._compengineopts)
2224 2231 self._decompressors[t] = compressor
2225 2232 except KeyError:
2226 2233 raise error.RevlogError(_(b'unknown compression type %r') % t)
2227 2234
2228 2235 return compressor.decompress(data)
2229 2236
2230 2237 def _addrevision(
2231 2238 self,
2232 2239 node,
2233 2240 rawtext,
2234 2241 transaction,
2235 2242 link,
2236 2243 p1,
2237 2244 p2,
2238 2245 flags,
2239 2246 cachedelta,
2240 2247 ifh,
2241 2248 dfh,
2242 2249 alwayscache=False,
2243 2250 deltacomputer=None,
2244 2251 ):
2245 2252 """internal function to add revisions to the log
2246 2253
2247 2254 see addrevision for argument descriptions.
2248 2255
2249 2256 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2250 2257
2251 2258 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2252 2259 be used.
2253 2260
2254 2261 invariants:
2255 2262 - rawtext is optional (can be None); if not set, cachedelta must be set.
2256 2263 if both are set, they must correspond to each other.
2257 2264 """
2258 2265 if node == nullid:
2259 2266 raise error.RevlogError(
2260 2267 _(b"%s: attempt to add null revision") % self.indexfile
2261 2268 )
2262 2269 if node == wdirid or node in wdirfilenodeids:
2263 2270 raise error.RevlogError(
2264 2271 _(b"%s: attempt to add wdir revision") % self.indexfile
2265 2272 )
2266 2273
2267 2274 if self._inline:
2268 2275 fh = ifh
2269 2276 else:
2270 2277 fh = dfh
2271 2278
2272 2279 btext = [rawtext]
2273 2280
2274 2281 curr = len(self)
2275 2282 prev = curr - 1
2276 2283 offset = self.end(prev)
2277 2284 p1r, p2r = self.rev(p1), self.rev(p2)
2278 2285
2279 2286 # full versions are inserted when the needed deltas
2280 2287 # become comparable to the uncompressed text
2281 2288 if rawtext is None:
2282 2289 # need rawtext size, before changed by flag processors, which is
2283 2290 # the non-raw size. use revlog explicitly to avoid filelog's extra
2284 2291 # logic that might remove metadata size.
2285 2292 textlen = mdiff.patchedsize(
2286 2293 revlog.size(self, cachedelta[0]), cachedelta[1]
2287 2294 )
2288 2295 else:
2289 2296 textlen = len(rawtext)
2290 2297
2291 2298 if deltacomputer is None:
2292 2299 deltacomputer = deltautil.deltacomputer(self)
2293 2300
2294 2301 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2295 2302
2296 2303 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2297 2304
2298 2305 e = (
2299 2306 offset_type(offset, flags),
2300 2307 deltainfo.deltalen,
2301 2308 textlen,
2302 2309 deltainfo.base,
2303 2310 link,
2304 2311 p1r,
2305 2312 p2r,
2306 2313 node,
2307 2314 )
2308 2315 self.index.append(e)
2309 2316
2310 2317 entry = self._io.packentry(e, self.node, self.version, curr)
2311 2318 self._writeentry(
2312 2319 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2313 2320 )
2314 2321
2315 2322 rawtext = btext[0]
2316 2323
2317 2324 if alwayscache and rawtext is None:
2318 2325 rawtext = deltacomputer.buildtext(revinfo, fh)
2319 2326
2320 2327 if type(rawtext) == bytes: # only accept immutable objects
2321 2328 self._revisioncache = (node, curr, rawtext)
2322 2329 self._chainbasecache[curr] = deltainfo.chainbase
2323 2330 return node
2324 2331
2325 2332 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2326 2333 # Files opened in a+ mode have inconsistent behavior on various
2327 2334 # platforms. Windows requires that a file positioning call be made
2328 2335 # when the file handle transitions between reads and writes. See
2329 2336 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2330 2337 # platforms, Python or the platform itself can be buggy. Some versions
2331 2338 # of Solaris have been observed to not append at the end of the file
2332 2339 # if the file was seeked to before the end. See issue4943 for more.
2333 2340 #
2334 2341 # We work around this issue by inserting a seek() before writing.
2335 2342 # Note: This is likely not necessary on Python 3. However, because
2336 2343 # the file handle is reused for reads and may be seeked there, we need
2337 2344 # to be careful before changing this.
2338 2345 ifh.seek(0, os.SEEK_END)
2339 2346 if dfh:
2340 2347 dfh.seek(0, os.SEEK_END)
2341 2348
2342 2349 curr = len(self) - 1
2343 2350 if not self._inline:
2344 2351 transaction.add(self.datafile, offset)
2345 2352 transaction.add(self.indexfile, curr * len(entry))
2346 2353 if data[0]:
2347 2354 dfh.write(data[0])
2348 2355 dfh.write(data[1])
2349 2356 ifh.write(entry)
2350 2357 else:
2351 2358 offset += curr * self._io.size
2352 2359 transaction.add(self.indexfile, offset, curr)
2353 2360 ifh.write(entry)
2354 2361 ifh.write(data[0])
2355 2362 ifh.write(data[1])
2356 2363 self._enforceinlinesize(transaction, ifh)
2357 2364 nodemaputil.setup_persistent_nodemap(transaction, self)
2358 2365
2359 2366 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2360 2367 """
2361 2368 add a delta group
2362 2369
2363 2370 given a set of deltas, add them to the revision log. the
2364 2371 first delta is against its parent, which should be in our
2365 2372 log, the rest are against the previous delta.
2366 2373
2367 2374 If ``addrevisioncb`` is defined, it will be called with arguments of
2368 2375 this revlog and the node that was added.
2369 2376 """
2370 2377
2371 2378 if self._writinghandles:
2372 2379 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2373 2380
2374 2381 nodes = []
2375 2382
2376 2383 r = len(self)
2377 2384 end = 0
2378 2385 if r:
2379 2386 end = self.end(r - 1)
2380 2387 ifh = self._indexfp(b"a+")
2381 2388 isize = r * self._io.size
2382 2389 if self._inline:
2383 2390 transaction.add(self.indexfile, end + isize, r)
2384 2391 dfh = None
2385 2392 else:
2386 2393 transaction.add(self.indexfile, isize, r)
2387 2394 transaction.add(self.datafile, end)
2388 2395 dfh = self._datafp(b"a+")
2389 2396
2390 2397 def flush():
2391 2398 if dfh:
2392 2399 dfh.flush()
2393 2400 ifh.flush()
2394 2401
2395 2402 self._writinghandles = (ifh, dfh)
2396 2403
2397 2404 try:
2398 2405 deltacomputer = deltautil.deltacomputer(self)
2399 2406 # loop through our set of deltas
2400 2407 for data in deltas:
2401 2408 node, p1, p2, linknode, deltabase, delta, flags = data
2402 2409 link = linkmapper(linknode)
2403 2410 flags = flags or REVIDX_DEFAULT_FLAGS
2404 2411
2405 2412 nodes.append(node)
2406 2413
2407 2414 if self.index.has_node(node):
2408 2415 self._nodeduplicatecallback(transaction, node)
2409 2416 # this can happen if two branches make the same change
2410 2417 continue
2411 2418
2412 2419 for p in (p1, p2):
2413 2420 if not self.index.has_node(p):
2414 2421 raise error.LookupError(
2415 2422 p, self.indexfile, _(b'unknown parent')
2416 2423 )
2417 2424
2418 2425 if not self.index.has_node(deltabase):
2419 2426 raise error.LookupError(
2420 2427 deltabase, self.indexfile, _(b'unknown delta base')
2421 2428 )
2422 2429
2423 2430 baserev = self.rev(deltabase)
2424 2431
2425 2432 if baserev != nullrev and self.iscensored(baserev):
2426 2433 # if base is censored, delta must be full replacement in a
2427 2434 # single patch operation
2428 2435 hlen = struct.calcsize(b">lll")
2429 2436 oldlen = self.rawsize(baserev)
2430 2437 newlen = len(delta) - hlen
2431 2438 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2432 2439 raise error.CensoredBaseError(
2433 2440 self.indexfile, self.node(baserev)
2434 2441 )
2435 2442
2436 2443 if not flags and self._peek_iscensored(baserev, delta, flush):
2437 2444 flags |= REVIDX_ISCENSORED
2438 2445
2439 2446 # We assume consumers of addrevisioncb will want to retrieve
2440 2447 # the added revision, which will require a call to
2441 2448 # revision(). revision() will fast path if there is a cache
2442 2449 # hit. So, we tell _addrevision() to always cache in this case.
2443 2450 # We're only using addgroup() in the context of changegroup
2444 2451 # generation so the revision data can always be handled as raw
2445 2452 # by the flagprocessor.
2446 2453 self._addrevision(
2447 2454 node,
2448 2455 None,
2449 2456 transaction,
2450 2457 link,
2451 2458 p1,
2452 2459 p2,
2453 2460 flags,
2454 2461 (baserev, delta),
2455 2462 ifh,
2456 2463 dfh,
2457 2464 alwayscache=bool(addrevisioncb),
2458 2465 deltacomputer=deltacomputer,
2459 2466 )
2460 2467
2461 2468 if addrevisioncb:
2462 2469 addrevisioncb(self, node)
2463 2470
2464 2471 if not dfh and not self._inline:
2465 2472 # addrevision switched from inline to conventional
2466 2473 # reopen the index
2467 2474 ifh.close()
2468 2475 dfh = self._datafp(b"a+")
2469 2476 ifh = self._indexfp(b"a+")
2470 2477 self._writinghandles = (ifh, dfh)
2471 2478 finally:
2472 2479 self._writinghandles = None
2473 2480
2474 2481 if dfh:
2475 2482 dfh.close()
2476 2483 ifh.close()
2477 2484
2478 2485 return nodes
2479 2486
2480 2487 def iscensored(self, rev):
2481 2488 """Check if a file revision is censored."""
2482 2489 if not self._censorable:
2483 2490 return False
2484 2491
2485 2492 return self.flags(rev) & REVIDX_ISCENSORED
2486 2493
2487 2494 def _peek_iscensored(self, baserev, delta, flush):
2488 2495 """Quickly check if a delta produces a censored revision."""
2489 2496 if not self._censorable:
2490 2497 return False
2491 2498
2492 2499 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2493 2500
2494 2501 def getstrippoint(self, minlink):
2495 2502 """find the minimum rev that must be stripped to strip the linkrev
2496 2503
2497 2504 Returns a tuple containing the minimum rev and a set of all revs that
2498 2505 have linkrevs that will be broken by this strip.
2499 2506 """
2500 2507 return storageutil.resolvestripinfo(
2501 2508 minlink,
2502 2509 len(self) - 1,
2503 2510 self.headrevs(),
2504 2511 self.linkrev,
2505 2512 self.parentrevs,
2506 2513 )
2507 2514
2508 2515 def strip(self, minlink, transaction):
2509 2516 """truncate the revlog on the first revision with a linkrev >= minlink
2510 2517
2511 2518 This function is called when we're stripping revision minlink and
2512 2519 its descendants from the repository.
2513 2520
2514 2521 We have to remove all revisions with linkrev >= minlink, because
2515 2522 the equivalent changelog revisions will be renumbered after the
2516 2523 strip.
2517 2524
2518 2525 So we truncate the revlog on the first of these revisions, and
2519 2526 trust that the caller has saved the revisions that shouldn't be
2520 2527 removed and that it'll re-add them after this truncation.
2521 2528 """
2522 2529 if len(self) == 0:
2523 2530 return
2524 2531
2525 2532 rev, _ = self.getstrippoint(minlink)
2526 2533 if rev == len(self):
2527 2534 return
2528 2535
2529 2536 # first truncate the files on disk
2530 2537 end = self.start(rev)
2531 2538 if not self._inline:
2532 2539 transaction.add(self.datafile, end)
2533 2540 end = rev * self._io.size
2534 2541 else:
2535 2542 end += rev * self._io.size
2536 2543
2537 2544 transaction.add(self.indexfile, end)
2538 2545
2539 2546 # then reset internal state in memory to forget those revisions
2540 2547 self._revisioncache = None
2541 2548 self._chaininfocache = {}
2542 2549 self._chunkclear()
2543 2550
2544 2551 del self.index[rev:-1]
2545 2552
2546 2553 def checksize(self):
2547 2554 """Check size of index and data files
2548 2555
2549 2556 return a (dd, di) tuple.
2550 2557 - dd: extra bytes for the "data" file
2551 2558 - di: extra bytes for the "index" file
2552 2559
2553 2560 A healthy revlog will return (0, 0).
2554 2561 """
2555 2562 expected = 0
2556 2563 if len(self):
2557 2564 expected = max(0, self.end(len(self) - 1))
2558 2565
2559 2566 try:
2560 2567 with self._datafp() as f:
2561 2568 f.seek(0, io.SEEK_END)
2562 2569 actual = f.tell()
2563 2570 dd = actual - expected
2564 2571 except IOError as inst:
2565 2572 if inst.errno != errno.ENOENT:
2566 2573 raise
2567 2574 dd = 0
2568 2575
2569 2576 try:
2570 2577 f = self.opener(self.indexfile)
2571 2578 f.seek(0, io.SEEK_END)
2572 2579 actual = f.tell()
2573 2580 f.close()
2574 2581 s = self._io.size
2575 2582 i = max(0, actual // s)
2576 2583 di = actual - (i * s)
2577 2584 if self._inline:
2578 2585 databytes = 0
2579 2586 for r in self:
2580 2587 databytes += max(0, self.length(r))
2581 2588 dd = 0
2582 2589 di = actual - len(self) * s - databytes
2583 2590 except IOError as inst:
2584 2591 if inst.errno != errno.ENOENT:
2585 2592 raise
2586 2593 di = 0
2587 2594
2588 2595 return (dd, di)
2589 2596
2590 2597 def files(self):
2591 2598 res = [self.indexfile]
2592 2599 if not self._inline:
2593 2600 res.append(self.datafile)
2594 2601 return res
2595 2602
2596 2603 def emitrevisions(
2597 2604 self,
2598 2605 nodes,
2599 2606 nodesorder=None,
2600 2607 revisiondata=False,
2601 2608 assumehaveparentrevisions=False,
2602 2609 deltamode=repository.CG_DELTAMODE_STD,
2603 2610 ):
2604 2611 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2605 2612 raise error.ProgrammingError(
2606 2613 b'unhandled value for nodesorder: %s' % nodesorder
2607 2614 )
2608 2615
2609 2616 if nodesorder is None and not self._generaldelta:
2610 2617 nodesorder = b'storage'
2611 2618
2612 2619 if (
2613 2620 not self._storedeltachains
2614 2621 and deltamode != repository.CG_DELTAMODE_PREV
2615 2622 ):
2616 2623 deltamode = repository.CG_DELTAMODE_FULL
2617 2624
2618 2625 return storageutil.emitrevisions(
2619 2626 self,
2620 2627 nodes,
2621 2628 nodesorder,
2622 2629 revlogrevisiondelta,
2623 2630 deltaparentfn=self.deltaparent,
2624 2631 candeltafn=self.candelta,
2625 2632 rawsizefn=self.rawsize,
2626 2633 revdifffn=self.revdiff,
2627 2634 flagsfn=self.flags,
2628 2635 deltamode=deltamode,
2629 2636 revisiondata=revisiondata,
2630 2637 assumehaveparentrevisions=assumehaveparentrevisions,
2631 2638 )
2632 2639
2633 2640 DELTAREUSEALWAYS = b'always'
2634 2641 DELTAREUSESAMEREVS = b'samerevs'
2635 2642 DELTAREUSENEVER = b'never'
2636 2643
2637 2644 DELTAREUSEFULLADD = b'fulladd'
2638 2645
2639 2646 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2640 2647
2641 2648 def clone(
2642 2649 self,
2643 2650 tr,
2644 2651 destrevlog,
2645 2652 addrevisioncb=None,
2646 2653 deltareuse=DELTAREUSESAMEREVS,
2647 2654 forcedeltabothparents=None,
2648 2655 sidedatacompanion=None,
2649 2656 ):
2650 2657 """Copy this revlog to another, possibly with format changes.
2651 2658
2652 2659 The destination revlog will contain the same revisions and nodes.
2653 2660 However, it may not be bit-for-bit identical due to e.g. delta encoding
2654 2661 differences.
2655 2662
2656 2663 The ``deltareuse`` argument control how deltas from the existing revlog
2657 2664 are preserved in the destination revlog. The argument can have the
2658 2665 following values:
2659 2666
2660 2667 DELTAREUSEALWAYS
2661 2668 Deltas will always be reused (if possible), even if the destination
2662 2669 revlog would not select the same revisions for the delta. This is the
2663 2670 fastest mode of operation.
2664 2671 DELTAREUSESAMEREVS
2665 2672 Deltas will be reused if the destination revlog would pick the same
2666 2673 revisions for the delta. This mode strikes a balance between speed
2667 2674 and optimization.
2668 2675 DELTAREUSENEVER
2669 2676 Deltas will never be reused. This is the slowest mode of execution.
2670 2677 This mode can be used to recompute deltas (e.g. if the diff/delta
2671 2678 algorithm changes).
2672 2679 DELTAREUSEFULLADD
2673 2680 Revision will be re-added as if their were new content. This is
2674 2681 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2675 2682 eg: large file detection and handling.
2676 2683
2677 2684 Delta computation can be slow, so the choice of delta reuse policy can
2678 2685 significantly affect run time.
2679 2686
2680 2687 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2681 2688 two extremes. Deltas will be reused if they are appropriate. But if the
2682 2689 delta could choose a better revision, it will do so. This means if you
2683 2690 are converting a non-generaldelta revlog to a generaldelta revlog,
2684 2691 deltas will be recomputed if the delta's parent isn't a parent of the
2685 2692 revision.
2686 2693
2687 2694 In addition to the delta policy, the ``forcedeltabothparents``
2688 2695 argument controls whether to force compute deltas against both parents
2689 2696 for merges. By default, the current default is used.
2690 2697
2691 2698 If not None, the `sidedatacompanion` is callable that accept two
2692 2699 arguments:
2693 2700
2694 2701 (srcrevlog, rev)
2695 2702
2696 2703 and return a triplet that control changes to sidedata content from the
2697 2704 old revision to the new clone result:
2698 2705
2699 2706 (dropall, filterout, update)
2700 2707
2701 2708 * if `dropall` is True, all sidedata should be dropped
2702 2709 * `filterout` is a set of sidedata keys that should be dropped
2703 2710 * `update` is a mapping of additionnal/new key -> value
2704 2711 """
2705 2712 if deltareuse not in self.DELTAREUSEALL:
2706 2713 raise ValueError(
2707 2714 _(b'value for deltareuse invalid: %s') % deltareuse
2708 2715 )
2709 2716
2710 2717 if len(destrevlog):
2711 2718 raise ValueError(_(b'destination revlog is not empty'))
2712 2719
2713 2720 if getattr(self, 'filteredrevs', None):
2714 2721 raise ValueError(_(b'source revlog has filtered revisions'))
2715 2722 if getattr(destrevlog, 'filteredrevs', None):
2716 2723 raise ValueError(_(b'destination revlog has filtered revisions'))
2717 2724
2718 2725 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2719 2726 # if possible.
2720 2727 oldlazydelta = destrevlog._lazydelta
2721 2728 oldlazydeltabase = destrevlog._lazydeltabase
2722 2729 oldamd = destrevlog._deltabothparents
2723 2730
2724 2731 try:
2725 2732 if deltareuse == self.DELTAREUSEALWAYS:
2726 2733 destrevlog._lazydeltabase = True
2727 2734 destrevlog._lazydelta = True
2728 2735 elif deltareuse == self.DELTAREUSESAMEREVS:
2729 2736 destrevlog._lazydeltabase = False
2730 2737 destrevlog._lazydelta = True
2731 2738 elif deltareuse == self.DELTAREUSENEVER:
2732 2739 destrevlog._lazydeltabase = False
2733 2740 destrevlog._lazydelta = False
2734 2741
2735 2742 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2736 2743
2737 2744 self._clone(
2738 2745 tr,
2739 2746 destrevlog,
2740 2747 addrevisioncb,
2741 2748 deltareuse,
2742 2749 forcedeltabothparents,
2743 2750 sidedatacompanion,
2744 2751 )
2745 2752
2746 2753 finally:
2747 2754 destrevlog._lazydelta = oldlazydelta
2748 2755 destrevlog._lazydeltabase = oldlazydeltabase
2749 2756 destrevlog._deltabothparents = oldamd
2750 2757
2751 2758 def _clone(
2752 2759 self,
2753 2760 tr,
2754 2761 destrevlog,
2755 2762 addrevisioncb,
2756 2763 deltareuse,
2757 2764 forcedeltabothparents,
2758 2765 sidedatacompanion,
2759 2766 ):
2760 2767 """perform the core duty of `revlog.clone` after parameter processing"""
2761 2768 deltacomputer = deltautil.deltacomputer(destrevlog)
2762 2769 index = self.index
2763 2770 for rev in self:
2764 2771 entry = index[rev]
2765 2772
2766 2773 # Some classes override linkrev to take filtered revs into
2767 2774 # account. Use raw entry from index.
2768 2775 flags = entry[0] & 0xFFFF
2769 2776 linkrev = entry[4]
2770 2777 p1 = index[entry[5]][7]
2771 2778 p2 = index[entry[6]][7]
2772 2779 node = entry[7]
2773 2780
2774 2781 sidedataactions = (False, [], {})
2775 2782 if sidedatacompanion is not None:
2776 2783 sidedataactions = sidedatacompanion(self, rev)
2777 2784
2778 2785 # (Possibly) reuse the delta from the revlog if allowed and
2779 2786 # the revlog chunk is a delta.
2780 2787 cachedelta = None
2781 2788 rawtext = None
2782 2789 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2783 2790 dropall, filterout, update = sidedataactions
2784 2791 text, sidedata = self._revisiondata(rev)
2785 2792 if dropall:
2786 2793 sidedata = {}
2787 2794 for key in filterout:
2788 2795 sidedata.pop(key, None)
2789 2796 sidedata.update(update)
2790 2797 if not sidedata:
2791 2798 sidedata = None
2792 2799 destrevlog.addrevision(
2793 2800 text,
2794 2801 tr,
2795 2802 linkrev,
2796 2803 p1,
2797 2804 p2,
2798 2805 cachedelta=cachedelta,
2799 2806 node=node,
2800 2807 flags=flags,
2801 2808 deltacomputer=deltacomputer,
2802 2809 sidedata=sidedata,
2803 2810 )
2804 2811 else:
2805 2812 if destrevlog._lazydelta:
2806 2813 dp = self.deltaparent(rev)
2807 2814 if dp != nullrev:
2808 2815 cachedelta = (dp, bytes(self._chunk(rev)))
2809 2816
2810 2817 if not cachedelta:
2811 2818 rawtext = self.rawdata(rev)
2812 2819
2813 2820 ifh = destrevlog.opener(
2814 2821 destrevlog.indexfile, b'a+', checkambig=False
2815 2822 )
2816 2823 dfh = None
2817 2824 if not destrevlog._inline:
2818 2825 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2819 2826 try:
2820 2827 destrevlog._addrevision(
2821 2828 node,
2822 2829 rawtext,
2823 2830 tr,
2824 2831 linkrev,
2825 2832 p1,
2826 2833 p2,
2827 2834 flags,
2828 2835 cachedelta,
2829 2836 ifh,
2830 2837 dfh,
2831 2838 deltacomputer=deltacomputer,
2832 2839 )
2833 2840 finally:
2834 2841 if dfh:
2835 2842 dfh.close()
2836 2843 ifh.close()
2837 2844
2838 2845 if addrevisioncb:
2839 2846 addrevisioncb(self, rev, node)
2840 2847
2841 2848 def censorrevision(self, tr, censornode, tombstone=b''):
2842 2849 if (self.version & 0xFFFF) == REVLOGV0:
2843 2850 raise error.RevlogError(
2844 2851 _(b'cannot censor with version %d revlogs') % self.version
2845 2852 )
2846 2853
2847 2854 censorrev = self.rev(censornode)
2848 2855 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2849 2856
2850 2857 if len(tombstone) > self.rawsize(censorrev):
2851 2858 raise error.Abort(
2852 2859 _(b'censor tombstone must be no longer than censored data')
2853 2860 )
2854 2861
2855 2862 # Rewriting the revlog in place is hard. Our strategy for censoring is
2856 2863 # to create a new revlog, copy all revisions to it, then replace the
2857 2864 # revlogs on transaction close.
2858 2865
2859 2866 newindexfile = self.indexfile + b'.tmpcensored'
2860 2867 newdatafile = self.datafile + b'.tmpcensored'
2861 2868
2862 2869 # This is a bit dangerous. We could easily have a mismatch of state.
2863 2870 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2864 2871 newrl.version = self.version
2865 2872 newrl._generaldelta = self._generaldelta
2866 2873 newrl._io = self._io
2867 2874
2868 2875 for rev in self.revs():
2869 2876 node = self.node(rev)
2870 2877 p1, p2 = self.parents(node)
2871 2878
2872 2879 if rev == censorrev:
2873 2880 newrl.addrawrevision(
2874 2881 tombstone,
2875 2882 tr,
2876 2883 self.linkrev(censorrev),
2877 2884 p1,
2878 2885 p2,
2879 2886 censornode,
2880 2887 REVIDX_ISCENSORED,
2881 2888 )
2882 2889
2883 2890 if newrl.deltaparent(rev) != nullrev:
2884 2891 raise error.Abort(
2885 2892 _(
2886 2893 b'censored revision stored as delta; '
2887 2894 b'cannot censor'
2888 2895 ),
2889 2896 hint=_(
2890 2897 b'censoring of revlogs is not '
2891 2898 b'fully implemented; please report '
2892 2899 b'this bug'
2893 2900 ),
2894 2901 )
2895 2902 continue
2896 2903
2897 2904 if self.iscensored(rev):
2898 2905 if self.deltaparent(rev) != nullrev:
2899 2906 raise error.Abort(
2900 2907 _(
2901 2908 b'cannot censor due to censored '
2902 2909 b'revision having delta stored'
2903 2910 )
2904 2911 )
2905 2912 rawtext = self._chunk(rev)
2906 2913 else:
2907 2914 rawtext = self.rawdata(rev)
2908 2915
2909 2916 newrl.addrawrevision(
2910 2917 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2911 2918 )
2912 2919
2913 2920 tr.addbackup(self.indexfile, location=b'store')
2914 2921 if not self._inline:
2915 2922 tr.addbackup(self.datafile, location=b'store')
2916 2923
2917 2924 self.opener.rename(newrl.indexfile, self.indexfile)
2918 2925 if not self._inline:
2919 2926 self.opener.rename(newrl.datafile, self.datafile)
2920 2927
2921 2928 self.clearcaches()
2922 2929 self._loadindex()
2923 2930
2924 2931 def verifyintegrity(self, state):
2925 2932 """Verifies the integrity of the revlog.
2926 2933
2927 2934 Yields ``revlogproblem`` instances describing problems that are
2928 2935 found.
2929 2936 """
2930 2937 dd, di = self.checksize()
2931 2938 if dd:
2932 2939 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2933 2940 if di:
2934 2941 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2935 2942
2936 2943 version = self.version & 0xFFFF
2937 2944
2938 2945 # The verifier tells us what version revlog we should be.
2939 2946 if version != state[b'expectedversion']:
2940 2947 yield revlogproblem(
2941 2948 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2942 2949 % (self.indexfile, version, state[b'expectedversion'])
2943 2950 )
2944 2951
2945 2952 state[b'skipread'] = set()
2946 2953 state[b'safe_renamed'] = set()
2947 2954
2948 2955 for rev in self:
2949 2956 node = self.node(rev)
2950 2957
2951 2958 # Verify contents. 4 cases to care about:
2952 2959 #
2953 2960 # common: the most common case
2954 2961 # rename: with a rename
2955 2962 # meta: file content starts with b'\1\n', the metadata
2956 2963 # header defined in filelog.py, but without a rename
2957 2964 # ext: content stored externally
2958 2965 #
2959 2966 # More formally, their differences are shown below:
2960 2967 #
2961 2968 # | common | rename | meta | ext
2962 2969 # -------------------------------------------------------
2963 2970 # flags() | 0 | 0 | 0 | not 0
2964 2971 # renamed() | False | True | False | ?
2965 2972 # rawtext[0:2]=='\1\n'| False | True | True | ?
2966 2973 #
2967 2974 # "rawtext" means the raw text stored in revlog data, which
2968 2975 # could be retrieved by "rawdata(rev)". "text"
2969 2976 # mentioned below is "revision(rev)".
2970 2977 #
2971 2978 # There are 3 different lengths stored physically:
2972 2979 # 1. L1: rawsize, stored in revlog index
2973 2980 # 2. L2: len(rawtext), stored in revlog data
2974 2981 # 3. L3: len(text), stored in revlog data if flags==0, or
2975 2982 # possibly somewhere else if flags!=0
2976 2983 #
2977 2984 # L1 should be equal to L2. L3 could be different from them.
2978 2985 # "text" may or may not affect commit hash depending on flag
2979 2986 # processors (see flagutil.addflagprocessor).
2980 2987 #
2981 2988 # | common | rename | meta | ext
2982 2989 # -------------------------------------------------
2983 2990 # rawsize() | L1 | L1 | L1 | L1
2984 2991 # size() | L1 | L2-LM | L1(*) | L1 (?)
2985 2992 # len(rawtext) | L2 | L2 | L2 | L2
2986 2993 # len(text) | L2 | L2 | L2 | L3
2987 2994 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2988 2995 #
2989 2996 # LM: length of metadata, depending on rawtext
2990 2997 # (*): not ideal, see comment in filelog.size
2991 2998 # (?): could be "- len(meta)" if the resolved content has
2992 2999 # rename metadata
2993 3000 #
2994 3001 # Checks needed to be done:
2995 3002 # 1. length check: L1 == L2, in all cases.
2996 3003 # 2. hash check: depending on flag processor, we may need to
2997 3004 # use either "text" (external), or "rawtext" (in revlog).
2998 3005
2999 3006 try:
3000 3007 skipflags = state.get(b'skipflags', 0)
3001 3008 if skipflags:
3002 3009 skipflags &= self.flags(rev)
3003 3010
3004 3011 _verify_revision(self, skipflags, state, node)
3005 3012
3006 3013 l1 = self.rawsize(rev)
3007 3014 l2 = len(self.rawdata(node))
3008 3015
3009 3016 if l1 != l2:
3010 3017 yield revlogproblem(
3011 3018 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3012 3019 node=node,
3013 3020 )
3014 3021
3015 3022 except error.CensoredNodeError:
3016 3023 if state[b'erroroncensored']:
3017 3024 yield revlogproblem(
3018 3025 error=_(b'censored file data'), node=node
3019 3026 )
3020 3027 state[b'skipread'].add(node)
3021 3028 except Exception as e:
3022 3029 yield revlogproblem(
3023 3030 error=_(b'unpacking %s: %s')
3024 3031 % (short(node), stringutil.forcebytestr(e)),
3025 3032 node=node,
3026 3033 )
3027 3034 state[b'skipread'].add(node)
3028 3035
3029 3036 def storageinfo(
3030 3037 self,
3031 3038 exclusivefiles=False,
3032 3039 sharedfiles=False,
3033 3040 revisionscount=False,
3034 3041 trackedsize=False,
3035 3042 storedsize=False,
3036 3043 ):
3037 3044 d = {}
3038 3045
3039 3046 if exclusivefiles:
3040 3047 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3041 3048 if not self._inline:
3042 3049 d[b'exclusivefiles'].append((self.opener, self.datafile))
3043 3050
3044 3051 if sharedfiles:
3045 3052 d[b'sharedfiles'] = []
3046 3053
3047 3054 if revisionscount:
3048 3055 d[b'revisionscount'] = len(self)
3049 3056
3050 3057 if trackedsize:
3051 3058 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3052 3059
3053 3060 if storedsize:
3054 3061 d[b'storedsize'] = sum(
3055 3062 self.opener.stat(path).st_size for path in self.files()
3056 3063 )
3057 3064
3058 3065 return d
@@ -1,589 +1,598
1 1 # nodemap.py - nodemap related code and utilities
2 2 #
3 3 # Copyright 2019 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 # Copyright 2019 George Racinet <georges.racinet@octobus.net>
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 absolute_import
10 10
11 11 import errno
12 12 import os
13 13 import re
14 14 import struct
15 15
16 16 from .. import (
17 17 error,
18 18 node as nodemod,
19 19 util,
20 20 )
21 21
22 22
23 23 class NodeMap(dict):
24 24 def __missing__(self, x):
25 25 raise error.RevlogError(b'unknown node: %s' % x)
26 26
27 27
28 28 def persisted_data(revlog):
29 29 """read the nodemap for a revlog from disk"""
30 30 if revlog.nodemap_file is None:
31 31 return None
32 32 pdata = revlog.opener.tryread(revlog.nodemap_file)
33 33 if not pdata:
34 34 return None
35 35 offset = 0
36 36 (version,) = S_VERSION.unpack(pdata[offset : offset + S_VERSION.size])
37 37 if version != ONDISK_VERSION:
38 38 return None
39 39 offset += S_VERSION.size
40 40 headers = S_HEADER.unpack(pdata[offset : offset + S_HEADER.size])
41 41 uid_size, tip_rev, data_length, data_unused, tip_node_size = headers
42 42 offset += S_HEADER.size
43 43 docket = NodeMapDocket(pdata[offset : offset + uid_size])
44 44 offset += uid_size
45 45 docket.tip_rev = tip_rev
46 46 docket.tip_node = pdata[offset : offset + tip_node_size]
47 47 docket.data_length = data_length
48 48 docket.data_unused = data_unused
49 49
50 50 filename = _rawdata_filepath(revlog, docket)
51 51 use_mmap = revlog.opener.options.get("exp-persistent-nodemap.mmap")
52 52 try:
53 53 with revlog.opener(filename) as fd:
54 54 if use_mmap:
55 55 data = util.buffer(util.mmapread(fd, data_length))
56 56 else:
57 57 data = fd.read(data_length)
58 58 except OSError as e:
59 59 if e.errno != errno.ENOENT:
60 60 raise
61 61 if len(data) < data_length:
62 62 return None
63 63 return docket, data
64 64
65 65
66 66 def setup_persistent_nodemap(tr, revlog):
67 67 """Install whatever is needed transaction side to persist a nodemap on disk
68 68
69 69 (only actually persist the nodemap if this is relevant for this revlog)
70 70 """
71 71 if revlog._inline:
72 72 return # inlined revlog are too small for this to be relevant
73 73 if revlog.nodemap_file is None:
74 74 return # we do not use persistent_nodemap on this revlog
75 75 callback_id = b"revlog-persistent-nodemap-%s" % revlog.nodemap_file
76 76 if tr.hasfinalize(callback_id):
77 77 return # no need to register again
78 tr.addpending(
79 callback_id, lambda tr: _persist_nodemap(tr, revlog, pending=True)
80 )
78 81 tr.addfinalize(callback_id, lambda tr: _persist_nodemap(tr, revlog))
79 82
80 83
81 84 class _NoTransaction(object):
82 85 """transaction like object to update the nodemap outside a transaction
83 86 """
84 87
85 88 def __init__(self):
86 89 self._postclose = {}
87 90
88 91 def addpostclose(self, callback_id, callback_func):
89 92 self._postclose[callback_id] = callback_func
90 93
91 94
92 95 def update_persistent_nodemap(revlog):
93 96 """update the persistent nodemap right now
94 97
95 98 To be used for updating the nodemap on disk outside of a normal transaction
96 99 setup (eg, `debugupdatecache`).
97 100 """
98 101 notr = _NoTransaction()
99 102 _persist_nodemap(notr, revlog)
100 103 for k in sorted(notr._postclose):
101 104 notr._postclose[k](None)
102 105
103 106
104 def _persist_nodemap(tr, revlog):
107 def _persist_nodemap(tr, revlog, pending=False):
105 108 """Write nodemap data on disk for a given revlog
106 109 """
107 110 if getattr(revlog, 'filteredrevs', ()):
108 111 raise error.ProgrammingError(
109 112 "cannot persist nodemap of a filtered changelog"
110 113 )
111 114 if revlog.nodemap_file is None:
112 115 msg = "calling persist nodemap on a revlog without the feature enableb"
113 116 raise error.ProgrammingError(msg)
114 117
115 118 can_incremental = util.safehasattr(revlog.index, "nodemap_data_incremental")
116 119 ondisk_docket = revlog._nodemap_docket
117 120 feed_data = util.safehasattr(revlog.index, "update_nodemap_data")
118 121 use_mmap = revlog.opener.options.get("exp-persistent-nodemap.mmap")
119 122
120 123 data = None
121 124 # first attemp an incremental update of the data
122 125 if can_incremental and ondisk_docket is not None:
123 126 target_docket = revlog._nodemap_docket.copy()
124 127 (
125 128 src_docket,
126 129 data_changed_count,
127 130 data,
128 131 ) = revlog.index.nodemap_data_incremental()
129 132 if src_docket != target_docket:
130 133 data = None
131 134 else:
132 135 datafile = _rawdata_filepath(revlog, target_docket)
133 136 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
134 137 # store vfs
135 138 new_length = target_docket.data_length + len(data)
136 139 with revlog.opener(datafile, b'r+') as fd:
137 140 fd.seek(target_docket.data_length)
138 141 fd.write(data)
139 142 if feed_data:
140 143 if use_mmap:
141 144 fd.seek(0)
142 145 new_data = fd.read(new_length)
143 146 else:
144 147 fd.flush()
145 148 new_data = util.buffer(util.mmapread(fd, new_length))
146 149 target_docket.data_length = new_length
147 150 target_docket.data_unused += data_changed_count
148 151
149 152 if data is None:
150 153 # otherwise fallback to a full new export
151 154 target_docket = NodeMapDocket()
152 155 datafile = _rawdata_filepath(revlog, target_docket)
153 156 if util.safehasattr(revlog.index, "nodemap_data_all"):
154 157 data = revlog.index.nodemap_data_all()
155 158 else:
156 159 data = persistent_data(revlog.index)
157 160 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
158 161 # store vfs
159 162 with revlog.opener(datafile, b'w+') as fd:
160 163 fd.write(data)
161 164 if feed_data:
162 165 if use_mmap:
163 166 new_data = data
164 167 else:
165 168 fd.flush()
166 169 new_data = util.buffer(util.mmapread(fd, len(data)))
167 170 target_docket.data_length = len(data)
168 171 target_docket.tip_rev = revlog.tiprev()
169 172 target_docket.tip_node = revlog.node(target_docket.tip_rev)
170 173 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
171 174 # store vfs
172 with revlog.opener(revlog.nodemap_file, b'w', atomictemp=True) as fp:
175 file_path = revlog.nodemap_file
176 if pending:
177 file_path += b'.a'
178 with revlog.opener(file_path, b'w', atomictemp=True) as fp:
173 179 fp.write(target_docket.serialize())
174 180 revlog._nodemap_docket = target_docket
175 181 if feed_data:
176 182 revlog.index.update_nodemap_data(target_docket, new_data)
177 183
178 184 # EXP-TODO: if the transaction abort, we should remove the new data and
179 185 # reinstall the old one.
180 186
181 187 # search for old index file in all cases, some older process might have
182 188 # left one behind.
183 189 olds = _other_rawdata_filepath(revlog, target_docket)
184 190 if olds:
185 191 realvfs = getattr(revlog, '_realopener', revlog.opener)
186 192
187 193 def cleanup(tr):
188 194 for oldfile in olds:
189 195 realvfs.tryunlink(oldfile)
190 196
191 197 callback_id = b"revlog-cleanup-nodemap-%s" % revlog.nodemap_file
192 198 tr.addpostclose(callback_id, cleanup)
193 199
194 200
195 201 ### Nodemap docket file
196 202 #
197 203 # The nodemap data are stored on disk using 2 files:
198 204 #
199 205 # * a raw data files containing a persistent nodemap
200 206 # (see `Nodemap Trie` section)
201 207 #
202 208 # * a small "docket" file containing medatadata
203 209 #
204 210 # While the nodemap data can be multiple tens of megabytes, the "docket" is
205 211 # small, it is easy to update it automatically or to duplicated its content
206 212 # during a transaction.
207 213 #
208 214 # Multiple raw data can exist at the same time (The currently valid one and a
209 215 # new one beind used by an in progress transaction). To accomodate this, the
210 216 # filename hosting the raw data has a variable parts. The exact filename is
211 217 # specified inside the "docket" file.
212 218 #
213 219 # The docket file contains information to find, qualify and validate the raw
214 220 # data. Its content is currently very light, but it will expand as the on disk
215 221 # nodemap gains the necessary features to be used in production.
216 222
217 223 # version 0 is experimental, no BC garantee, do no use outside of tests.
218 224 ONDISK_VERSION = 0
219 225 S_VERSION = struct.Struct(">B")
220 226 S_HEADER = struct.Struct(">BQQQQ")
221 227
222 228 ID_SIZE = 8
223 229
224 230
225 231 def _make_uid():
226 232 """return a new unique identifier.
227 233
228 234 The identifier is random and composed of ascii characters."""
229 235 return nodemod.hex(os.urandom(ID_SIZE))
230 236
231 237
232 238 class NodeMapDocket(object):
233 239 """metadata associated with persistent nodemap data
234 240
235 241 The persistent data may come from disk or be on their way to disk.
236 242 """
237 243
238 244 def __init__(self, uid=None):
239 245 if uid is None:
240 246 uid = _make_uid()
241 247 # a unique identifier for the data file:
242 248 # - When new data are appended, it is preserved.
243 249 # - When a new data file is created, a new identifier is generated.
244 250 self.uid = uid
245 251 # the tipmost revision stored in the data file. This revision and all
246 252 # revision before it are expected to be encoded in the data file.
247 253 self.tip_rev = None
248 254 # the node of that tipmost revision, if it mismatch the current index
249 255 # data the docket is not valid for the current index and should be
250 256 # discarded.
251 257 #
252 258 # note: this method is not perfect as some destructive operation could
253 259 # preserve the same tip_rev + tip_node while altering lower revision.
254 260 # However this multiple other caches have the same vulnerability (eg:
255 261 # brancmap cache).
256 262 self.tip_node = None
257 263 # the size (in bytes) of the persisted data to encode the nodemap valid
258 264 # for `tip_rev`.
259 265 # - data file shorter than this are corrupted,
260 266 # - any extra data should be ignored.
261 267 self.data_length = None
262 268 # the amount (in bytes) of "dead" data, still in the data file but no
263 269 # longer used for the nodemap.
264 270 self.data_unused = 0
265 271
266 272 def copy(self):
267 273 new = NodeMapDocket(uid=self.uid)
268 274 new.tip_rev = self.tip_rev
269 275 new.tip_node = self.tip_node
270 276 new.data_length = self.data_length
271 277 new.data_unused = self.data_unused
272 278 return new
273 279
274 280 def __cmp__(self, other):
275 281 if self.uid < other.uid:
276 282 return -1
277 283 if self.uid > other.uid:
278 284 return 1
279 285 elif self.data_length < other.data_length:
280 286 return -1
281 287 elif self.data_length > other.data_length:
282 288 return 1
283 289 return 0
284 290
285 291 def __eq__(self, other):
286 292 return self.uid == other.uid and self.data_length == other.data_length
287 293
288 294 def serialize(self):
289 295 """return serialized bytes for a docket using the passed uid"""
290 296 data = []
291 297 data.append(S_VERSION.pack(ONDISK_VERSION))
292 298 headers = (
293 299 len(self.uid),
294 300 self.tip_rev,
295 301 self.data_length,
296 302 self.data_unused,
297 303 len(self.tip_node),
298 304 )
299 305 data.append(S_HEADER.pack(*headers))
300 306 data.append(self.uid)
301 307 data.append(self.tip_node)
302 308 return b''.join(data)
303 309
304 310
305 311 def _rawdata_filepath(revlog, docket):
306 312 """The (vfs relative) nodemap's rawdata file for a given uid"""
307 prefix = revlog.nodemap_file[:-2]
313 if revlog.nodemap_file.endswith(b'.n.a'):
314 prefix = revlog.nodemap_file[:-4]
315 else:
316 prefix = revlog.nodemap_file[:-2]
308 317 return b"%s-%s.nd" % (prefix, docket.uid)
309 318
310 319
311 320 def _other_rawdata_filepath(revlog, docket):
312 321 prefix = revlog.nodemap_file[:-2]
313 322 pattern = re.compile(br"(^|/)%s-[0-9a-f]+\.nd$" % prefix)
314 323 new_file_path = _rawdata_filepath(revlog, docket)
315 324 new_file_name = revlog.opener.basename(new_file_path)
316 325 dirpath = revlog.opener.dirname(new_file_path)
317 326 others = []
318 327 for f in revlog.opener.listdir(dirpath):
319 328 if pattern.match(f) and f != new_file_name:
320 329 others.append(f)
321 330 return others
322 331
323 332
324 333 ### Nodemap Trie
325 334 #
326 335 # This is a simple reference implementation to compute and persist a nodemap
327 336 # trie. This reference implementation is write only. The python version of this
328 337 # is not expected to be actually used, since it wont provide performance
329 338 # improvement over existing non-persistent C implementation.
330 339 #
331 340 # The nodemap is persisted as Trie using 4bits-address/16-entries block. each
332 341 # revision can be adressed using its node shortest prefix.
333 342 #
334 343 # The trie is stored as a sequence of block. Each block contains 16 entries
335 344 # (signed 64bit integer, big endian). Each entry can be one of the following:
336 345 #
337 346 # * value >= 0 -> index of sub-block
338 347 # * value == -1 -> no value
339 348 # * value < -1 -> a revision value: rev = -(value+10)
340 349 #
341 350 # The implementation focus on simplicity, not on performance. A Rust
342 351 # implementation should provide a efficient version of the same binary
343 352 # persistence. This reference python implementation is never meant to be
344 353 # extensively use in production.
345 354
346 355
347 356 def persistent_data(index):
348 357 """return the persistent binary form for a nodemap for a given index
349 358 """
350 359 trie = _build_trie(index)
351 360 return _persist_trie(trie)
352 361
353 362
354 363 def update_persistent_data(index, root, max_idx, last_rev):
355 364 """return the incremental update for persistent nodemap from a given index
356 365 """
357 366 changed_block, trie = _update_trie(index, root, last_rev)
358 367 return (
359 368 changed_block * S_BLOCK.size,
360 369 _persist_trie(trie, existing_idx=max_idx),
361 370 )
362 371
363 372
364 373 S_BLOCK = struct.Struct(">" + ("l" * 16))
365 374
366 375 NO_ENTRY = -1
367 376 # rev 0 need to be -2 because 0 is used by block, -1 is a special value.
368 377 REV_OFFSET = 2
369 378
370 379
371 380 def _transform_rev(rev):
372 381 """Return the number used to represent the rev in the tree.
373 382
374 383 (or retrieve a rev number from such representation)
375 384
376 385 Note that this is an involution, a function equal to its inverse (i.e.
377 386 which gives the identity when applied to itself).
378 387 """
379 388 return -(rev + REV_OFFSET)
380 389
381 390
382 391 def _to_int(hex_digit):
383 392 """turn an hexadecimal digit into a proper integer"""
384 393 return int(hex_digit, 16)
385 394
386 395
387 396 class Block(dict):
388 397 """represent a block of the Trie
389 398
390 399 contains up to 16 entry indexed from 0 to 15"""
391 400
392 401 def __init__(self):
393 402 super(Block, self).__init__()
394 403 # If this block exist on disk, here is its ID
395 404 self.ondisk_id = None
396 405
397 406 def __iter__(self):
398 407 return iter(self.get(i) for i in range(16))
399 408
400 409
401 410 def _build_trie(index):
402 411 """build a nodemap trie
403 412
404 413 The nodemap stores revision number for each unique prefix.
405 414
406 415 Each block is a dictionary with keys in `[0, 15]`. Values are either
407 416 another block or a revision number.
408 417 """
409 418 root = Block()
410 419 for rev in range(len(index)):
411 420 hex = nodemod.hex(index[rev][7])
412 421 _insert_into_block(index, 0, root, rev, hex)
413 422 return root
414 423
415 424
416 425 def _update_trie(index, root, last_rev):
417 426 """consume"""
418 427 changed = 0
419 428 for rev in range(last_rev + 1, len(index)):
420 429 hex = nodemod.hex(index[rev][7])
421 430 changed += _insert_into_block(index, 0, root, rev, hex)
422 431 return changed, root
423 432
424 433
425 434 def _insert_into_block(index, level, block, current_rev, current_hex):
426 435 """insert a new revision in a block
427 436
428 437 index: the index we are adding revision for
429 438 level: the depth of the current block in the trie
430 439 block: the block currently being considered
431 440 current_rev: the revision number we are adding
432 441 current_hex: the hexadecimal representation of the of that revision
433 442 """
434 443 changed = 1
435 444 if block.ondisk_id is not None:
436 445 block.ondisk_id = None
437 446 hex_digit = _to_int(current_hex[level : level + 1])
438 447 entry = block.get(hex_digit)
439 448 if entry is None:
440 449 # no entry, simply store the revision number
441 450 block[hex_digit] = current_rev
442 451 elif isinstance(entry, dict):
443 452 # need to recurse to an underlying block
444 453 changed += _insert_into_block(
445 454 index, level + 1, entry, current_rev, current_hex
446 455 )
447 456 else:
448 457 # collision with a previously unique prefix, inserting new
449 458 # vertices to fit both entry.
450 459 other_hex = nodemod.hex(index[entry][7])
451 460 other_rev = entry
452 461 new = Block()
453 462 block[hex_digit] = new
454 463 _insert_into_block(index, level + 1, new, other_rev, other_hex)
455 464 _insert_into_block(index, level + 1, new, current_rev, current_hex)
456 465 return changed
457 466
458 467
459 468 def _persist_trie(root, existing_idx=None):
460 469 """turn a nodemap trie into persistent binary data
461 470
462 471 See `_build_trie` for nodemap trie structure"""
463 472 block_map = {}
464 473 if existing_idx is not None:
465 474 base_idx = existing_idx + 1
466 475 else:
467 476 base_idx = 0
468 477 chunks = []
469 478 for tn in _walk_trie(root):
470 479 if tn.ondisk_id is not None:
471 480 block_map[id(tn)] = tn.ondisk_id
472 481 else:
473 482 block_map[id(tn)] = len(chunks) + base_idx
474 483 chunks.append(_persist_block(tn, block_map))
475 484 return b''.join(chunks)
476 485
477 486
478 487 def _walk_trie(block):
479 488 """yield all the block in a trie
480 489
481 490 Children blocks are always yield before their parent block.
482 491 """
483 492 for (_, item) in sorted(block.items()):
484 493 if isinstance(item, dict):
485 494 for sub_block in _walk_trie(item):
486 495 yield sub_block
487 496 yield block
488 497
489 498
490 499 def _persist_block(block_node, block_map):
491 500 """produce persistent binary data for a single block
492 501
493 502 Children block are assumed to be already persisted and present in
494 503 block_map.
495 504 """
496 505 data = tuple(_to_value(v, block_map) for v in block_node)
497 506 return S_BLOCK.pack(*data)
498 507
499 508
500 509 def _to_value(item, block_map):
501 510 """persist any value as an integer"""
502 511 if item is None:
503 512 return NO_ENTRY
504 513 elif isinstance(item, dict):
505 514 return block_map[id(item)]
506 515 else:
507 516 return _transform_rev(item)
508 517
509 518
510 519 def parse_data(data):
511 520 """parse parse nodemap data into a nodemap Trie"""
512 521 if (len(data) % S_BLOCK.size) != 0:
513 522 msg = "nodemap data size is not a multiple of block size (%d): %d"
514 523 raise error.Abort(msg % (S_BLOCK.size, len(data)))
515 524 if not data:
516 525 return Block(), None
517 526 block_map = {}
518 527 new_blocks = []
519 528 for i in range(0, len(data), S_BLOCK.size):
520 529 block = Block()
521 530 block.ondisk_id = len(block_map)
522 531 block_map[block.ondisk_id] = block
523 532 block_data = data[i : i + S_BLOCK.size]
524 533 values = S_BLOCK.unpack(block_data)
525 534 new_blocks.append((block, values))
526 535 for b, values in new_blocks:
527 536 for idx, v in enumerate(values):
528 537 if v == NO_ENTRY:
529 538 continue
530 539 elif v >= 0:
531 540 b[idx] = block_map[v]
532 541 else:
533 542 b[idx] = _transform_rev(v)
534 543 return block, i // S_BLOCK.size
535 544
536 545
537 546 # debug utility
538 547
539 548
540 549 def check_data(ui, index, data):
541 550 """verify that the provided nodemap data are valid for the given idex"""
542 551 ret = 0
543 552 ui.status((b"revision in index: %d\n") % len(index))
544 553 root, __ = parse_data(data)
545 554 all_revs = set(_all_revisions(root))
546 555 ui.status((b"revision in nodemap: %d\n") % len(all_revs))
547 556 for r in range(len(index)):
548 557 if r not in all_revs:
549 558 msg = b" revision missing from nodemap: %d\n" % r
550 559 ui.write_err(msg)
551 560 ret = 1
552 561 else:
553 562 all_revs.remove(r)
554 563 nm_rev = _find_node(root, nodemod.hex(index[r][7]))
555 564 if nm_rev is None:
556 565 msg = b" revision node does not match any entries: %d\n" % r
557 566 ui.write_err(msg)
558 567 ret = 1
559 568 elif nm_rev != r:
560 569 msg = (
561 570 b" revision node does not match the expected revision: "
562 571 b"%d != %d\n" % (r, nm_rev)
563 572 )
564 573 ui.write_err(msg)
565 574 ret = 1
566 575
567 576 if all_revs:
568 577 for r in sorted(all_revs):
569 578 msg = b" extra revision in nodemap: %d\n" % r
570 579 ui.write_err(msg)
571 580 ret = 1
572 581 return ret
573 582
574 583
575 584 def _all_revisions(root):
576 585 """return all revisions stored in a Trie"""
577 586 for block in _walk_trie(root):
578 587 for v in block:
579 588 if v is None or isinstance(v, Block):
580 589 continue
581 590 yield v
582 591
583 592
584 593 def _find_node(block, node):
585 594 """find the revision associated with a given node"""
586 595 entry = block.get(_to_int(node[0:1]))
587 596 if isinstance(entry, dict):
588 597 return _find_node(entry, node[1:])
589 598 return entry
@@ -1,283 +1,319
1 1 ===================================
2 2 Test the persistent on-disk nodemap
3 3 ===================================
4 4
5 5 $ hg init test-repo
6 6 $ cd test-repo
7 7 $ cat << EOF >> .hg/hgrc
8 8 > [experimental]
9 9 > exp-persistent-nodemap=yes
10 10 > [devel]
11 11 > persistent-nodemap=yes
12 12 > EOF
13 13 $ hg debugbuilddag .+5000
14 14 $ hg debugnodemap --metadata
15 15 uid: ???????????????? (glob)
16 16 tip-rev: 5000
17 17 tip-node: 06ddac466af534d365326c13c3879f97caca3cb1
18 18 data-length: 122880
19 19 data-unused: 0
20 20 $ f --size .hg/store/00changelog.n
21 21 .hg/store/00changelog.n: size=70
22 22
23 23 Simple lookup works
24 24
25 25 $ ANYNODE=`hg log --template '{node|short}\n' --rev tip`
26 26 $ hg log -r "$ANYNODE" --template '{rev}\n'
27 27 5000
28 28
29 29
30 30 #if rust
31 31
32 32 $ f --sha256 .hg/store/00changelog-*.nd
33 33 .hg/store/00changelog-????????????????.nd: sha256=1e38e9ffaa45cad13f15c1a9880ad606f4241e8beea2f61b4d5365abadfb55f6 (glob)
34 34 $ hg debugnodemap --dump-new | f --sha256 --size
35 35 size=122880, sha256=1e38e9ffaa45cad13f15c1a9880ad606f4241e8beea2f61b4d5365abadfb55f6
36 36 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
37 37 size=122880, sha256=1e38e9ffaa45cad13f15c1a9880ad606f4241e8beea2f61b4d5365abadfb55f6
38 38 0000: 00 00 00 76 00 00 01 65 00 00 00 95 00 00 01 34 |...v...e.......4|
39 39 0010: 00 00 00 19 00 00 01 69 00 00 00 ab 00 00 00 4b |.......i.......K|
40 40 0020: 00 00 00 07 00 00 01 4c 00 00 00 f8 00 00 00 8f |.......L........|
41 41 0030: 00 00 00 c0 00 00 00 a7 00 00 00 89 00 00 01 46 |...............F|
42 42 0040: 00 00 00 92 00 00 01 bc 00 00 00 71 00 00 00 ac |...........q....|
43 43 0050: 00 00 00 af 00 00 00 b4 00 00 00 34 00 00 01 ca |...........4....|
44 44 0060: 00 00 00 23 00 00 01 45 00 00 00 2d 00 00 00 b2 |...#...E...-....|
45 45 0070: 00 00 00 56 00 00 01 0f 00 00 00 4e 00 00 02 4c |...V.......N...L|
46 46 0080: 00 00 00 e7 00 00 00 cd 00 00 01 5b 00 00 00 78 |...........[...x|
47 47 0090: 00 00 00 e3 00 00 01 8e 00 00 00 4f 00 00 00 b1 |...........O....|
48 48 00a0: 00 00 00 30 00 00 00 11 00 00 00 25 00 00 00 d2 |...0.......%....|
49 49 00b0: 00 00 00 ec 00 00 00 69 00 00 01 2b 00 00 01 2e |.......i...+....|
50 50 00c0: 00 00 00 aa 00 00 00 15 00 00 00 3a 00 00 01 4e |...........:...N|
51 51 00d0: 00 00 00 4d 00 00 00 9d 00 00 00 8e 00 00 00 a4 |...M............|
52 52 00e0: 00 00 00 c3 00 00 00 eb 00 00 00 29 00 00 00 ad |...........)....|
53 53 00f0: 00 00 01 3a 00 00 01 32 00 00 00 04 00 00 00 53 |...:...2.......S|
54 54
55 55
56 56 #else
57 57
58 58 $ f --sha256 .hg/store/00changelog-*.nd
59 59 .hg/store/00changelog-????????????????.nd: sha256=b961925120e1c9bc345c199b2cc442abc477029fdece37ef9d99cbe59c0558b7 (glob)
60 60 $ hg debugnodemap --dump-new | f --sha256 --size
61 61 size=122880, sha256=b961925120e1c9bc345c199b2cc442abc477029fdece37ef9d99cbe59c0558b7
62 62 $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
63 63 size=122880, sha256=b961925120e1c9bc345c199b2cc442abc477029fdece37ef9d99cbe59c0558b7
64 64 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
65 65 0010: ff ff ff ff ff ff ff ff ff ff fa c2 ff ff ff ff |................|
66 66 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
67 67 0030: ff ff ff ff ff ff ed b3 ff ff ff ff ff ff ff ff |................|
68 68 0040: ff ff ff ff ff ff ee 34 00 00 00 00 ff ff ff ff |.......4........|
69 69 0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
70 70 0060: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
71 71 0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
72 72 0080: ff ff ff ff ff ff f8 50 ff ff ff ff ff ff ff ff |.......P........|
73 73 0090: ff ff ff ff ff ff ff ff ff ff ec c7 ff ff ff ff |................|
74 74 00a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
75 75 00b0: ff ff ff ff ff ff fa be ff ff f2 fc ff ff ff ff |................|
76 76 00c0: ff ff ff ff ff ff ef ea ff ff ff ff ff ff f9 17 |................|
77 77 00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
78 78 00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
79 79 00f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
80 80
81 81 #endif
82 82
83 83 $ hg debugnodemap --check
84 84 revision in index: 5001
85 85 revision in nodemap: 5001
86 86
87 87 add a new commit
88 88
89 89 $ hg up
90 90 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 91 $ echo foo > foo
92 92 $ hg add foo
93 93 $ hg ci -m 'foo'
94 94
95 95 #if no-pure no-rust
96 96 $ hg debugnodemap --metadata
97 97 uid: ???????????????? (glob)
98 98 tip-rev: 5001
99 99 tip-node: 2dd9b5258caa46469ff07d4a3da1eb3529a51f49
100 100 data-length: 122880
101 101 data-unused: 0
102 102 #else
103 103 $ hg debugnodemap --metadata
104 104 uid: ???????????????? (glob)
105 105 tip-rev: 5001
106 106 tip-node: 2dd9b5258caa46469ff07d4a3da1eb3529a51f49
107 107 data-length: 123072
108 108 data-unused: 192
109 109 #endif
110 110
111 111 $ f --size .hg/store/00changelog.n
112 112 .hg/store/00changelog.n: size=70
113 113
114 114 (The pure code use the debug code that perform incremental update, the C code reencode from scratch)
115 115
116 116 #if pure
117 117 $ f --sha256 .hg/store/00changelog-*.nd --size
118 118 .hg/store/00changelog-????????????????.nd: size=123072, sha256=136472751566c8198ff09e306a7d2f9bd18bd32298d614752b73da4d6df23340 (glob)
119 119 #endif
120 120
121 121 #if rust
122 122 $ f --sha256 .hg/store/00changelog-*.nd --size
123 123 .hg/store/00changelog-????????????????.nd: size=123072, sha256=ccc8a43310ace13812fcc648683e259346754ef934c12dd238cf9b7fadfe9a4b (glob)
124 124 #endif
125 125
126 126 #if no-pure no-rust
127 127 $ f --sha256 .hg/store/00changelog-*.nd --size
128 128 .hg/store/00changelog-????????????????.nd: size=122880, sha256=bfafebd751c4f6d116a76a37a1dee2a251747affe7efbcc4f4842ccc746d4db9 (glob)
129 129 #endif
130 130
131 131 $ hg debugnodemap --check
132 132 revision in index: 5002
133 133 revision in nodemap: 5002
134 134
135 135 Test code path without mmap
136 136 ---------------------------
137 137
138 138 $ echo bar > bar
139 139 $ hg add bar
140 140 $ hg ci -m 'bar' --config experimental.exp-persistent-nodemap.mmap=no
141 141
142 142 $ hg debugnodemap --check --config experimental.exp-persistent-nodemap.mmap=yes
143 143 revision in index: 5003
144 144 revision in nodemap: 5003
145 145 $ hg debugnodemap --check --config experimental.exp-persistent-nodemap.mmap=no
146 146 revision in index: 5003
147 147 revision in nodemap: 5003
148 148
149 149
150 150 #if pure
151 151 $ hg debugnodemap --metadata
152 152 uid: ???????????????? (glob)
153 153 tip-rev: 5002
154 154 tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
155 155 data-length: 123328
156 156 data-unused: 384
157 157 $ f --sha256 .hg/store/00changelog-*.nd --size
158 158 .hg/store/00changelog-????????????????.nd: size=123328, sha256=10d26e9776b6596af0f89143a54eba8cc581e929c38242a02a7b0760698c6c70 (glob)
159 159 #endif
160 160 #if rust
161 161 $ hg debugnodemap --metadata
162 162 uid: ???????????????? (glob)
163 163 tip-rev: 5002
164 164 tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
165 165 data-length: 123328
166 166 data-unused: 384
167 167 $ f --sha256 .hg/store/00changelog-*.nd --size
168 168 .hg/store/00changelog-????????????????.nd: size=123328, sha256=081eec9eb6708f2bf085d939b4c97bc0b6762bc8336bc4b93838f7fffa1516bf (glob)
169 169 #endif
170 170 #if no-pure no-rust
171 171 $ hg debugnodemap --metadata
172 172 uid: ???????????????? (glob)
173 173 tip-rev: 5002
174 174 tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
175 175 data-length: 122944
176 176 data-unused: 0
177 177 $ f --sha256 .hg/store/00changelog-*.nd --size
178 178 .hg/store/00changelog-????????????????.nd: size=122944, sha256=755976b22b64ab680401b45395953504e64e7fa8c31ac570f58dee21e15f9bc0 (glob)
179 179 #endif
180 180
181 181 Test force warming the cache
182 182
183 183 $ rm .hg/store/00changelog.n
184 184 $ hg debugnodemap --metadata
185 185 $ hg debugupdatecache
186 186 #if pure
187 187 $ hg debugnodemap --metadata
188 188 uid: ???????????????? (glob)
189 189 tip-rev: 5002
190 190 tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
191 191 data-length: 122944
192 192 data-unused: 0
193 193 #else
194 194 $ hg debugnodemap --metadata
195 195 uid: ???????????????? (glob)
196 196 tip-rev: 5002
197 197 tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
198 198 data-length: 122944
199 199 data-unused: 0
200 200 #endif
201 201
202 202 Check out of sync nodemap
203 203 =========================
204 204
205 205 First copy old data on the side.
206 206
207 207 $ mkdir ../tmp-copies
208 208 $ cp .hg/store/00changelog-????????????????.nd .hg/store/00changelog.n ../tmp-copies
209 209
210 210 Nodemap lagging behind
211 211 ----------------------
212 212
213 213 make a new commit
214 214
215 215 $ echo bar2 > bar
216 216 $ hg ci -m 'bar2'
217 217 $ NODE=`hg log -r tip -T '{node}\n'`
218 218 $ hg log -r "$NODE" -T '{rev}\n'
219 219 5003
220 220
221 221 If the nodemap is lagging behind, it can catch up fine
222 222
223 223 $ hg debugnodemap --metadata
224 224 uid: ???????????????? (glob)
225 225 tip-rev: 5003
226 226 tip-node: 5c049e9c4a4af159bdcd65dce1b6bf303a0da6cf
227 227 data-length: 123200 (pure !)
228 228 data-length: 123200 (rust !)
229 229 data-length: 122944 (no-rust no-pure !)
230 230 data-unused: 256 (pure !)
231 231 data-unused: 256 (rust !)
232 232 data-unused: 0 (no-rust no-pure !)
233 233 $ cp -f ../tmp-copies/* .hg/store/
234 234 $ hg debugnodemap --metadata
235 235 uid: ???????????????? (glob)
236 236 tip-rev: 5002
237 237 tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
238 238 data-length: 122944
239 239 data-unused: 0
240 240 $ hg log -r "$NODE" -T '{rev}\n'
241 241 5003
242 242
243 243 changelog altered
244 244 -----------------
245 245
246 246 If the nodemap is not gated behind a requirements, an unaware client can alter
247 247 the repository so the revlog used to generate the nodemap is not longer
248 248 compatible with the persistent nodemap. We need to detect that.
249 249
250 250 $ hg up "$NODE~5"
251 251 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
252 252 $ echo bar > babar
253 253 $ hg add babar
254 254 $ hg ci -m 'babar'
255 255 created new head
256 256 $ OTHERNODE=`hg log -r tip -T '{node}\n'`
257 257 $ hg log -r "$OTHERNODE" -T '{rev}\n'
258 258 5004
259 259
260 260 $ hg --config extensions.strip= strip --rev "$NODE~1" --no-backup
261 261
262 262 the nodemap should detect the changelog have been tampered with and recover.
263 263
264 264 $ hg debugnodemap --metadata
265 265 uid: ???????????????? (glob)
266 266 tip-rev: 5002
267 267 tip-node: 42bf3068c7ddfdfded53c4eb11d02266faeebfee
268 268 data-length: 123456 (pure !)
269 269 data-length: 246464 (rust !)
270 270 data-length: 123008 (no-pure no-rust !)
271 271 data-unused: 448 (pure !)
272 272 data-unused: 123904 (rust !)
273 273 data-unused: 0 (no-pure no-rust !)
274 274
275 275 $ cp -f ../tmp-copies/* .hg/store/
276 276 $ hg debugnodemap --metadata
277 277 uid: ???????????????? (glob)
278 278 tip-rev: 5002
279 279 tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
280 280 data-length: 122944
281 281 data-unused: 0
282 282 $ hg log -r "$OTHERNODE" -T '{rev}\n'
283 283 5002
284
285 Check transaction related property
286 ==================================
287
288 An up to date nodemap should be available to shell hooks,
289
290 $ echo dsljfl > a
291 $ hg add a
292 $ hg ci -m a
293 $ hg debugnodemap --metadata
294 uid: ???????????????? (glob)
295 tip-rev: 5003
296 tip-node: c91af76d172f1053cca41b83f7c2e4e514fe2bcf
297 data-length: 123008
298 data-unused: 0
299 $ echo babar2 > babar
300 $ hg ci -m 'babar2' --config "hooks.pretxnclose.nodemap-test=hg debugnodemap --metadata"
301 uid: ???????????????? (glob)
302 tip-rev: 5004
303 tip-node: ba87cd9559559e4b91b28cb140d003985315e031
304 data-length: 123328 (pure !)
305 data-length: 123328 (rust !)
306 data-length: 123136 (no-pure no-rust !)
307 data-unused: 192 (pure !)
308 data-unused: 192 (rust !)
309 data-unused: 0 (no-pure no-rust !)
310 $ hg debugnodemap --metadata
311 uid: ???????????????? (glob)
312 tip-rev: 5004
313 tip-node: ba87cd9559559e4b91b28cb140d003985315e031
314 data-length: 123328 (pure !)
315 data-length: 123328 (rust !)
316 data-length: 123136 (no-pure no-rust !)
317 data-unused: 192 (pure !)
318 data-unused: 192 (rust !)
319 data-unused: 0 (no-pure no-rust !)
General Comments 0
You need to be logged in to leave comments. Login now