##// END OF EJS Templates
merge: add a config option to disable path conflict checking...
Siddharth Agarwal -
r34942:37450a12 stable
parent child Browse files
Show More
@@ -1,1143 +1,1146 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 f = functools.partial(_register, configtable)
110 110 # export pseudo enum as configitem.*
111 111 f.dynamicdefault = dynamicdefault
112 112 return f
113 113
114 114 coreconfigitem = getitemregister(coreitems)
115 115
116 116 coreconfigitem('alias', '.*',
117 117 default=None,
118 118 generic=True,
119 119 )
120 120 coreconfigitem('annotate', 'nodates',
121 121 default=False,
122 122 )
123 123 coreconfigitem('annotate', 'showfunc',
124 124 default=False,
125 125 )
126 126 coreconfigitem('annotate', 'unified',
127 127 default=None,
128 128 )
129 129 coreconfigitem('annotate', 'git',
130 130 default=False,
131 131 )
132 132 coreconfigitem('annotate', 'ignorews',
133 133 default=False,
134 134 )
135 135 coreconfigitem('annotate', 'ignorewsamount',
136 136 default=False,
137 137 )
138 138 coreconfigitem('annotate', 'ignoreblanklines',
139 139 default=False,
140 140 )
141 141 coreconfigitem('annotate', 'ignorewseol',
142 142 default=False,
143 143 )
144 144 coreconfigitem('annotate', 'nobinary',
145 145 default=False,
146 146 )
147 147 coreconfigitem('annotate', 'noprefix',
148 148 default=False,
149 149 )
150 150 coreconfigitem('auth', 'cookiefile',
151 151 default=None,
152 152 )
153 153 # bookmarks.pushing: internal hack for discovery
154 154 coreconfigitem('bookmarks', 'pushing',
155 155 default=list,
156 156 )
157 157 # bundle.mainreporoot: internal hack for bundlerepo
158 158 coreconfigitem('bundle', 'mainreporoot',
159 159 default='',
160 160 )
161 161 # bundle.reorder: experimental config
162 162 coreconfigitem('bundle', 'reorder',
163 163 default='auto',
164 164 )
165 165 coreconfigitem('censor', 'policy',
166 166 default='abort',
167 167 )
168 168 coreconfigitem('chgserver', 'idletimeout',
169 169 default=3600,
170 170 )
171 171 coreconfigitem('chgserver', 'skiphash',
172 172 default=False,
173 173 )
174 174 coreconfigitem('cmdserver', 'log',
175 175 default=None,
176 176 )
177 177 coreconfigitem('color', '.*',
178 178 default=None,
179 179 generic=True,
180 180 )
181 181 coreconfigitem('color', 'mode',
182 182 default='auto',
183 183 )
184 184 coreconfigitem('color', 'pagermode',
185 185 default=dynamicdefault,
186 186 )
187 187 coreconfigitem('commands', 'show.aliasprefix',
188 188 default=list,
189 189 )
190 190 coreconfigitem('commands', 'status.relative',
191 191 default=False,
192 192 )
193 193 coreconfigitem('commands', 'status.skipstates',
194 194 default=[],
195 195 )
196 196 coreconfigitem('commands', 'status.verbose',
197 197 default=False,
198 198 )
199 199 coreconfigitem('commands', 'update.check',
200 200 default=None,
201 201 # Deprecated, remove after 4.4 release
202 202 alias=[('experimental', 'updatecheck')]
203 203 )
204 204 coreconfigitem('commands', 'update.requiredest',
205 205 default=False,
206 206 )
207 207 coreconfigitem('committemplate', '.*',
208 208 default=None,
209 209 generic=True,
210 210 )
211 211 coreconfigitem('debug', 'dirstate.delaywrite',
212 212 default=0,
213 213 )
214 214 coreconfigitem('defaults', '.*',
215 215 default=None,
216 216 generic=True,
217 217 )
218 218 coreconfigitem('devel', 'all-warnings',
219 219 default=False,
220 220 )
221 221 coreconfigitem('devel', 'bundle2.debug',
222 222 default=False,
223 223 )
224 224 coreconfigitem('devel', 'cache-vfs',
225 225 default=None,
226 226 )
227 227 coreconfigitem('devel', 'check-locks',
228 228 default=False,
229 229 )
230 230 coreconfigitem('devel', 'check-relroot',
231 231 default=False,
232 232 )
233 233 coreconfigitem('devel', 'default-date',
234 234 default=None,
235 235 )
236 236 coreconfigitem('devel', 'deprec-warn',
237 237 default=False,
238 238 )
239 239 coreconfigitem('devel', 'disableloaddefaultcerts',
240 240 default=False,
241 241 )
242 242 coreconfigitem('devel', 'warn-empty-changegroup',
243 243 default=False,
244 244 )
245 245 coreconfigitem('devel', 'legacy.exchange',
246 246 default=list,
247 247 )
248 248 coreconfigitem('devel', 'servercafile',
249 249 default='',
250 250 )
251 251 coreconfigitem('devel', 'serverexactprotocol',
252 252 default='',
253 253 )
254 254 coreconfigitem('devel', 'serverrequirecert',
255 255 default=False,
256 256 )
257 257 coreconfigitem('devel', 'strip-obsmarkers',
258 258 default=True,
259 259 )
260 260 coreconfigitem('devel', 'warn-config',
261 261 default=None,
262 262 )
263 263 coreconfigitem('devel', 'warn-config-default',
264 264 default=None,
265 265 )
266 266 coreconfigitem('devel', 'user.obsmarker',
267 267 default=None,
268 268 )
269 269 coreconfigitem('devel', 'warn-config-unknown',
270 270 default=None,
271 271 )
272 272 coreconfigitem('diff', 'nodates',
273 273 default=False,
274 274 )
275 275 coreconfigitem('diff', 'showfunc',
276 276 default=False,
277 277 )
278 278 coreconfigitem('diff', 'unified',
279 279 default=None,
280 280 )
281 281 coreconfigitem('diff', 'git',
282 282 default=False,
283 283 )
284 284 coreconfigitem('diff', 'ignorews',
285 285 default=False,
286 286 )
287 287 coreconfigitem('diff', 'ignorewsamount',
288 288 default=False,
289 289 )
290 290 coreconfigitem('diff', 'ignoreblanklines',
291 291 default=False,
292 292 )
293 293 coreconfigitem('diff', 'ignorewseol',
294 294 default=False,
295 295 )
296 296 coreconfigitem('diff', 'nobinary',
297 297 default=False,
298 298 )
299 299 coreconfigitem('diff', 'noprefix',
300 300 default=False,
301 301 )
302 302 coreconfigitem('email', 'bcc',
303 303 default=None,
304 304 )
305 305 coreconfigitem('email', 'cc',
306 306 default=None,
307 307 )
308 308 coreconfigitem('email', 'charsets',
309 309 default=list,
310 310 )
311 311 coreconfigitem('email', 'from',
312 312 default=None,
313 313 )
314 314 coreconfigitem('email', 'method',
315 315 default='smtp',
316 316 )
317 317 coreconfigitem('email', 'reply-to',
318 318 default=None,
319 319 )
320 320 coreconfigitem('email', 'to',
321 321 default=None,
322 322 )
323 323 coreconfigitem('experimental', 'archivemetatemplate',
324 324 default=dynamicdefault,
325 325 )
326 326 coreconfigitem('experimental', 'bundle-phases',
327 327 default=False,
328 328 )
329 329 coreconfigitem('experimental', 'bundle2-advertise',
330 330 default=True,
331 331 )
332 332 coreconfigitem('experimental', 'bundle2-output-capture',
333 333 default=False,
334 334 )
335 335 coreconfigitem('experimental', 'bundle2.pushback',
336 336 default=False,
337 337 )
338 338 coreconfigitem('experimental', 'bundle2lazylocking',
339 339 default=False,
340 340 )
341 341 coreconfigitem('experimental', 'bundlecomplevel',
342 342 default=None,
343 343 )
344 344 coreconfigitem('experimental', 'changegroup3',
345 345 default=False,
346 346 )
347 347 coreconfigitem('experimental', 'clientcompressionengines',
348 348 default=list,
349 349 )
350 350 coreconfigitem('experimental', 'copytrace',
351 351 default='on',
352 352 )
353 353 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
354 354 default=100,
355 355 )
356 356 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
357 357 default=100,
358 358 )
359 359 coreconfigitem('experimental', 'crecordtest',
360 360 default=None,
361 361 )
362 362 coreconfigitem('experimental', 'editortmpinhg',
363 363 default=False,
364 364 )
365 365 coreconfigitem('experimental', 'evolution',
366 366 default=list,
367 367 )
368 368 coreconfigitem('experimental', 'evolution.allowdivergence',
369 369 default=False,
370 370 alias=[('experimental', 'allowdivergence')]
371 371 )
372 372 coreconfigitem('experimental', 'evolution.allowunstable',
373 373 default=None,
374 374 )
375 375 coreconfigitem('experimental', 'evolution.createmarkers',
376 376 default=None,
377 377 )
378 378 coreconfigitem('experimental', 'evolution.effect-flags',
379 379 default=False,
380 380 alias=[('experimental', 'effect-flags')]
381 381 )
382 382 coreconfigitem('experimental', 'evolution.exchange',
383 383 default=None,
384 384 )
385 385 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
386 386 default=False,
387 387 )
388 388 coreconfigitem('experimental', 'evolution.track-operation',
389 389 default=True,
390 390 )
391 391 coreconfigitem('experimental', 'maxdeltachainspan',
392 392 default=-1,
393 393 )
394 394 coreconfigitem('experimental', 'mmapindexthreshold',
395 395 default=None,
396 396 )
397 397 coreconfigitem('experimental', 'nonnormalparanoidcheck',
398 398 default=False,
399 399 )
400 400 coreconfigitem('experimental', 'exportableenviron',
401 401 default=list,
402 402 )
403 403 coreconfigitem('experimental', 'extendedheader.index',
404 404 default=None,
405 405 )
406 406 coreconfigitem('experimental', 'extendedheader.similarity',
407 407 default=False,
408 408 )
409 409 coreconfigitem('experimental', 'format.compression',
410 410 default='zlib',
411 411 )
412 412 coreconfigitem('experimental', 'graphshorten',
413 413 default=False,
414 414 )
415 415 coreconfigitem('experimental', 'graphstyle.parent',
416 416 default=dynamicdefault,
417 417 )
418 418 coreconfigitem('experimental', 'graphstyle.missing',
419 419 default=dynamicdefault,
420 420 )
421 421 coreconfigitem('experimental', 'graphstyle.grandparent',
422 422 default=dynamicdefault,
423 423 )
424 424 coreconfigitem('experimental', 'hook-track-tags',
425 425 default=False,
426 426 )
427 427 coreconfigitem('experimental', 'httppostargs',
428 428 default=False,
429 429 )
430 430 coreconfigitem('experimental', 'manifestv2',
431 431 default=False,
432 432 )
433 433 coreconfigitem('experimental', 'mergedriver',
434 434 default=None,
435 435 )
436 436 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
437 437 default=False,
438 438 )
439 439 coreconfigitem('experimental', 'rebase.multidest',
440 440 default=False,
441 441 )
442 442 coreconfigitem('experimental', 'revertalternateinteractivemode',
443 443 default=True,
444 444 )
445 445 coreconfigitem('experimental', 'revlogv2',
446 446 default=None,
447 447 )
448 448 coreconfigitem('experimental', 'spacemovesdown',
449 449 default=False,
450 450 )
451 451 coreconfigitem('experimental', 'sparse-read',
452 452 default=False,
453 453 )
454 454 coreconfigitem('experimental', 'sparse-read.density-threshold',
455 455 default=0.25,
456 456 )
457 457 coreconfigitem('experimental', 'sparse-read.min-gap-size',
458 458 default='256K',
459 459 )
460 460 coreconfigitem('experimental', 'treemanifest',
461 461 default=False,
462 462 )
463 463 coreconfigitem('extensions', '.*',
464 464 default=None,
465 465 generic=True,
466 466 )
467 467 coreconfigitem('extdata', '.*',
468 468 default=None,
469 469 generic=True,
470 470 )
471 471 coreconfigitem('format', 'aggressivemergedeltas',
472 472 default=False,
473 473 )
474 474 coreconfigitem('format', 'chunkcachesize',
475 475 default=None,
476 476 )
477 477 coreconfigitem('format', 'dotencode',
478 478 default=True,
479 479 )
480 480 coreconfigitem('format', 'generaldelta',
481 481 default=False,
482 482 )
483 483 coreconfigitem('format', 'manifestcachesize',
484 484 default=None,
485 485 )
486 486 coreconfigitem('format', 'maxchainlen',
487 487 default=None,
488 488 )
489 489 coreconfigitem('format', 'obsstore-version',
490 490 default=None,
491 491 )
492 492 coreconfigitem('format', 'usefncache',
493 493 default=True,
494 494 )
495 495 coreconfigitem('format', 'usegeneraldelta',
496 496 default=True,
497 497 )
498 498 coreconfigitem('format', 'usestore',
499 499 default=True,
500 500 )
501 501 coreconfigitem('fsmonitor', 'warn_when_unused',
502 502 default=True,
503 503 )
504 504 coreconfigitem('fsmonitor', 'warn_update_file_count',
505 505 default=50000,
506 506 )
507 507 coreconfigitem('hooks', '.*',
508 508 default=dynamicdefault,
509 509 generic=True,
510 510 )
511 511 coreconfigitem('hgweb-paths', '.*',
512 512 default=list,
513 513 generic=True,
514 514 )
515 515 coreconfigitem('hostfingerprints', '.*',
516 516 default=list,
517 517 generic=True,
518 518 )
519 519 coreconfigitem('hostsecurity', 'ciphers',
520 520 default=None,
521 521 )
522 522 coreconfigitem('hostsecurity', 'disabletls10warning',
523 523 default=False,
524 524 )
525 525 coreconfigitem('hostsecurity', 'minimumprotocol',
526 526 default=dynamicdefault,
527 527 )
528 528 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
529 529 default=dynamicdefault,
530 530 generic=True,
531 531 )
532 532 coreconfigitem('hostsecurity', '.*:ciphers$',
533 533 default=dynamicdefault,
534 534 generic=True,
535 535 )
536 536 coreconfigitem('hostsecurity', '.*:fingerprints$',
537 537 default=list,
538 538 generic=True,
539 539 )
540 540 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
541 541 default=None,
542 542 generic=True,
543 543 )
544 544
545 545 coreconfigitem('http_proxy', 'always',
546 546 default=False,
547 547 )
548 548 coreconfigitem('http_proxy', 'host',
549 549 default=None,
550 550 )
551 551 coreconfigitem('http_proxy', 'no',
552 552 default=list,
553 553 )
554 554 coreconfigitem('http_proxy', 'passwd',
555 555 default=None,
556 556 )
557 557 coreconfigitem('http_proxy', 'user',
558 558 default=None,
559 559 )
560 560 coreconfigitem('logtoprocess', 'commandexception',
561 561 default=None,
562 562 )
563 563 coreconfigitem('logtoprocess', 'commandfinish',
564 564 default=None,
565 565 )
566 566 coreconfigitem('logtoprocess', 'command',
567 567 default=None,
568 568 )
569 569 coreconfigitem('logtoprocess', 'develwarn',
570 570 default=None,
571 571 )
572 572 coreconfigitem('logtoprocess', 'uiblocked',
573 573 default=None,
574 574 )
575 575 coreconfigitem('merge', 'checkunknown',
576 576 default='abort',
577 577 )
578 578 coreconfigitem('merge', 'checkignored',
579 579 default='abort',
580 580 )
581 coreconfigitem('experimental', 'merge.checkpathconflicts',
582 default=True,
583 )
581 584 coreconfigitem('merge', 'followcopies',
582 585 default=True,
583 586 )
584 587 coreconfigitem('merge', 'on-failure',
585 588 default='continue',
586 589 )
587 590 coreconfigitem('merge', 'preferancestor',
588 591 default=lambda: ['*'],
589 592 )
590 593 coreconfigitem('merge-tools', '.*',
591 594 default=None,
592 595 generic=True,
593 596 )
594 597 coreconfigitem('merge-tools', br'.*\.args$',
595 598 default="$local $base $other",
596 599 generic=True,
597 600 priority=-1,
598 601 )
599 602 coreconfigitem('merge-tools', br'.*\.binary$',
600 603 default=False,
601 604 generic=True,
602 605 priority=-1,
603 606 )
604 607 coreconfigitem('merge-tools', br'.*\.check$',
605 608 default=list,
606 609 generic=True,
607 610 priority=-1,
608 611 )
609 612 coreconfigitem('merge-tools', br'.*\.checkchanged$',
610 613 default=False,
611 614 generic=True,
612 615 priority=-1,
613 616 )
614 617 coreconfigitem('merge-tools', br'.*\.executable$',
615 618 default=dynamicdefault,
616 619 generic=True,
617 620 priority=-1,
618 621 )
619 622 coreconfigitem('merge-tools', br'.*\.fixeol$',
620 623 default=False,
621 624 generic=True,
622 625 priority=-1,
623 626 )
624 627 coreconfigitem('merge-tools', br'.*\.gui$',
625 628 default=False,
626 629 generic=True,
627 630 priority=-1,
628 631 )
629 632 coreconfigitem('merge-tools', br'.*\.priority$',
630 633 default=0,
631 634 generic=True,
632 635 priority=-1,
633 636 )
634 637 coreconfigitem('merge-tools', br'.*\.premerge$',
635 638 default=dynamicdefault,
636 639 generic=True,
637 640 priority=-1,
638 641 )
639 642 coreconfigitem('merge-tools', br'.*\.symlink$',
640 643 default=False,
641 644 generic=True,
642 645 priority=-1,
643 646 )
644 647 coreconfigitem('pager', 'attend-.*',
645 648 default=dynamicdefault,
646 649 generic=True,
647 650 )
648 651 coreconfigitem('pager', 'ignore',
649 652 default=list,
650 653 )
651 654 coreconfigitem('pager', 'pager',
652 655 default=dynamicdefault,
653 656 )
654 657 coreconfigitem('patch', 'eol',
655 658 default='strict',
656 659 )
657 660 coreconfigitem('patch', 'fuzz',
658 661 default=2,
659 662 )
660 663 coreconfigitem('paths', 'default',
661 664 default=None,
662 665 )
663 666 coreconfigitem('paths', 'default-push',
664 667 default=None,
665 668 )
666 669 coreconfigitem('paths', '.*',
667 670 default=None,
668 671 generic=True,
669 672 )
670 673 coreconfigitem('phases', 'checksubrepos',
671 674 default='follow',
672 675 )
673 676 coreconfigitem('phases', 'new-commit',
674 677 default='draft',
675 678 )
676 679 coreconfigitem('phases', 'publish',
677 680 default=True,
678 681 )
679 682 coreconfigitem('profiling', 'enabled',
680 683 default=False,
681 684 )
682 685 coreconfigitem('profiling', 'format',
683 686 default='text',
684 687 )
685 688 coreconfigitem('profiling', 'freq',
686 689 default=1000,
687 690 )
688 691 coreconfigitem('profiling', 'limit',
689 692 default=30,
690 693 )
691 694 coreconfigitem('profiling', 'nested',
692 695 default=0,
693 696 )
694 697 coreconfigitem('profiling', 'output',
695 698 default=None,
696 699 )
697 700 coreconfigitem('profiling', 'showmax',
698 701 default=0.999,
699 702 )
700 703 coreconfigitem('profiling', 'showmin',
701 704 default=dynamicdefault,
702 705 )
703 706 coreconfigitem('profiling', 'sort',
704 707 default='inlinetime',
705 708 )
706 709 coreconfigitem('profiling', 'statformat',
707 710 default='hotpath',
708 711 )
709 712 coreconfigitem('profiling', 'type',
710 713 default='stat',
711 714 )
712 715 coreconfigitem('progress', 'assume-tty',
713 716 default=False,
714 717 )
715 718 coreconfigitem('progress', 'changedelay',
716 719 default=1,
717 720 )
718 721 coreconfigitem('progress', 'clear-complete',
719 722 default=True,
720 723 )
721 724 coreconfigitem('progress', 'debug',
722 725 default=False,
723 726 )
724 727 coreconfigitem('progress', 'delay',
725 728 default=3,
726 729 )
727 730 coreconfigitem('progress', 'disable',
728 731 default=False,
729 732 )
730 733 coreconfigitem('progress', 'estimateinterval',
731 734 default=60.0,
732 735 )
733 736 coreconfigitem('progress', 'format',
734 737 default=lambda: ['topic', 'bar', 'number', 'estimate'],
735 738 )
736 739 coreconfigitem('progress', 'refresh',
737 740 default=0.1,
738 741 )
739 742 coreconfigitem('progress', 'width',
740 743 default=dynamicdefault,
741 744 )
742 745 coreconfigitem('push', 'pushvars.server',
743 746 default=False,
744 747 )
745 748 coreconfigitem('server', 'bundle1',
746 749 default=True,
747 750 )
748 751 coreconfigitem('server', 'bundle1gd',
749 752 default=None,
750 753 )
751 754 coreconfigitem('server', 'bundle1.pull',
752 755 default=None,
753 756 )
754 757 coreconfigitem('server', 'bundle1gd.pull',
755 758 default=None,
756 759 )
757 760 coreconfigitem('server', 'bundle1.push',
758 761 default=None,
759 762 )
760 763 coreconfigitem('server', 'bundle1gd.push',
761 764 default=None,
762 765 )
763 766 coreconfigitem('server', 'compressionengines',
764 767 default=list,
765 768 )
766 769 coreconfigitem('server', 'concurrent-push-mode',
767 770 default='strict',
768 771 )
769 772 coreconfigitem('server', 'disablefullbundle',
770 773 default=False,
771 774 )
772 775 coreconfigitem('server', 'maxhttpheaderlen',
773 776 default=1024,
774 777 )
775 778 coreconfigitem('server', 'preferuncompressed',
776 779 default=False,
777 780 )
778 781 coreconfigitem('server', 'uncompressed',
779 782 default=True,
780 783 )
781 784 coreconfigitem('server', 'uncompressedallowsecret',
782 785 default=False,
783 786 )
784 787 coreconfigitem('server', 'validate',
785 788 default=False,
786 789 )
787 790 coreconfigitem('server', 'zliblevel',
788 791 default=-1,
789 792 )
790 793 coreconfigitem('smtp', 'host',
791 794 default=None,
792 795 )
793 796 coreconfigitem('smtp', 'local_hostname',
794 797 default=None,
795 798 )
796 799 coreconfigitem('smtp', 'password',
797 800 default=None,
798 801 )
799 802 coreconfigitem('smtp', 'port',
800 803 default=dynamicdefault,
801 804 )
802 805 coreconfigitem('smtp', 'tls',
803 806 default='none',
804 807 )
805 808 coreconfigitem('smtp', 'username',
806 809 default=None,
807 810 )
808 811 coreconfigitem('sparse', 'missingwarning',
809 812 default=True,
810 813 )
811 814 coreconfigitem('templates', '.*',
812 815 default=None,
813 816 generic=True,
814 817 )
815 818 coreconfigitem('trusted', 'groups',
816 819 default=list,
817 820 )
818 821 coreconfigitem('trusted', 'users',
819 822 default=list,
820 823 )
821 824 coreconfigitem('ui', '_usedassubrepo',
822 825 default=False,
823 826 )
824 827 coreconfigitem('ui', 'allowemptycommit',
825 828 default=False,
826 829 )
827 830 coreconfigitem('ui', 'archivemeta',
828 831 default=True,
829 832 )
830 833 coreconfigitem('ui', 'askusername',
831 834 default=False,
832 835 )
833 836 coreconfigitem('ui', 'clonebundlefallback',
834 837 default=False,
835 838 )
836 839 coreconfigitem('ui', 'clonebundleprefers',
837 840 default=list,
838 841 )
839 842 coreconfigitem('ui', 'clonebundles',
840 843 default=True,
841 844 )
842 845 coreconfigitem('ui', 'color',
843 846 default='auto',
844 847 )
845 848 coreconfigitem('ui', 'commitsubrepos',
846 849 default=False,
847 850 )
848 851 coreconfigitem('ui', 'debug',
849 852 default=False,
850 853 )
851 854 coreconfigitem('ui', 'debugger',
852 855 default=None,
853 856 )
854 857 coreconfigitem('ui', 'editor',
855 858 default=dynamicdefault,
856 859 )
857 860 coreconfigitem('ui', 'fallbackencoding',
858 861 default=None,
859 862 )
860 863 coreconfigitem('ui', 'forcecwd',
861 864 default=None,
862 865 )
863 866 coreconfigitem('ui', 'forcemerge',
864 867 default=None,
865 868 )
866 869 coreconfigitem('ui', 'formatdebug',
867 870 default=False,
868 871 )
869 872 coreconfigitem('ui', 'formatjson',
870 873 default=False,
871 874 )
872 875 coreconfigitem('ui', 'formatted',
873 876 default=None,
874 877 )
875 878 coreconfigitem('ui', 'graphnodetemplate',
876 879 default=None,
877 880 )
878 881 coreconfigitem('ui', 'http2debuglevel',
879 882 default=None,
880 883 )
881 884 coreconfigitem('ui', 'interactive',
882 885 default=None,
883 886 )
884 887 coreconfigitem('ui', 'interface',
885 888 default=None,
886 889 )
887 890 coreconfigitem('ui', 'interface.chunkselector',
888 891 default=None,
889 892 )
890 893 coreconfigitem('ui', 'logblockedtimes',
891 894 default=False,
892 895 )
893 896 coreconfigitem('ui', 'logtemplate',
894 897 default=None,
895 898 )
896 899 coreconfigitem('ui', 'merge',
897 900 default=None,
898 901 )
899 902 coreconfigitem('ui', 'mergemarkers',
900 903 default='basic',
901 904 )
902 905 coreconfigitem('ui', 'mergemarkertemplate',
903 906 default=('{node|short} '
904 907 '{ifeq(tags, "tip", "", '
905 908 'ifeq(tags, "", "", "{tags} "))}'
906 909 '{if(bookmarks, "{bookmarks} ")}'
907 910 '{ifeq(branch, "default", "", "{branch} ")}'
908 911 '- {author|user}: {desc|firstline}')
909 912 )
910 913 coreconfigitem('ui', 'nontty',
911 914 default=False,
912 915 )
913 916 coreconfigitem('ui', 'origbackuppath',
914 917 default=None,
915 918 )
916 919 coreconfigitem('ui', 'paginate',
917 920 default=True,
918 921 )
919 922 coreconfigitem('ui', 'patch',
920 923 default=None,
921 924 )
922 925 coreconfigitem('ui', 'portablefilenames',
923 926 default='warn',
924 927 )
925 928 coreconfigitem('ui', 'promptecho',
926 929 default=False,
927 930 )
928 931 coreconfigitem('ui', 'quiet',
929 932 default=False,
930 933 )
931 934 coreconfigitem('ui', 'quietbookmarkmove',
932 935 default=False,
933 936 )
934 937 coreconfigitem('ui', 'remotecmd',
935 938 default='hg',
936 939 )
937 940 coreconfigitem('ui', 'report_untrusted',
938 941 default=True,
939 942 )
940 943 coreconfigitem('ui', 'rollback',
941 944 default=True,
942 945 )
943 946 coreconfigitem('ui', 'slash',
944 947 default=False,
945 948 )
946 949 coreconfigitem('ui', 'ssh',
947 950 default='ssh',
948 951 )
949 952 coreconfigitem('ui', 'statuscopies',
950 953 default=False,
951 954 )
952 955 coreconfigitem('ui', 'strict',
953 956 default=False,
954 957 )
955 958 coreconfigitem('ui', 'style',
956 959 default='',
957 960 )
958 961 coreconfigitem('ui', 'supportcontact',
959 962 default=None,
960 963 )
961 964 coreconfigitem('ui', 'textwidth',
962 965 default=78,
963 966 )
964 967 coreconfigitem('ui', 'timeout',
965 968 default='600',
966 969 )
967 970 coreconfigitem('ui', 'traceback',
968 971 default=False,
969 972 )
970 973 coreconfigitem('ui', 'tweakdefaults',
971 974 default=False,
972 975 )
973 976 coreconfigitem('ui', 'usehttp2',
974 977 default=False,
975 978 )
976 979 coreconfigitem('ui', 'username',
977 980 alias=[('ui', 'user')]
978 981 )
979 982 coreconfigitem('ui', 'verbose',
980 983 default=False,
981 984 )
982 985 coreconfigitem('verify', 'skipflags',
983 986 default=None,
984 987 )
985 988 coreconfigitem('web', 'allowbz2',
986 989 default=False,
987 990 )
988 991 coreconfigitem('web', 'allowgz',
989 992 default=False,
990 993 )
991 994 coreconfigitem('web', 'allowpull',
992 995 default=True,
993 996 )
994 997 coreconfigitem('web', 'allow_push',
995 998 default=list,
996 999 )
997 1000 coreconfigitem('web', 'allowzip',
998 1001 default=False,
999 1002 )
1000 1003 coreconfigitem('web', 'archivesubrepos',
1001 1004 default=False,
1002 1005 )
1003 1006 coreconfigitem('web', 'cache',
1004 1007 default=True,
1005 1008 )
1006 1009 coreconfigitem('web', 'contact',
1007 1010 default=None,
1008 1011 )
1009 1012 coreconfigitem('web', 'deny_push',
1010 1013 default=list,
1011 1014 )
1012 1015 coreconfigitem('web', 'guessmime',
1013 1016 default=False,
1014 1017 )
1015 1018 coreconfigitem('web', 'hidden',
1016 1019 default=False,
1017 1020 )
1018 1021 coreconfigitem('web', 'labels',
1019 1022 default=list,
1020 1023 )
1021 1024 coreconfigitem('web', 'logoimg',
1022 1025 default='hglogo.png',
1023 1026 )
1024 1027 coreconfigitem('web', 'logourl',
1025 1028 default='https://mercurial-scm.org/',
1026 1029 )
1027 1030 coreconfigitem('web', 'accesslog',
1028 1031 default='-',
1029 1032 )
1030 1033 coreconfigitem('web', 'address',
1031 1034 default='',
1032 1035 )
1033 1036 coreconfigitem('web', 'allow_archive',
1034 1037 default=list,
1035 1038 )
1036 1039 coreconfigitem('web', 'allow_read',
1037 1040 default=list,
1038 1041 )
1039 1042 coreconfigitem('web', 'baseurl',
1040 1043 default=None,
1041 1044 )
1042 1045 coreconfigitem('web', 'cacerts',
1043 1046 default=None,
1044 1047 )
1045 1048 coreconfigitem('web', 'certificate',
1046 1049 default=None,
1047 1050 )
1048 1051 coreconfigitem('web', 'collapse',
1049 1052 default=False,
1050 1053 )
1051 1054 coreconfigitem('web', 'csp',
1052 1055 default=None,
1053 1056 )
1054 1057 coreconfigitem('web', 'deny_read',
1055 1058 default=list,
1056 1059 )
1057 1060 coreconfigitem('web', 'descend',
1058 1061 default=True,
1059 1062 )
1060 1063 coreconfigitem('web', 'description',
1061 1064 default="",
1062 1065 )
1063 1066 coreconfigitem('web', 'encoding',
1064 1067 default=lambda: encoding.encoding,
1065 1068 )
1066 1069 coreconfigitem('web', 'errorlog',
1067 1070 default='-',
1068 1071 )
1069 1072 coreconfigitem('web', 'ipv6',
1070 1073 default=False,
1071 1074 )
1072 1075 coreconfigitem('web', 'maxchanges',
1073 1076 default=10,
1074 1077 )
1075 1078 coreconfigitem('web', 'maxfiles',
1076 1079 default=10,
1077 1080 )
1078 1081 coreconfigitem('web', 'maxshortchanges',
1079 1082 default=60,
1080 1083 )
1081 1084 coreconfigitem('web', 'motd',
1082 1085 default='',
1083 1086 )
1084 1087 coreconfigitem('web', 'name',
1085 1088 default=dynamicdefault,
1086 1089 )
1087 1090 coreconfigitem('web', 'port',
1088 1091 default=8000,
1089 1092 )
1090 1093 coreconfigitem('web', 'prefix',
1091 1094 default='',
1092 1095 )
1093 1096 coreconfigitem('web', 'push_ssl',
1094 1097 default=True,
1095 1098 )
1096 1099 coreconfigitem('web', 'refreshinterval',
1097 1100 default=20,
1098 1101 )
1099 1102 coreconfigitem('web', 'staticurl',
1100 1103 default=None,
1101 1104 )
1102 1105 coreconfigitem('web', 'stripes',
1103 1106 default=1,
1104 1107 )
1105 1108 coreconfigitem('web', 'style',
1106 1109 default='paper',
1107 1110 )
1108 1111 coreconfigitem('web', 'templates',
1109 1112 default=None,
1110 1113 )
1111 1114 coreconfigitem('web', 'view',
1112 1115 default='served',
1113 1116 )
1114 1117 coreconfigitem('worker', 'backgroundclose',
1115 1118 default=dynamicdefault,
1116 1119 )
1117 1120 # Windows defaults to a limit of 512 open files. A buffer of 128
1118 1121 # should give us enough headway.
1119 1122 coreconfigitem('worker', 'backgroundclosemaxqueue',
1120 1123 default=384,
1121 1124 )
1122 1125 coreconfigitem('worker', 'backgroundcloseminfilecount',
1123 1126 default=2048,
1124 1127 )
1125 1128 coreconfigitem('worker', 'backgroundclosethreadcount',
1126 1129 default=4,
1127 1130 )
1128 1131 coreconfigitem('worker', 'numcpus',
1129 1132 default=None,
1130 1133 )
1131 1134
1132 1135 # Rebase related configuration moved to core because other extension are doing
1133 1136 # strange things. For example, shelve import the extensions to reuse some bit
1134 1137 # without formally loading it.
1135 1138 coreconfigitem('commands', 'rebase.requiredest',
1136 1139 default=False,
1137 1140 )
1138 1141 coreconfigitem('experimental', 'rebaseskipobsolete',
1139 1142 default=True,
1140 1143 )
1141 1144 coreconfigitem('rebase', 'singletransaction',
1142 1145 default=False,
1143 1146 )
@@ -1,2038 +1,2040 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import hashlib
12 12 import shutil
13 13 import struct
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 addednodeid,
18 18 bin,
19 19 hex,
20 20 modifiednodeid,
21 21 nullhex,
22 22 nullid,
23 23 nullrev,
24 24 )
25 25 from . import (
26 26 copies,
27 27 error,
28 28 extensions,
29 29 filemerge,
30 30 match as matchmod,
31 31 obsutil,
32 32 pycompat,
33 33 scmutil,
34 34 subrepo,
35 35 util,
36 36 worker,
37 37 )
38 38
39 39 _pack = struct.pack
40 40 _unpack = struct.unpack
41 41
42 42 def _droponode(data):
43 43 # used for compatibility for v1
44 44 bits = data.split('\0')
45 45 bits = bits[:-2] + bits[-1:]
46 46 return '\0'.join(bits)
47 47
48 48 class mergestate(object):
49 49 '''track 3-way merge state of individual files
50 50
51 51 The merge state is stored on disk when needed. Two files are used: one with
52 52 an old format (version 1), and one with a new format (version 2). Version 2
53 53 stores a superset of the data in version 1, including new kinds of records
54 54 in the future. For more about the new format, see the documentation for
55 55 `_readrecordsv2`.
56 56
57 57 Each record can contain arbitrary content, and has an associated type. This
58 58 `type` should be a letter. If `type` is uppercase, the record is mandatory:
59 59 versions of Mercurial that don't support it should abort. If `type` is
60 60 lowercase, the record can be safely ignored.
61 61
62 62 Currently known records:
63 63
64 64 L: the node of the "local" part of the merge (hexified version)
65 65 O: the node of the "other" part of the merge (hexified version)
66 66 F: a file to be merged entry
67 67 C: a change/delete or delete/change conflict
68 68 D: a file that the external merge driver will merge internally
69 69 (experimental)
70 70 P: a path conflict (file vs directory)
71 71 m: the external merge driver defined for this merge plus its run state
72 72 (experimental)
73 73 f: a (filename, dictionary) tuple of optional values for a given file
74 74 X: unsupported mandatory record type (used in tests)
75 75 x: unsupported advisory record type (used in tests)
76 76 l: the labels for the parts of the merge.
77 77
78 78 Merge driver run states (experimental):
79 79 u: driver-resolved files unmarked -- needs to be run next time we're about
80 80 to resolve or commit
81 81 m: driver-resolved files marked -- only needs to be run before commit
82 82 s: success/skipped -- does not need to be run any more
83 83
84 84 Merge record states (stored in self._state, indexed by filename):
85 85 u: unresolved conflict
86 86 r: resolved conflict
87 87 pu: unresolved path conflict (file conflicts with directory)
88 88 pr: resolved path conflict
89 89 d: driver-resolved conflict
90 90
91 91 The resolve command transitions between 'u' and 'r' for conflicts and
92 92 'pu' and 'pr' for path conflicts.
93 93 '''
94 94 statepathv1 = 'merge/state'
95 95 statepathv2 = 'merge/state2'
96 96
97 97 @staticmethod
98 98 def clean(repo, node=None, other=None, labels=None):
99 99 """Initialize a brand new merge state, removing any existing state on
100 100 disk."""
101 101 ms = mergestate(repo)
102 102 ms.reset(node, other, labels)
103 103 return ms
104 104
105 105 @staticmethod
106 106 def read(repo):
107 107 """Initialize the merge state, reading it from disk."""
108 108 ms = mergestate(repo)
109 109 ms._read()
110 110 return ms
111 111
112 112 def __init__(self, repo):
113 113 """Initialize the merge state.
114 114
115 115 Do not use this directly! Instead call read() or clean()."""
116 116 self._repo = repo
117 117 self._dirty = False
118 118 self._labels = None
119 119
120 120 def reset(self, node=None, other=None, labels=None):
121 121 self._state = {}
122 122 self._stateextras = {}
123 123 self._local = None
124 124 self._other = None
125 125 self._labels = labels
126 126 for var in ('localctx', 'otherctx'):
127 127 if var in vars(self):
128 128 delattr(self, var)
129 129 if node:
130 130 self._local = node
131 131 self._other = other
132 132 self._readmergedriver = None
133 133 if self.mergedriver:
134 134 self._mdstate = 's'
135 135 else:
136 136 self._mdstate = 'u'
137 137 shutil.rmtree(self._repo.vfs.join('merge'), True)
138 138 self._results = {}
139 139 self._dirty = False
140 140
141 141 def _read(self):
142 142 """Analyse each record content to restore a serialized state from disk
143 143
144 144 This function process "record" entry produced by the de-serialization
145 145 of on disk file.
146 146 """
147 147 self._state = {}
148 148 self._stateextras = {}
149 149 self._local = None
150 150 self._other = None
151 151 for var in ('localctx', 'otherctx'):
152 152 if var in vars(self):
153 153 delattr(self, var)
154 154 self._readmergedriver = None
155 155 self._mdstate = 's'
156 156 unsupported = set()
157 157 records = self._readrecords()
158 158 for rtype, record in records:
159 159 if rtype == 'L':
160 160 self._local = bin(record)
161 161 elif rtype == 'O':
162 162 self._other = bin(record)
163 163 elif rtype == 'm':
164 164 bits = record.split('\0', 1)
165 165 mdstate = bits[1]
166 166 if len(mdstate) != 1 or mdstate not in 'ums':
167 167 # the merge driver should be idempotent, so just rerun it
168 168 mdstate = 'u'
169 169
170 170 self._readmergedriver = bits[0]
171 171 self._mdstate = mdstate
172 172 elif rtype in 'FDCP':
173 173 bits = record.split('\0')
174 174 self._state[bits[0]] = bits[1:]
175 175 elif rtype == 'f':
176 176 filename, rawextras = record.split('\0', 1)
177 177 extraparts = rawextras.split('\0')
178 178 extras = {}
179 179 i = 0
180 180 while i < len(extraparts):
181 181 extras[extraparts[i]] = extraparts[i + 1]
182 182 i += 2
183 183
184 184 self._stateextras[filename] = extras
185 185 elif rtype == 'l':
186 186 labels = record.split('\0', 2)
187 187 self._labels = [l for l in labels if len(l) > 0]
188 188 elif not rtype.islower():
189 189 unsupported.add(rtype)
190 190 self._results = {}
191 191 self._dirty = False
192 192
193 193 if unsupported:
194 194 raise error.UnsupportedMergeRecords(unsupported)
195 195
196 196 def _readrecords(self):
197 197 """Read merge state from disk and return a list of record (TYPE, data)
198 198
199 199 We read data from both v1 and v2 files and decide which one to use.
200 200
201 201 V1 has been used by version prior to 2.9.1 and contains less data than
202 202 v2. We read both versions and check if no data in v2 contradicts
203 203 v1. If there is not contradiction we can safely assume that both v1
204 204 and v2 were written at the same time and use the extract data in v2. If
205 205 there is contradiction we ignore v2 content as we assume an old version
206 206 of Mercurial has overwritten the mergestate file and left an old v2
207 207 file around.
208 208
209 209 returns list of record [(TYPE, data), ...]"""
210 210 v1records = self._readrecordsv1()
211 211 v2records = self._readrecordsv2()
212 212 if self._v1v2match(v1records, v2records):
213 213 return v2records
214 214 else:
215 215 # v1 file is newer than v2 file, use it
216 216 # we have to infer the "other" changeset of the merge
217 217 # we cannot do better than that with v1 of the format
218 218 mctx = self._repo[None].parents()[-1]
219 219 v1records.append(('O', mctx.hex()))
220 220 # add place holder "other" file node information
221 221 # nobody is using it yet so we do no need to fetch the data
222 222 # if mctx was wrong `mctx[bits[-2]]` may fails.
223 223 for idx, r in enumerate(v1records):
224 224 if r[0] == 'F':
225 225 bits = r[1].split('\0')
226 226 bits.insert(-2, '')
227 227 v1records[idx] = (r[0], '\0'.join(bits))
228 228 return v1records
229 229
230 230 def _v1v2match(self, v1records, v2records):
231 231 oldv2 = set() # old format version of v2 record
232 232 for rec in v2records:
233 233 if rec[0] == 'L':
234 234 oldv2.add(rec)
235 235 elif rec[0] == 'F':
236 236 # drop the onode data (not contained in v1)
237 237 oldv2.add(('F', _droponode(rec[1])))
238 238 for rec in v1records:
239 239 if rec not in oldv2:
240 240 return False
241 241 else:
242 242 return True
243 243
244 244 def _readrecordsv1(self):
245 245 """read on disk merge state for version 1 file
246 246
247 247 returns list of record [(TYPE, data), ...]
248 248
249 249 Note: the "F" data from this file are one entry short
250 250 (no "other file node" entry)
251 251 """
252 252 records = []
253 253 try:
254 254 f = self._repo.vfs(self.statepathv1)
255 255 for i, l in enumerate(f):
256 256 if i == 0:
257 257 records.append(('L', l[:-1]))
258 258 else:
259 259 records.append(('F', l[:-1]))
260 260 f.close()
261 261 except IOError as err:
262 262 if err.errno != errno.ENOENT:
263 263 raise
264 264 return records
265 265
266 266 def _readrecordsv2(self):
267 267 """read on disk merge state for version 2 file
268 268
269 269 This format is a list of arbitrary records of the form:
270 270
271 271 [type][length][content]
272 272
273 273 `type` is a single character, `length` is a 4 byte integer, and
274 274 `content` is an arbitrary byte sequence of length `length`.
275 275
276 276 Mercurial versions prior to 3.7 have a bug where if there are
277 277 unsupported mandatory merge records, attempting to clear out the merge
278 278 state with hg update --clean or similar aborts. The 't' record type
279 279 works around that by writing out what those versions treat as an
280 280 advisory record, but later versions interpret as special: the first
281 281 character is the 'real' record type and everything onwards is the data.
282 282
283 283 Returns list of records [(TYPE, data), ...]."""
284 284 records = []
285 285 try:
286 286 f = self._repo.vfs(self.statepathv2)
287 287 data = f.read()
288 288 off = 0
289 289 end = len(data)
290 290 while off < end:
291 291 rtype = data[off]
292 292 off += 1
293 293 length = _unpack('>I', data[off:(off + 4)])[0]
294 294 off += 4
295 295 record = data[off:(off + length)]
296 296 off += length
297 297 if rtype == 't':
298 298 rtype, record = record[0], record[1:]
299 299 records.append((rtype, record))
300 300 f.close()
301 301 except IOError as err:
302 302 if err.errno != errno.ENOENT:
303 303 raise
304 304 return records
305 305
306 306 @util.propertycache
307 307 def mergedriver(self):
308 308 # protect against the following:
309 309 # - A configures a malicious merge driver in their hgrc, then
310 310 # pauses the merge
311 311 # - A edits their hgrc to remove references to the merge driver
312 312 # - A gives a copy of their entire repo, including .hg, to B
313 313 # - B inspects .hgrc and finds it to be clean
314 314 # - B then continues the merge and the malicious merge driver
315 315 # gets invoked
316 316 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
317 317 if (self._readmergedriver is not None
318 318 and self._readmergedriver != configmergedriver):
319 319 raise error.ConfigError(
320 320 _("merge driver changed since merge started"),
321 321 hint=_("revert merge driver change or abort merge"))
322 322
323 323 return configmergedriver
324 324
325 325 @util.propertycache
326 326 def localctx(self):
327 327 if self._local is None:
328 328 msg = "localctx accessed but self._local isn't set"
329 329 raise error.ProgrammingError(msg)
330 330 return self._repo[self._local]
331 331
332 332 @util.propertycache
333 333 def otherctx(self):
334 334 if self._other is None:
335 335 msg = "otherctx accessed but self._other isn't set"
336 336 raise error.ProgrammingError(msg)
337 337 return self._repo[self._other]
338 338
339 339 def active(self):
340 340 """Whether mergestate is active.
341 341
342 342 Returns True if there appears to be mergestate. This is a rough proxy
343 343 for "is a merge in progress."
344 344 """
345 345 # Check local variables before looking at filesystem for performance
346 346 # reasons.
347 347 return bool(self._local) or bool(self._state) or \
348 348 self._repo.vfs.exists(self.statepathv1) or \
349 349 self._repo.vfs.exists(self.statepathv2)
350 350
351 351 def commit(self):
352 352 """Write current state on disk (if necessary)"""
353 353 if self._dirty:
354 354 records = self._makerecords()
355 355 self._writerecords(records)
356 356 self._dirty = False
357 357
358 358 def _makerecords(self):
359 359 records = []
360 360 records.append(('L', hex(self._local)))
361 361 records.append(('O', hex(self._other)))
362 362 if self.mergedriver:
363 363 records.append(('m', '\0'.join([
364 364 self.mergedriver, self._mdstate])))
365 365 # Write out state items. In all cases, the value of the state map entry
366 366 # is written as the contents of the record. The record type depends on
367 367 # the type of state that is stored, and capital-letter records are used
368 368 # to prevent older versions of Mercurial that do not support the feature
369 369 # from loading them.
370 370 for filename, v in self._state.iteritems():
371 371 if v[0] == 'd':
372 372 # Driver-resolved merge. These are stored in 'D' records.
373 373 records.append(('D', '\0'.join([filename] + v)))
374 374 elif v[0] in ('pu', 'pr'):
375 375 # Path conflicts. These are stored in 'P' records. The current
376 376 # resolution state ('pu' or 'pr') is stored within the record.
377 377 records.append(('P', '\0'.join([filename] + v)))
378 378 elif v[1] == nullhex or v[6] == nullhex:
379 379 # Change/Delete or Delete/Change conflicts. These are stored in
380 380 # 'C' records. v[1] is the local file, and is nullhex when the
381 381 # file is deleted locally ('dc'). v[6] is the remote file, and
382 382 # is nullhex when the file is deleted remotely ('cd').
383 383 records.append(('C', '\0'.join([filename] + v)))
384 384 else:
385 385 # Normal files. These are stored in 'F' records.
386 386 records.append(('F', '\0'.join([filename] + v)))
387 387 for filename, extras in sorted(self._stateextras.iteritems()):
388 388 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
389 389 extras.iteritems())
390 390 records.append(('f', '%s\0%s' % (filename, rawextras)))
391 391 if self._labels is not None:
392 392 labels = '\0'.join(self._labels)
393 393 records.append(('l', labels))
394 394 return records
395 395
396 396 def _writerecords(self, records):
397 397 """Write current state on disk (both v1 and v2)"""
398 398 self._writerecordsv1(records)
399 399 self._writerecordsv2(records)
400 400
401 401 def _writerecordsv1(self, records):
402 402 """Write current state on disk in a version 1 file"""
403 403 f = self._repo.vfs(self.statepathv1, 'w')
404 404 irecords = iter(records)
405 405 lrecords = next(irecords)
406 406 assert lrecords[0] == 'L'
407 407 f.write(hex(self._local) + '\n')
408 408 for rtype, data in irecords:
409 409 if rtype == 'F':
410 410 f.write('%s\n' % _droponode(data))
411 411 f.close()
412 412
413 413 def _writerecordsv2(self, records):
414 414 """Write current state on disk in a version 2 file
415 415
416 416 See the docstring for _readrecordsv2 for why we use 't'."""
417 417 # these are the records that all version 2 clients can read
418 418 whitelist = 'LOF'
419 419 f = self._repo.vfs(self.statepathv2, 'w')
420 420 for key, data in records:
421 421 assert len(key) == 1
422 422 if key not in whitelist:
423 423 key, data = 't', '%s%s' % (key, data)
424 424 format = '>sI%is' % len(data)
425 425 f.write(_pack(format, key, len(data), data))
426 426 f.close()
427 427
428 428 def add(self, fcl, fco, fca, fd):
429 429 """add a new (potentially?) conflicting file the merge state
430 430 fcl: file context for local,
431 431 fco: file context for remote,
432 432 fca: file context for ancestors,
433 433 fd: file path of the resulting merge.
434 434
435 435 note: also write the local version to the `.hg/merge` directory.
436 436 """
437 437 if fcl.isabsent():
438 438 hash = nullhex
439 439 else:
440 440 hash = hex(hashlib.sha1(fcl.path()).digest())
441 441 self._repo.vfs.write('merge/' + hash, fcl.data())
442 442 self._state[fd] = ['u', hash, fcl.path(),
443 443 fca.path(), hex(fca.filenode()),
444 444 fco.path(), hex(fco.filenode()),
445 445 fcl.flags()]
446 446 self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
447 447 self._dirty = True
448 448
449 449 def addpath(self, path, frename, forigin):
450 450 """add a new conflicting path to the merge state
451 451 path: the path that conflicts
452 452 frename: the filename the conflicting file was renamed to
453 453 forigin: origin of the file ('l' or 'r' for local/remote)
454 454 """
455 455 self._state[path] = ['pu', frename, forigin]
456 456 self._dirty = True
457 457
458 458 def __contains__(self, dfile):
459 459 return dfile in self._state
460 460
461 461 def __getitem__(self, dfile):
462 462 return self._state[dfile][0]
463 463
464 464 def __iter__(self):
465 465 return iter(sorted(self._state))
466 466
467 467 def files(self):
468 468 return self._state.keys()
469 469
470 470 def mark(self, dfile, state):
471 471 self._state[dfile][0] = state
472 472 self._dirty = True
473 473
474 474 def mdstate(self):
475 475 return self._mdstate
476 476
477 477 def unresolved(self):
478 478 """Obtain the paths of unresolved files."""
479 479
480 480 for f, entry in self._state.iteritems():
481 481 if entry[0] in ('u', 'pu'):
482 482 yield f
483 483
484 484 def driverresolved(self):
485 485 """Obtain the paths of driver-resolved files."""
486 486
487 487 for f, entry in self._state.items():
488 488 if entry[0] == 'd':
489 489 yield f
490 490
491 491 def extras(self, filename):
492 492 return self._stateextras.setdefault(filename, {})
493 493
494 494 def _resolve(self, preresolve, dfile, wctx):
495 495 """rerun merge process for file path `dfile`"""
496 496 if self[dfile] in 'rd':
497 497 return True, 0
498 498 stateentry = self._state[dfile]
499 499 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
500 500 octx = self._repo[self._other]
501 501 extras = self.extras(dfile)
502 502 anccommitnode = extras.get('ancestorlinknode')
503 503 if anccommitnode:
504 504 actx = self._repo[anccommitnode]
505 505 else:
506 506 actx = None
507 507 fcd = self._filectxorabsent(hash, wctx, dfile)
508 508 fco = self._filectxorabsent(onode, octx, ofile)
509 509 # TODO: move this to filectxorabsent
510 510 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
511 511 # "premerge" x flags
512 512 flo = fco.flags()
513 513 fla = fca.flags()
514 514 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
515 515 if fca.node() == nullid and flags != flo:
516 516 if preresolve:
517 517 self._repo.ui.warn(
518 518 _('warning: cannot merge flags for %s '
519 519 'without common ancestor - keeping local flags\n')
520 520 % afile)
521 521 elif flags == fla:
522 522 flags = flo
523 523 if preresolve:
524 524 # restore local
525 525 if hash != nullhex:
526 526 f = self._repo.vfs('merge/' + hash)
527 527 wctx[dfile].write(f.read(), flags)
528 528 f.close()
529 529 else:
530 530 wctx[dfile].remove(ignoremissing=True)
531 531 complete, r, deleted = filemerge.premerge(self._repo, wctx,
532 532 self._local, lfile, fcd,
533 533 fco, fca,
534 534 labels=self._labels)
535 535 else:
536 536 complete, r, deleted = filemerge.filemerge(self._repo, wctx,
537 537 self._local, lfile, fcd,
538 538 fco, fca,
539 539 labels=self._labels)
540 540 if r is None:
541 541 # no real conflict
542 542 del self._state[dfile]
543 543 self._stateextras.pop(dfile, None)
544 544 self._dirty = True
545 545 elif not r:
546 546 self.mark(dfile, 'r')
547 547
548 548 if complete:
549 549 action = None
550 550 if deleted:
551 551 if fcd.isabsent():
552 552 # dc: local picked. Need to drop if present, which may
553 553 # happen on re-resolves.
554 554 action = 'f'
555 555 else:
556 556 # cd: remote picked (or otherwise deleted)
557 557 action = 'r'
558 558 else:
559 559 if fcd.isabsent(): # dc: remote picked
560 560 action = 'g'
561 561 elif fco.isabsent(): # cd: local picked
562 562 if dfile in self.localctx:
563 563 action = 'am'
564 564 else:
565 565 action = 'a'
566 566 # else: regular merges (no action necessary)
567 567 self._results[dfile] = r, action
568 568
569 569 return complete, r
570 570
571 571 def _filectxorabsent(self, hexnode, ctx, f):
572 572 if hexnode == nullhex:
573 573 return filemerge.absentfilectx(ctx, f)
574 574 else:
575 575 return ctx[f]
576 576
577 577 def preresolve(self, dfile, wctx):
578 578 """run premerge process for dfile
579 579
580 580 Returns whether the merge is complete, and the exit code."""
581 581 return self._resolve(True, dfile, wctx)
582 582
583 583 def resolve(self, dfile, wctx):
584 584 """run merge process (assuming premerge was run) for dfile
585 585
586 586 Returns the exit code of the merge."""
587 587 return self._resolve(False, dfile, wctx)[1]
588 588
589 589 def counts(self):
590 590 """return counts for updated, merged and removed files in this
591 591 session"""
592 592 updated, merged, removed = 0, 0, 0
593 593 for r, action in self._results.itervalues():
594 594 if r is None:
595 595 updated += 1
596 596 elif r == 0:
597 597 if action == 'r':
598 598 removed += 1
599 599 else:
600 600 merged += 1
601 601 return updated, merged, removed
602 602
603 603 def unresolvedcount(self):
604 604 """get unresolved count for this merge (persistent)"""
605 605 return len(list(self.unresolved()))
606 606
607 607 def actions(self):
608 608 """return lists of actions to perform on the dirstate"""
609 609 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
610 610 for f, (r, action) in self._results.iteritems():
611 611 if action is not None:
612 612 actions[action].append((f, None, "merge result"))
613 613 return actions
614 614
615 615 def recordactions(self):
616 616 """record remove/add/get actions in the dirstate"""
617 617 branchmerge = self._repo.dirstate.p2() != nullid
618 618 recordupdates(self._repo, self.actions(), branchmerge)
619 619
620 620 def queueremove(self, f):
621 621 """queues a file to be removed from the dirstate
622 622
623 623 Meant for use by custom merge drivers."""
624 624 self._results[f] = 0, 'r'
625 625
626 626 def queueadd(self, f):
627 627 """queues a file to be added to the dirstate
628 628
629 629 Meant for use by custom merge drivers."""
630 630 self._results[f] = 0, 'a'
631 631
632 632 def queueget(self, f):
633 633 """queues a file to be marked modified in the dirstate
634 634
635 635 Meant for use by custom merge drivers."""
636 636 self._results[f] = 0, 'g'
637 637
638 638 def _getcheckunknownconfig(repo, section, name):
639 639 config = repo.ui.config(section, name)
640 640 valid = ['abort', 'ignore', 'warn']
641 641 if config not in valid:
642 642 validstr = ', '.join(["'" + v + "'" for v in valid])
643 643 raise error.ConfigError(_("%s.%s not valid "
644 644 "('%s' is none of %s)")
645 645 % (section, name, config, validstr))
646 646 return config
647 647
648 648 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
649 649 if f2 is None:
650 650 f2 = f
651 651 return (repo.wvfs.audit.check(f)
652 652 and repo.wvfs.isfileorlink(f)
653 653 and repo.dirstate.normalize(f) not in repo.dirstate
654 654 and mctx[f2].cmp(wctx[f]))
655 655
656 656 def _checkunknowndirs(repo, f):
657 657 """
658 658 Look for any unknown files or directories that may have a path conflict
659 659 with a file. If any path prefix of the file exists as a file or link,
660 660 then it conflicts. If the file itself is a directory that contains any
661 661 file that is not tracked, then it conflicts.
662 662
663 663 Returns the shortest path at which a conflict occurs, or None if there is
664 664 no conflict.
665 665 """
666 666
667 667 # Check for path prefixes that exist as unknown files.
668 668 for p in reversed(list(util.finddirs(f))):
669 669 if (repo.wvfs.audit.check(p)
670 670 and repo.wvfs.isfileorlink(p)
671 671 and repo.dirstate.normalize(p) not in repo.dirstate):
672 672 return p
673 673
674 674 # Check if the file conflicts with a directory containing unknown files.
675 675 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
676 676 # Does the directory contain any files that are not in the dirstate?
677 677 for p, dirs, files in repo.wvfs.walk(f):
678 678 for fn in files:
679 679 relf = repo.dirstate.normalize(repo.wvfs.reljoin(p, fn))
680 680 if relf not in repo.dirstate:
681 681 return f
682 682 return None
683 683
684 684 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
685 685 """
686 686 Considers any actions that care about the presence of conflicting unknown
687 687 files. For some actions, the result is to abort; for others, it is to
688 688 choose a different action.
689 689 """
690 690 fileconflicts = set()
691 691 pathconflicts = set()
692 692 warnconflicts = set()
693 693 abortconflicts = set()
694 694 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
695 695 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
696 pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts')
696 697 if not force:
697 698 def collectconflicts(conflicts, config):
698 699 if config == 'abort':
699 700 abortconflicts.update(conflicts)
700 701 elif config == 'warn':
701 702 warnconflicts.update(conflicts)
702 703
703 704 for f, (m, args, msg) in actions.iteritems():
704 705 if m in ('c', 'dc'):
705 706 if _checkunknownfile(repo, wctx, mctx, f):
706 707 fileconflicts.add(f)
707 elif f not in wctx:
708 elif pathconfig and f not in wctx:
708 709 path = _checkunknowndirs(repo, f)
709 710 if path is not None:
710 711 pathconflicts.add(path)
711 712 elif m == 'dg':
712 713 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
713 714 fileconflicts.add(f)
714 715
715 716 allconflicts = fileconflicts | pathconflicts
716 717 ignoredconflicts = set([c for c in allconflicts
717 718 if repo.dirstate._ignore(c)])
718 719 unknownconflicts = allconflicts - ignoredconflicts
719 720 collectconflicts(ignoredconflicts, ignoredconfig)
720 721 collectconflicts(unknownconflicts, unknownconfig)
721 722 else:
722 723 for f, (m, args, msg) in actions.iteritems():
723 724 if m == 'cm':
724 725 fl2, anc = args
725 726 different = _checkunknownfile(repo, wctx, mctx, f)
726 727 if repo.dirstate._ignore(f):
727 728 config = ignoredconfig
728 729 else:
729 730 config = unknownconfig
730 731
731 732 # The behavior when force is True is described by this table:
732 733 # config different mergeforce | action backup
733 734 # * n * | get n
734 735 # * y y | merge -
735 736 # abort y n | merge - (1)
736 737 # warn y n | warn + get y
737 738 # ignore y n | get y
738 739 #
739 740 # (1) this is probably the wrong behavior here -- we should
740 741 # probably abort, but some actions like rebases currently
741 742 # don't like an abort happening in the middle of
742 743 # merge.update.
743 744 if not different:
744 745 actions[f] = ('g', (fl2, False), "remote created")
745 746 elif mergeforce or config == 'abort':
746 747 actions[f] = ('m', (f, f, None, False, anc),
747 748 "remote differs from untracked local")
748 749 elif config == 'abort':
749 750 abortconflicts.add(f)
750 751 else:
751 752 if config == 'warn':
752 753 warnconflicts.add(f)
753 754 actions[f] = ('g', (fl2, True), "remote created")
754 755
755 756 for f in sorted(abortconflicts):
756 757 warn = repo.ui.warn
757 758 if f in pathconflicts:
758 759 if repo.wvfs.isfileorlink(f):
759 760 warn(_("%s: untracked file conflicts with directory\n") % f)
760 761 else:
761 762 warn(_("%s: untracked directory conflicts with file\n") % f)
762 763 else:
763 764 warn(_("%s: untracked file differs\n") % f)
764 765 if abortconflicts:
765 766 raise error.Abort(_("untracked files in working directory "
766 767 "differ from files in requested revision"))
767 768
768 769 for f in sorted(warnconflicts):
769 770 if repo.wvfs.isfileorlink(f):
770 771 repo.ui.warn(_("%s: replacing untracked file\n") % f)
771 772 else:
772 773 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
773 774
774 775 for f, (m, args, msg) in actions.iteritems():
775 776 if m == 'c':
776 777 backup = (f in fileconflicts or f in pathconflicts or
777 778 any(p in pathconflicts for p in util.finddirs(f)))
778 779 flags, = args
779 780 actions[f] = ('g', (flags, backup), msg)
780 781
781 782 def _forgetremoved(wctx, mctx, branchmerge):
782 783 """
783 784 Forget removed files
784 785
785 786 If we're jumping between revisions (as opposed to merging), and if
786 787 neither the working directory nor the target rev has the file,
787 788 then we need to remove it from the dirstate, to prevent the
788 789 dirstate from listing the file when it is no longer in the
789 790 manifest.
790 791
791 792 If we're merging, and the other revision has removed a file
792 793 that is not present in the working directory, we need to mark it
793 794 as removed.
794 795 """
795 796
796 797 actions = {}
797 798 m = 'f'
798 799 if branchmerge:
799 800 m = 'r'
800 801 for f in wctx.deleted():
801 802 if f not in mctx:
802 803 actions[f] = m, None, "forget deleted"
803 804
804 805 if not branchmerge:
805 806 for f in wctx.removed():
806 807 if f not in mctx:
807 808 actions[f] = 'f', None, "forget removed"
808 809
809 810 return actions
810 811
811 812 def _checkcollision(repo, wmf, actions):
812 813 # build provisional merged manifest up
813 814 pmmf = set(wmf)
814 815
815 816 if actions:
816 817 # k, dr, e and rd are no-op
817 818 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
818 819 for f, args, msg in actions[m]:
819 820 pmmf.add(f)
820 821 for f, args, msg in actions['r']:
821 822 pmmf.discard(f)
822 823 for f, args, msg in actions['dm']:
823 824 f2, flags = args
824 825 pmmf.discard(f2)
825 826 pmmf.add(f)
826 827 for f, args, msg in actions['dg']:
827 828 pmmf.add(f)
828 829 for f, args, msg in actions['m']:
829 830 f1, f2, fa, move, anc = args
830 831 if move:
831 832 pmmf.discard(f1)
832 833 pmmf.add(f)
833 834
834 835 # check case-folding collision in provisional merged manifest
835 836 foldmap = {}
836 837 for f in pmmf:
837 838 fold = util.normcase(f)
838 839 if fold in foldmap:
839 840 raise error.Abort(_("case-folding collision between %s and %s")
840 841 % (f, foldmap[fold]))
841 842 foldmap[fold] = f
842 843
843 844 # check case-folding of directories
844 845 foldprefix = unfoldprefix = lastfull = ''
845 846 for fold, f in sorted(foldmap.items()):
846 847 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
847 848 # the folded prefix matches but actual casing is different
848 849 raise error.Abort(_("case-folding collision between "
849 850 "%s and directory of %s") % (lastfull, f))
850 851 foldprefix = fold + '/'
851 852 unfoldprefix = f + '/'
852 853 lastfull = f
853 854
854 855 def driverpreprocess(repo, ms, wctx, labels=None):
855 856 """run the preprocess step of the merge driver, if any
856 857
857 858 This is currently not implemented -- it's an extension point."""
858 859 return True
859 860
860 861 def driverconclude(repo, ms, wctx, labels=None):
861 862 """run the conclude step of the merge driver, if any
862 863
863 864 This is currently not implemented -- it's an extension point."""
864 865 return True
865 866
866 867 def _filesindirs(repo, manifest, dirs):
867 868 """
868 869 Generator that yields pairs of all the files in the manifest that are found
869 870 inside the directories listed in dirs, and which directory they are found
870 871 in.
871 872 """
872 873 for f in manifest:
873 874 for p in util.finddirs(f):
874 875 if p in dirs:
875 876 yield f, p
876 877 break
877 878
878 879 def checkpathconflicts(repo, wctx, mctx, actions):
879 880 """
880 881 Check if any actions introduce path conflicts in the repository, updating
881 882 actions to record or handle the path conflict accordingly.
882 883 """
883 884 mf = wctx.manifest()
884 885
885 886 # The set of local files that conflict with a remote directory.
886 887 localconflicts = set()
887 888
888 889 # The set of directories that conflict with a remote file, and so may cause
889 890 # conflicts if they still contain any files after the merge.
890 891 remoteconflicts = set()
891 892
892 893 # The set of directories that appear as both a file and a directory in the
893 894 # remote manifest. These indicate an invalid remote manifest, which
894 895 # can't be updated to cleanly.
895 896 invalidconflicts = set()
896 897
897 898 # The set of files deleted by all the actions.
898 899 deletedfiles = set()
899 900
900 901 for f, (m, args, msg) in actions.items():
901 902 if m in ('c', 'dc', 'm', 'cm'):
902 903 # This action may create a new local file.
903 904 if mf.hasdir(f):
904 905 # The file aliases a local directory. This might be ok if all
905 906 # the files in the local directory are being deleted. This
906 907 # will be checked once we know what all the deleted files are.
907 908 remoteconflicts.add(f)
908 909 for p in util.finddirs(f):
909 910 if p in mf:
910 911 if p in mctx:
911 912 # The file is in a directory which aliases both a local
912 913 # and a remote file. This is an internal inconsistency
913 914 # within the remote manifest.
914 915 invalidconflicts.add(p)
915 916 else:
916 917 # The file is in a directory which aliases a local file.
917 918 # We will need to rename the local file.
918 919 localconflicts.add(p)
919 920 if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
920 921 # The file is in a directory which aliases a remote file.
921 922 # This is an internal inconsistency within the remote
922 923 # manifest.
923 924 invalidconflicts.add(p)
924 925
925 926 # Track the names of all deleted files.
926 927 if m == 'r':
927 928 deletedfiles.add(f)
928 929 if m == 'm':
929 930 f1, f2, fa, move, anc = args
930 931 if move:
931 932 deletedfiles.add(f1)
932 933 if m == 'dm':
933 934 f2, flags = args
934 935 deletedfiles.add(f2)
935 936
936 937 # Rename all local conflicting files that have not been deleted.
937 938 for p in localconflicts:
938 939 if p not in deletedfiles:
939 940 ctxname = str(wctx).rstrip('+')
940 941 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
941 942 actions[pnew] = ('pr', (p,), "local path conflict")
942 943 actions[p] = ('p', (pnew, 'l'), "path conflict")
943 944
944 945 if remoteconflicts:
945 946 # Check if all files in the conflicting directories have been removed.
946 947 ctxname = str(mctx).rstrip('+')
947 948 for f, p in _filesindirs(repo, mf, remoteconflicts):
948 949 if f not in deletedfiles:
949 950 m, args, msg = actions[p]
950 951 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
951 952 if m in ('dc', 'm'):
952 953 # Action was merge, just update target.
953 954 actions[pnew] = (m, args, msg)
954 955 else:
955 956 # Action was create, change to renamed get action.
956 957 fl = args[0]
957 958 actions[pnew] = ('dg', (p, fl), "remote path conflict")
958 959 actions[p] = ('p', (pnew, 'r'), "path conflict")
959 960 remoteconflicts.remove(p)
960 961 break
961 962
962 963 if invalidconflicts:
963 964 for p in invalidconflicts:
964 965 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
965 966 raise error.Abort(_("destination manifest contains path conflicts"))
966 967
967 968 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
968 969 acceptremote, followcopies, forcefulldiff=False):
969 970 """
970 971 Merge wctx and p2 with ancestor pa and generate merge action list
971 972
972 973 branchmerge and force are as passed in to update
973 974 matcher = matcher to filter file lists
974 975 acceptremote = accept the incoming changes without prompting
975 976 """
976 977 if matcher is not None and matcher.always():
977 978 matcher = None
978 979
979 980 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
980 981
981 982 # manifests fetched in order are going to be faster, so prime the caches
982 983 [x.manifest() for x in
983 984 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
984 985
985 986 if followcopies:
986 987 ret = copies.mergecopies(repo, wctx, p2, pa)
987 988 copy, movewithdir, diverge, renamedelete, dirmove = ret
988 989
989 990 boolbm = pycompat.bytestr(bool(branchmerge))
990 991 boolf = pycompat.bytestr(bool(force))
991 992 boolm = pycompat.bytestr(bool(matcher))
992 993 repo.ui.note(_("resolving manifests\n"))
993 994 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
994 995 % (boolbm, boolf, boolm))
995 996 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
996 997
997 998 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
998 999 copied = set(copy.values())
999 1000 copied.update(movewithdir.values())
1000 1001
1001 1002 if '.hgsubstate' in m1:
1002 1003 # check whether sub state is modified
1003 1004 if any(wctx.sub(s).dirty() for s in wctx.substate):
1004 1005 m1['.hgsubstate'] = modifiednodeid
1005 1006
1006 1007 # Don't use m2-vs-ma optimization if:
1007 1008 # - ma is the same as m1 or m2, which we're just going to diff again later
1008 1009 # - The caller specifically asks for a full diff, which is useful during bid
1009 1010 # merge.
1010 1011 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
1011 1012 # Identify which files are relevant to the merge, so we can limit the
1012 1013 # total m1-vs-m2 diff to just those files. This has significant
1013 1014 # performance benefits in large repositories.
1014 1015 relevantfiles = set(ma.diff(m2).keys())
1015 1016
1016 1017 # For copied and moved files, we need to add the source file too.
1017 1018 for copykey, copyvalue in copy.iteritems():
1018 1019 if copyvalue in relevantfiles:
1019 1020 relevantfiles.add(copykey)
1020 1021 for movedirkey in movewithdir:
1021 1022 relevantfiles.add(movedirkey)
1022 1023 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1023 1024 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1024 1025
1025 1026 diff = m1.diff(m2, match=matcher)
1026 1027
1027 1028 if matcher is None:
1028 1029 matcher = matchmod.always('', '')
1029 1030
1030 1031 actions = {}
1031 1032 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
1032 1033 if n1 and n2: # file exists on both local and remote side
1033 1034 if f not in ma:
1034 1035 fa = copy.get(f, None)
1035 1036 if fa is not None:
1036 1037 actions[f] = ('m', (f, f, fa, False, pa.node()),
1037 1038 "both renamed from " + fa)
1038 1039 else:
1039 1040 actions[f] = ('m', (f, f, None, False, pa.node()),
1040 1041 "both created")
1041 1042 else:
1042 1043 a = ma[f]
1043 1044 fla = ma.flags(f)
1044 1045 nol = 'l' not in fl1 + fl2 + fla
1045 1046 if n2 == a and fl2 == fla:
1046 1047 actions[f] = ('k', (), "remote unchanged")
1047 1048 elif n1 == a and fl1 == fla: # local unchanged - use remote
1048 1049 if n1 == n2: # optimization: keep local content
1049 1050 actions[f] = ('e', (fl2,), "update permissions")
1050 1051 else:
1051 1052 actions[f] = ('g', (fl2, False), "remote is newer")
1052 1053 elif nol and n2 == a: # remote only changed 'x'
1053 1054 actions[f] = ('e', (fl2,), "update permissions")
1054 1055 elif nol and n1 == a: # local only changed 'x'
1055 1056 actions[f] = ('g', (fl1, False), "remote is newer")
1056 1057 else: # both changed something
1057 1058 actions[f] = ('m', (f, f, f, False, pa.node()),
1058 1059 "versions differ")
1059 1060 elif n1: # file exists only on local side
1060 1061 if f in copied:
1061 1062 pass # we'll deal with it on m2 side
1062 1063 elif f in movewithdir: # directory rename, move local
1063 1064 f2 = movewithdir[f]
1064 1065 if f2 in m2:
1065 1066 actions[f2] = ('m', (f, f2, None, True, pa.node()),
1066 1067 "remote directory rename, both created")
1067 1068 else:
1068 1069 actions[f2] = ('dm', (f, fl1),
1069 1070 "remote directory rename - move from " + f)
1070 1071 elif f in copy:
1071 1072 f2 = copy[f]
1072 1073 actions[f] = ('m', (f, f2, f2, False, pa.node()),
1073 1074 "local copied/moved from " + f2)
1074 1075 elif f in ma: # clean, a different, no remote
1075 1076 if n1 != ma[f]:
1076 1077 if acceptremote:
1077 1078 actions[f] = ('r', None, "remote delete")
1078 1079 else:
1079 1080 actions[f] = ('cd', (f, None, f, False, pa.node()),
1080 1081 "prompt changed/deleted")
1081 1082 elif n1 == addednodeid:
1082 1083 # This extra 'a' is added by working copy manifest to mark
1083 1084 # the file as locally added. We should forget it instead of
1084 1085 # deleting it.
1085 1086 actions[f] = ('f', None, "remote deleted")
1086 1087 else:
1087 1088 actions[f] = ('r', None, "other deleted")
1088 1089 elif n2: # file exists only on remote side
1089 1090 if f in copied:
1090 1091 pass # we'll deal with it on m1 side
1091 1092 elif f in movewithdir:
1092 1093 f2 = movewithdir[f]
1093 1094 if f2 in m1:
1094 1095 actions[f2] = ('m', (f2, f, None, False, pa.node()),
1095 1096 "local directory rename, both created")
1096 1097 else:
1097 1098 actions[f2] = ('dg', (f, fl2),
1098 1099 "local directory rename - get from " + f)
1099 1100 elif f in copy:
1100 1101 f2 = copy[f]
1101 1102 if f2 in m2:
1102 1103 actions[f] = ('m', (f2, f, f2, False, pa.node()),
1103 1104 "remote copied from " + f2)
1104 1105 else:
1105 1106 actions[f] = ('m', (f2, f, f2, True, pa.node()),
1106 1107 "remote moved from " + f2)
1107 1108 elif f not in ma:
1108 1109 # local unknown, remote created: the logic is described by the
1109 1110 # following table:
1110 1111 #
1111 1112 # force branchmerge different | action
1112 1113 # n * * | create
1113 1114 # y n * | create
1114 1115 # y y n | create
1115 1116 # y y y | merge
1116 1117 #
1117 1118 # Checking whether the files are different is expensive, so we
1118 1119 # don't do that when we can avoid it.
1119 1120 if not force:
1120 1121 actions[f] = ('c', (fl2,), "remote created")
1121 1122 elif not branchmerge:
1122 1123 actions[f] = ('c', (fl2,), "remote created")
1123 1124 else:
1124 1125 actions[f] = ('cm', (fl2, pa.node()),
1125 1126 "remote created, get or merge")
1126 1127 elif n2 != ma[f]:
1127 1128 df = None
1128 1129 for d in dirmove:
1129 1130 if f.startswith(d):
1130 1131 # new file added in a directory that was moved
1131 1132 df = dirmove[d] + f[len(d):]
1132 1133 break
1133 1134 if df is not None and df in m1:
1134 1135 actions[df] = ('m', (df, f, f, False, pa.node()),
1135 1136 "local directory rename - respect move from " + f)
1136 1137 elif acceptremote:
1137 1138 actions[f] = ('c', (fl2,), "remote recreating")
1138 1139 else:
1139 1140 actions[f] = ('dc', (None, f, f, False, pa.node()),
1140 1141 "prompt deleted/changed")
1141 1142
1142 # If we are merging, look for path conflicts.
1143 checkpathconflicts(repo, wctx, p2, actions)
1143 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1144 # If we are merging, look for path conflicts.
1145 checkpathconflicts(repo, wctx, p2, actions)
1144 1146
1145 1147 return actions, diverge, renamedelete
1146 1148
1147 1149 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1148 1150 """Resolves false conflicts where the nodeid changed but the content
1149 1151 remained the same."""
1150 1152
1151 1153 for f, (m, args, msg) in actions.items():
1152 1154 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
1153 1155 # local did change but ended up with same content
1154 1156 actions[f] = 'r', None, "prompt same"
1155 1157 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
1156 1158 # remote did change but ended up with same content
1157 1159 del actions[f] # don't get = keep local deleted
1158 1160
1159 1161 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1160 1162 acceptremote, followcopies, matcher=None,
1161 1163 mergeforce=False):
1162 1164 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1163 1165 # Avoid cycle.
1164 1166 from . import sparse
1165 1167
1166 1168 if len(ancestors) == 1: # default
1167 1169 actions, diverge, renamedelete = manifestmerge(
1168 1170 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1169 1171 acceptremote, followcopies)
1170 1172 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1171 1173
1172 1174 else: # only when merge.preferancestor=* - the default
1173 1175 repo.ui.note(
1174 1176 _("note: merging %s and %s using bids from ancestors %s\n") %
1175 1177 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1176 1178 for anc in ancestors)))
1177 1179
1178 1180 # Call for bids
1179 1181 fbids = {} # mapping filename to bids (action method to list af actions)
1180 1182 diverge, renamedelete = None, None
1181 1183 for ancestor in ancestors:
1182 1184 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1183 1185 actions, diverge1, renamedelete1 = manifestmerge(
1184 1186 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1185 1187 acceptremote, followcopies, forcefulldiff=True)
1186 1188 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1187 1189
1188 1190 # Track the shortest set of warning on the theory that bid
1189 1191 # merge will correctly incorporate more information
1190 1192 if diverge is None or len(diverge1) < len(diverge):
1191 1193 diverge = diverge1
1192 1194 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1193 1195 renamedelete = renamedelete1
1194 1196
1195 1197 for f, a in sorted(actions.iteritems()):
1196 1198 m, args, msg = a
1197 1199 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1198 1200 if f in fbids:
1199 1201 d = fbids[f]
1200 1202 if m in d:
1201 1203 d[m].append(a)
1202 1204 else:
1203 1205 d[m] = [a]
1204 1206 else:
1205 1207 fbids[f] = {m: [a]}
1206 1208
1207 1209 # Pick the best bid for each file
1208 1210 repo.ui.note(_('\nauction for merging merge bids\n'))
1209 1211 actions = {}
1210 1212 dms = [] # filenames that have dm actions
1211 1213 for f, bids in sorted(fbids.items()):
1212 1214 # bids is a mapping from action method to list af actions
1213 1215 # Consensus?
1214 1216 if len(bids) == 1: # all bids are the same kind of method
1215 1217 m, l = list(bids.items())[0]
1216 1218 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1217 1219 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1218 1220 actions[f] = l[0]
1219 1221 if m == 'dm':
1220 1222 dms.append(f)
1221 1223 continue
1222 1224 # If keep is an option, just do it.
1223 1225 if 'k' in bids:
1224 1226 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1225 1227 actions[f] = bids['k'][0]
1226 1228 continue
1227 1229 # If there are gets and they all agree [how could they not?], do it.
1228 1230 if 'g' in bids:
1229 1231 ga0 = bids['g'][0]
1230 1232 if all(a == ga0 for a in bids['g'][1:]):
1231 1233 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1232 1234 actions[f] = ga0
1233 1235 continue
1234 1236 # TODO: Consider other simple actions such as mode changes
1235 1237 # Handle inefficient democrazy.
1236 1238 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1237 1239 for m, l in sorted(bids.items()):
1238 1240 for _f, args, msg in l:
1239 1241 repo.ui.note(' %s -> %s\n' % (msg, m))
1240 1242 # Pick random action. TODO: Instead, prompt user when resolving
1241 1243 m, l = list(bids.items())[0]
1242 1244 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1243 1245 (f, m))
1244 1246 actions[f] = l[0]
1245 1247 if m == 'dm':
1246 1248 dms.append(f)
1247 1249 continue
1248 1250 # Work around 'dm' that can cause multiple actions for the same file
1249 1251 for f in dms:
1250 1252 dm, (f0, flags), msg = actions[f]
1251 1253 assert dm == 'dm', dm
1252 1254 if f0 in actions and actions[f0][0] == 'r':
1253 1255 # We have one bid for removing a file and another for moving it.
1254 1256 # These two could be merged as first move and then delete ...
1255 1257 # but instead drop moving and just delete.
1256 1258 del actions[f]
1257 1259 repo.ui.note(_('end of auction\n\n'))
1258 1260
1259 1261 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1260 1262
1261 1263 if wctx.rev() is None:
1262 1264 fractions = _forgetremoved(wctx, mctx, branchmerge)
1263 1265 actions.update(fractions)
1264 1266
1265 1267 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1266 1268 actions)
1267 1269
1268 1270 return prunedactions, diverge, renamedelete
1269 1271
1270 1272 def _getcwd():
1271 1273 try:
1272 1274 return pycompat.getcwd()
1273 1275 except OSError as err:
1274 1276 if err.errno == errno.ENOENT:
1275 1277 return None
1276 1278 raise
1277 1279
1278 1280 def batchremove(repo, wctx, actions):
1279 1281 """apply removes to the working directory
1280 1282
1281 1283 yields tuples for progress updates
1282 1284 """
1283 1285 verbose = repo.ui.verbose
1284 1286 cwd = _getcwd()
1285 1287 i = 0
1286 1288 for f, args, msg in actions:
1287 1289 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1288 1290 if verbose:
1289 1291 repo.ui.note(_("removing %s\n") % f)
1290 1292 wctx[f].audit()
1291 1293 try:
1292 1294 wctx[f].remove(ignoremissing=True)
1293 1295 except OSError as inst:
1294 1296 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1295 1297 (f, inst.strerror))
1296 1298 if i == 100:
1297 1299 yield i, f
1298 1300 i = 0
1299 1301 i += 1
1300 1302 if i > 0:
1301 1303 yield i, f
1302 1304
1303 1305 if cwd and not _getcwd():
1304 1306 # cwd was removed in the course of removing files; print a helpful
1305 1307 # warning.
1306 1308 repo.ui.warn(_("current directory was removed\n"
1307 1309 "(consider changing to repo root: %s)\n") % repo.root)
1308 1310
1309 1311 # It's necessary to flush here in case we're inside a worker fork and will
1310 1312 # quit after this function.
1311 1313 wctx.flushall()
1312 1314
1313 1315 def batchget(repo, mctx, wctx, actions):
1314 1316 """apply gets to the working directory
1315 1317
1316 1318 mctx is the context to get from
1317 1319
1318 1320 yields tuples for progress updates
1319 1321 """
1320 1322 verbose = repo.ui.verbose
1321 1323 fctx = mctx.filectx
1322 1324 ui = repo.ui
1323 1325 i = 0
1324 1326 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1325 1327 for f, (flags, backup), msg in actions:
1326 1328 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1327 1329 if verbose:
1328 1330 repo.ui.note(_("getting %s\n") % f)
1329 1331
1330 1332 if backup:
1331 1333 # If a file or directory exists with the same name, back that
1332 1334 # up. Otherwise, look to see if there is a file that conflicts
1333 1335 # with a directory this file is in, and if so, back that up.
1334 1336 absf = repo.wjoin(f)
1335 1337 if not repo.wvfs.lexists(f):
1336 1338 for p in util.finddirs(f):
1337 1339 if repo.wvfs.isfileorlink(p):
1338 1340 absf = repo.wjoin(p)
1339 1341 break
1340 1342 orig = scmutil.origpath(ui, repo, absf)
1341 1343 if repo.wvfs.lexists(absf):
1342 1344 util.rename(absf, orig)
1343 1345 wctx[f].clearunknown()
1344 1346 wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
1345 1347 if i == 100:
1346 1348 yield i, f
1347 1349 i = 0
1348 1350 i += 1
1349 1351 if i > 0:
1350 1352 yield i, f
1351 1353
1352 1354 # It's necessary to flush here in case we're inside a worker fork and will
1353 1355 # quit after this function.
1354 1356 wctx.flushall()
1355 1357
1356 1358 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1357 1359 """apply the merge action list to the working directory
1358 1360
1359 1361 wctx is the working copy context
1360 1362 mctx is the context to be merged into the working copy
1361 1363
1362 1364 Return a tuple of counts (updated, merged, removed, unresolved) that
1363 1365 describes how many files were affected by the update.
1364 1366 """
1365 1367
1366 1368 updated, merged, removed = 0, 0, 0
1367 1369 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1368 1370 moves = []
1369 1371 for m, l in actions.items():
1370 1372 l.sort()
1371 1373
1372 1374 # 'cd' and 'dc' actions are treated like other merge conflicts
1373 1375 mergeactions = sorted(actions['cd'])
1374 1376 mergeactions.extend(sorted(actions['dc']))
1375 1377 mergeactions.extend(actions['m'])
1376 1378 for f, args, msg in mergeactions:
1377 1379 f1, f2, fa, move, anc = args
1378 1380 if f == '.hgsubstate': # merged internally
1379 1381 continue
1380 1382 if f1 is None:
1381 1383 fcl = filemerge.absentfilectx(wctx, fa)
1382 1384 else:
1383 1385 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1384 1386 fcl = wctx[f1]
1385 1387 if f2 is None:
1386 1388 fco = filemerge.absentfilectx(mctx, fa)
1387 1389 else:
1388 1390 fco = mctx[f2]
1389 1391 actx = repo[anc]
1390 1392 if fa in actx:
1391 1393 fca = actx[fa]
1392 1394 else:
1393 1395 # TODO: move to absentfilectx
1394 1396 fca = repo.filectx(f1, fileid=nullrev)
1395 1397 ms.add(fcl, fco, fca, f)
1396 1398 if f1 != f and move:
1397 1399 moves.append(f1)
1398 1400
1399 1401 _updating = _('updating')
1400 1402 _files = _('files')
1401 1403 progress = repo.ui.progress
1402 1404
1403 1405 # remove renamed files after safely stored
1404 1406 for f in moves:
1405 1407 if wctx[f].lexists():
1406 1408 repo.ui.debug("removing %s\n" % f)
1407 1409 wctx[f].audit()
1408 1410 wctx[f].remove()
1409 1411
1410 1412 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1411 1413 z = 0
1412 1414
1413 1415 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1414 1416 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1415 1417
1416 1418 # record path conflicts
1417 1419 for f, args, msg in actions['p']:
1418 1420 f1, fo = args
1419 1421 s = repo.ui.status
1420 1422 s(_("%s: path conflict - a file or link has the same name as a "
1421 1423 "directory\n") % f)
1422 1424 if fo == 'l':
1423 1425 s(_("the local file has been renamed to %s\n") % f1)
1424 1426 else:
1425 1427 s(_("the remote file has been renamed to %s\n") % f1)
1426 1428 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1427 1429 ms.addpath(f, f1, fo)
1428 1430 z += 1
1429 1431 progress(_updating, z, item=f, total=numupdates, unit=_files)
1430 1432
1431 1433 # When merging in-memory, we can't support worker processes, so set the
1432 1434 # per-item cost at 0 in that case.
1433 1435 cost = 0 if wctx.isinmemory() else 0.001
1434 1436
1435 1437 # remove in parallel (must come before resolving path conflicts and getting)
1436 1438 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
1437 1439 actions['r'])
1438 1440 for i, item in prog:
1439 1441 z += i
1440 1442 progress(_updating, z, item=item, total=numupdates, unit=_files)
1441 1443 removed = len(actions['r'])
1442 1444
1443 1445 # resolve path conflicts (must come before getting)
1444 1446 for f, args, msg in actions['pr']:
1445 1447 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1446 1448 f0, = args
1447 1449 if wctx[f0].lexists():
1448 1450 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1449 1451 wctx[f].audit()
1450 1452 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1451 1453 wctx[f0].remove()
1452 1454 z += 1
1453 1455 progress(_updating, z, item=f, total=numupdates, unit=_files)
1454 1456
1455 1457 # We should flush before forking into worker processes, since those workers
1456 1458 # flush when they complete, and we don't want to duplicate work.
1457 1459 wctx.flushall()
1458 1460
1459 1461 # get in parallel
1460 1462 prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
1461 1463 actions['g'])
1462 1464 for i, item in prog:
1463 1465 z += i
1464 1466 progress(_updating, z, item=item, total=numupdates, unit=_files)
1465 1467 updated = len(actions['g'])
1466 1468
1467 1469 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1468 1470 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1469 1471
1470 1472 # forget (manifest only, just log it) (must come first)
1471 1473 for f, args, msg in actions['f']:
1472 1474 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1473 1475 z += 1
1474 1476 progress(_updating, z, item=f, total=numupdates, unit=_files)
1475 1477
1476 1478 # re-add (manifest only, just log it)
1477 1479 for f, args, msg in actions['a']:
1478 1480 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1479 1481 z += 1
1480 1482 progress(_updating, z, item=f, total=numupdates, unit=_files)
1481 1483
1482 1484 # re-add/mark as modified (manifest only, just log it)
1483 1485 for f, args, msg in actions['am']:
1484 1486 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1485 1487 z += 1
1486 1488 progress(_updating, z, item=f, total=numupdates, unit=_files)
1487 1489
1488 1490 # keep (noop, just log it)
1489 1491 for f, args, msg in actions['k']:
1490 1492 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1491 1493 # no progress
1492 1494
1493 1495 # directory rename, move local
1494 1496 for f, args, msg in actions['dm']:
1495 1497 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1496 1498 z += 1
1497 1499 progress(_updating, z, item=f, total=numupdates, unit=_files)
1498 1500 f0, flags = args
1499 1501 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1500 1502 wctx[f].audit()
1501 1503 wctx[f].write(wctx.filectx(f0).data(), flags)
1502 1504 wctx[f0].remove()
1503 1505 updated += 1
1504 1506
1505 1507 # local directory rename, get
1506 1508 for f, args, msg in actions['dg']:
1507 1509 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1508 1510 z += 1
1509 1511 progress(_updating, z, item=f, total=numupdates, unit=_files)
1510 1512 f0, flags = args
1511 1513 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1512 1514 wctx[f].write(mctx.filectx(f0).data(), flags)
1513 1515 updated += 1
1514 1516
1515 1517 # exec
1516 1518 for f, args, msg in actions['e']:
1517 1519 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1518 1520 z += 1
1519 1521 progress(_updating, z, item=f, total=numupdates, unit=_files)
1520 1522 flags, = args
1521 1523 wctx[f].audit()
1522 1524 wctx[f].setflags('l' in flags, 'x' in flags)
1523 1525 updated += 1
1524 1526
1525 1527 # the ordering is important here -- ms.mergedriver will raise if the merge
1526 1528 # driver has changed, and we want to be able to bypass it when overwrite is
1527 1529 # True
1528 1530 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1529 1531
1530 1532 if usemergedriver:
1531 1533 ms.commit()
1532 1534 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1533 1535 # the driver might leave some files unresolved
1534 1536 unresolvedf = set(ms.unresolved())
1535 1537 if not proceed:
1536 1538 # XXX setting unresolved to at least 1 is a hack to make sure we
1537 1539 # error out
1538 1540 return updated, merged, removed, max(len(unresolvedf), 1)
1539 1541 newactions = []
1540 1542 for f, args, msg in mergeactions:
1541 1543 if f in unresolvedf:
1542 1544 newactions.append((f, args, msg))
1543 1545 mergeactions = newactions
1544 1546
1545 1547 try:
1546 1548 # premerge
1547 1549 tocomplete = []
1548 1550 for f, args, msg in mergeactions:
1549 1551 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1550 1552 z += 1
1551 1553 progress(_updating, z, item=f, total=numupdates, unit=_files)
1552 1554 if f == '.hgsubstate': # subrepo states need updating
1553 1555 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1554 1556 overwrite, labels)
1555 1557 continue
1556 1558 wctx[f].audit()
1557 1559 complete, r = ms.preresolve(f, wctx)
1558 1560 if not complete:
1559 1561 numupdates += 1
1560 1562 tocomplete.append((f, args, msg))
1561 1563
1562 1564 # merge
1563 1565 for f, args, msg in tocomplete:
1564 1566 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1565 1567 z += 1
1566 1568 progress(_updating, z, item=f, total=numupdates, unit=_files)
1567 1569 ms.resolve(f, wctx)
1568 1570
1569 1571 finally:
1570 1572 ms.commit()
1571 1573
1572 1574 unresolved = ms.unresolvedcount()
1573 1575
1574 1576 if usemergedriver and not unresolved and ms.mdstate() != 's':
1575 1577 if not driverconclude(repo, ms, wctx, labels=labels):
1576 1578 # XXX setting unresolved to at least 1 is a hack to make sure we
1577 1579 # error out
1578 1580 unresolved = max(unresolved, 1)
1579 1581
1580 1582 ms.commit()
1581 1583
1582 1584 msupdated, msmerged, msremoved = ms.counts()
1583 1585 updated += msupdated
1584 1586 merged += msmerged
1585 1587 removed += msremoved
1586 1588
1587 1589 extraactions = ms.actions()
1588 1590 if extraactions:
1589 1591 mfiles = set(a[0] for a in actions['m'])
1590 1592 for k, acts in extraactions.iteritems():
1591 1593 actions[k].extend(acts)
1592 1594 # Remove these files from actions['m'] as well. This is important
1593 1595 # because in recordupdates, files in actions['m'] are processed
1594 1596 # after files in other actions, and the merge driver might add
1595 1597 # files to those actions via extraactions above. This can lead to a
1596 1598 # file being recorded twice, with poor results. This is especially
1597 1599 # problematic for actions['r'] (currently only possible with the
1598 1600 # merge driver in the initial merge process; interrupted merges
1599 1601 # don't go through this flow).
1600 1602 #
1601 1603 # The real fix here is to have indexes by both file and action so
1602 1604 # that when the action for a file is changed it is automatically
1603 1605 # reflected in the other action lists. But that involves a more
1604 1606 # complex data structure, so this will do for now.
1605 1607 #
1606 1608 # We don't need to do the same operation for 'dc' and 'cd' because
1607 1609 # those lists aren't consulted again.
1608 1610 mfiles.difference_update(a[0] for a in acts)
1609 1611
1610 1612 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1611 1613
1612 1614 progress(_updating, None, total=numupdates, unit=_files)
1613 1615
1614 1616 return updated, merged, removed, unresolved
1615 1617
1616 1618 def recordupdates(repo, actions, branchmerge):
1617 1619 "record merge actions to the dirstate"
1618 1620 # remove (must come first)
1619 1621 for f, args, msg in actions.get('r', []):
1620 1622 if branchmerge:
1621 1623 repo.dirstate.remove(f)
1622 1624 else:
1623 1625 repo.dirstate.drop(f)
1624 1626
1625 1627 # forget (must come first)
1626 1628 for f, args, msg in actions.get('f', []):
1627 1629 repo.dirstate.drop(f)
1628 1630
1629 1631 # resolve path conflicts
1630 1632 for f, args, msg in actions.get('pr', []):
1631 1633 f0, = args
1632 1634 origf0 = repo.dirstate.copied(f0) or f0
1633 1635 repo.dirstate.add(f)
1634 1636 repo.dirstate.copy(origf0, f)
1635 1637 if f0 == origf0:
1636 1638 repo.dirstate.remove(f0)
1637 1639 else:
1638 1640 repo.dirstate.drop(f0)
1639 1641
1640 1642 # re-add
1641 1643 for f, args, msg in actions.get('a', []):
1642 1644 repo.dirstate.add(f)
1643 1645
1644 1646 # re-add/mark as modified
1645 1647 for f, args, msg in actions.get('am', []):
1646 1648 if branchmerge:
1647 1649 repo.dirstate.normallookup(f)
1648 1650 else:
1649 1651 repo.dirstate.add(f)
1650 1652
1651 1653 # exec change
1652 1654 for f, args, msg in actions.get('e', []):
1653 1655 repo.dirstate.normallookup(f)
1654 1656
1655 1657 # keep
1656 1658 for f, args, msg in actions.get('k', []):
1657 1659 pass
1658 1660
1659 1661 # get
1660 1662 for f, args, msg in actions.get('g', []):
1661 1663 if branchmerge:
1662 1664 repo.dirstate.otherparent(f)
1663 1665 else:
1664 1666 repo.dirstate.normal(f)
1665 1667
1666 1668 # merge
1667 1669 for f, args, msg in actions.get('m', []):
1668 1670 f1, f2, fa, move, anc = args
1669 1671 if branchmerge:
1670 1672 # We've done a branch merge, mark this file as merged
1671 1673 # so that we properly record the merger later
1672 1674 repo.dirstate.merge(f)
1673 1675 if f1 != f2: # copy/rename
1674 1676 if move:
1675 1677 repo.dirstate.remove(f1)
1676 1678 if f1 != f:
1677 1679 repo.dirstate.copy(f1, f)
1678 1680 else:
1679 1681 repo.dirstate.copy(f2, f)
1680 1682 else:
1681 1683 # We've update-merged a locally modified file, so
1682 1684 # we set the dirstate to emulate a normal checkout
1683 1685 # of that file some time in the past. Thus our
1684 1686 # merge will appear as a normal local file
1685 1687 # modification.
1686 1688 if f2 == f: # file not locally copied/moved
1687 1689 repo.dirstate.normallookup(f)
1688 1690 if move:
1689 1691 repo.dirstate.drop(f1)
1690 1692
1691 1693 # directory rename, move local
1692 1694 for f, args, msg in actions.get('dm', []):
1693 1695 f0, flag = args
1694 1696 if branchmerge:
1695 1697 repo.dirstate.add(f)
1696 1698 repo.dirstate.remove(f0)
1697 1699 repo.dirstate.copy(f0, f)
1698 1700 else:
1699 1701 repo.dirstate.normal(f)
1700 1702 repo.dirstate.drop(f0)
1701 1703
1702 1704 # directory rename, get
1703 1705 for f, args, msg in actions.get('dg', []):
1704 1706 f0, flag = args
1705 1707 if branchmerge:
1706 1708 repo.dirstate.add(f)
1707 1709 repo.dirstate.copy(f0, f)
1708 1710 else:
1709 1711 repo.dirstate.normal(f)
1710 1712
1711 1713 def update(repo, node, branchmerge, force, ancestor=None,
1712 1714 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1713 1715 updatecheck=None, wc=None):
1714 1716 """
1715 1717 Perform a merge between the working directory and the given node
1716 1718
1717 1719 node = the node to update to
1718 1720 branchmerge = whether to merge between branches
1719 1721 force = whether to force branch merging or file overwriting
1720 1722 matcher = a matcher to filter file lists (dirstate not updated)
1721 1723 mergeancestor = whether it is merging with an ancestor. If true,
1722 1724 we should accept the incoming changes for any prompts that occur.
1723 1725 If false, merging with an ancestor (fast-forward) is only allowed
1724 1726 between different named branches. This flag is used by rebase extension
1725 1727 as a temporary fix and should be avoided in general.
1726 1728 labels = labels to use for base, local and other
1727 1729 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1728 1730 this is True, then 'force' should be True as well.
1729 1731
1730 1732 The table below shows all the behaviors of the update command given the
1731 1733 -c/--check and -C/--clean or no options, whether the working directory is
1732 1734 dirty, whether a revision is specified, and the relationship of the parent
1733 1735 rev to the target rev (linear or not). Match from top first. The -n
1734 1736 option doesn't exist on the command line, but represents the
1735 1737 experimental.updatecheck=noconflict option.
1736 1738
1737 1739 This logic is tested by test-update-branches.t.
1738 1740
1739 1741 -c -C -n -m dirty rev linear | result
1740 1742 y y * * * * * | (1)
1741 1743 y * y * * * * | (1)
1742 1744 y * * y * * * | (1)
1743 1745 * y y * * * * | (1)
1744 1746 * y * y * * * | (1)
1745 1747 * * y y * * * | (1)
1746 1748 * * * * * n n | x
1747 1749 * * * * n * * | ok
1748 1750 n n n n y * y | merge
1749 1751 n n n n y y n | (2)
1750 1752 n n n y y * * | merge
1751 1753 n n y n y * * | merge if no conflict
1752 1754 n y n n y * * | discard
1753 1755 y n n n y * * | (3)
1754 1756
1755 1757 x = can't happen
1756 1758 * = don't-care
1757 1759 1 = incompatible options (checked in commands.py)
1758 1760 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1759 1761 3 = abort: uncommitted changes (checked in commands.py)
1760 1762
1761 1763 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1762 1764 to repo[None] if None is passed.
1763 1765
1764 1766 Return the same tuple as applyupdates().
1765 1767 """
1766 1768 # Avoid cycle.
1767 1769 from . import sparse
1768 1770
1769 1771 # This function used to find the default destination if node was None, but
1770 1772 # that's now in destutil.py.
1771 1773 assert node is not None
1772 1774 if not branchmerge and not force:
1773 1775 # TODO: remove the default once all callers that pass branchmerge=False
1774 1776 # and force=False pass a value for updatecheck. We may want to allow
1775 1777 # updatecheck='abort' to better suppport some of these callers.
1776 1778 if updatecheck is None:
1777 1779 updatecheck = 'linear'
1778 1780 assert updatecheck in ('none', 'linear', 'noconflict')
1779 1781 # If we're doing a partial update, we need to skip updating
1780 1782 # the dirstate, so make a note of any partial-ness to the
1781 1783 # update here.
1782 1784 if matcher is None or matcher.always():
1783 1785 partial = False
1784 1786 else:
1785 1787 partial = True
1786 1788 with repo.wlock():
1787 1789 if wc is None:
1788 1790 wc = repo[None]
1789 1791 pl = wc.parents()
1790 1792 p1 = pl[0]
1791 1793 pas = [None]
1792 1794 if ancestor is not None:
1793 1795 pas = [repo[ancestor]]
1794 1796
1795 1797 overwrite = force and not branchmerge
1796 1798
1797 1799 p2 = repo[node]
1798 1800 if pas[0] is None:
1799 1801 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
1800 1802 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1801 1803 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1802 1804 else:
1803 1805 pas = [p1.ancestor(p2, warn=branchmerge)]
1804 1806
1805 1807 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1806 1808
1807 1809 ### check phase
1808 1810 if not overwrite:
1809 1811 if len(pl) > 1:
1810 1812 raise error.Abort(_("outstanding uncommitted merge"))
1811 1813 ms = mergestate.read(repo)
1812 1814 if list(ms.unresolved()):
1813 1815 raise error.Abort(_("outstanding merge conflicts"))
1814 1816 if branchmerge:
1815 1817 if pas == [p2]:
1816 1818 raise error.Abort(_("merging with a working directory ancestor"
1817 1819 " has no effect"))
1818 1820 elif pas == [p1]:
1819 1821 if not mergeancestor and wc.branch() == p2.branch():
1820 1822 raise error.Abort(_("nothing to merge"),
1821 1823 hint=_("use 'hg update' "
1822 1824 "or check 'hg heads'"))
1823 1825 if not force and (wc.files() or wc.deleted()):
1824 1826 raise error.Abort(_("uncommitted changes"),
1825 1827 hint=_("use 'hg status' to list changes"))
1826 1828 for s in sorted(wc.substate):
1827 1829 wc.sub(s).bailifchanged()
1828 1830
1829 1831 elif not overwrite:
1830 1832 if p1 == p2: # no-op update
1831 1833 # call the hooks and exit early
1832 1834 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1833 1835 repo.hook('update', parent1=xp2, parent2='', error=0)
1834 1836 return 0, 0, 0, 0
1835 1837
1836 1838 if (updatecheck == 'linear' and
1837 1839 pas not in ([p1], [p2])): # nonlinear
1838 1840 dirty = wc.dirty(missing=True)
1839 1841 if dirty:
1840 1842 # Branching is a bit strange to ensure we do the minimal
1841 1843 # amount of call to obsutil.foreground.
1842 1844 foreground = obsutil.foreground(repo, [p1.node()])
1843 1845 # note: the <node> variable contains a random identifier
1844 1846 if repo[node].node() in foreground:
1845 1847 pass # allow updating to successors
1846 1848 else:
1847 1849 msg = _("uncommitted changes")
1848 1850 hint = _("commit or update --clean to discard changes")
1849 1851 raise error.UpdateAbort(msg, hint=hint)
1850 1852 else:
1851 1853 # Allow jumping branches if clean and specific rev given
1852 1854 pass
1853 1855
1854 1856 if overwrite:
1855 1857 pas = [wc]
1856 1858 elif not branchmerge:
1857 1859 pas = [p1]
1858 1860
1859 1861 # deprecated config: merge.followcopies
1860 1862 followcopies = repo.ui.configbool('merge', 'followcopies')
1861 1863 if overwrite:
1862 1864 followcopies = False
1863 1865 elif not pas[0]:
1864 1866 followcopies = False
1865 1867 if not branchmerge and not wc.dirty(missing=True):
1866 1868 followcopies = False
1867 1869
1868 1870 ### calculate phase
1869 1871 actionbyfile, diverge, renamedelete = calculateupdates(
1870 1872 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1871 1873 followcopies, matcher=matcher, mergeforce=mergeforce)
1872 1874
1873 1875 if updatecheck == 'noconflict':
1874 1876 for f, (m, args, msg) in actionbyfile.iteritems():
1875 1877 if m not in ('g', 'k', 'e', 'r', 'pr'):
1876 1878 msg = _("conflicting changes")
1877 1879 hint = _("commit or update --clean to discard changes")
1878 1880 raise error.Abort(msg, hint=hint)
1879 1881
1880 1882 # Prompt and create actions. Most of this is in the resolve phase
1881 1883 # already, but we can't handle .hgsubstate in filemerge or
1882 1884 # subrepo.submerge yet so we have to keep prompting for it.
1883 1885 if '.hgsubstate' in actionbyfile:
1884 1886 f = '.hgsubstate'
1885 1887 m, args, msg = actionbyfile[f]
1886 1888 prompts = filemerge.partextras(labels)
1887 1889 prompts['f'] = f
1888 1890 if m == 'cd':
1889 1891 if repo.ui.promptchoice(
1890 1892 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1891 1893 "use (c)hanged version or (d)elete?"
1892 1894 "$$ &Changed $$ &Delete") % prompts, 0):
1893 1895 actionbyfile[f] = ('r', None, "prompt delete")
1894 1896 elif f in p1:
1895 1897 actionbyfile[f] = ('am', None, "prompt keep")
1896 1898 else:
1897 1899 actionbyfile[f] = ('a', None, "prompt keep")
1898 1900 elif m == 'dc':
1899 1901 f1, f2, fa, move, anc = args
1900 1902 flags = p2[f2].flags()
1901 1903 if repo.ui.promptchoice(
1902 1904 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1903 1905 "use (c)hanged version or leave (d)eleted?"
1904 1906 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1905 1907 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1906 1908 else:
1907 1909 del actionbyfile[f]
1908 1910
1909 1911 # Convert to dictionary-of-lists format
1910 1912 actions = dict((m, [])
1911 1913 for m in 'a am f g cd dc r dm dg m e k p pr'.split())
1912 1914 for f, (m, args, msg) in actionbyfile.iteritems():
1913 1915 if m not in actions:
1914 1916 actions[m] = []
1915 1917 actions[m].append((f, args, msg))
1916 1918
1917 1919 if not util.fscasesensitive(repo.path):
1918 1920 # check collision between files only in p2 for clean update
1919 1921 if (not branchmerge and
1920 1922 (force or not wc.dirty(missing=True, branch=False))):
1921 1923 _checkcollision(repo, p2.manifest(), None)
1922 1924 else:
1923 1925 _checkcollision(repo, wc.manifest(), actions)
1924 1926
1925 1927 # divergent renames
1926 1928 for f, fl in sorted(diverge.iteritems()):
1927 1929 repo.ui.warn(_("note: possible conflict - %s was renamed "
1928 1930 "multiple times to:\n") % f)
1929 1931 for nf in fl:
1930 1932 repo.ui.warn(" %s\n" % nf)
1931 1933
1932 1934 # rename and delete
1933 1935 for f, fl in sorted(renamedelete.iteritems()):
1934 1936 repo.ui.warn(_("note: possible conflict - %s was deleted "
1935 1937 "and renamed to:\n") % f)
1936 1938 for nf in fl:
1937 1939 repo.ui.warn(" %s\n" % nf)
1938 1940
1939 1941 ### apply phase
1940 1942 if not branchmerge: # just jump to the new rev
1941 1943 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1942 1944 if not partial:
1943 1945 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1944 1946 # note that we're in the middle of an update
1945 1947 repo.vfs.write('updatestate', p2.hex())
1946 1948
1947 1949 # Advertise fsmonitor when its presence could be useful.
1948 1950 #
1949 1951 # We only advertise when performing an update from an empty working
1950 1952 # directory. This typically only occurs during initial clone.
1951 1953 #
1952 1954 # We give users a mechanism to disable the warning in case it is
1953 1955 # annoying.
1954 1956 #
1955 1957 # We only allow on Linux and MacOS because that's where fsmonitor is
1956 1958 # considered stable.
1957 1959 fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
1958 1960 fsmonitorthreshold = repo.ui.configint('fsmonitor',
1959 1961 'warn_update_file_count')
1960 1962 try:
1961 1963 extensions.find('fsmonitor')
1962 1964 fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
1963 1965 # We intentionally don't look at whether fsmonitor has disabled
1964 1966 # itself because a) fsmonitor may have already printed a warning
1965 1967 # b) we only care about the config state here.
1966 1968 except KeyError:
1967 1969 fsmonitorenabled = False
1968 1970
1969 1971 if (fsmonitorwarning
1970 1972 and not fsmonitorenabled
1971 1973 and p1.node() == nullid
1972 1974 and len(actions['g']) >= fsmonitorthreshold
1973 1975 and pycompat.sysplatform.startswith(('linux', 'darwin'))):
1974 1976 repo.ui.warn(
1975 1977 _('(warning: large working directory being used without '
1976 1978 'fsmonitor enabled; enable fsmonitor to improve performance; '
1977 1979 'see "hg help -e fsmonitor")\n'))
1978 1980
1979 1981 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1980 1982 wc.flushall()
1981 1983
1982 1984 if not partial:
1983 1985 with repo.dirstate.parentchange():
1984 1986 repo.setparents(fp1, fp2)
1985 1987 recordupdates(repo, actions, branchmerge)
1986 1988 # update completed, clear state
1987 1989 util.unlink(repo.vfs.join('updatestate'))
1988 1990
1989 1991 if not branchmerge:
1990 1992 repo.dirstate.setbranch(p2.branch())
1991 1993
1992 1994 # If we're updating to a location, clean up any stale temporary includes
1993 1995 # (ex: this happens during hg rebase --abort).
1994 1996 if not branchmerge:
1995 1997 sparse.prunetemporaryincludes(repo)
1996 1998
1997 1999 if not partial:
1998 2000 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1999 2001 return stats
2000 2002
2001 2003 def graft(repo, ctx, pctx, labels, keepparent=False):
2002 2004 """Do a graft-like merge.
2003 2005
2004 2006 This is a merge where the merge ancestor is chosen such that one
2005 2007 or more changesets are grafted onto the current changeset. In
2006 2008 addition to the merge, this fixes up the dirstate to include only
2007 2009 a single parent (if keepparent is False) and tries to duplicate any
2008 2010 renames/copies appropriately.
2009 2011
2010 2012 ctx - changeset to rebase
2011 2013 pctx - merge base, usually ctx.p1()
2012 2014 labels - merge labels eg ['local', 'graft']
2013 2015 keepparent - keep second parent if any
2014 2016
2015 2017 """
2016 2018 # If we're grafting a descendant onto an ancestor, be sure to pass
2017 2019 # mergeancestor=True to update. This does two things: 1) allows the merge if
2018 2020 # the destination is the same as the parent of the ctx (so we can use graft
2019 2021 # to copy commits), and 2) informs update that the incoming changes are
2020 2022 # newer than the destination so it doesn't prompt about "remote changed foo
2021 2023 # which local deleted".
2022 2024 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
2023 2025
2024 2026 stats = update(repo, ctx.node(), True, True, pctx.node(),
2025 2027 mergeancestor=mergeancestor, labels=labels)
2026 2028
2027 2029 pother = nullid
2028 2030 parents = ctx.parents()
2029 2031 if keepparent and len(parents) == 2 and pctx in parents:
2030 2032 parents.remove(pctx)
2031 2033 pother = parents[0].node()
2032 2034
2033 2035 with repo.dirstate.parentchange():
2034 2036 repo.setparents(repo['.'].node(), pother)
2035 2037 repo.dirstate.write(repo.currenttransaction())
2036 2038 # fix up dirstate for copies and renames
2037 2039 copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
2038 2040 return stats
General Comments 0
You need to be logged in to leave comments. Login now