comparison mercurial/merge.py @ 21545:43eecb4e23f8

merge: use separate lists for each action type This replaces the grand unified action list that had multiple action types as tuples in one big list. That list was iterated multiple times just to find actions of a specific type. This data model also made some code more convoluted than necessary. Instead we now store actions as a tuple of lists. Using multiple lists gives a bit of cut'n'pasted code but also enables other optimizations. This patch uses 'if True:' to preserve indentations and help reviewing. It also limits the number of conflicts with other pending patches. It can trivially be cleaned up later.
author Mads Kiilerich <madski@unity3d.com>
date Fri, 28 Feb 2014 02:25:58 +0100
parents 47b97d9af27e
children bde505f47141
comparison
equal deleted inserted replaced
21541:6062593d8b06 21545:43eecb4e23f8
329 If we're merging, and the other revision has removed a file 329 If we're merging, and the other revision has removed a file
330 that is not present in the working directory, we need to mark it 330 that is not present in the working directory, we need to mark it
331 as removed. 331 as removed.
332 """ 332 """
333 333
334 actions = [] 334 ractions = []
335 state = branchmerge and 'r' or 'f' 335 factions = xactions = []
336 if branchmerge:
337 xactions = ractions
336 for f in wctx.deleted(): 338 for f in wctx.deleted():
337 if f not in mctx: 339 if f not in mctx:
338 actions.append((f, state, None, "forget deleted")) 340 xactions.append((f, None, "forget deleted"))
339 341
340 if not branchmerge: 342 if not branchmerge:
341 for f in wctx.removed(): 343 for f in wctx.removed():
342 if f not in mctx: 344 if f not in mctx:
343 actions.append((f, "f", None, "forget removed")) 345 factions.append((f, None, "forget removed"))
344 346
345 return actions 347 return ractions, factions
346 348
347 def _checkcollision(repo, wmf, actions): 349 def _checkcollision(repo, wmf, actions):
348 # build provisional merged manifest up 350 # build provisional merged manifest up
349 pmmf = set(wmf) 351 pmmf = set(wmf)
350 352
351 def addop(f, args): 353 if actions:
352 pmmf.add(f) 354 # k, dr, e and rd are no-op
353 def removeop(f, args): 355 for m in 'a', 'f', 'g', 'cd', 'dc':
354 pmmf.discard(f) 356 for f, args, msg in actions[m]:
355 def nop(f, args): 357 pmmf.add(f)
356 pass 358 for f, args, msg in actions['r']:
357 359 pmmf.discard(f)
358 def renamemoveop(f, args): 360 for f, args, msg in actions['dm']:
359 f2, flags = args 361 f2, flags = args
360 pmmf.discard(f2) 362 pmmf.discard(f2)
361 pmmf.add(f) 363 pmmf.add(f)
362 def renamegetop(f, args): 364 for f, args, msg in actions['dg']:
363 f2, flags = args 365 f2, flags = args
364 pmmf.add(f) 366 pmmf.add(f)
365 def mergeop(f, args): 367 for f, args, msg in actions['m']:
366 f1, f2, fa, move, anc = args 368 f1, f2, fa, move, anc = args
367 if move: 369 if move:
368 pmmf.discard(f1) 370 pmmf.discard(f1)
369 pmmf.add(f) 371 pmmf.add(f)
370
371 opmap = {
372 "a": addop,
373 "dm": renamemoveop,
374 "dg": renamegetop,
375 "dr": nop,
376 "e": nop,
377 "k": nop,
378 "f": addop, # untracked file should be kept in working directory
379 "g": addop,
380 "m": mergeop,
381 "r": removeop,
382 "rd": nop,
383 "cd": addop,
384 "dc": addop,
385 }
386 for f, m, args, msg in actions:
387 op = opmap.get(m)
388 assert op, m
389 op(f, args)
390 372
391 # check case-folding collision in provisional merged manifest 373 # check case-folding collision in provisional merged manifest
392 foldmap = {} 374 foldmap = {}
393 for f in sorted(pmmf): 375 for f in sorted(pmmf):
394 fold = util.normcase(f) 376 fold = util.normcase(f)
405 branchmerge and force are as passed in to update 387 branchmerge and force are as passed in to update
406 partial = function to filter file lists 388 partial = function to filter file lists
407 acceptremote = accept the incoming changes without prompting 389 acceptremote = accept the incoming changes without prompting
408 """ 390 """
409 391
410 actions, copy, movewithdir = [], {}, {} 392 actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split())
393 copy, movewithdir = {}, {}
411 394
412 # manifests fetched in order are going to be faster, so prime the caches 395 # manifests fetched in order are going to be faster, so prime the caches
413 [x.manifest() for x in 396 [x.manifest() for x in
414 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())] 397 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
415 398
416 if followcopies: 399 if followcopies:
417 ret = copies.mergecopies(repo, wctx, p2, pa) 400 ret = copies.mergecopies(repo, wctx, p2, pa)
418 copy, movewithdir, diverge, renamedelete = ret 401 copy, movewithdir, diverge, renamedelete = ret
419 for of, fl in diverge.iteritems(): 402 for of, fl in diverge.iteritems():
420 actions.append((of, "dr", (fl,), "divergent renames")) 403 actions['dr'].append((of, (fl,), "divergent renames"))
421 for of, fl in renamedelete.iteritems(): 404 for of, fl in renamedelete.iteritems():
422 actions.append((of, "rd", (fl,), "rename and delete")) 405 actions['rd'].append((of, (fl,), "rename and delete"))
423 406
424 repo.ui.note(_("resolving manifests\n")) 407 repo.ui.note(_("resolving manifests\n"))
425 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n" 408 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
426 % (bool(branchmerge), bool(force), bool(partial))) 409 % (bool(branchmerge), bool(force), bool(partial)))
427 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) 410 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
469 # Note: f as default is wrong - we can't really make a 3-way 452 # Note: f as default is wrong - we can't really make a 3-way
470 # merge without an ancestor file. 453 # merge without an ancestor file.
471 fla = ma.flags(fa) 454 fla = ma.flags(fa)
472 nol = 'l' not in fl1 + fl2 + fla 455 nol = 'l' not in fl1 + fl2 + fla
473 if n2 == a and fl2 == fla: 456 if n2 == a and fl2 == fla:
474 actions.append((f, "k", (), "keep")) # remote unchanged 457 actions['k'].append((f, (), "keep")) # remote unchanged
475 elif n1 == a and fl1 == fla: # local unchanged - use remote 458 elif n1 == a and fl1 == fla: # local unchanged - use remote
476 if n1 == n2: # optimization: keep local content 459 if n1 == n2: # optimization: keep local content
477 actions.append((f, "e", (fl2,), "update permissions")) 460 actions['e'].append((f, (fl2,), "update permissions"))
478 else: 461 else:
479 actions.append((f, "g", (fl2,), "remote is newer")) 462 actions['g'].append((f, (fl2,), "remote is newer"))
480 elif nol and n2 == a: # remote only changed 'x' 463 elif nol and n2 == a: # remote only changed 'x'
481 actions.append((f, "e", (fl2,), "update permissions")) 464 actions['e'].append((f, (fl2,), "update permissions"))
482 elif nol and n1 == a: # local only changed 'x' 465 elif nol and n1 == a: # local only changed 'x'
483 actions.append((f, "g", (fl1,), "remote is newer")) 466 actions['g'].append((f, (fl1,), "remote is newer"))
484 else: # both changed something 467 else: # both changed something
485 actions.append((f, "m", (f, f, fa, False, pa.node()), 468 actions['m'].append((f, (f, f, fa, False, pa.node()),
486 "versions differ")) 469 "versions differ"))
487 elif f in copied: # files we'll deal with on m2 side 470 elif f in copied: # files we'll deal with on m2 side
488 pass 471 pass
489 elif n1 and f in movewithdir: # directory rename, move local 472 elif n1 and f in movewithdir: # directory rename, move local
490 f2 = movewithdir[f] 473 f2 = movewithdir[f]
491 actions.append((f2, "dm", (f, fl1), 474 actions['dm'].append((f2, (f, fl1),
492 "remote directory rename - move from " + f)) 475 "remote directory rename - move from " + f))
493 elif n1 and f in copy: 476 elif n1 and f in copy:
494 f2 = copy[f] 477 f2 = copy[f]
495 actions.append((f, "m", (f, f2, f2, False, pa.node()), 478 actions['m'].append((f, (f, f2, f2, False, pa.node()),
496 "local copied/moved from " + f2)) 479 "local copied/moved from " + f2))
497 elif n1 and f in ma: # clean, a different, no remote 480 elif n1 and f in ma: # clean, a different, no remote
498 if n1 != ma[f]: 481 if n1 != ma[f]:
499 if acceptremote: 482 if acceptremote:
500 actions.append((f, "r", None, "remote delete")) 483 actions['r'].append((f, None, "remote delete"))
501 else: 484 else:
502 actions.append((f, "cd", None, "prompt changed/deleted")) 485 actions['cd'].append((f, None, "prompt changed/deleted"))
503 elif n1[20:] == "a": # added, no remote 486 elif n1[20:] == "a": # added, no remote
504 actions.append((f, "f", None, "remote deleted")) 487 actions['f'].append((f, None, "remote deleted"))
505 else: 488 else:
506 actions.append((f, "r", None, "other deleted")) 489 actions['r'].append((f, None, "other deleted"))
507 elif n2 and f in movewithdir: 490 elif n2 and f in movewithdir:
508 f2 = movewithdir[f] 491 f2 = movewithdir[f]
509 actions.append((f2, "dg", (f, fl2), 492 actions['dg'].append((f2, (f, fl2),
510 "local directory rename - get from " + f)) 493 "local directory rename - get from " + f))
511 elif n2 and f in copy: 494 elif n2 and f in copy:
512 f2 = copy[f] 495 f2 = copy[f]
513 if f2 in m2: 496 if f2 in m2:
514 actions.append((f, "m", (f2, f, f2, False, pa.node()), 497 actions['m'].append((f, (f2, f, f2, False, pa.node()),
515 "remote copied from " + f2)) 498 "remote copied from " + f2))
516 else: 499 else:
517 actions.append((f, "m", (f2, f, f2, True, pa.node()), 500 actions['m'].append((f, (f2, f, f2, True, pa.node()),
518 "remote moved from " + f2)) 501 "remote moved from " + f2))
519 elif n2 and f not in ma: 502 elif n2 and f not in ma:
520 # local unknown, remote created: the logic is described by the 503 # local unknown, remote created: the logic is described by the
521 # following table: 504 # following table:
522 # 505 #
528 # y y y | merge 511 # y y y | merge
529 # 512 #
530 # Checking whether the files are different is expensive, so we 513 # Checking whether the files are different is expensive, so we
531 # don't do that when we can avoid it. 514 # don't do that when we can avoid it.
532 if force and not branchmerge: 515 if force and not branchmerge:
533 actions.append((f, "g", (fl2,), "remote created")) 516 actions['g'].append((f, (fl2,), "remote created"))
534 else: 517 else:
535 different = _checkunknownfile(repo, wctx, p2, f) 518 different = _checkunknownfile(repo, wctx, p2, f)
536 if force and branchmerge and different: 519 if force and branchmerge and different:
537 # FIXME: This is wrong - f is not in ma ... 520 # FIXME: This is wrong - f is not in ma ...
538 actions.append((f, "m", (f, f, f, False, pa.node()), 521 actions['m'].append((f, (f, f, f, False, pa.node()),
539 "remote differs from untracked local")) 522 "remote differs from untracked local"))
540 elif not force and different: 523 elif not force and different:
541 aborts.append((f, "ud")) 524 aborts.append((f, "ud"))
542 else: 525 else:
543 actions.append((f, "g", (fl2,), "remote created")) 526 actions['g'].append((f, (fl2,), "remote created"))
544 elif n2 and n2 != ma[f]: 527 elif n2 and n2 != ma[f]:
545 different = _checkunknownfile(repo, wctx, p2, f) 528 different = _checkunknownfile(repo, wctx, p2, f)
546 if not force and different: 529 if not force and different:
547 aborts.append((f, "ud")) 530 aborts.append((f, "ud"))
548 else: 531 else:
549 # if different: old untracked f may be overwritten and lost 532 # if different: old untracked f may be overwritten and lost
550 if acceptremote: 533 if acceptremote:
551 actions.append((f, "g", (m2.flags(f),), 534 actions['g'].append((f, (m2.flags(f),),
552 "remote recreating")) 535 "remote recreating"))
553 else: 536 else:
554 actions.append((f, "dc", (m2.flags(f),), 537 actions['dc'].append((f, (m2.flags(f),),
555 "prompt deleted/changed")) 538 "prompt deleted/changed"))
556 539
557 for f, m in sorted(aborts): 540 for f, m in sorted(aborts):
558 if m == "ud": 541 if m == "ud":
559 repo.ui.warn(_("%s: untracked file differs\n") % f) 542 repo.ui.warn(_("%s: untracked file differs\n") % f)
564 547
565 if not util.checkcase(repo.path): 548 if not util.checkcase(repo.path):
566 # check collision between files only in p2 for clean update 549 # check collision between files only in p2 for clean update
567 if (not branchmerge and 550 if (not branchmerge and
568 (force or not wctx.dirty(missing=True, branch=False))): 551 (force or not wctx.dirty(missing=True, branch=False))):
569 _checkcollision(repo, m2, []) 552 _checkcollision(repo, m2, None)
570 else: 553 else:
571 _checkcollision(repo, m1, actions) 554 _checkcollision(repo, m1, actions)
572 555
573 return actions 556 return actions
574
575 actionpriority = dict((m, p) for p, m in enumerate(
576 ['r', 'f', 'g', 'a', 'k', 'm', 'dm', 'dg', 'dr', 'cd', 'dc', 'rd', 'e']))
577
578 def actionkey(a):
579 return actionpriority[a[1]], a
580 557
581 def batchremove(repo, actions): 558 def batchremove(repo, actions):
582 """apply removes to the working directory 559 """apply removes to the working directory
583 560
584 yields tuples for progress updates 561 yields tuples for progress updates
586 verbose = repo.ui.verbose 563 verbose = repo.ui.verbose
587 unlink = util.unlinkpath 564 unlink = util.unlinkpath
588 wjoin = repo.wjoin 565 wjoin = repo.wjoin
589 audit = repo.wopener.audit 566 audit = repo.wopener.audit
590 i = 0 567 i = 0
591 for f, m, args, msg in actions: 568 for f, args, msg in actions:
592 repo.ui.debug(" %s: %s -> r\n" % (f, msg)) 569 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
593 if True: 570 if True:
594 if verbose: 571 if verbose:
595 repo.ui.note(_("removing %s\n") % f) 572 repo.ui.note(_("removing %s\n") % f)
596 audit(f) 573 audit(f)
615 """ 592 """
616 verbose = repo.ui.verbose 593 verbose = repo.ui.verbose
617 fctx = mctx.filectx 594 fctx = mctx.filectx
618 wwrite = repo.wwrite 595 wwrite = repo.wwrite
619 i = 0 596 i = 0
620 for f, m, args, msg in actions: 597 for f, args, msg in actions:
621 repo.ui.debug(" %s: %s -> g\n" % (f, msg)) 598 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
622 if True: 599 if True:
623 if verbose: 600 if verbose:
624 repo.ui.note(_("getting %s\n") % f) 601 repo.ui.note(_("getting %s\n") % f)
625 wwrite(f, fctx(f).data(), args[0]) 602 wwrite(f, fctx(f).data(), args[0])
642 619
643 updated, merged, removed, unresolved = 0, 0, 0, 0 620 updated, merged, removed, unresolved = 0, 0, 0, 0
644 ms = mergestate(repo) 621 ms = mergestate(repo)
645 ms.reset(wctx.p1().node(), mctx.node()) 622 ms.reset(wctx.p1().node(), mctx.node())
646 moves = [] 623 moves = []
647 actions.sort(key=actionkey) 624 for m, l in actions.items():
625 l.sort()
648 626
649 # prescan for merges 627 # prescan for merges
650 for a in actions: 628 for f, args, msg in actions['m']:
651 f, m, args, msg = a 629 if True:
652 if m == "m": # merge
653 f1, f2, fa, move, anc = args 630 f1, f2, fa, move, anc = args
654 if f == '.hgsubstate': # merged internally 631 if f == '.hgsubstate': # merged internally
655 continue 632 continue
656 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f)) 633 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
657 fcl = wctx[f1] 634 fcl = wctx[f1]
675 if os.path.lexists(repo.wjoin(f)): 652 if os.path.lexists(repo.wjoin(f)):
676 repo.ui.debug("removing %s\n" % f) 653 repo.ui.debug("removing %s\n" % f)
677 audit(f) 654 audit(f)
678 util.unlinkpath(repo.wjoin(f)) 655 util.unlinkpath(repo.wjoin(f))
679 656
680 numupdates = len([a for a in actions if a[1] != 'k']) 657 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
681 workeractions = [a for a in actions if a[1] in 'gr'] 658
682 updateactions = [a for a in workeractions if a[1] == 'g'] 659 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
683 updated = len(updateactions)
684 removeactions = [a for a in workeractions if a[1] == 'r']
685 removed = len(removeactions)
686 actions = [a for a in actions if a[1] not in 'gr']
687
688 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
689 if hgsub and hgsub[0] == 'r':
690 subrepo.submerge(repo, wctx, mctx, wctx, overwrite) 660 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
691 661
692 # remove in parallel (must come first) 662 # remove in parallel (must come first)
693 z = 0 663 z = 0
694 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), removeactions) 664 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
695 for i, item in prog: 665 for i, item in prog:
696 z += i 666 z += i
697 progress(_updating, z, item=item, total=numupdates, unit=_files) 667 progress(_updating, z, item=item, total=numupdates, unit=_files)
668 removed = len(actions['r'])
698 669
699 # get in parallel 670 # get in parallel
700 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), updateactions) 671 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
701 for i, item in prog: 672 for i, item in prog:
702 z += i 673 z += i
703 progress(_updating, z, item=item, total=numupdates, unit=_files) 674 progress(_updating, z, item=item, total=numupdates, unit=_files)
704 675 updated = len(actions['g'])
705 if hgsub and hgsub[0] == 'g': 676
677 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
706 subrepo.submerge(repo, wctx, mctx, wctx, overwrite) 678 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
707 679
708 for f, m, args, msg in actions: 680 if True:
709 681
710 # forget (manifest only, just log it) (must come first) 682 # forget (manifest only, just log it) (must come first)
711 if m == "f": 683 for f, args, msg in actions['f']:
712 repo.ui.debug(" %s: %s -> f\n" % (f, msg)) 684 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
713 z += 1 685 z += 1
714 progress(_updating, z, item=f, total=numupdates, unit=_files) 686 progress(_updating, z, item=f, total=numupdates, unit=_files)
715 687
716 # re-add (manifest only, just log it) 688 # re-add (manifest only, just log it)
717 elif m == "a": 689 for f, args, msg in actions['a']:
718 repo.ui.debug(" %s: %s -> a\n" % (f, msg)) 690 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
719 z += 1 691 z += 1
720 progress(_updating, z, item=f, total=numupdates, unit=_files) 692 progress(_updating, z, item=f, total=numupdates, unit=_files)
721 693
722 # keep (noop, just log it) 694 # keep (noop, just log it)
723 elif m == "k": 695 for f, args, msg in actions['k']:
724 repo.ui.debug(" %s: %s -> k\n" % (f, msg)) 696 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
725 # no progress 697 # no progress
726 698
727 # merge 699 # merge
728 elif m == "m": 700 for f, args, msg in actions['m']:
729 repo.ui.debug(" %s: %s -> m\n" % (f, msg)) 701 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
730 z += 1 702 z += 1
731 progress(_updating, z, item=f, total=numupdates, unit=_files) 703 progress(_updating, z, item=f, total=numupdates, unit=_files)
732 f1, f2, fa, move, anc = args 704 f1, f2, fa, move, anc = args
733 if f == '.hgsubstate': # subrepo states need updating 705 if f == '.hgsubstate': # subrepo states need updating
743 updated += 1 715 updated += 1
744 else: 716 else:
745 merged += 1 717 merged += 1
746 718
747 # directory rename, move local 719 # directory rename, move local
748 elif m == "dm": 720 for f, args, msg in actions['dm']:
749 repo.ui.debug(" %s: %s -> dm\n" % (f, msg)) 721 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
750 z += 1 722 z += 1
751 progress(_updating, z, item=f, total=numupdates, unit=_files) 723 progress(_updating, z, item=f, total=numupdates, unit=_files)
752 f0, flags = args 724 f0, flags = args
753 repo.ui.note(_("moving %s to %s\n") % (f0, f)) 725 repo.ui.note(_("moving %s to %s\n") % (f0, f))
755 repo.wwrite(f, wctx.filectx(f0).data(), flags) 727 repo.wwrite(f, wctx.filectx(f0).data(), flags)
756 util.unlinkpath(repo.wjoin(f0)) 728 util.unlinkpath(repo.wjoin(f0))
757 updated += 1 729 updated += 1
758 730
759 # local directory rename, get 731 # local directory rename, get
760 elif m == "dg": 732 for f, args, msg in actions['dg']:
761 repo.ui.debug(" %s: %s -> dg\n" % (f, msg)) 733 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
762 z += 1 734 z += 1
763 progress(_updating, z, item=f, total=numupdates, unit=_files) 735 progress(_updating, z, item=f, total=numupdates, unit=_files)
764 f0, flags = args 736 f0, flags = args
765 repo.ui.note(_("getting %s to %s\n") % (f0, f)) 737 repo.ui.note(_("getting %s to %s\n") % (f0, f))
766 repo.wwrite(f, mctx.filectx(f0).data(), flags) 738 repo.wwrite(f, mctx.filectx(f0).data(), flags)
767 updated += 1 739 updated += 1
768 740
769 # divergent renames 741 # divergent renames
770 elif m == "dr": 742 for f, args, msg in actions['dr']:
771 repo.ui.debug(" %s: %s -> dr\n" % (f, msg)) 743 repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
772 z += 1 744 z += 1
773 progress(_updating, z, item=f, total=numupdates, unit=_files) 745 progress(_updating, z, item=f, total=numupdates, unit=_files)
774 fl, = args 746 fl, = args
775 repo.ui.warn(_("note: possible conflict - %s was renamed " 747 repo.ui.warn(_("note: possible conflict - %s was renamed "
776 "multiple times to:\n") % f) 748 "multiple times to:\n") % f)
777 for nf in fl: 749 for nf in fl:
778 repo.ui.warn(" %s\n" % nf) 750 repo.ui.warn(" %s\n" % nf)
779 751
780 # rename and delete 752 # rename and delete
781 elif m == "rd": 753 for f, args, msg in actions['rd']:
782 repo.ui.debug(" %s: %s -> rd\n" % (f, msg)) 754 repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
783 z += 1 755 z += 1
784 progress(_updating, z, item=f, total=numupdates, unit=_files) 756 progress(_updating, z, item=f, total=numupdates, unit=_files)
785 fl, = args 757 fl, = args
786 repo.ui.warn(_("note: possible conflict - %s was deleted " 758 repo.ui.warn(_("note: possible conflict - %s was deleted "
787 "and renamed to:\n") % f) 759 "and renamed to:\n") % f)
788 for nf in fl: 760 for nf in fl:
789 repo.ui.warn(" %s\n" % nf) 761 repo.ui.warn(" %s\n" % nf)
790 762
791 # exec 763 # exec
792 elif m == "e": 764 for f, args, msg in actions['e']:
793 repo.ui.debug(" %s: %s -> e\n" % (f, msg)) 765 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
794 z += 1 766 z += 1
795 progress(_updating, z, item=f, total=numupdates, unit=_files) 767 progress(_updating, z, item=f, total=numupdates, unit=_files)
796 flags, = args 768 flags, = args
797 audit(f) 769 audit(f)
816 repo.ui.status( 788 repo.ui.status(
817 _("note: merging %s and %s using bids from ancestors %s\n") % 789 _("note: merging %s and %s using bids from ancestors %s\n") %
818 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors))) 790 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
819 791
820 # Call for bids 792 # Call for bids
821 fbids = {} # mapping filename to list af action bids 793 fbids = {} # mapping filename to bids (action method to list af actions)
822 for ancestor in ancestors: 794 for ancestor in ancestors:
823 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor) 795 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
824 actions = manifestmerge(repo, wctx, mctx, ancestor, 796 actions = manifestmerge(repo, wctx, mctx, ancestor,
825 branchmerge, force, 797 branchmerge, force,
826 partial, acceptremote, followcopies) 798 partial, acceptremote, followcopies)
827 for a in sorted(actions, key=lambda a: (a[1], a)): 799 for m, l in sorted(actions.items()):
828 f, m, args, msg = a 800 for a in l:
829 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m)) 801 f, args, msg = a
830 if f in fbids: 802 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
831 fbids[f].append(a) 803 if f in fbids:
832 else: 804 d = fbids[f]
833 fbids[f] = [a] 805 if m in d:
806 d[m].append(a)
807 else:
808 d[m] = [a]
809 else:
810 fbids[f] = {m: [a]}
834 811
835 # Pick the best bid for each file 812 # Pick the best bid for each file
836 repo.ui.note(_('\nauction for merging merge bids\n')) 813 repo.ui.note(_('\nauction for merging merge bids\n'))
837 actions = [] 814 actions = dict((m, []) for m in actions.keys())
838 for f, bidsl in sorted(fbids.items()): 815 for f, bids in sorted(fbids.items()):
816 # bids is a mapping from action method to list af actions
839 # Consensus? 817 # Consensus?
840 a0 = bidsl[0] 818 if len(bids) == 1: # all bids are the same kind of method
841 if util.all(a == a0 for a in bidsl[1:]): # len(bidsl) is > 1 819 m, l = bids.items()[0]
842 repo.ui.note(" %s: consensus for %s\n" % (f, a0[1])) 820 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
843 actions.append(a0) 821 repo.ui.note(" %s: consensus for %s\n" % (f, m))
844 continue 822 actions[m].append(l[0])
845 # Group bids by kind of action 823 continue
846 bids = {}
847 for a in bidsl:
848 m = a[1]
849 if m in bids:
850 bids[m].append(a)
851 else:
852 bids[m] = [a]
853 # If keep is an option, just do it. 824 # If keep is an option, just do it.
854 if "k" in bids: 825 if "k" in bids:
855 repo.ui.note(" %s: picking 'keep' action\n" % f) 826 repo.ui.note(" %s: picking 'keep' action\n" % f)
856 actions.append(bids["k"][0]) 827 actions['k'].append(bids["k"][0])
857 continue 828 continue
858 # If all gets agree [how could they not?], just do it. 829 # If there are gets and they all agree [how could they not?], do it.
859 if "g" in bids: 830 if "g" in bids:
860 ga0 = bids["g"][0] 831 ga0 = bids["g"][0]
861 if util.all(a == ga0 for a in bids["g"][1:]): 832 if util.all(a == ga0 for a in bids["g"][1:]):
862 repo.ui.note(" %s: picking 'get' action\n" % f) 833 repo.ui.note(" %s: picking 'get' action\n" % f)
863 actions.append(ga0) 834 actions['g'].append(ga0)
864 continue 835 continue
865 # TODO: Consider other simple actions such as mode changes 836 # TODO: Consider other simple actions such as mode changes
866 # Handle inefficient democrazy. 837 # Handle inefficient democrazy.
867 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f) 838 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
868 for _f, m, args, msg in bidsl: 839 for m, l in sorted(bids.items()):
869 repo.ui.note(' %s -> %s\n' % (msg, m)) 840 for _f, args, msg in l:
841 repo.ui.note(' %s -> %s\n' % (msg, m))
870 # Pick random action. TODO: Instead, prompt user when resolving 842 # Pick random action. TODO: Instead, prompt user when resolving
871 a0 = bidsl[0] 843 m, l = bids.items()[0]
872 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') % 844 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
873 (f, a0[1])) 845 (f, m))
874 actions.append(a0) 846 actions[m].append(l[0])
875 continue 847 continue
876 repo.ui.note(_('end of auction\n\n')) 848 repo.ui.note(_('end of auction\n\n'))
877 849
878 # Filter out prompts.
879 newactions, prompts = [], []
880 for a in actions:
881 if a[1] in ("cd", "dc"):
882 prompts.append(a)
883 else:
884 newactions.append(a)
885 # Prompt and create actions. TODO: Move this towards resolve phase. 850 # Prompt and create actions. TODO: Move this towards resolve phase.
886 for f, m, args, msg in sorted(prompts): 851 if True:
887 if m == "cd": 852 for f, args, msg in actions['cd']:
888 if repo.ui.promptchoice( 853 if repo.ui.promptchoice(
889 _("local changed %s which remote deleted\n" 854 _("local changed %s which remote deleted\n"
890 "use (c)hanged version or (d)elete?" 855 "use (c)hanged version or (d)elete?"
891 "$$ &Changed $$ &Delete") % f, 0): 856 "$$ &Changed $$ &Delete") % f, 0):
892 newactions.append((f, "r", None, "prompt delete")) 857 actions['r'].append((f, None, "prompt delete"))
893 else: 858 else:
894 newactions.append((f, "a", None, "prompt keep")) 859 actions['a'].append((f, None, "prompt keep"))
895 elif m == "dc": 860 del actions['cd'][:]
861
862 for f, args, msg in actions['dc']:
896 flags, = args 863 flags, = args
897 if repo.ui.promptchoice( 864 if repo.ui.promptchoice(
898 _("remote changed %s which local deleted\n" 865 _("remote changed %s which local deleted\n"
899 "use (c)hanged version or leave (d)eleted?" 866 "use (c)hanged version or leave (d)eleted?"
900 "$$ &Changed $$ &Deleted") % f, 0) == 0: 867 "$$ &Changed $$ &Deleted") % f, 0) == 0:
901 newactions.append((f, "g", (flags,), "prompt recreating")) 868 actions['g'].append((f, (flags,), "prompt recreating"))
902 else: assert False, m 869 del actions['dc'][:]
903 870
904 if wctx.rev() is None: 871 if wctx.rev() is None:
905 newactions += _forgetremoved(wctx, mctx, branchmerge) 872 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
906 873 actions['r'].extend(ractions)
907 return newactions 874 actions['f'].extend(factions)
875
876 return actions
908 877
909 def recordupdates(repo, actions, branchmerge): 878 def recordupdates(repo, actions, branchmerge):
910 "record merge actions to the dirstate" 879 "record merge actions to the dirstate"
911 880 if True:
912 for f, m, args, msg in actions:
913
914 # remove (must come first) 881 # remove (must come first)
915 if m == "r": # remove 882 for f, args, msg in actions['r']:
916 if branchmerge: 883 if branchmerge:
917 repo.dirstate.remove(f) 884 repo.dirstate.remove(f)
918 else: 885 else:
919 repo.dirstate.drop(f) 886 repo.dirstate.drop(f)
920 887
921 # forget (must come first) 888 # forget (must come first)
922 elif m == "f": 889 for f, args, msg in actions['f']:
923 repo.dirstate.drop(f) 890 repo.dirstate.drop(f)
924 891
925 # re-add 892 # re-add
926 elif m == "a": 893 for f, args, msg in actions['a']:
927 if not branchmerge: 894 if not branchmerge:
928 repo.dirstate.add(f) 895 repo.dirstate.add(f)
929 896
930 # exec change 897 # exec change
931 elif m == "e": 898 for f, args, msg in actions['e']:
932 repo.dirstate.normallookup(f) 899 repo.dirstate.normallookup(f)
933 900
934 # keep 901 # keep
935 elif m == "k": 902 for f, args, msg in actions['k']:
936 pass 903 pass
937 904
938 # get 905 # get
939 elif m == "g": 906 for f, args, msg in actions['g']:
940 if branchmerge: 907 if branchmerge:
941 repo.dirstate.otherparent(f) 908 repo.dirstate.otherparent(f)
942 else: 909 else:
943 repo.dirstate.normal(f) 910 repo.dirstate.normal(f)
944 911
945 # merge 912 # merge
946 elif m == "m": 913 for f, args, msg in actions['m']:
947 f1, f2, fa, move, anc = args 914 f1, f2, fa, move, anc = args
948 if branchmerge: 915 if branchmerge:
949 # We've done a branch merge, mark this file as merged 916 # We've done a branch merge, mark this file as merged
950 # so that we properly record the merger later 917 # so that we properly record the merger later
951 repo.dirstate.merge(f) 918 repo.dirstate.merge(f)
966 repo.dirstate.normallookup(f) 933 repo.dirstate.normallookup(f)
967 if move: 934 if move:
968 repo.dirstate.drop(f1) 935 repo.dirstate.drop(f1)
969 936
970 # directory rename, move local 937 # directory rename, move local
971 elif m == "dm": 938 for f, args, msg in actions['dm']:
972 f0, flag = args 939 f0, flag = args
973 if f0 not in repo.dirstate: 940 if f0 not in repo.dirstate:
974 # untracked file moved 941 # untracked file moved
975 continue 942 continue
976 if branchmerge: 943 if branchmerge:
980 else: 947 else:
981 repo.dirstate.normal(f) 948 repo.dirstate.normal(f)
982 repo.dirstate.drop(f0) 949 repo.dirstate.drop(f0)
983 950
984 # directory rename, get 951 # directory rename, get
985 elif m == "dg": 952 for f, args, msg in actions['dg']:
986 f0, flag = args 953 f0, flag = args
987 if branchmerge: 954 if branchmerge:
988 repo.dirstate.add(f) 955 repo.dirstate.add(f)
989 repo.dirstate.copy(f0, f) 956 repo.dirstate.copy(f0, f)
990 else: 957 else: