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 |
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 |