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