comparison mercurial/merge.py @ 23637:13f53a2aa342

merge: write manifestmerge() using dictionary with entry per file In the same vein as 38e55e55ae4d (largefiles: rewrite merge code using dictionary with entry per file, 2014-12-09), rewrite manifestmerge() itself as dictionary with the filename as key. This will let us simplify some of the other code in merge.py and eventually drop the conversion in the largefiles code. No difference in speed could be detected (well within the noise level when run in Mozilla repo).
author Martin von Zweigbergk <martinvonz@google.com>
date Mon, 08 Dec 2014 13:24:10 -0800
parents 7cc0fb0080b6
children 09be050ca98c
comparison
equal deleted inserted replaced
23636:ab3b8d8fd2a0 23637:13f53a2aa342
373 branchmerge and force are as passed in to update 373 branchmerge and force are as passed in to update
374 partial = function to filter file lists 374 partial = function to filter file lists
375 acceptremote = accept the incoming changes without prompting 375 acceptremote = accept the incoming changes without prompting
376 """ 376 """
377 377
378 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
379 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {} 378 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
380 379
381 # manifests fetched in order are going to be faster, so prime the caches 380 # manifests fetched in order are going to be faster, so prime the caches
382 [x.manifest() for x in 381 [x.manifest() for x in
383 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())] 382 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
404 403
405 aborts = [] 404 aborts = []
406 # Compare manifests 405 # Compare manifests
407 diff = m1.diff(m2) 406 diff = m1.diff(m2)
408 407
408 actions = {}
409 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems(): 409 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
410 if partial and not partial(f): 410 if partial and not partial(f):
411 continue 411 continue
412 if n1 and n2: # file exists on both local and remote side 412 if n1 and n2: # file exists on both local and remote side
413 if f not in ma: 413 if f not in ma:
414 fa = copy.get(f, None) 414 fa = copy.get(f, None)
415 if fa is not None: 415 if fa is not None:
416 actions['m'].append((f, (f, f, fa, False, pa.node()), 416 actions[f] = ('m', (f, f, fa, False, pa.node()),
417 "both renamed from " + fa)) 417 "both renamed from " + fa)
418 else: 418 else:
419 actions['m'].append((f, (f, f, None, False, pa.node()), 419 actions[f] = ('m', (f, f, None, False, pa.node()),
420 "both created")) 420 "both created")
421 else: 421 else:
422 a = ma[f] 422 a = ma[f]
423 fla = ma.flags(f) 423 fla = ma.flags(f)
424 nol = 'l' not in fl1 + fl2 + fla 424 nol = 'l' not in fl1 + fl2 + fla
425 if n2 == a and fl2 == fla: 425 if n2 == a and fl2 == fla:
426 actions['k'].append((f, (), "remote unchanged")) 426 actions[f] = ('k' , (), "remote unchanged")
427 elif n1 == a and fl1 == fla: # local unchanged - use remote 427 elif n1 == a and fl1 == fla: # local unchanged - use remote
428 if n1 == n2: # optimization: keep local content 428 if n1 == n2: # optimization: keep local content
429 actions['e'].append((f, (fl2,), "update permissions")) 429 actions[f] = ('e', (fl2,), "update permissions")
430 else: 430 else:
431 actions['g'].append((f, (fl2,), "remote is newer")) 431 actions[f] = ('g', (fl2,), "remote is newer")
432 elif nol and n2 == a: # remote only changed 'x' 432 elif nol and n2 == a: # remote only changed 'x'
433 actions['e'].append((f, (fl2,), "update permissions")) 433 actions[f] = ('e', (fl2,), "update permissions")
434 elif nol and n1 == a: # local only changed 'x' 434 elif nol and n1 == a: # local only changed 'x'
435 actions['g'].append((f, (fl1,), "remote is newer")) 435 actions[f] = ('g', (fl1,), "remote is newer")
436 else: # both changed something 436 else: # both changed something
437 actions['m'].append((f, (f, f, f, False, pa.node()), 437 actions[f] = ('m', (f, f, f, False, pa.node()),
438 "versions differ")) 438 "versions differ")
439 elif n1: # file exists only on local side 439 elif n1: # file exists only on local side
440 if f in copied: 440 if f in copied:
441 pass # we'll deal with it on m2 side 441 pass # we'll deal with it on m2 side
442 elif f in movewithdir: # directory rename, move local 442 elif f in movewithdir: # directory rename, move local
443 f2 = movewithdir[f] 443 f2 = movewithdir[f]
444 if f2 in m2: 444 if f2 in m2:
445 actions['m'].append((f2, (f, f2, None, True, pa.node()), 445 actions[f2] = ('m', (f, f2, None, True, pa.node()),
446 "remote directory rename, both created")) 446 "remote directory rename, both created")
447 else: 447 else:
448 actions['dm'].append((f2, (f, fl1), 448 actions[f2] = ('dm', (f, fl1),
449 "remote directory rename - move from " + f)) 449 "remote directory rename - move from " + f)
450 elif f in copy: 450 elif f in copy:
451 f2 = copy[f] 451 f2 = copy[f]
452 actions['m'].append((f, (f, f2, f2, False, pa.node()), 452 actions[f] = ('m', (f, f2, f2, False, pa.node()),
453 "local copied/moved from " + f2)) 453 "local copied/moved from " + f2)
454 elif f in ma: # clean, a different, no remote 454 elif f in ma: # clean, a different, no remote
455 if n1 != ma[f]: 455 if n1 != ma[f]:
456 if acceptremote: 456 if acceptremote:
457 actions['r'].append((f, None, "remote delete")) 457 actions[f] = ('r', None, "remote delete")
458 else: 458 else:
459 actions['cd'].append((f, None, 459 actions[f] = ('cd', None, "prompt changed/deleted")
460 "prompt changed/deleted"))
461 elif n1[20:] == 'a': 460 elif n1[20:] == 'a':
462 # This extra 'a' is added by working copy manifest to mark 461 # This extra 'a' is added by working copy manifest to mark
463 # the file as locally added. We should forget it instead of 462 # the file as locally added. We should forget it instead of
464 # deleting it. 463 # deleting it.
465 actions['f'].append((f, None, "remote deleted")) 464 actions[f] = ('f', None, "remote deleted")
466 else: 465 else:
467 actions['r'].append((f, None, "other deleted")) 466 actions[f] = ('r', None, "other deleted")
468 elif n2: # file exists only on remote side 467 elif n2: # file exists only on remote side
469 if f in copied: 468 if f in copied:
470 pass # we'll deal with it on m1 side 469 pass # we'll deal with it on m1 side
471 elif f in movewithdir: 470 elif f in movewithdir:
472 f2 = movewithdir[f] 471 f2 = movewithdir[f]
473 if f2 in m1: 472 if f2 in m1:
474 actions['m'].append((f2, (f2, f, None, False, pa.node()), 473 actions[f2] = ('m', (f2, f, None, False, pa.node()),
475 "local directory rename, both created")) 474 "local directory rename, both created")
476 else: 475 else:
477 actions['dg'].append((f2, (f, fl2), 476 actions[f2] = ('dg', (f, fl2),
478 "local directory rename - get from " + f)) 477 "local directory rename - get from " + f)
479 elif f in copy: 478 elif f in copy:
480 f2 = copy[f] 479 f2 = copy[f]
481 if f2 in m2: 480 if f2 in m2:
482 actions['m'].append((f, (f2, f, f2, False, pa.node()), 481 actions[f] = ('m', (f2, f, f2, False, pa.node()),
483 "remote copied from " + f2)) 482 "remote copied from " + f2)
484 else: 483 else:
485 actions['m'].append((f, (f2, f, f2, True, pa.node()), 484 actions[f] = ('m', (f2, f, f2, True, pa.node()),
486 "remote moved from " + f2)) 485 "remote moved from " + f2)
487 elif f not in ma: 486 elif f not in ma:
488 # local unknown, remote created: the logic is described by the 487 # local unknown, remote created: the logic is described by the
489 # following table: 488 # following table:
490 # 489 #
491 # force branchmerge different | action 490 # force branchmerge different | action
496 # y y y | merge 495 # y y y | merge
497 # 496 #
498 # Checking whether the files are different is expensive, so we 497 # Checking whether the files are different is expensive, so we
499 # don't do that when we can avoid it. 498 # don't do that when we can avoid it.
500 if force and not branchmerge: 499 if force and not branchmerge:
501 actions['g'].append((f, (fl2,), "remote created")) 500 actions[f] = ('g', (fl2,), "remote created")
502 else: 501 else:
503 different = _checkunknownfile(repo, wctx, p2, f) 502 different = _checkunknownfile(repo, wctx, p2, f)
504 if force and branchmerge and different: 503 if force and branchmerge and different:
505 actions['m'].append((f, (f, f, None, False, pa.node()), 504 actions[f] = ('m', (f, f, None, False, pa.node()),
506 "remote differs from untracked local")) 505 "remote differs from untracked local")
507 elif not force and different: 506 elif not force and different:
508 aborts.append((f, 'ud')) 507 aborts.append((f, 'ud'))
509 else: 508 else:
510 actions['g'].append((f, (fl2,), "remote created")) 509 actions[f] = ('g', (fl2,), "remote created")
511 elif n2 != ma[f]: 510 elif n2 != ma[f]:
512 different = _checkunknownfile(repo, wctx, p2, f) 511 different = _checkunknownfile(repo, wctx, p2, f)
513 if not force and different: 512 if not force and different:
514 aborts.append((f, 'ud')) 513 aborts.append((f, 'ud'))
515 else: 514 else:
516 if acceptremote: 515 if acceptremote:
517 actions['g'].append((f, (fl2,), "remote recreating")) 516 actions[f] = ('g', (fl2,), "remote recreating")
518 else: 517 else:
519 actions['dc'].append((f, (fl2,), 518 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
520 "prompt deleted/changed"))
521 519
522 for f, m in sorted(aborts): 520 for f, m in sorted(aborts):
523 if m == 'ud': 521 if m == 'ud':
524 repo.ui.warn(_("%s: untracked file differs\n") % f) 522 repo.ui.warn(_("%s: untracked file differs\n") % f)
525 else: assert False, m 523 else: assert False, m
526 if aborts: 524 if aborts:
527 raise util.Abort(_("untracked files in working directory differ " 525 raise util.Abort(_("untracked files in working directory differ "
528 "from files in requested revision")) 526 "from files in requested revision"))
527
528 # Convert to dictionary-of-lists format
529 actionbyfile = actions
530 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
531 for f, (m, args, msg) in actionbyfile.iteritems():
532 actions[m].append((f, args, msg))
529 533
530 return actions, diverge, renamedelete 534 return actions, diverge, renamedelete
531 535
532 def _resolvetrivial(repo, wctx, mctx, ancestor, actions): 536 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
533 """Resolves false conflicts where the nodeid changed but the content 537 """Resolves false conflicts where the nodeid changed but the content