Mercurial > public > mercurial-scm > hg-stable
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: |