mercurial/merge.py
branchstable
changeset 49366 288de6f5d724
parent 49306 2e726c934fcd
parent 49344 0cc5f74ff7f0
child 49883 becd16690cbe
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