##// END OF EJS Templates
config: also gather effect-flags on experimental.evolution...
Boris Feld -
r34903:cc977ec0 default
parent child Browse files
Show More
@@ -1,1133 +1,1134 b''
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 def loadconfigtable(ui, extname, configtable):
19 19 """update config item known to the ui with the extension ones"""
20 20 for section, items in configtable.items():
21 21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 22 knownkeys = set(knownitems)
23 23 newkeys = set(items)
24 24 for key in sorted(knownkeys & newkeys):
25 25 msg = "extension '%s' overwrite config item '%s.%s'"
26 26 msg %= (extname, section, key)
27 27 ui.develwarn(msg, config='warn-config')
28 28
29 29 knownitems.update(items)
30 30
31 31 class configitem(object):
32 32 """represent a known config item
33 33
34 34 :section: the official config section where to find this item,
35 35 :name: the official name within the section,
36 36 :default: default value for this item,
37 37 :alias: optional list of tuples as alternatives,
38 38 :generic: this is a generic definition, match name using regular expression.
39 39 """
40 40
41 41 def __init__(self, section, name, default=None, alias=(),
42 42 generic=False, priority=0):
43 43 self.section = section
44 44 self.name = name
45 45 self.default = default
46 46 self.alias = list(alias)
47 47 self.generic = generic
48 48 self.priority = priority
49 49 self._re = None
50 50 if generic:
51 51 self._re = re.compile(self.name)
52 52
53 53 class itemregister(dict):
54 54 """A specialized dictionary that can handle wild-card selection"""
55 55
56 56 def __init__(self):
57 57 super(itemregister, self).__init__()
58 58 self._generics = set()
59 59
60 60 def update(self, other):
61 61 super(itemregister, self).update(other)
62 62 self._generics.update(other._generics)
63 63
64 64 def __setitem__(self, key, item):
65 65 super(itemregister, self).__setitem__(key, item)
66 66 if item.generic:
67 67 self._generics.add(item)
68 68
69 69 def get(self, key):
70 70 baseitem = super(itemregister, self).get(key)
71 71 if baseitem is not None and not baseitem.generic:
72 72 return baseitem
73 73
74 74 # search for a matching generic item
75 75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 76 for item in generics:
77 77 # we use 'match' instead of 'search' to make the matching simpler
78 78 # for people unfamiliar with regular expression. Having the match
79 79 # rooted to the start of the string will produce less surprising
80 80 # result for user writing simple regex for sub-attribute.
81 81 #
82 82 # For example using "color\..*" match produces an unsurprising
83 83 # result, while using search could suddenly match apparently
84 84 # unrelated configuration that happens to contains "color."
85 85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 86 # some match to avoid the need to prefix most pattern with "^".
87 87 # The "^" seems more error prone.
88 88 if item._re.match(key):
89 89 return item
90 90
91 91 return None
92 92
93 93 coreitems = {}
94 94
95 95 def _register(configtable, *args, **kwargs):
96 96 item = configitem(*args, **kwargs)
97 97 section = configtable.setdefault(item.section, itemregister())
98 98 if item.name in section:
99 99 msg = "duplicated config item registration for '%s.%s'"
100 100 raise error.ProgrammingError(msg % (item.section, item.name))
101 101 section[item.name] = item
102 102
103 103 # special value for case where the default is derived from other values
104 104 dynamicdefault = object()
105 105
106 106 # Registering actual config items
107 107
108 108 def getitemregister(configtable):
109 109 return functools.partial(_register, configtable)
110 110
111 111 coreconfigitem = getitemregister(coreitems)
112 112
113 113 coreconfigitem('alias', '.*',
114 114 default=None,
115 115 generic=True,
116 116 )
117 117 coreconfigitem('annotate', 'nodates',
118 118 default=False,
119 119 )
120 120 coreconfigitem('annotate', 'showfunc',
121 121 default=False,
122 122 )
123 123 coreconfigitem('annotate', 'unified',
124 124 default=None,
125 125 )
126 126 coreconfigitem('annotate', 'git',
127 127 default=False,
128 128 )
129 129 coreconfigitem('annotate', 'ignorews',
130 130 default=False,
131 131 )
132 132 coreconfigitem('annotate', 'ignorewsamount',
133 133 default=False,
134 134 )
135 135 coreconfigitem('annotate', 'ignoreblanklines',
136 136 default=False,
137 137 )
138 138 coreconfigitem('annotate', 'ignorewseol',
139 139 default=False,
140 140 )
141 141 coreconfigitem('annotate', 'nobinary',
142 142 default=False,
143 143 )
144 144 coreconfigitem('annotate', 'noprefix',
145 145 default=False,
146 146 )
147 147 coreconfigitem('auth', 'cookiefile',
148 148 default=None,
149 149 )
150 150 # bookmarks.pushing: internal hack for discovery
151 151 coreconfigitem('bookmarks', 'pushing',
152 152 default=list,
153 153 )
154 154 # bundle.mainreporoot: internal hack for bundlerepo
155 155 coreconfigitem('bundle', 'mainreporoot',
156 156 default='',
157 157 )
158 158 # bundle.reorder: experimental config
159 159 coreconfigitem('bundle', 'reorder',
160 160 default='auto',
161 161 )
162 162 coreconfigitem('censor', 'policy',
163 163 default='abort',
164 164 )
165 165 coreconfigitem('chgserver', 'idletimeout',
166 166 default=3600,
167 167 )
168 168 coreconfigitem('chgserver', 'skiphash',
169 169 default=False,
170 170 )
171 171 coreconfigitem('cmdserver', 'log',
172 172 default=None,
173 173 )
174 174 coreconfigitem('color', '.*',
175 175 default=None,
176 176 generic=True,
177 177 )
178 178 coreconfigitem('color', 'mode',
179 179 default='auto',
180 180 )
181 181 coreconfigitem('color', 'pagermode',
182 182 default=dynamicdefault,
183 183 )
184 184 coreconfigitem('commands', 'show.aliasprefix',
185 185 default=list,
186 186 )
187 187 coreconfigitem('commands', 'status.relative',
188 188 default=False,
189 189 )
190 190 coreconfigitem('commands', 'status.skipstates',
191 191 default=[],
192 192 )
193 193 coreconfigitem('commands', 'status.verbose',
194 194 default=False,
195 195 )
196 196 coreconfigitem('commands', 'update.check',
197 197 default=None,
198 198 # Deprecated, remove after 4.4 release
199 199 alias=[('experimental', 'updatecheck')]
200 200 )
201 201 coreconfigitem('commands', 'update.requiredest',
202 202 default=False,
203 203 )
204 204 coreconfigitem('committemplate', '.*',
205 205 default=None,
206 206 generic=True,
207 207 )
208 208 coreconfigitem('debug', 'dirstate.delaywrite',
209 209 default=0,
210 210 )
211 211 coreconfigitem('defaults', '.*',
212 212 default=None,
213 213 generic=True,
214 214 )
215 215 coreconfigitem('devel', 'all-warnings',
216 216 default=False,
217 217 )
218 218 coreconfigitem('devel', 'bundle2.debug',
219 219 default=False,
220 220 )
221 221 coreconfigitem('devel', 'cache-vfs',
222 222 default=None,
223 223 )
224 224 coreconfigitem('devel', 'check-locks',
225 225 default=False,
226 226 )
227 227 coreconfigitem('devel', 'check-relroot',
228 228 default=False,
229 229 )
230 230 coreconfigitem('devel', 'default-date',
231 231 default=None,
232 232 )
233 233 coreconfigitem('devel', 'deprec-warn',
234 234 default=False,
235 235 )
236 236 coreconfigitem('devel', 'disableloaddefaultcerts',
237 237 default=False,
238 238 )
239 239 coreconfigitem('devel', 'warn-empty-changegroup',
240 240 default=False,
241 241 )
242 242 coreconfigitem('devel', 'legacy.exchange',
243 243 default=list,
244 244 )
245 245 coreconfigitem('devel', 'servercafile',
246 246 default='',
247 247 )
248 248 coreconfigitem('devel', 'serverexactprotocol',
249 249 default='',
250 250 )
251 251 coreconfigitem('devel', 'serverrequirecert',
252 252 default=False,
253 253 )
254 254 coreconfigitem('devel', 'strip-obsmarkers',
255 255 default=True,
256 256 )
257 257 coreconfigitem('devel', 'warn-config',
258 258 default=None,
259 259 )
260 260 coreconfigitem('devel', 'warn-config-default',
261 261 default=None,
262 262 )
263 263 coreconfigitem('devel', 'user.obsmarker',
264 264 default=None,
265 265 )
266 266 coreconfigitem('devel', 'warn-config-unknown',
267 267 default=None,
268 268 )
269 269 coreconfigitem('diff', 'nodates',
270 270 default=False,
271 271 )
272 272 coreconfigitem('diff', 'showfunc',
273 273 default=False,
274 274 )
275 275 coreconfigitem('diff', 'unified',
276 276 default=None,
277 277 )
278 278 coreconfigitem('diff', 'git',
279 279 default=False,
280 280 )
281 281 coreconfigitem('diff', 'ignorews',
282 282 default=False,
283 283 )
284 284 coreconfigitem('diff', 'ignorewsamount',
285 285 default=False,
286 286 )
287 287 coreconfigitem('diff', 'ignoreblanklines',
288 288 default=False,
289 289 )
290 290 coreconfigitem('diff', 'ignorewseol',
291 291 default=False,
292 292 )
293 293 coreconfigitem('diff', 'nobinary',
294 294 default=False,
295 295 )
296 296 coreconfigitem('diff', 'noprefix',
297 297 default=False,
298 298 )
299 299 coreconfigitem('email', 'bcc',
300 300 default=None,
301 301 )
302 302 coreconfigitem('email', 'cc',
303 303 default=None,
304 304 )
305 305 coreconfigitem('email', 'charsets',
306 306 default=list,
307 307 )
308 308 coreconfigitem('email', 'from',
309 309 default=None,
310 310 )
311 311 coreconfigitem('email', 'method',
312 312 default='smtp',
313 313 )
314 314 coreconfigitem('email', 'reply-to',
315 315 default=None,
316 316 )
317 317 coreconfigitem('experimental', 'archivemetatemplate',
318 318 default=dynamicdefault,
319 319 )
320 320 coreconfigitem('experimental', 'bundle-phases',
321 321 default=False,
322 322 )
323 323 coreconfigitem('experimental', 'bundle2-advertise',
324 324 default=True,
325 325 )
326 326 coreconfigitem('experimental', 'bundle2-output-capture',
327 327 default=False,
328 328 )
329 329 coreconfigitem('experimental', 'bundle2.pushback',
330 330 default=False,
331 331 )
332 332 coreconfigitem('experimental', 'bundle2lazylocking',
333 333 default=False,
334 334 )
335 335 coreconfigitem('experimental', 'bundlecomplevel',
336 336 default=None,
337 337 )
338 338 coreconfigitem('experimental', 'changegroup3',
339 339 default=False,
340 340 )
341 341 coreconfigitem('experimental', 'clientcompressionengines',
342 342 default=list,
343 343 )
344 344 coreconfigitem('experimental', 'copytrace',
345 345 default='on',
346 346 )
347 347 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
348 348 default=100,
349 349 )
350 350 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
351 351 default=100,
352 352 )
353 353 coreconfigitem('experimental', 'crecordtest',
354 354 default=None,
355 355 )
356 356 coreconfigitem('experimental', 'editortmpinhg',
357 357 default=False,
358 358 )
359 359 coreconfigitem('experimental', 'evolution',
360 360 default=list,
361 361 )
362 362 coreconfigitem('experimental', 'evolution.allowdivergence',
363 363 default=False,
364 364 alias=[('experimental', 'allowdivergence')]
365 365 )
366 366 coreconfigitem('experimental', 'evolution.allowunstable',
367 367 default=None,
368 368 )
369 369 coreconfigitem('experimental', 'evolution.createmarkers',
370 370 default=None,
371 371 )
372 coreconfigitem('experimental', 'evolution.effect-flags',
373 default=False,
374 alias=[('experimental', 'effect-flags')]
375 )
372 376 coreconfigitem('experimental', 'evolution.exchange',
373 377 default=None,
374 378 )
375 379 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
376 380 default=False,
377 381 )
378 382 coreconfigitem('experimental', 'evolution.track-operation',
379 383 default=True,
380 384 )
381 385 coreconfigitem('experimental', 'maxdeltachainspan',
382 386 default=-1,
383 387 )
384 388 coreconfigitem('experimental', 'mmapindexthreshold',
385 389 default=None,
386 390 )
387 391 coreconfigitem('experimental', 'nonnormalparanoidcheck',
388 392 default=False,
389 393 )
390 coreconfigitem('experimental', 'effect-flags',
391 default=False,
392 )
393 394 coreconfigitem('experimental', 'exportableenviron',
394 395 default=list,
395 396 )
396 397 coreconfigitem('experimental', 'extendedheader.index',
397 398 default=None,
398 399 )
399 400 coreconfigitem('experimental', 'extendedheader.similarity',
400 401 default=False,
401 402 )
402 403 coreconfigitem('experimental', 'format.compression',
403 404 default='zlib',
404 405 )
405 406 coreconfigitem('experimental', 'graphshorten',
406 407 default=False,
407 408 )
408 409 coreconfigitem('experimental', 'graphstyle.parent',
409 410 default=dynamicdefault,
410 411 )
411 412 coreconfigitem('experimental', 'graphstyle.missing',
412 413 default=dynamicdefault,
413 414 )
414 415 coreconfigitem('experimental', 'graphstyle.grandparent',
415 416 default=dynamicdefault,
416 417 )
417 418 coreconfigitem('experimental', 'hook-track-tags',
418 419 default=False,
419 420 )
420 421 coreconfigitem('experimental', 'httppostargs',
421 422 default=False,
422 423 )
423 424 coreconfigitem('experimental', 'manifestv2',
424 425 default=False,
425 426 )
426 427 coreconfigitem('experimental', 'mergedriver',
427 428 default=None,
428 429 )
429 430 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
430 431 default=False,
431 432 )
432 433 coreconfigitem('experimental', 'rebase.multidest',
433 434 default=False,
434 435 )
435 436 coreconfigitem('experimental', 'revertalternateinteractivemode',
436 437 default=True,
437 438 )
438 439 coreconfigitem('experimental', 'revlogv2',
439 440 default=None,
440 441 )
441 442 coreconfigitem('experimental', 'spacemovesdown',
442 443 default=False,
443 444 )
444 445 coreconfigitem('experimental', 'sparse-read',
445 446 default=False,
446 447 )
447 448 coreconfigitem('experimental', 'sparse-read.density-threshold',
448 449 default=0.25,
449 450 )
450 451 coreconfigitem('experimental', 'sparse-read.min-gap-size',
451 452 default='256K',
452 453 )
453 454 coreconfigitem('experimental', 'treemanifest',
454 455 default=False,
455 456 )
456 457 coreconfigitem('extensions', '.*',
457 458 default=None,
458 459 generic=True,
459 460 )
460 461 coreconfigitem('extdata', '.*',
461 462 default=None,
462 463 generic=True,
463 464 )
464 465 coreconfigitem('format', 'aggressivemergedeltas',
465 466 default=False,
466 467 )
467 468 coreconfigitem('format', 'chunkcachesize',
468 469 default=None,
469 470 )
470 471 coreconfigitem('format', 'dotencode',
471 472 default=True,
472 473 )
473 474 coreconfigitem('format', 'generaldelta',
474 475 default=False,
475 476 )
476 477 coreconfigitem('format', 'manifestcachesize',
477 478 default=None,
478 479 )
479 480 coreconfigitem('format', 'maxchainlen',
480 481 default=None,
481 482 )
482 483 coreconfigitem('format', 'obsstore-version',
483 484 default=None,
484 485 )
485 486 coreconfigitem('format', 'usefncache',
486 487 default=True,
487 488 )
488 489 coreconfigitem('format', 'usegeneraldelta',
489 490 default=True,
490 491 )
491 492 coreconfigitem('format', 'usestore',
492 493 default=True,
493 494 )
494 495 coreconfigitem('fsmonitor', 'warn_when_unused',
495 496 default=True,
496 497 )
497 498 coreconfigitem('fsmonitor', 'warn_update_file_count',
498 499 default=50000,
499 500 )
500 501 coreconfigitem('hooks', '.*',
501 502 default=dynamicdefault,
502 503 generic=True,
503 504 )
504 505 coreconfigitem('hgweb-paths', '.*',
505 506 default=list,
506 507 generic=True,
507 508 )
508 509 coreconfigitem('hostfingerprints', '.*',
509 510 default=list,
510 511 generic=True,
511 512 )
512 513 coreconfigitem('hostsecurity', 'ciphers',
513 514 default=None,
514 515 )
515 516 coreconfigitem('hostsecurity', 'disabletls10warning',
516 517 default=False,
517 518 )
518 519 coreconfigitem('hostsecurity', 'minimumprotocol',
519 520 default=dynamicdefault,
520 521 )
521 522 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
522 523 default=dynamicdefault,
523 524 generic=True,
524 525 )
525 526 coreconfigitem('hostsecurity', '.*:ciphers$',
526 527 default=dynamicdefault,
527 528 generic=True,
528 529 )
529 530 coreconfigitem('hostsecurity', '.*:fingerprints$',
530 531 default=list,
531 532 generic=True,
532 533 )
533 534 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
534 535 default=None,
535 536 generic=True,
536 537 )
537 538
538 539 coreconfigitem('http_proxy', 'always',
539 540 default=False,
540 541 )
541 542 coreconfigitem('http_proxy', 'host',
542 543 default=None,
543 544 )
544 545 coreconfigitem('http_proxy', 'no',
545 546 default=list,
546 547 )
547 548 coreconfigitem('http_proxy', 'passwd',
548 549 default=None,
549 550 )
550 551 coreconfigitem('http_proxy', 'user',
551 552 default=None,
552 553 )
553 554 coreconfigitem('logtoprocess', 'commandexception',
554 555 default=None,
555 556 )
556 557 coreconfigitem('logtoprocess', 'commandfinish',
557 558 default=None,
558 559 )
559 560 coreconfigitem('logtoprocess', 'command',
560 561 default=None,
561 562 )
562 563 coreconfigitem('logtoprocess', 'develwarn',
563 564 default=None,
564 565 )
565 566 coreconfigitem('logtoprocess', 'uiblocked',
566 567 default=None,
567 568 )
568 569 coreconfigitem('merge', 'checkunknown',
569 570 default='abort',
570 571 )
571 572 coreconfigitem('merge', 'checkignored',
572 573 default='abort',
573 574 )
574 575 coreconfigitem('merge', 'followcopies',
575 576 default=True,
576 577 )
577 578 coreconfigitem('merge', 'on-failure',
578 579 default='continue',
579 580 )
580 581 coreconfigitem('merge', 'preferancestor',
581 582 default=lambda: ['*'],
582 583 )
583 584 coreconfigitem('merge-tools', '.*',
584 585 default=None,
585 586 generic=True,
586 587 )
587 588 coreconfigitem('merge-tools', br'.*\.args$',
588 589 default="$local $base $other",
589 590 generic=True,
590 591 priority=-1,
591 592 )
592 593 coreconfigitem('merge-tools', br'.*\.binary$',
593 594 default=False,
594 595 generic=True,
595 596 priority=-1,
596 597 )
597 598 coreconfigitem('merge-tools', br'.*\.check$',
598 599 default=list,
599 600 generic=True,
600 601 priority=-1,
601 602 )
602 603 coreconfigitem('merge-tools', br'.*\.checkchanged$',
603 604 default=False,
604 605 generic=True,
605 606 priority=-1,
606 607 )
607 608 coreconfigitem('merge-tools', br'.*\.executable$',
608 609 default=dynamicdefault,
609 610 generic=True,
610 611 priority=-1,
611 612 )
612 613 coreconfigitem('merge-tools', br'.*\.fixeol$',
613 614 default=False,
614 615 generic=True,
615 616 priority=-1,
616 617 )
617 618 coreconfigitem('merge-tools', br'.*\.gui$',
618 619 default=False,
619 620 generic=True,
620 621 priority=-1,
621 622 )
622 623 coreconfigitem('merge-tools', br'.*\.priority$',
623 624 default=0,
624 625 generic=True,
625 626 priority=-1,
626 627 )
627 628 coreconfigitem('merge-tools', br'.*\.premerge$',
628 629 default=dynamicdefault,
629 630 generic=True,
630 631 priority=-1,
631 632 )
632 633 coreconfigitem('merge-tools', br'.*\.symlink$',
633 634 default=False,
634 635 generic=True,
635 636 priority=-1,
636 637 )
637 638 coreconfigitem('pager', 'attend-.*',
638 639 default=dynamicdefault,
639 640 generic=True,
640 641 )
641 642 coreconfigitem('pager', 'ignore',
642 643 default=list,
643 644 )
644 645 coreconfigitem('pager', 'pager',
645 646 default=dynamicdefault,
646 647 )
647 648 coreconfigitem('patch', 'eol',
648 649 default='strict',
649 650 )
650 651 coreconfigitem('patch', 'fuzz',
651 652 default=2,
652 653 )
653 654 coreconfigitem('paths', 'default',
654 655 default=None,
655 656 )
656 657 coreconfigitem('paths', 'default-push',
657 658 default=None,
658 659 )
659 660 coreconfigitem('paths', '.*',
660 661 default=None,
661 662 generic=True,
662 663 )
663 664 coreconfigitem('phases', 'checksubrepos',
664 665 default='follow',
665 666 )
666 667 coreconfigitem('phases', 'new-commit',
667 668 default='draft',
668 669 )
669 670 coreconfigitem('phases', 'publish',
670 671 default=True,
671 672 )
672 673 coreconfigitem('profiling', 'enabled',
673 674 default=False,
674 675 )
675 676 coreconfigitem('profiling', 'format',
676 677 default='text',
677 678 )
678 679 coreconfigitem('profiling', 'freq',
679 680 default=1000,
680 681 )
681 682 coreconfigitem('profiling', 'limit',
682 683 default=30,
683 684 )
684 685 coreconfigitem('profiling', 'nested',
685 686 default=0,
686 687 )
687 688 coreconfigitem('profiling', 'output',
688 689 default=None,
689 690 )
690 691 coreconfigitem('profiling', 'showmax',
691 692 default=0.999,
692 693 )
693 694 coreconfigitem('profiling', 'showmin',
694 695 default=dynamicdefault,
695 696 )
696 697 coreconfigitem('profiling', 'sort',
697 698 default='inlinetime',
698 699 )
699 700 coreconfigitem('profiling', 'statformat',
700 701 default='hotpath',
701 702 )
702 703 coreconfigitem('profiling', 'type',
703 704 default='stat',
704 705 )
705 706 coreconfigitem('progress', 'assume-tty',
706 707 default=False,
707 708 )
708 709 coreconfigitem('progress', 'changedelay',
709 710 default=1,
710 711 )
711 712 coreconfigitem('progress', 'clear-complete',
712 713 default=True,
713 714 )
714 715 coreconfigitem('progress', 'debug',
715 716 default=False,
716 717 )
717 718 coreconfigitem('progress', 'delay',
718 719 default=3,
719 720 )
720 721 coreconfigitem('progress', 'disable',
721 722 default=False,
722 723 )
723 724 coreconfigitem('progress', 'estimateinterval',
724 725 default=60.0,
725 726 )
726 727 coreconfigitem('progress', 'format',
727 728 default=lambda: ['topic', 'bar', 'number', 'estimate'],
728 729 )
729 730 coreconfigitem('progress', 'refresh',
730 731 default=0.1,
731 732 )
732 733 coreconfigitem('progress', 'width',
733 734 default=dynamicdefault,
734 735 )
735 736 coreconfigitem('push', 'pushvars.server',
736 737 default=False,
737 738 )
738 739 coreconfigitem('server', 'bundle1',
739 740 default=True,
740 741 )
741 742 coreconfigitem('server', 'bundle1gd',
742 743 default=None,
743 744 )
744 745 coreconfigitem('server', 'bundle1.pull',
745 746 default=None,
746 747 )
747 748 coreconfigitem('server', 'bundle1gd.pull',
748 749 default=None,
749 750 )
750 751 coreconfigitem('server', 'bundle1.push',
751 752 default=None,
752 753 )
753 754 coreconfigitem('server', 'bundle1gd.push',
754 755 default=None,
755 756 )
756 757 coreconfigitem('server', 'compressionengines',
757 758 default=list,
758 759 )
759 760 coreconfigitem('server', 'concurrent-push-mode',
760 761 default='strict',
761 762 )
762 763 coreconfigitem('server', 'disablefullbundle',
763 764 default=False,
764 765 )
765 766 coreconfigitem('server', 'maxhttpheaderlen',
766 767 default=1024,
767 768 )
768 769 coreconfigitem('server', 'preferuncompressed',
769 770 default=False,
770 771 )
771 772 coreconfigitem('server', 'uncompressed',
772 773 default=True,
773 774 )
774 775 coreconfigitem('server', 'uncompressedallowsecret',
775 776 default=False,
776 777 )
777 778 coreconfigitem('server', 'validate',
778 779 default=False,
779 780 )
780 781 coreconfigitem('server', 'zliblevel',
781 782 default=-1,
782 783 )
783 784 coreconfigitem('smtp', 'host',
784 785 default=None,
785 786 )
786 787 coreconfigitem('smtp', 'local_hostname',
787 788 default=None,
788 789 )
789 790 coreconfigitem('smtp', 'password',
790 791 default=None,
791 792 )
792 793 coreconfigitem('smtp', 'port',
793 794 default=dynamicdefault,
794 795 )
795 796 coreconfigitem('smtp', 'tls',
796 797 default='none',
797 798 )
798 799 coreconfigitem('smtp', 'username',
799 800 default=None,
800 801 )
801 802 coreconfigitem('sparse', 'missingwarning',
802 803 default=True,
803 804 )
804 805 coreconfigitem('templates', '.*',
805 806 default=None,
806 807 generic=True,
807 808 )
808 809 coreconfigitem('trusted', 'groups',
809 810 default=list,
810 811 )
811 812 coreconfigitem('trusted', 'users',
812 813 default=list,
813 814 )
814 815 coreconfigitem('ui', '_usedassubrepo',
815 816 default=False,
816 817 )
817 818 coreconfigitem('ui', 'allowemptycommit',
818 819 default=False,
819 820 )
820 821 coreconfigitem('ui', 'archivemeta',
821 822 default=True,
822 823 )
823 824 coreconfigitem('ui', 'askusername',
824 825 default=False,
825 826 )
826 827 coreconfigitem('ui', 'clonebundlefallback',
827 828 default=False,
828 829 )
829 830 coreconfigitem('ui', 'clonebundleprefers',
830 831 default=list,
831 832 )
832 833 coreconfigitem('ui', 'clonebundles',
833 834 default=True,
834 835 )
835 836 coreconfigitem('ui', 'color',
836 837 default='auto',
837 838 )
838 839 coreconfigitem('ui', 'commitsubrepos',
839 840 default=False,
840 841 )
841 842 coreconfigitem('ui', 'debug',
842 843 default=False,
843 844 )
844 845 coreconfigitem('ui', 'debugger',
845 846 default=None,
846 847 )
847 848 coreconfigitem('ui', 'fallbackencoding',
848 849 default=None,
849 850 )
850 851 coreconfigitem('ui', 'forcecwd',
851 852 default=None,
852 853 )
853 854 coreconfigitem('ui', 'forcemerge',
854 855 default=None,
855 856 )
856 857 coreconfigitem('ui', 'formatdebug',
857 858 default=False,
858 859 )
859 860 coreconfigitem('ui', 'formatjson',
860 861 default=False,
861 862 )
862 863 coreconfigitem('ui', 'formatted',
863 864 default=None,
864 865 )
865 866 coreconfigitem('ui', 'graphnodetemplate',
866 867 default=None,
867 868 )
868 869 coreconfigitem('ui', 'http2debuglevel',
869 870 default=None,
870 871 )
871 872 coreconfigitem('ui', 'interactive',
872 873 default=None,
873 874 )
874 875 coreconfigitem('ui', 'interface',
875 876 default=None,
876 877 )
877 878 coreconfigitem('ui', 'interface.chunkselector',
878 879 default=None,
879 880 )
880 881 coreconfigitem('ui', 'logblockedtimes',
881 882 default=False,
882 883 )
883 884 coreconfigitem('ui', 'logtemplate',
884 885 default=None,
885 886 )
886 887 coreconfigitem('ui', 'merge',
887 888 default=None,
888 889 )
889 890 coreconfigitem('ui', 'mergemarkers',
890 891 default='basic',
891 892 )
892 893 coreconfigitem('ui', 'mergemarkertemplate',
893 894 default=('{node|short} '
894 895 '{ifeq(tags, "tip", "", '
895 896 'ifeq(tags, "", "", "{tags} "))}'
896 897 '{if(bookmarks, "{bookmarks} ")}'
897 898 '{ifeq(branch, "default", "", "{branch} ")}'
898 899 '- {author|user}: {desc|firstline}')
899 900 )
900 901 coreconfigitem('ui', 'nontty',
901 902 default=False,
902 903 )
903 904 coreconfigitem('ui', 'origbackuppath',
904 905 default=None,
905 906 )
906 907 coreconfigitem('ui', 'paginate',
907 908 default=True,
908 909 )
909 910 coreconfigitem('ui', 'patch',
910 911 default=None,
911 912 )
912 913 coreconfigitem('ui', 'portablefilenames',
913 914 default='warn',
914 915 )
915 916 coreconfigitem('ui', 'promptecho',
916 917 default=False,
917 918 )
918 919 coreconfigitem('ui', 'quiet',
919 920 default=False,
920 921 )
921 922 coreconfigitem('ui', 'quietbookmarkmove',
922 923 default=False,
923 924 )
924 925 coreconfigitem('ui', 'remotecmd',
925 926 default='hg',
926 927 )
927 928 coreconfigitem('ui', 'report_untrusted',
928 929 default=True,
929 930 )
930 931 coreconfigitem('ui', 'rollback',
931 932 default=True,
932 933 )
933 934 coreconfigitem('ui', 'slash',
934 935 default=False,
935 936 )
936 937 coreconfigitem('ui', 'ssh',
937 938 default='ssh',
938 939 )
939 940 coreconfigitem('ui', 'statuscopies',
940 941 default=False,
941 942 )
942 943 coreconfigitem('ui', 'strict',
943 944 default=False,
944 945 )
945 946 coreconfigitem('ui', 'style',
946 947 default='',
947 948 )
948 949 coreconfigitem('ui', 'supportcontact',
949 950 default=None,
950 951 )
951 952 coreconfigitem('ui', 'textwidth',
952 953 default=78,
953 954 )
954 955 coreconfigitem('ui', 'timeout',
955 956 default='600',
956 957 )
957 958 coreconfigitem('ui', 'traceback',
958 959 default=False,
959 960 )
960 961 coreconfigitem('ui', 'tweakdefaults',
961 962 default=False,
962 963 )
963 964 coreconfigitem('ui', 'usehttp2',
964 965 default=False,
965 966 )
966 967 coreconfigitem('ui', 'username',
967 968 alias=[('ui', 'user')]
968 969 )
969 970 coreconfigitem('ui', 'verbose',
970 971 default=False,
971 972 )
972 973 coreconfigitem('verify', 'skipflags',
973 974 default=None,
974 975 )
975 976 coreconfigitem('web', 'allowbz2',
976 977 default=False,
977 978 )
978 979 coreconfigitem('web', 'allowgz',
979 980 default=False,
980 981 )
981 982 coreconfigitem('web', 'allowpull',
982 983 default=True,
983 984 )
984 985 coreconfigitem('web', 'allow_push',
985 986 default=list,
986 987 )
987 988 coreconfigitem('web', 'allowzip',
988 989 default=False,
989 990 )
990 991 coreconfigitem('web', 'archivesubrepos',
991 992 default=False,
992 993 )
993 994 coreconfigitem('web', 'cache',
994 995 default=True,
995 996 )
996 997 coreconfigitem('web', 'contact',
997 998 default=None,
998 999 )
999 1000 coreconfigitem('web', 'deny_push',
1000 1001 default=list,
1001 1002 )
1002 1003 coreconfigitem('web', 'guessmime',
1003 1004 default=False,
1004 1005 )
1005 1006 coreconfigitem('web', 'hidden',
1006 1007 default=False,
1007 1008 )
1008 1009 coreconfigitem('web', 'labels',
1009 1010 default=list,
1010 1011 )
1011 1012 coreconfigitem('web', 'logoimg',
1012 1013 default='hglogo.png',
1013 1014 )
1014 1015 coreconfigitem('web', 'logourl',
1015 1016 default='https://mercurial-scm.org/',
1016 1017 )
1017 1018 coreconfigitem('web', 'accesslog',
1018 1019 default='-',
1019 1020 )
1020 1021 coreconfigitem('web', 'address',
1021 1022 default='',
1022 1023 )
1023 1024 coreconfigitem('web', 'allow_archive',
1024 1025 default=list,
1025 1026 )
1026 1027 coreconfigitem('web', 'allow_read',
1027 1028 default=list,
1028 1029 )
1029 1030 coreconfigitem('web', 'baseurl',
1030 1031 default=None,
1031 1032 )
1032 1033 coreconfigitem('web', 'cacerts',
1033 1034 default=None,
1034 1035 )
1035 1036 coreconfigitem('web', 'certificate',
1036 1037 default=None,
1037 1038 )
1038 1039 coreconfigitem('web', 'collapse',
1039 1040 default=False,
1040 1041 )
1041 1042 coreconfigitem('web', 'csp',
1042 1043 default=None,
1043 1044 )
1044 1045 coreconfigitem('web', 'deny_read',
1045 1046 default=list,
1046 1047 )
1047 1048 coreconfigitem('web', 'descend',
1048 1049 default=True,
1049 1050 )
1050 1051 coreconfigitem('web', 'description',
1051 1052 default="",
1052 1053 )
1053 1054 coreconfigitem('web', 'encoding',
1054 1055 default=lambda: encoding.encoding,
1055 1056 )
1056 1057 coreconfigitem('web', 'errorlog',
1057 1058 default='-',
1058 1059 )
1059 1060 coreconfigitem('web', 'ipv6',
1060 1061 default=False,
1061 1062 )
1062 1063 coreconfigitem('web', 'maxchanges',
1063 1064 default=10,
1064 1065 )
1065 1066 coreconfigitem('web', 'maxfiles',
1066 1067 default=10,
1067 1068 )
1068 1069 coreconfigitem('web', 'maxshortchanges',
1069 1070 default=60,
1070 1071 )
1071 1072 coreconfigitem('web', 'motd',
1072 1073 default='',
1073 1074 )
1074 1075 coreconfigitem('web', 'name',
1075 1076 default=dynamicdefault,
1076 1077 )
1077 1078 coreconfigitem('web', 'port',
1078 1079 default=8000,
1079 1080 )
1080 1081 coreconfigitem('web', 'prefix',
1081 1082 default='',
1082 1083 )
1083 1084 coreconfigitem('web', 'push_ssl',
1084 1085 default=True,
1085 1086 )
1086 1087 coreconfigitem('web', 'refreshinterval',
1087 1088 default=20,
1088 1089 )
1089 1090 coreconfigitem('web', 'staticurl',
1090 1091 default=None,
1091 1092 )
1092 1093 coreconfigitem('web', 'stripes',
1093 1094 default=1,
1094 1095 )
1095 1096 coreconfigitem('web', 'style',
1096 1097 default='paper',
1097 1098 )
1098 1099 coreconfigitem('web', 'templates',
1099 1100 default=None,
1100 1101 )
1101 1102 coreconfigitem('web', 'view',
1102 1103 default='served',
1103 1104 )
1104 1105 coreconfigitem('worker', 'backgroundclose',
1105 1106 default=dynamicdefault,
1106 1107 )
1107 1108 # Windows defaults to a limit of 512 open files. A buffer of 128
1108 1109 # should give us enough headway.
1109 1110 coreconfigitem('worker', 'backgroundclosemaxqueue',
1110 1111 default=384,
1111 1112 )
1112 1113 coreconfigitem('worker', 'backgroundcloseminfilecount',
1113 1114 default=2048,
1114 1115 )
1115 1116 coreconfigitem('worker', 'backgroundclosethreadcount',
1116 1117 default=4,
1117 1118 )
1118 1119 coreconfigitem('worker', 'numcpus',
1119 1120 default=None,
1120 1121 )
1121 1122
1122 1123 # Rebase related configuration moved to core because other extension are doing
1123 1124 # strange things. For example, shelve import the extensions to reuse some bit
1124 1125 # without formally loading it.
1125 1126 coreconfigitem('commands', 'rebase.requiredest',
1126 1127 default=False,
1127 1128 )
1128 1129 coreconfigitem('experimental', 'rebaseskipobsolete',
1129 1130 default=True,
1130 1131 )
1131 1132 coreconfigitem('rebase', 'singletransaction',
1132 1133 default=False,
1133 1134 )
@@ -1,1126 +1,1126 b''
1 1 # obsolete.py - obsolete markers handling
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
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 """Obsolete marker handling
10 10
11 11 An obsolete marker maps an old changeset to a list of new
12 12 changesets. If the list of new changesets is empty, the old changeset
13 13 is said to be "killed". Otherwise, the old changeset is being
14 14 "replaced" by the new changesets.
15 15
16 16 Obsolete markers can be used to record and distribute changeset graph
17 17 transformations performed by history rewrite operations, and help
18 18 building new tools to reconcile conflicting rewrite actions. To
19 19 facilitate conflict resolution, markers include various annotations
20 20 besides old and news changeset identifiers, such as creation date or
21 21 author name.
22 22
23 23 The old obsoleted changeset is called a "predecessor" and possible
24 24 replacements are called "successors". Markers that used changeset X as
25 25 a predecessor are called "successor markers of X" because they hold
26 26 information about the successors of X. Markers that use changeset Y as
27 27 a successors are call "predecessor markers of Y" because they hold
28 28 information about the predecessors of Y.
29 29
30 30 Examples:
31 31
32 32 - When changeset A is replaced by changeset A', one marker is stored:
33 33
34 34 (A, (A',))
35 35
36 36 - When changesets A and B are folded into a new changeset C, two markers are
37 37 stored:
38 38
39 39 (A, (C,)) and (B, (C,))
40 40
41 41 - When changeset A is simply "pruned" from the graph, a marker is created:
42 42
43 43 (A, ())
44 44
45 45 - When changeset A is split into B and C, a single marker is used:
46 46
47 47 (A, (B, C))
48 48
49 49 We use a single marker to distinguish the "split" case from the "divergence"
50 50 case. If two independent operations rewrite the same changeset A in to A' and
51 51 A'', we have an error case: divergent rewriting. We can detect it because
52 52 two markers will be created independently:
53 53
54 54 (A, (B,)) and (A, (C,))
55 55
56 56 Format
57 57 ------
58 58
59 59 Markers are stored in an append-only file stored in
60 60 '.hg/store/obsstore'.
61 61
62 62 The file starts with a version header:
63 63
64 64 - 1 unsigned byte: version number, starting at zero.
65 65
66 66 The header is followed by the markers. Marker format depend of the version. See
67 67 comment associated with each format for details.
68 68
69 69 """
70 70 from __future__ import absolute_import
71 71
72 72 import errno
73 73 import struct
74 74
75 75 from .i18n import _
76 76 from . import (
77 77 error,
78 78 node,
79 79 obsutil,
80 80 phases,
81 81 policy,
82 82 util,
83 83 )
84 84
85 85 parsers = policy.importmod(r'parsers')
86 86
87 87 _pack = struct.pack
88 88 _unpack = struct.unpack
89 89 _calcsize = struct.calcsize
90 90 propertycache = util.propertycache
91 91
92 92 # the obsolete feature is not mature enough to be enabled by default.
93 93 # you have to rely on third party extension extension to enable this.
94 94 _enabled = False
95 95
96 96 # Options for obsolescence
97 97 createmarkersopt = 'createmarkers'
98 98 allowunstableopt = 'allowunstable'
99 99 exchangeopt = 'exchange'
100 100
101 101 def _getoptionvalue(repo, option):
102 102 """Returns True if the given repository has the given obsolete option
103 103 enabled.
104 104 """
105 105 configkey = 'evolution.%s' % option
106 106 newconfig = repo.ui.configbool('experimental', configkey)
107 107
108 108 # Return the value only if defined
109 109 if newconfig is not None:
110 110 return newconfig
111 111
112 112 # Fallback on generic option
113 113 try:
114 114 return repo.ui.configbool('experimental', 'evolution')
115 115 except (error.ConfigError, AttributeError):
116 116 # Fallback on old-fashion config
117 117 # inconsistent config: experimental.evolution
118 118 result = set(repo.ui.configlist('experimental', 'evolution'))
119 119
120 120 if 'all' in result:
121 121 return True
122 122
123 123 # For migration purposes, temporarily return true if the config hasn't
124 124 # been set but _enabled is true.
125 125 if len(result) == 0 and _enabled:
126 126 return True
127 127
128 128 # Temporary hack for next check
129 129 newconfig = repo.ui.config('experimental', 'evolution.createmarkers')
130 130 if newconfig:
131 131 result.add('createmarkers')
132 132
133 133 return option in result
134 134
135 135 def isenabled(repo, option):
136 136 """Returns True if the given repository has the given obsolete option
137 137 enabled.
138 138 """
139 139 createmarkersvalue = _getoptionvalue(repo, createmarkersopt)
140 140 unstabluevalue = _getoptionvalue(repo, allowunstableopt)
141 141 exchangevalue = _getoptionvalue(repo, exchangeopt)
142 142
143 143 # createmarkers must be enabled if other options are enabled
144 144 if ((unstabluevalue or exchangevalue) and not createmarkersvalue):
145 145 raise error.Abort(_("'createmarkers' obsolete option must be enabled "
146 146 "if other obsolete options are enabled"))
147 147
148 148 return _getoptionvalue(repo, option)
149 149
150 150 ### obsolescence marker flag
151 151
152 152 ## bumpedfix flag
153 153 #
154 154 # When a changeset A' succeed to a changeset A which became public, we call A'
155 155 # "bumped" because it's a successors of a public changesets
156 156 #
157 157 # o A' (bumped)
158 158 # |`:
159 159 # | o A
160 160 # |/
161 161 # o Z
162 162 #
163 163 # The way to solve this situation is to create a new changeset Ad as children
164 164 # of A. This changeset have the same content than A'. So the diff from A to A'
165 165 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
166 166 #
167 167 # o Ad
168 168 # |`:
169 169 # | x A'
170 170 # |'|
171 171 # o | A
172 172 # |/
173 173 # o Z
174 174 #
175 175 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
176 176 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
177 177 # This flag mean that the successors express the changes between the public and
178 178 # bumped version and fix the situation, breaking the transitivity of
179 179 # "bumped" here.
180 180 bumpedfix = 1
181 181 usingsha256 = 2
182 182
183 183 ## Parsing and writing of version "0"
184 184 #
185 185 # The header is followed by the markers. Each marker is made of:
186 186 #
187 187 # - 1 uint8 : number of new changesets "N", can be zero.
188 188 #
189 189 # - 1 uint32: metadata size "M" in bytes.
190 190 #
191 191 # - 1 byte: a bit field. It is reserved for flags used in common
192 192 # obsolete marker operations, to avoid repeated decoding of metadata
193 193 # entries.
194 194 #
195 195 # - 20 bytes: obsoleted changeset identifier.
196 196 #
197 197 # - N*20 bytes: new changesets identifiers.
198 198 #
199 199 # - M bytes: metadata as a sequence of nul-terminated strings. Each
200 200 # string contains a key and a value, separated by a colon ':', without
201 201 # additional encoding. Keys cannot contain '\0' or ':' and values
202 202 # cannot contain '\0'.
203 203 _fm0version = 0
204 204 _fm0fixed = '>BIB20s'
205 205 _fm0node = '20s'
206 206 _fm0fsize = _calcsize(_fm0fixed)
207 207 _fm0fnodesize = _calcsize(_fm0node)
208 208
209 209 def _fm0readmarkers(data, off, stop):
210 210 # Loop on markers
211 211 while off < stop:
212 212 # read fixed part
213 213 cur = data[off:off + _fm0fsize]
214 214 off += _fm0fsize
215 215 numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
216 216 # read replacement
217 217 sucs = ()
218 218 if numsuc:
219 219 s = (_fm0fnodesize * numsuc)
220 220 cur = data[off:off + s]
221 221 sucs = _unpack(_fm0node * numsuc, cur)
222 222 off += s
223 223 # read metadata
224 224 # (metadata will be decoded on demand)
225 225 metadata = data[off:off + mdsize]
226 226 if len(metadata) != mdsize:
227 227 raise error.Abort(_('parsing obsolete marker: metadata is too '
228 228 'short, %d bytes expected, got %d')
229 229 % (mdsize, len(metadata)))
230 230 off += mdsize
231 231 metadata = _fm0decodemeta(metadata)
232 232 try:
233 233 when, offset = metadata.pop('date', '0 0').split(' ')
234 234 date = float(when), int(offset)
235 235 except ValueError:
236 236 date = (0., 0)
237 237 parents = None
238 238 if 'p2' in metadata:
239 239 parents = (metadata.pop('p1', None), metadata.pop('p2', None))
240 240 elif 'p1' in metadata:
241 241 parents = (metadata.pop('p1', None),)
242 242 elif 'p0' in metadata:
243 243 parents = ()
244 244 if parents is not None:
245 245 try:
246 246 parents = tuple(node.bin(p) for p in parents)
247 247 # if parent content is not a nodeid, drop the data
248 248 for p in parents:
249 249 if len(p) != 20:
250 250 parents = None
251 251 break
252 252 except TypeError:
253 253 # if content cannot be translated to nodeid drop the data.
254 254 parents = None
255 255
256 256 metadata = tuple(sorted(metadata.iteritems()))
257 257
258 258 yield (pre, sucs, flags, metadata, date, parents)
259 259
260 260 def _fm0encodeonemarker(marker):
261 261 pre, sucs, flags, metadata, date, parents = marker
262 262 if flags & usingsha256:
263 263 raise error.Abort(_('cannot handle sha256 with old obsstore format'))
264 264 metadata = dict(metadata)
265 265 time, tz = date
266 266 metadata['date'] = '%r %i' % (time, tz)
267 267 if parents is not None:
268 268 if not parents:
269 269 # mark that we explicitly recorded no parents
270 270 metadata['p0'] = ''
271 271 for i, p in enumerate(parents, 1):
272 272 metadata['p%i' % i] = node.hex(p)
273 273 metadata = _fm0encodemeta(metadata)
274 274 numsuc = len(sucs)
275 275 format = _fm0fixed + (_fm0node * numsuc)
276 276 data = [numsuc, len(metadata), flags, pre]
277 277 data.extend(sucs)
278 278 return _pack(format, *data) + metadata
279 279
280 280 def _fm0encodemeta(meta):
281 281 """Return encoded metadata string to string mapping.
282 282
283 283 Assume no ':' in key and no '\0' in both key and value."""
284 284 for key, value in meta.iteritems():
285 285 if ':' in key or '\0' in key:
286 286 raise ValueError("':' and '\0' are forbidden in metadata key'")
287 287 if '\0' in value:
288 288 raise ValueError("':' is forbidden in metadata value'")
289 289 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
290 290
291 291 def _fm0decodemeta(data):
292 292 """Return string to string dictionary from encoded version."""
293 293 d = {}
294 294 for l in data.split('\0'):
295 295 if l:
296 296 key, value = l.split(':')
297 297 d[key] = value
298 298 return d
299 299
300 300 ## Parsing and writing of version "1"
301 301 #
302 302 # The header is followed by the markers. Each marker is made of:
303 303 #
304 304 # - uint32: total size of the marker (including this field)
305 305 #
306 306 # - float64: date in seconds since epoch
307 307 #
308 308 # - int16: timezone offset in minutes
309 309 #
310 310 # - uint16: a bit field. It is reserved for flags used in common
311 311 # obsolete marker operations, to avoid repeated decoding of metadata
312 312 # entries.
313 313 #
314 314 # - uint8: number of successors "N", can be zero.
315 315 #
316 316 # - uint8: number of parents "P", can be zero.
317 317 #
318 318 # 0: parents data stored but no parent,
319 319 # 1: one parent stored,
320 320 # 2: two parents stored,
321 321 # 3: no parent data stored
322 322 #
323 323 # - uint8: number of metadata entries M
324 324 #
325 325 # - 20 or 32 bytes: predecessor changeset identifier.
326 326 #
327 327 # - N*(20 or 32) bytes: successors changesets identifiers.
328 328 #
329 329 # - P*(20 or 32) bytes: parents of the predecessors changesets.
330 330 #
331 331 # - M*(uint8, uint8): size of all metadata entries (key and value)
332 332 #
333 333 # - remaining bytes: the metadata, each (key, value) pair after the other.
334 334 _fm1version = 1
335 335 _fm1fixed = '>IdhHBBB20s'
336 336 _fm1nodesha1 = '20s'
337 337 _fm1nodesha256 = '32s'
338 338 _fm1nodesha1size = _calcsize(_fm1nodesha1)
339 339 _fm1nodesha256size = _calcsize(_fm1nodesha256)
340 340 _fm1fsize = _calcsize(_fm1fixed)
341 341 _fm1parentnone = 3
342 342 _fm1parentshift = 14
343 343 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
344 344 _fm1metapair = 'BB'
345 345 _fm1metapairsize = _calcsize(_fm1metapair)
346 346
347 347 def _fm1purereadmarkers(data, off, stop):
348 348 # make some global constants local for performance
349 349 noneflag = _fm1parentnone
350 350 sha2flag = usingsha256
351 351 sha1size = _fm1nodesha1size
352 352 sha2size = _fm1nodesha256size
353 353 sha1fmt = _fm1nodesha1
354 354 sha2fmt = _fm1nodesha256
355 355 metasize = _fm1metapairsize
356 356 metafmt = _fm1metapair
357 357 fsize = _fm1fsize
358 358 unpack = _unpack
359 359
360 360 # Loop on markers
361 361 ufixed = struct.Struct(_fm1fixed).unpack
362 362
363 363 while off < stop:
364 364 # read fixed part
365 365 o1 = off + fsize
366 366 t, secs, tz, flags, numsuc, numpar, nummeta, prec = ufixed(data[off:o1])
367 367
368 368 if flags & sha2flag:
369 369 # FIXME: prec was read as a SHA1, needs to be amended
370 370
371 371 # read 0 or more successors
372 372 if numsuc == 1:
373 373 o2 = o1 + sha2size
374 374 sucs = (data[o1:o2],)
375 375 else:
376 376 o2 = o1 + sha2size * numsuc
377 377 sucs = unpack(sha2fmt * numsuc, data[o1:o2])
378 378
379 379 # read parents
380 380 if numpar == noneflag:
381 381 o3 = o2
382 382 parents = None
383 383 elif numpar == 1:
384 384 o3 = o2 + sha2size
385 385 parents = (data[o2:o3],)
386 386 else:
387 387 o3 = o2 + sha2size * numpar
388 388 parents = unpack(sha2fmt * numpar, data[o2:o3])
389 389 else:
390 390 # read 0 or more successors
391 391 if numsuc == 1:
392 392 o2 = o1 + sha1size
393 393 sucs = (data[o1:o2],)
394 394 else:
395 395 o2 = o1 + sha1size * numsuc
396 396 sucs = unpack(sha1fmt * numsuc, data[o1:o2])
397 397
398 398 # read parents
399 399 if numpar == noneflag:
400 400 o3 = o2
401 401 parents = None
402 402 elif numpar == 1:
403 403 o3 = o2 + sha1size
404 404 parents = (data[o2:o3],)
405 405 else:
406 406 o3 = o2 + sha1size * numpar
407 407 parents = unpack(sha1fmt * numpar, data[o2:o3])
408 408
409 409 # read metadata
410 410 off = o3 + metasize * nummeta
411 411 metapairsize = unpack('>' + (metafmt * nummeta), data[o3:off])
412 412 metadata = []
413 413 for idx in xrange(0, len(metapairsize), 2):
414 414 o1 = off + metapairsize[idx]
415 415 o2 = o1 + metapairsize[idx + 1]
416 416 metadata.append((data[off:o1], data[o1:o2]))
417 417 off = o2
418 418
419 419 yield (prec, sucs, flags, tuple(metadata), (secs, tz * 60), parents)
420 420
421 421 def _fm1encodeonemarker(marker):
422 422 pre, sucs, flags, metadata, date, parents = marker
423 423 # determine node size
424 424 _fm1node = _fm1nodesha1
425 425 if flags & usingsha256:
426 426 _fm1node = _fm1nodesha256
427 427 numsuc = len(sucs)
428 428 numextranodes = numsuc
429 429 if parents is None:
430 430 numpar = _fm1parentnone
431 431 else:
432 432 numpar = len(parents)
433 433 numextranodes += numpar
434 434 formatnodes = _fm1node * numextranodes
435 435 formatmeta = _fm1metapair * len(metadata)
436 436 format = _fm1fixed + formatnodes + formatmeta
437 437 # tz is stored in minutes so we divide by 60
438 438 tz = date[1]//60
439 439 data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
440 440 data.extend(sucs)
441 441 if parents is not None:
442 442 data.extend(parents)
443 443 totalsize = _calcsize(format)
444 444 for key, value in metadata:
445 445 lk = len(key)
446 446 lv = len(value)
447 447 if lk > 255:
448 448 msg = ('obsstore metadata key cannot be longer than 255 bytes'
449 449 ' (key "%s" is %u bytes)') % (key, lk)
450 450 raise error.ProgrammingError(msg)
451 451 if lv > 255:
452 452 msg = ('obsstore metadata value cannot be longer than 255 bytes'
453 453 ' (value "%s" for key "%s" is %u bytes)') % (value, key, lv)
454 454 raise error.ProgrammingError(msg)
455 455 data.append(lk)
456 456 data.append(lv)
457 457 totalsize += lk + lv
458 458 data[0] = totalsize
459 459 data = [_pack(format, *data)]
460 460 for key, value in metadata:
461 461 data.append(key)
462 462 data.append(value)
463 463 return ''.join(data)
464 464
465 465 def _fm1readmarkers(data, off, stop):
466 466 native = getattr(parsers, 'fm1readmarkers', None)
467 467 if not native:
468 468 return _fm1purereadmarkers(data, off, stop)
469 469 return native(data, off, stop)
470 470
471 471 # mapping to read/write various marker formats
472 472 # <version> -> (decoder, encoder)
473 473 formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
474 474 _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
475 475
476 476 def _readmarkerversion(data):
477 477 return _unpack('>B', data[0:1])[0]
478 478
479 479 @util.nogc
480 480 def _readmarkers(data, off=None, stop=None):
481 481 """Read and enumerate markers from raw data"""
482 482 diskversion = _readmarkerversion(data)
483 483 if not off:
484 484 off = 1 # skip 1 byte version number
485 485 if stop is None:
486 486 stop = len(data)
487 487 if diskversion not in formats:
488 488 msg = _('parsing obsolete marker: unknown version %r') % diskversion
489 489 raise error.UnknownVersion(msg, version=diskversion)
490 490 return diskversion, formats[diskversion][0](data, off, stop)
491 491
492 492 def encodeheader(version=_fm0version):
493 493 return _pack('>B', version)
494 494
495 495 def encodemarkers(markers, addheader=False, version=_fm0version):
496 496 # Kept separate from flushmarkers(), it will be reused for
497 497 # markers exchange.
498 498 encodeone = formats[version][1]
499 499 if addheader:
500 500 yield encodeheader(version)
501 501 for marker in markers:
502 502 yield encodeone(marker)
503 503
504 504 @util.nogc
505 505 def _addsuccessors(successors, markers):
506 506 for mark in markers:
507 507 successors.setdefault(mark[0], set()).add(mark)
508 508
509 509 def _addprecursors(*args, **kwargs):
510 510 msg = ("'obsolete._addprecursors' is deprecated, "
511 511 "use 'obsolete._addpredecessors'")
512 512 util.nouideprecwarn(msg, '4.4')
513 513
514 514 return _addpredecessors(*args, **kwargs)
515 515
516 516 @util.nogc
517 517 def _addpredecessors(predecessors, markers):
518 518 for mark in markers:
519 519 for suc in mark[1]:
520 520 predecessors.setdefault(suc, set()).add(mark)
521 521
522 522 @util.nogc
523 523 def _addchildren(children, markers):
524 524 for mark in markers:
525 525 parents = mark[5]
526 526 if parents is not None:
527 527 for p in parents:
528 528 children.setdefault(p, set()).add(mark)
529 529
530 530 def _checkinvalidmarkers(markers):
531 531 """search for marker with invalid data and raise error if needed
532 532
533 533 Exist as a separated function to allow the evolve extension for a more
534 534 subtle handling.
535 535 """
536 536 for mark in markers:
537 537 if node.nullid in mark[1]:
538 538 raise error.Abort(_('bad obsolescence marker detected: '
539 539 'invalid successors nullid'))
540 540
541 541 class obsstore(object):
542 542 """Store obsolete markers
543 543
544 544 Markers can be accessed with two mappings:
545 545 - predecessors[x] -> set(markers on predecessors edges of x)
546 546 - successors[x] -> set(markers on successors edges of x)
547 547 - children[x] -> set(markers on predecessors edges of children(x)
548 548 """
549 549
550 550 fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
551 551 # prec: nodeid, predecessors changesets
552 552 # succs: tuple of nodeid, successor changesets (0-N length)
553 553 # flag: integer, flag field carrying modifier for the markers (see doc)
554 554 # meta: binary blob, encoded metadata dictionary
555 555 # date: (float, int) tuple, date of marker creation
556 556 # parents: (tuple of nodeid) or None, parents of predecessors
557 557 # None is used when no data has been recorded
558 558
559 559 def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
560 560 # caches for various obsolescence related cache
561 561 self.caches = {}
562 562 self.svfs = svfs
563 563 self._defaultformat = defaultformat
564 564 self._readonly = readonly
565 565
566 566 def __iter__(self):
567 567 return iter(self._all)
568 568
569 569 def __len__(self):
570 570 return len(self._all)
571 571
572 572 def __nonzero__(self):
573 573 if not self._cached('_all'):
574 574 try:
575 575 return self.svfs.stat('obsstore').st_size > 1
576 576 except OSError as inst:
577 577 if inst.errno != errno.ENOENT:
578 578 raise
579 579 # just build an empty _all list if no obsstore exists, which
580 580 # avoids further stat() syscalls
581 581 return bool(self._all)
582 582
583 583 __bool__ = __nonzero__
584 584
585 585 @property
586 586 def readonly(self):
587 587 """True if marker creation is disabled
588 588
589 589 Remove me in the future when obsolete marker is always on."""
590 590 return self._readonly
591 591
592 592 def create(self, transaction, prec, succs=(), flag=0, parents=None,
593 593 date=None, metadata=None, ui=None):
594 594 """obsolete: add a new obsolete marker
595 595
596 596 * ensuring it is hashable
597 597 * check mandatory metadata
598 598 * encode metadata
599 599
600 600 If you are a human writing code creating marker you want to use the
601 601 `createmarkers` function in this module instead.
602 602
603 603 return True if a new marker have been added, False if the markers
604 604 already existed (no op).
605 605 """
606 606 if metadata is None:
607 607 metadata = {}
608 608 if date is None:
609 609 if 'date' in metadata:
610 610 # as a courtesy for out-of-tree extensions
611 611 date = util.parsedate(metadata.pop('date'))
612 612 elif ui is not None:
613 613 date = ui.configdate('devel', 'default-date')
614 614 if date is None:
615 615 date = util.makedate()
616 616 else:
617 617 date = util.makedate()
618 618 if len(prec) != 20:
619 619 raise ValueError(prec)
620 620 for succ in succs:
621 621 if len(succ) != 20:
622 622 raise ValueError(succ)
623 623 if prec in succs:
624 624 raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
625 625
626 626 metadata = tuple(sorted(metadata.iteritems()))
627 627
628 628 marker = (bytes(prec), tuple(succs), int(flag), metadata, date, parents)
629 629 return bool(self.add(transaction, [marker]))
630 630
631 631 def add(self, transaction, markers):
632 632 """Add new markers to the store
633 633
634 634 Take care of filtering duplicate.
635 635 Return the number of new marker."""
636 636 if self._readonly:
637 637 raise error.Abort(_('creating obsolete markers is not enabled on '
638 638 'this repo'))
639 639 known = set()
640 640 getsuccessors = self.successors.get
641 641 new = []
642 642 for m in markers:
643 643 if m not in getsuccessors(m[0], ()) and m not in known:
644 644 known.add(m)
645 645 new.append(m)
646 646 if new:
647 647 f = self.svfs('obsstore', 'ab')
648 648 try:
649 649 offset = f.tell()
650 650 transaction.add('obsstore', offset)
651 651 # offset == 0: new file - add the version header
652 652 data = b''.join(encodemarkers(new, offset == 0, self._version))
653 653 f.write(data)
654 654 finally:
655 655 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
656 656 # call 'filecacheentry.refresh()' here
657 657 f.close()
658 658 addedmarkers = transaction.changes.get('obsmarkers')
659 659 if addedmarkers is not None:
660 660 addedmarkers.update(new)
661 661 self._addmarkers(new, data)
662 662 # new marker *may* have changed several set. invalidate the cache.
663 663 self.caches.clear()
664 664 # records the number of new markers for the transaction hooks
665 665 previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
666 666 transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
667 667 return len(new)
668 668
669 669 def mergemarkers(self, transaction, data):
670 670 """merge a binary stream of markers inside the obsstore
671 671
672 672 Returns the number of new markers added."""
673 673 version, markers = _readmarkers(data)
674 674 return self.add(transaction, markers)
675 675
676 676 @propertycache
677 677 def _data(self):
678 678 return self.svfs.tryread('obsstore')
679 679
680 680 @propertycache
681 681 def _version(self):
682 682 if len(self._data) >= 1:
683 683 return _readmarkerversion(self._data)
684 684 else:
685 685 return self._defaultformat
686 686
687 687 @propertycache
688 688 def _all(self):
689 689 data = self._data
690 690 if not data:
691 691 return []
692 692 self._version, markers = _readmarkers(data)
693 693 markers = list(markers)
694 694 _checkinvalidmarkers(markers)
695 695 return markers
696 696
697 697 @propertycache
698 698 def successors(self):
699 699 successors = {}
700 700 _addsuccessors(successors, self._all)
701 701 return successors
702 702
703 703 @property
704 704 def precursors(self):
705 705 msg = ("'obsstore.precursors' is deprecated, "
706 706 "use 'obsstore.predecessors'")
707 707 util.nouideprecwarn(msg, '4.4')
708 708
709 709 return self.predecessors
710 710
711 711 @propertycache
712 712 def predecessors(self):
713 713 predecessors = {}
714 714 _addpredecessors(predecessors, self._all)
715 715 return predecessors
716 716
717 717 @propertycache
718 718 def children(self):
719 719 children = {}
720 720 _addchildren(children, self._all)
721 721 return children
722 722
723 723 def _cached(self, attr):
724 724 return attr in self.__dict__
725 725
726 726 def _addmarkers(self, markers, rawdata):
727 727 markers = list(markers) # to allow repeated iteration
728 728 self._data = self._data + rawdata
729 729 self._all.extend(markers)
730 730 if self._cached('successors'):
731 731 _addsuccessors(self.successors, markers)
732 732 if self._cached('predecessors'):
733 733 _addpredecessors(self.predecessors, markers)
734 734 if self._cached('children'):
735 735 _addchildren(self.children, markers)
736 736 _checkinvalidmarkers(markers)
737 737
738 738 def relevantmarkers(self, nodes):
739 739 """return a set of all obsolescence markers relevant to a set of nodes.
740 740
741 741 "relevant" to a set of nodes mean:
742 742
743 743 - marker that use this changeset as successor
744 744 - prune marker of direct children on this changeset
745 745 - recursive application of the two rules on predecessors of these
746 746 markers
747 747
748 748 It is a set so you cannot rely on order."""
749 749
750 750 pendingnodes = set(nodes)
751 751 seenmarkers = set()
752 752 seennodes = set(pendingnodes)
753 753 precursorsmarkers = self.predecessors
754 754 succsmarkers = self.successors
755 755 children = self.children
756 756 while pendingnodes:
757 757 direct = set()
758 758 for current in pendingnodes:
759 759 direct.update(precursorsmarkers.get(current, ()))
760 760 pruned = [m for m in children.get(current, ()) if not m[1]]
761 761 direct.update(pruned)
762 762 pruned = [m for m in succsmarkers.get(current, ()) if not m[1]]
763 763 direct.update(pruned)
764 764 direct -= seenmarkers
765 765 pendingnodes = set([m[0] for m in direct])
766 766 seenmarkers |= direct
767 767 pendingnodes -= seennodes
768 768 seennodes |= pendingnodes
769 769 return seenmarkers
770 770
771 771 def makestore(ui, repo):
772 772 """Create an obsstore instance from a repo."""
773 773 # read default format for new obsstore.
774 774 # developer config: format.obsstore-version
775 775 defaultformat = ui.configint('format', 'obsstore-version')
776 776 # rely on obsstore class default when possible.
777 777 kwargs = {}
778 778 if defaultformat is not None:
779 779 kwargs['defaultformat'] = defaultformat
780 780 readonly = not isenabled(repo, createmarkersopt)
781 781 store = obsstore(repo.svfs, readonly=readonly, **kwargs)
782 782 if store and readonly:
783 783 ui.warn(_('obsolete feature not enabled but %i markers found!\n')
784 784 % len(list(store)))
785 785 return store
786 786
787 787 def commonversion(versions):
788 788 """Return the newest version listed in both versions and our local formats.
789 789
790 790 Returns None if no common version exists.
791 791 """
792 792 versions.sort(reverse=True)
793 793 # search for highest version known on both side
794 794 for v in versions:
795 795 if v in formats:
796 796 return v
797 797 return None
798 798
799 799 # arbitrary picked to fit into 8K limit from HTTP server
800 800 # you have to take in account:
801 801 # - the version header
802 802 # - the base85 encoding
803 803 _maxpayload = 5300
804 804
805 805 def _pushkeyescape(markers):
806 806 """encode markers into a dict suitable for pushkey exchange
807 807
808 808 - binary data is base85 encoded
809 809 - split in chunks smaller than 5300 bytes"""
810 810 keys = {}
811 811 parts = []
812 812 currentlen = _maxpayload * 2 # ensure we create a new part
813 813 for marker in markers:
814 814 nextdata = _fm0encodeonemarker(marker)
815 815 if (len(nextdata) + currentlen > _maxpayload):
816 816 currentpart = []
817 817 currentlen = 0
818 818 parts.append(currentpart)
819 819 currentpart.append(nextdata)
820 820 currentlen += len(nextdata)
821 821 for idx, part in enumerate(reversed(parts)):
822 822 data = ''.join([_pack('>B', _fm0version)] + part)
823 823 keys['dump%i' % idx] = util.b85encode(data)
824 824 return keys
825 825
826 826 def listmarkers(repo):
827 827 """List markers over pushkey"""
828 828 if not repo.obsstore:
829 829 return {}
830 830 return _pushkeyescape(sorted(repo.obsstore))
831 831
832 832 def pushmarker(repo, key, old, new):
833 833 """Push markers over pushkey"""
834 834 if not key.startswith('dump'):
835 835 repo.ui.warn(_('unknown key: %r') % key)
836 836 return False
837 837 if old:
838 838 repo.ui.warn(_('unexpected old value for %r') % key)
839 839 return False
840 840 data = util.b85decode(new)
841 841 lock = repo.lock()
842 842 try:
843 843 tr = repo.transaction('pushkey: obsolete markers')
844 844 try:
845 845 repo.obsstore.mergemarkers(tr, data)
846 846 repo.invalidatevolatilesets()
847 847 tr.close()
848 848 return True
849 849 finally:
850 850 tr.release()
851 851 finally:
852 852 lock.release()
853 853
854 854 # keep compatibility for the 4.3 cycle
855 855 def allprecursors(obsstore, nodes, ignoreflags=0):
856 856 movemsg = 'obsolete.allprecursors moved to obsutil.allprecursors'
857 857 util.nouideprecwarn(movemsg, '4.3')
858 858 return obsutil.allprecursors(obsstore, nodes, ignoreflags)
859 859
860 860 def allsuccessors(obsstore, nodes, ignoreflags=0):
861 861 movemsg = 'obsolete.allsuccessors moved to obsutil.allsuccessors'
862 862 util.nouideprecwarn(movemsg, '4.3')
863 863 return obsutil.allsuccessors(obsstore, nodes, ignoreflags)
864 864
865 865 def marker(repo, data):
866 866 movemsg = 'obsolete.marker moved to obsutil.marker'
867 867 repo.ui.deprecwarn(movemsg, '4.3')
868 868 return obsutil.marker(repo, data)
869 869
870 870 def getmarkers(repo, nodes=None, exclusive=False):
871 871 movemsg = 'obsolete.getmarkers moved to obsutil.getmarkers'
872 872 repo.ui.deprecwarn(movemsg, '4.3')
873 873 return obsutil.getmarkers(repo, nodes=nodes, exclusive=exclusive)
874 874
875 875 def exclusivemarkers(repo, nodes):
876 876 movemsg = 'obsolete.exclusivemarkers moved to obsutil.exclusivemarkers'
877 877 repo.ui.deprecwarn(movemsg, '4.3')
878 878 return obsutil.exclusivemarkers(repo, nodes)
879 879
880 880 def foreground(repo, nodes):
881 881 movemsg = 'obsolete.foreground moved to obsutil.foreground'
882 882 repo.ui.deprecwarn(movemsg, '4.3')
883 883 return obsutil.foreground(repo, nodes)
884 884
885 885 def successorssets(repo, initialnode, cache=None):
886 886 movemsg = 'obsolete.successorssets moved to obsutil.successorssets'
887 887 repo.ui.deprecwarn(movemsg, '4.3')
888 888 return obsutil.successorssets(repo, initialnode, cache=cache)
889 889
890 890 # mapping of 'set-name' -> <function to compute this set>
891 891 cachefuncs = {}
892 892 def cachefor(name):
893 893 """Decorator to register a function as computing the cache for a set"""
894 894 def decorator(func):
895 895 if name in cachefuncs:
896 896 msg = "duplicated registration for volatileset '%s' (existing: %r)"
897 897 raise error.ProgrammingError(msg % (name, cachefuncs[name]))
898 898 cachefuncs[name] = func
899 899 return func
900 900 return decorator
901 901
902 902 def getrevs(repo, name):
903 903 """Return the set of revision that belong to the <name> set
904 904
905 905 Such access may compute the set and cache it for future use"""
906 906 repo = repo.unfiltered()
907 907 if not repo.obsstore:
908 908 return frozenset()
909 909 if name not in repo.obsstore.caches:
910 910 repo.obsstore.caches[name] = cachefuncs[name](repo)
911 911 return repo.obsstore.caches[name]
912 912
913 913 # To be simple we need to invalidate obsolescence cache when:
914 914 #
915 915 # - new changeset is added:
916 916 # - public phase is changed
917 917 # - obsolescence marker are added
918 918 # - strip is used a repo
919 919 def clearobscaches(repo):
920 920 """Remove all obsolescence related cache from a repo
921 921
922 922 This remove all cache in obsstore is the obsstore already exist on the
923 923 repo.
924 924
925 925 (We could be smarter here given the exact event that trigger the cache
926 926 clearing)"""
927 927 # only clear cache is there is obsstore data in this repo
928 928 if 'obsstore' in repo._filecache:
929 929 repo.obsstore.caches.clear()
930 930
931 931 def _mutablerevs(repo):
932 932 """the set of mutable revision in the repository"""
933 933 return repo._phasecache.getrevset(repo, (phases.draft, phases.secret))
934 934
935 935 @cachefor('obsolete')
936 936 def _computeobsoleteset(repo):
937 937 """the set of obsolete revisions"""
938 938 getnode = repo.changelog.node
939 939 notpublic = _mutablerevs(repo)
940 940 isobs = repo.obsstore.successors.__contains__
941 941 obs = set(r for r in notpublic if isobs(getnode(r)))
942 942 return obs
943 943
944 944 @cachefor('unstable')
945 945 def _computeunstableset(repo):
946 946 msg = ("'unstable' volatile set is deprecated, "
947 947 "use 'orphan'")
948 948 repo.ui.deprecwarn(msg, '4.4')
949 949
950 950 return _computeorphanset(repo)
951 951
952 952 @cachefor('orphan')
953 953 def _computeorphanset(repo):
954 954 """the set of non obsolete revisions with obsolete parents"""
955 955 pfunc = repo.changelog.parentrevs
956 956 mutable = _mutablerevs(repo)
957 957 obsolete = getrevs(repo, 'obsolete')
958 958 others = mutable - obsolete
959 959 unstable = set()
960 960 for r in sorted(others):
961 961 # A rev is unstable if one of its parent is obsolete or unstable
962 962 # this works since we traverse following growing rev order
963 963 for p in pfunc(r):
964 964 if p in obsolete or p in unstable:
965 965 unstable.add(r)
966 966 break
967 967 return unstable
968 968
969 969 @cachefor('suspended')
970 970 def _computesuspendedset(repo):
971 971 """the set of obsolete parents with non obsolete descendants"""
972 972 suspended = repo.changelog.ancestors(getrevs(repo, 'orphan'))
973 973 return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
974 974
975 975 @cachefor('extinct')
976 976 def _computeextinctset(repo):
977 977 """the set of obsolete parents without non obsolete descendants"""
978 978 return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
979 979
980 980 @cachefor('bumped')
981 981 def _computebumpedset(repo):
982 982 msg = ("'bumped' volatile set is deprecated, "
983 983 "use 'phasedivergent'")
984 984 repo.ui.deprecwarn(msg, '4.4')
985 985
986 986 return _computephasedivergentset(repo)
987 987
988 988 @cachefor('phasedivergent')
989 989 def _computephasedivergentset(repo):
990 990 """the set of revs trying to obsolete public revisions"""
991 991 bumped = set()
992 992 # util function (avoid attribute lookup in the loop)
993 993 phase = repo._phasecache.phase # would be faster to grab the full list
994 994 public = phases.public
995 995 cl = repo.changelog
996 996 torev = cl.nodemap.get
997 997 for ctx in repo.set('(not public()) and (not obsolete())'):
998 998 rev = ctx.rev()
999 999 # We only evaluate mutable, non-obsolete revision
1000 1000 node = ctx.node()
1001 1001 # (future) A cache of predecessors may worth if split is very common
1002 1002 for pnode in obsutil.allpredecessors(repo.obsstore, [node],
1003 1003 ignoreflags=bumpedfix):
1004 1004 prev = torev(pnode) # unfiltered! but so is phasecache
1005 1005 if (prev is not None) and (phase(repo, prev) <= public):
1006 1006 # we have a public predecessor
1007 1007 bumped.add(rev)
1008 1008 break # Next draft!
1009 1009 return bumped
1010 1010
1011 1011 @cachefor('divergent')
1012 1012 def _computedivergentset(repo):
1013 1013 msg = ("'divergent' volatile set is deprecated, "
1014 1014 "use 'contentdivergent'")
1015 1015 repo.ui.deprecwarn(msg, '4.4')
1016 1016
1017 1017 return _computecontentdivergentset(repo)
1018 1018
1019 1019 @cachefor('contentdivergent')
1020 1020 def _computecontentdivergentset(repo):
1021 1021 """the set of rev that compete to be the final successors of some revision.
1022 1022 """
1023 1023 divergent = set()
1024 1024 obsstore = repo.obsstore
1025 1025 newermap = {}
1026 1026 for ctx in repo.set('(not public()) - obsolete()'):
1027 1027 mark = obsstore.predecessors.get(ctx.node(), ())
1028 1028 toprocess = set(mark)
1029 1029 seen = set()
1030 1030 while toprocess:
1031 1031 prec = toprocess.pop()[0]
1032 1032 if prec in seen:
1033 1033 continue # emergency cycle hanging prevention
1034 1034 seen.add(prec)
1035 1035 if prec not in newermap:
1036 1036 obsutil.successorssets(repo, prec, cache=newermap)
1037 1037 newer = [n for n in newermap[prec] if n]
1038 1038 if len(newer) > 1:
1039 1039 divergent.add(ctx.rev())
1040 1040 break
1041 1041 toprocess.update(obsstore.predecessors.get(prec, ()))
1042 1042 return divergent
1043 1043
1044 1044
1045 1045 def createmarkers(repo, relations, flag=0, date=None, metadata=None,
1046 1046 operation=None):
1047 1047 """Add obsolete markers between changesets in a repo
1048 1048
1049 1049 <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
1050 1050 tuple. `old` and `news` are changectx. metadata is an optional dictionary
1051 1051 containing metadata for this marker only. It is merged with the global
1052 1052 metadata specified through the `metadata` argument of this function,
1053 1053
1054 1054 Trying to obsolete a public changeset will raise an exception.
1055 1055
1056 1056 Current user and date are used except if specified otherwise in the
1057 1057 metadata attribute.
1058 1058
1059 1059 This function operates within a transaction of its own, but does
1060 1060 not take any lock on the repo.
1061 1061 """
1062 1062 # prepare metadata
1063 1063 if metadata is None:
1064 1064 metadata = {}
1065 1065 if 'user' not in metadata:
1066 1066 develuser = repo.ui.config('devel', 'user.obsmarker')
1067 1067 if develuser:
1068 1068 metadata['user'] = develuser
1069 1069 else:
1070 1070 metadata['user'] = repo.ui.username()
1071 1071
1072 1072 # Operation metadata handling
1073 1073 useoperation = repo.ui.configbool('experimental',
1074 1074 'evolution.track-operation')
1075 1075 if useoperation and operation:
1076 1076 metadata['operation'] = operation
1077 1077
1078 1078 # Effect flag metadata handling
1079 1079 saveeffectflag = repo.ui.configbool('experimental',
1080 'effect-flags')
1080 'evolution.effect-flags')
1081 1081
1082 1082 tr = repo.transaction('add-obsolescence-marker')
1083 1083 try:
1084 1084 markerargs = []
1085 1085 for rel in relations:
1086 1086 prec = rel[0]
1087 1087 sucs = rel[1]
1088 1088 localmetadata = metadata.copy()
1089 1089 if 2 < len(rel):
1090 1090 localmetadata.update(rel[2])
1091 1091
1092 1092 if not prec.mutable():
1093 1093 raise error.Abort(_("cannot obsolete public changeset: %s")
1094 1094 % prec,
1095 1095 hint="see 'hg help phases' for details")
1096 1096 nprec = prec.node()
1097 1097 nsucs = tuple(s.node() for s in sucs)
1098 1098 npare = None
1099 1099 if not nsucs:
1100 1100 npare = tuple(p.node() for p in prec.parents())
1101 1101 if nprec in nsucs:
1102 1102 raise error.Abort(_("changeset %s cannot obsolete itself")
1103 1103 % prec)
1104 1104
1105 1105 # Effect flag can be different by relation
1106 1106 if saveeffectflag:
1107 1107 # The effect flag is saved in a versioned field name for future
1108 1108 # evolution
1109 1109 effectflag = obsutil.geteffectflag(rel)
1110 1110 localmetadata[obsutil.EFFECTFLAGFIELD] = "%d" % effectflag
1111 1111
1112 1112 # Creating the marker causes the hidden cache to become invalid,
1113 1113 # which causes recomputation when we ask for prec.parents() above.
1114 1114 # Resulting in n^2 behavior. So let's prepare all of the args
1115 1115 # first, then create the markers.
1116 1116 markerargs.append((nprec, nsucs, npare, localmetadata))
1117 1117
1118 1118 for args in markerargs:
1119 1119 nprec, nsucs, npare, localmetadata = args
1120 1120 repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
1121 1121 date=date, metadata=localmetadata,
1122 1122 ui=repo.ui)
1123 1123 repo.filteredrevcache.clear()
1124 1124 tr.close()
1125 1125 finally:
1126 1126 tr.release()
@@ -1,167 +1,167 b''
1 1 Test the 'effect-flags' feature
2 2
3 3 Global setup
4 4 ============
5 5
6 6 $ . $TESTDIR/testlib/obsmarker-common.sh
7 7 $ cat >> $HGRCPATH <<EOF
8 8 > [ui]
9 9 > interactive = true
10 10 > [phases]
11 11 > publish=False
12 12 > [extensions]
13 13 > rebase =
14 14 > [experimental]
15 15 > evolution = all
16 > effect-flags = 1
16 > evolution.effect-flags = 1
17 17 > EOF
18 18
19 19 $ hg init $TESTTMP/effect-flags
20 20 $ cd $TESTTMP/effect-flags
21 21 $ mkcommit ROOT
22 22
23 23 amend touching the description only
24 24 -----------------------------------
25 25
26 26 $ mkcommit A0
27 27 $ hg commit --amend -m "A1"
28 28
29 29 check result
30 30
31 31 $ hg debugobsolete --rev .
32 32 471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
33 33
34 34 amend touching the user only
35 35 ----------------------------
36 36
37 37 $ mkcommit B0
38 38 $ hg commit --amend -u "bob <bob@bob.com>"
39 39
40 40 check result
41 41
42 42 $ hg debugobsolete --rev .
43 43 ef4a313b1e0ade55718395d80e6b88c5ccd875eb 5485c92d34330dac9d7a63dc07e1e3373835b964 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
44 44
45 45 amend touching the date only
46 46 ----------------------------
47 47
48 48 $ mkcommit B1
49 49 $ hg commit --amend -d "42 0"
50 50
51 51 check result
52 52
53 53 $ hg debugobsolete --rev .
54 54 2ef0680ff45038ac28c9f1ff3644341f54487280 4dd84345082e9e5291c2e6b3f335bbf8bf389378 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '32', 'operation': 'amend', 'user': 'test'}
55 55
56 56 amend touching the branch only
57 57 ----------------------------
58 58
59 59 $ mkcommit B2
60 60 $ hg branch my-branch
61 61 marked working directory as branch my-branch
62 62 (branches are permanent and global, did you want a bookmark?)
63 63 $ hg commit --amend
64 64
65 65 check result
66 66
67 67 $ hg debugobsolete --rev .
68 68 bd3db8264ceebf1966319f5df3be7aac6acd1a8e 14a01456e0574f0e0a0b15b2345486a6364a8d79 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '64', 'operation': 'amend', 'user': 'test'}
69 69
70 70 $ hg up default
71 71 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
72 72
73 73 rebase (parents change)
74 74 -----------------------
75 75
76 76 $ mkcommit C0
77 77 $ mkcommit D0
78 78 $ hg rebase -r . -d 'desc(B0)'
79 79 rebasing 10:c85eff83a034 "D0" (tip)
80 80
81 81 check result
82 82
83 83 $ hg debugobsolete --rev .
84 84 c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
85 85
86 86 amend touching the diff
87 87 -----------------------
88 88
89 89 $ mkcommit E0
90 90 $ echo 42 >> E0
91 91 $ hg commit --amend
92 92
93 93 check result
94 94
95 95 $ hg debugobsolete --rev .
96 96 ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f 75781fdbdbf58a987516b00c980bccda1e9ae588 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
97 97
98 98 amend with multiple effect (desc and meta)
99 99 -------------------------------------------
100 100
101 101 $ mkcommit F0
102 102 $ hg branch my-other-branch
103 103 marked working directory as branch my-other-branch
104 104 $ hg commit --amend -m F1 -u "bob <bob@bob.com>" -d "42 0"
105 105
106 106 check result
107 107
108 108 $ hg debugobsolete --rev .
109 109 fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '113', 'operation': 'amend', 'user': 'test'}
110 110
111 111 rebase not touching the diff
112 112 ----------------------------
113 113
114 114 $ cat << EOF > H0
115 115 > 0
116 116 > 1
117 117 > 2
118 118 > 3
119 119 > 4
120 120 > 5
121 121 > 6
122 122 > 7
123 123 > 8
124 124 > 9
125 125 > 10
126 126 > EOF
127 127 $ hg add H0
128 128 $ hg commit -m 'H0'
129 129 $ echo "H1" >> H0
130 130 $ hg commit -m "H1"
131 131 $ hg up -r "desc(H0)"
132 132 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 133 $ cat << EOF > H0
134 134 > H2
135 135 > 0
136 136 > 1
137 137 > 2
138 138 > 3
139 139 > 4
140 140 > 5
141 141 > 6
142 142 > 7
143 143 > 8
144 144 > 9
145 145 > 10
146 146 > EOF
147 147 $ hg commit -m "H2"
148 148 created new head
149 149 $ hg rebase -s "desc(H1)" -d "desc(H2)" -t :merge3
150 150 rebasing 17:b57fed8d8322 "H1"
151 151 merging H0
152 152 $ hg debugobsolete -r tip
153 153 b57fed8d83228a8ae3748d8c3760a77638dd4f8c e509e2eb3df5d131ff7c02350bf2a9edd0c09478 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
154 154
155 155 amend closing the branch should be detected as meta change
156 156 ----------------------------------------------------------
157 157
158 158 $ hg branch closedbranch
159 159 marked working directory as branch closedbranch
160 160 $ mkcommit G0
161 161 $ mkcommit I0
162 162 $ hg commit --amend --close-branch
163 163
164 164 check result
165 165
166 166 $ hg debugobsolete -r .
167 167 2f599e54c1c6974299065cdf54e1ad640bfb7b5d 12c6238b5e371eea00fd2013b12edce3f070928b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '2', 'operation': 'amend', 'user': 'test'}
General Comments 0
You need to be logged in to leave comments. Login now