comparison mercurial/merge.py @ 49366:288de6f5d724 stable 6.2rc0

branching: merge default into stable
author Rapha?l Gom?s <rgomes@octobus.net>
date Thu, 16 Jun 2022 15:28:54 +0200
parents 2e726c934fcd 0cc5f74ff7f0
children becd16690cbe
comparison
equal deleted inserted replaced
49364:e8ea403b1c46 49366:288de6f5d724
3 # Copyright 2006, 2007 Olivia Mackall <olivia@selenic.com> 3 # Copyright 2006, 2007 Olivia Mackall <olivia@selenic.com>
4 # 4 #
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 from __future__ import absolute_import
9 8
10 import collections 9 import collections
11 import errno
12 import struct 10 import struct
13 11
14 from .i18n import _ 12 from .i18n import _
15 from .node import nullrev 13 from .node import nullrev
16 from .thirdparty import attr 14 from .thirdparty import attr
65 and repo.dirstate.normalize(f) not in repo.dirstate 63 and repo.dirstate.normalize(f) not in repo.dirstate
66 and mctx[f2].cmp(wctx[f]) 64 and mctx[f2].cmp(wctx[f])
67 ) 65 )
68 66
69 67
70 class _unknowndirschecker(object): 68 class _unknowndirschecker:
71 """ 69 """
72 Look for any unknown files or directories that may have a path conflict 70 Look for any unknown files or directories that may have a path conflict
73 with a file. If any path prefix of the file exists as a file or link, 71 with a file. If any path prefix of the file exists as a file or link,
74 then it conflicts. If the file itself is a directory that contains any 72 then it conflicts. If the file itself is a directory that contains any
75 file that is not tracked, then it conflicts. 73 file that is not tracked, then it conflicts.
536 else: 534 else:
537 msg = _(b'conflict in file \'%s\' is outside narrow clone') 535 msg = _(b'conflict in file \'%s\' is outside narrow clone')
538 raise error.StateError(msg % f) 536 raise error.StateError(msg % f)
539 537
540 538
541 class mergeresult(object): 539 class mergeresult:
542 """An object representing result of merging manifests. 540 """An object representing result of merging manifests.
543 541
544 It has information about what actions need to be performed on dirstate 542 It has information about what actions need to be performed on dirstate
545 mapping of divergent renames and other such cases.""" 543 mapping of divergent renames and other such cases."""
546 544
624 if sort: 622 if sort:
625 for f in sorted(self._actionmapping[a]): 623 for f in sorted(self._actionmapping[a]):
626 args, msg = self._actionmapping[a][f] 624 args, msg = self._actionmapping[a][f]
627 yield f, args, msg 625 yield f, args, msg
628 else: 626 else:
629 for f, (args, msg) in pycompat.iteritems( 627 for f, (args, msg) in self._actionmapping[a].items():
630 self._actionmapping[a]
631 ):
632 yield f, args, msg 628 yield f, args, msg
633 629
634 def len(self, actions=None): 630 def len(self, actions=None):
635 """returns number of files which needs actions 631 """returns number of files which needs actions
636 632
642 638
643 return sum(len(self._actionmapping[a]) for a in actions) 639 return sum(len(self._actionmapping[a]) for a in actions)
644 640
645 def filemap(self, sort=False): 641 def filemap(self, sort=False):
646 if sorted: 642 if sorted:
647 for key, val in sorted(pycompat.iteritems(self._filemapping)): 643 for key, val in sorted(self._filemapping.items()):
648 yield key, val 644 yield key, val
649 else: 645 else:
650 for key, val in pycompat.iteritems(self._filemapping): 646 for key, val in self._filemapping.items():
651 yield key, val 647 yield key, val
652 648
653 def addcommitinfo(self, filename, key, value): 649 def addcommitinfo(self, filename, key, value):
654 """adds key-value information about filename which will be required 650 """adds key-value information about filename which will be required
655 while committing this merge""" 651 while committing this merge"""
670 @property 666 @property
671 def actionsdict(self): 667 def actionsdict(self):
672 """returns a dictionary of actions to be perfomed with action as key 668 """returns a dictionary of actions to be perfomed with action as key
673 and a list of files and related arguments as values""" 669 and a list of files and related arguments as values"""
674 res = collections.defaultdict(list) 670 res = collections.defaultdict(list)
675 for a, d in pycompat.iteritems(self._actionmapping): 671 for a, d in self._actionmapping.items():
676 for f, (args, msg) in pycompat.iteritems(d): 672 for f, (args, msg) in d.items():
677 res[a].append((f, args, msg)) 673 res[a].append((f, args, msg))
678 return res 674 return res
679 675
680 def setactions(self, actions): 676 def setactions(self, actions):
681 self._filemapping = actions 677 self._filemapping = actions
682 self._actionmapping = collections.defaultdict(dict) 678 self._actionmapping = collections.defaultdict(dict)
683 for f, (act, data, msg) in pycompat.iteritems(self._filemapping): 679 for f, (act, data, msg) in self._filemapping.items():
684 self._actionmapping[act][f] = data, msg 680 self._actionmapping[act][f] = data, msg
685 681
686 def hasconflicts(self): 682 def hasconflicts(self):
687 """tells whether this merge resulted in some actions which can 683 """tells whether this merge resulted in some actions which can
688 result in conflicts or not""" 684 result in conflicts or not"""
785 # total m1-vs-m2 diff to just those files. This has significant 781 # total m1-vs-m2 diff to just those files. This has significant
786 # performance benefits in large repositories. 782 # performance benefits in large repositories.
787 relevantfiles = set(ma.diff(m2).keys()) 783 relevantfiles = set(ma.diff(m2).keys())
788 784
789 # For copied and moved files, we need to add the source file too. 785 # For copied and moved files, we need to add the source file too.
790 for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy): 786 for copykey, copyvalue in branch_copies1.copy.items():
791 if copyvalue in relevantfiles: 787 if copyvalue in relevantfiles:
792 relevantfiles.add(copykey) 788 relevantfiles.add(copykey)
793 for movedirkey in branch_copies1.movewithdir: 789 for movedirkey in branch_copies1.movewithdir:
794 relevantfiles.add(movedirkey) 790 relevantfiles.add(movedirkey)
795 filesmatcher = scmutil.matchfiles(repo, relevantfiles) 791 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
796 matcher = matchmod.intersectmatchers(matcher, filesmatcher) 792 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
797 793
798 diff = m1.diff(m2, match=matcher) 794 diff = m1.diff(m2, match=matcher)
799 795
800 for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff): 796 for f, ((n1, fl1), (n2, fl2)) in diff.items():
801 if n1 and n2: # file exists on both local and remote side 797 if n1 and n2: # file exists on both local and remote side
802 if f not in ma: 798 if f not in ma:
803 # TODO: what if they're renamed from different sources? 799 # TODO: what if they're renamed from different sources?
804 fa = branch_copies1.copy.get( 800 fa = branch_copies1.copy.get(
805 f, None 801 f, None
1307 1303
1308 1304
1309 def _getcwd(): 1305 def _getcwd():
1310 try: 1306 try:
1311 return encoding.getcwd() 1307 return encoding.getcwd()
1312 except OSError as err: 1308 except FileNotFoundError:
1313 if err.errno == errno.ENOENT: 1309 return None
1314 return None
1315 raise
1316 1310
1317 1311
1318 def batchremove(repo, wctx, actions): 1312 def batchremove(repo, wctx, actions):
1319 """apply removes to the working directory 1313 """apply removes to the working directory
1320 1314
1468 ], 1462 ],
1469 ) 1463 )
1470 1464
1471 1465
1472 @attr.s(frozen=True) 1466 @attr.s(frozen=True)
1473 class updateresult(object): 1467 class updateresult:
1474 updatedcount = attr.ib() 1468 updatedcount = attr.ib()
1475 mergedcount = attr.ib() 1469 mergedcount = attr.ib()
1476 removedcount = attr.ib() 1470 removedcount = attr.ib()
1477 unresolvedcount = attr.ib() 1471 unresolvedcount = attr.ib()
1478 1472
1510 1504
1511 updated, merged, removed = 0, 0, 0 1505 updated, merged, removed = 0, 0, 0
1512 ms = wctx.mergestate(clean=True) 1506 ms = wctx.mergestate(clean=True)
1513 ms.start(wctx.p1().node(), mctx.node(), labels) 1507 ms.start(wctx.p1().node(), mctx.node(), labels)
1514 1508
1515 for f, op in pycompat.iteritems(mresult.commitinfo): 1509 for f, op in mresult.commitinfo.items():
1516 # the other side of filenode was choosen while merging, store this in 1510 # the other side of filenode was choosen while merging, store this in
1517 # mergestate so that it can be reused on commit 1511 # mergestate so that it can be reused on commit
1518 ms.addcommitinfo(f, op) 1512 ms.addcommitinfo(f, op)
1519 1513
1520 num_no_op = mresult.len(mergestatemod.MergeAction.NO_OP_ACTIONS) 1514 num_no_op = mresult.len(mergestatemod.MergeAction.NO_OP_ACTIONS)
2071 _checkcollision(repo, p2.manifest(), None) 2065 _checkcollision(repo, p2.manifest(), None)
2072 else: 2066 else:
2073 _checkcollision(repo, wc.manifest(), mresult) 2067 _checkcollision(repo, wc.manifest(), mresult)
2074 2068
2075 # divergent renames 2069 # divergent renames
2076 for f, fl in sorted(pycompat.iteritems(mresult.diverge)): 2070 for f, fl in sorted(mresult.diverge.items()):
2077 repo.ui.warn( 2071 repo.ui.warn(
2078 _( 2072 _(
2079 b"note: possible conflict - %s was renamed " 2073 b"note: possible conflict - %s was renamed "
2080 b"multiple times to:\n" 2074 b"multiple times to:\n"
2081 ) 2075 )
2083 ) 2077 )
2084 for nf in sorted(fl): 2078 for nf in sorted(fl):
2085 repo.ui.warn(b" %s\n" % nf) 2079 repo.ui.warn(b" %s\n" % nf)
2086 2080
2087 # rename and delete 2081 # rename and delete
2088 for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)): 2082 for f, fl in sorted(mresult.renamedelete.items()):
2089 repo.ui.warn( 2083 repo.ui.warn(
2090 _( 2084 _(
2091 b"note: possible conflict - %s was deleted " 2085 b"note: possible conflict - %s was deleted "
2092 b"and renamed to:\n" 2086 b"and renamed to:\n"
2093 ) 2087 )
2123 labels=labels, 2117 labels=labels,
2124 ) 2118 )
2125 2119
2126 if updatedirstate: 2120 if updatedirstate:
2127 if extraactions: 2121 if extraactions:
2128 for k, acts in pycompat.iteritems(extraactions): 2122 for k, acts in extraactions.items():
2129 for a in acts: 2123 for a in acts:
2130 mresult.addfile(a[0], k, *a[1:]) 2124 mresult.addfile(a[0], k, *a[1:])
2131 if k == mergestatemod.ACTION_GET and wantfiledata: 2125 if k == mergestatemod.ACTION_GET and wantfiledata:
2132 # no filedata until mergestate is updated to provide it 2126 # no filedata until mergestate is updated to provide it
2133 for a in acts: 2127 for a in acts:
2194 # the dirstate content anyway, no need to put cache 2188 # the dirstate content anyway, no need to put cache
2195 # information. 2189 # information.
2196 getfiledata = None 2190 getfiledata = None
2197 else: 2191 else:
2198 now_sec = now[0] 2192 now_sec = now[0]
2199 for f, m in pycompat.iteritems(getfiledata): 2193 for f, m in getfiledata.items():
2200 if m is not None and m[2][0] >= now_sec: 2194 if m is not None and m[2][0] >= now_sec:
2201 ambiguous_mtime[f] = (m[0], m[1], None) 2195 ambiguous_mtime[f] = (m[0], m[1], None)
2202 for f, m in pycompat.iteritems(ambiguous_mtime): 2196 for f, m in ambiguous_mtime.items():
2203 getfiledata[f] = m 2197 getfiledata[f] = m
2204 2198
2205 repo.setparents(fp1, fp2) 2199 repo.setparents(fp1, fp2)
2206 mergestatemod.recordupdates( 2200 mergestatemod.recordupdates(
2207 repo, mresult.actionsdict, branchmerge, getfiledata 2201 repo, mresult.actionsdict, branchmerge, getfiledata