mercurial/merge.py
changeset 6269 ffdf70e74623
parent 6268 7e4f66fe964b
child 6270 14f0fe2e2db7
equal deleted inserted replaced
6268:7e4f66fe964b 6269:ffdf70e74623
     7 
     7 
     8 from node import nullid, nullrev
     8 from node import nullid, nullrev
     9 from i18n import _
     9 from i18n import _
    10 import errno, util, os, heapq, filemerge
    10 import errno, util, os, heapq, filemerge
    11 
    11 
    12 def checkunknown(wctx, mctx):
    12 def _checkunknown(wctx, mctx):
    13     "check for collisions between unknown files and files in mctx"
    13     "check for collisions between unknown files and files in mctx"
    14     man = mctx.manifest()
    14     man = mctx.manifest()
    15     for f in wctx.unknown():
    15     for f in wctx.unknown():
    16         if f in man:
    16         if f in man:
    17             if mctx.filectx(f).cmp(wctx.filectx(f).data()):
    17             if mctx.filectx(f).cmp(wctx.filectx(f).data()):
    18                 raise util.Abort(_("untracked file in working directory differs"
    18                 raise util.Abort(_("untracked file in working directory differs"
    19                                    " from file in requested revision: '%s'")
    19                                    " from file in requested revision: '%s'")
    20                                  % f)
    20                                  % f)
    21 
    21 
    22 def checkcollision(mctx):
    22 def _checkcollision(mctx):
    23     "check for case folding collisions in the destination context"
    23     "check for case folding collisions in the destination context"
    24     folded = {}
    24     folded = {}
    25     for fn in mctx.manifest():
    25     for fn in mctx.manifest():
    26         fold = fn.lower()
    26         fold = fn.lower()
    27         if fold in folded:
    27         if fold in folded:
    28             raise util.Abort(_("case-folding collision between %s and %s")
    28             raise util.Abort(_("case-folding collision between %s and %s")
    29                              % (fn, folded[fold]))
    29                              % (fn, folded[fold]))
    30         folded[fold] = fn
    30         folded[fold] = fn
    31 
    31 
    32 def forgetremoved(wctx, mctx, branchmerge):
    32 def _forgetremoved(wctx, mctx, branchmerge):
    33     """
    33     """
    34     Forget removed files
    34     Forget removed files
    35 
    35 
    36     If we're jumping between revisions (as opposed to merging), and if
    36     If we're jumping between revisions (as opposed to merging), and if
    37     neither the working directory nor the target rev has the file,
    37     neither the working directory nor the target rev has the file,
    56             if f not in man:
    56             if f not in man:
    57                 action.append((f, "f"))
    57                 action.append((f, "f"))
    58 
    58 
    59     return action
    59     return action
    60 
    60 
       
    61 def _nonoverlap(d1, d2, d3):
       
    62     "Return list of elements in d1 not in d2 or d3"
       
    63     l = [d for d in d1 if d not in d3 and d not in d2]
       
    64     l.sort()
       
    65     return l
       
    66 
       
    67 def _dirname(f):
       
    68     s = f.rfind("/")
       
    69     if s == -1:
       
    70         return ""
       
    71     return f[:s]
       
    72 
       
    73 def _dirs(files):
       
    74     d = {}
       
    75     for f in files:
       
    76         f = _dirname(f)
       
    77         while f not in d:
       
    78             d[f] = True
       
    79             f = _dirname(f)
       
    80     return d
       
    81 
       
    82 def _findoldnames(fctx, limit):
       
    83     "find files that path was copied from, back to linkrev limit"
       
    84     old = {}
       
    85     seen = {}
       
    86     orig = fctx.path()
       
    87     visit = [fctx]
       
    88     while visit:
       
    89         fc = visit.pop()
       
    90         s = str(fc)
       
    91         if s in seen:
       
    92             continue
       
    93         seen[s] = 1
       
    94         if fc.path() != orig and fc.path() not in old:
       
    95             old[fc.path()] = 1
       
    96         if fc.rev() < limit:
       
    97             continue
       
    98         visit += fc.parents()
       
    99 
       
   100     old = old.keys()
       
   101     old.sort()
       
   102     return old
       
   103 
    61 def findcopies(repo, m1, m2, ma, limit):
   104 def findcopies(repo, m1, m2, ma, limit):
    62     """
   105     """
    63     Find moves and copies between m1 and m2 back to limit linkrev
   106     Find moves and copies between m1 and m2 back to limit linkrev
    64     """
   107     """
    65 
       
    66     def nonoverlap(d1, d2, d3):
       
    67         "Return list of elements in d1 not in d2 or d3"
       
    68         l = [d for d in d1 if d not in d3 and d not in d2]
       
    69         l.sort()
       
    70         return l
       
    71 
       
    72     def dirname(f):
       
    73         s = f.rfind("/")
       
    74         if s == -1:
       
    75             return ""
       
    76         return f[:s]
       
    77 
       
    78     def dirs(files):
       
    79         d = {}
       
    80         for f in files:
       
    81             f = dirname(f)
       
    82             while f not in d:
       
    83                 d[f] = True
       
    84                 f = dirname(f)
       
    85         return d
       
    86 
   108 
    87     wctx = repo.workingctx()
   109     wctx = repo.workingctx()
    88 
   110 
    89     def makectx(f, n):
   111     def makectx(f, n):
    90         if len(n) == 20:
   112         if len(n) == 20:
    91             return repo.filectx(f, fileid=n)
   113             return repo.filectx(f, fileid=n)
    92         return wctx.filectx(f)
   114         return wctx.filectx(f)
    93     ctx = util.cachefunc(makectx)
   115     ctx = util.cachefunc(makectx)
    94 
   116 
    95     def findold(fctx):
       
    96         "find files that path was copied from, back to linkrev limit"
       
    97         old = {}
       
    98         seen = {}
       
    99         orig = fctx.path()
       
   100         visit = [fctx]
       
   101         while visit:
       
   102             fc = visit.pop()
       
   103             s = str(fc)
       
   104             if s in seen:
       
   105                 continue
       
   106             seen[s] = 1
       
   107             if fc.path() != orig and fc.path() not in old:
       
   108                 old[fc.path()] = 1
       
   109             if fc.rev() < limit and fc.rev() is not None:
       
   110                 continue
       
   111             visit += fc.parents()
       
   112 
       
   113         old = old.keys()
       
   114         old.sort()
       
   115         return old
       
   116 
       
   117     copy = {}
   117     copy = {}
   118     fullcopy = {}
   118     fullcopy = {}
   119     diverge = {}
   119     diverge = {}
   120 
   120 
   121     def checkcopies(c, man, aman):
   121     def checkcopies(c, man, aman):
   122         '''check possible copies for filectx c'''
   122         '''check possible copies for filectx c'''
   123         for of in findold(c):
   123         for of in _findoldnames(c, limit):
   124             fullcopy[c.path()] = of # remember for dir rename detection
   124             fullcopy[c.path()] = of # remember for dir rename detection
   125             if of not in man: # original file not in other manifest?
   125             if of not in man: # original file not in other manifest?
   126                 if of in ma:
   126                 if of in ma:
   127                     diverge.setdefault(of, []).append(c.path())
   127                     diverge.setdefault(of, []).append(c.path())
   128                 continue
   128                 continue
   147     if not m1 or not m2 or not ma:
   147     if not m1 or not m2 or not ma:
   148         return {}, {}
   148         return {}, {}
   149 
   149 
   150     repo.ui.debug(_("  searching for copies back to rev %d\n") % limit)
   150     repo.ui.debug(_("  searching for copies back to rev %d\n") % limit)
   151 
   151 
   152     u1 = nonoverlap(m1, m2, ma)
   152     u1 = _nonoverlap(m1, m2, ma)
   153     u2 = nonoverlap(m2, m1, ma)
   153     u2 = _nonoverlap(m2, m1, ma)
   154 
   154 
   155     if u1:
   155     if u1:
   156         repo.ui.debug(_("  unmatched files in local:\n   %s\n")
   156         repo.ui.debug(_("  unmatched files in local:\n   %s\n")
   157                       % "\n   ".join(u1))
   157                       % "\n   ".join(u1))
   158     if u2:
   158     if u2:
   186         return copy, diverge
   186         return copy, diverge
   187 
   187 
   188     repo.ui.debug(_("  checking for directory renames\n"))
   188     repo.ui.debug(_("  checking for directory renames\n"))
   189 
   189 
   190     # generate a directory move map
   190     # generate a directory move map
   191     d1, d2 = dirs(m1), dirs(m2)
   191     d1, d2 = _dirs(m1), _dirs(m2)
   192     invalid = {}
   192     invalid = {}
   193     dirmove = {}
   193     dirmove = {}
   194 
   194 
   195     # examine each file copy for a potential directory move, which is
   195     # examine each file copy for a potential directory move, which is
   196     # when all the files in a directory are moved to a new directory
   196     # when all the files in a directory are moved to a new directory
   197     for dst, src in fullcopy.items():
   197     for dst, src in fullcopy.items():
   198         dsrc, ddst = dirname(src), dirname(dst)
   198         dsrc, ddst = _dirname(src), _dirname(dst)
   199         if dsrc in invalid:
   199         if dsrc in invalid:
   200             # already seen to be uninteresting
   200             # already seen to be uninteresting
   201             continue
   201             continue
   202         elif dsrc in d1 and ddst in d1:
   202         elif dsrc in d1 and ddst in d1:
   203             # directory wasn't entirely moved locally
   203             # directory wasn't entirely moved locally
   234                     repo.ui.debug(_("  file %s -> %s\n") % (f, copy[f]))
   234                     repo.ui.debug(_("  file %s -> %s\n") % (f, copy[f]))
   235                     break
   235                     break
   236 
   236 
   237     return copy, diverge
   237     return copy, diverge
   238 
   238 
   239 def symmetricdifference(repo, rev1, rev2):
   239 def _symmetricdifference(repo, rev1, rev2):
   240     """symmetric difference of the sets of ancestors of rev1 and rev2
   240     """symmetric difference of the sets of ancestors of rev1 and rev2
   241 
   241 
   242     I.e. revisions that are ancestors of rev1 or rev2, but not both.
   242     I.e. revisions that are ancestors of rev1 or rev2, but not both.
   243     """
   243     """
   244     # basic idea:
   244     # basic idea:
   338     if not (backwards or overwrite):
   338     if not (backwards or overwrite):
   339         rev1 = p1.rev()
   339         rev1 = p1.rev()
   340         if rev1 is None:
   340         if rev1 is None:
   341             # p1 is a workingctx
   341             # p1 is a workingctx
   342             rev1 = p1.parents()[0].rev()
   342             rev1 = p1.parents()[0].rev()
   343         limit = min(symmetricdifference(repo, rev1, p2.rev()))
   343         limit = min(_symmetricdifference(repo, rev1, p2.rev()))
   344         copy, diverge = findcopies(repo, m1, m2, ma, limit)
   344         copy, diverge = findcopies(repo, m1, m2, ma, limit)
   345 
   345 
   346     for of, fl in diverge.items():
   346     for of, fl in diverge.items():
   347         act("divergent renames", "dr", of, fl)
   347         act("divergent renames", "dr", of, fl)
   348 
   348 
   613                 raise util.Abort(_("outstanding uncommitted changes"))
   613                 raise util.Abort(_("outstanding uncommitted changes"))
   614 
   614 
   615         ### calculate phase
   615         ### calculate phase
   616         action = []
   616         action = []
   617         if not force:
   617         if not force:
   618             checkunknown(wc, p2)
   618             _checkunknown(wc, p2)
   619         if not util.checkfolding(repo.path):
   619         if not util.checkfolding(repo.path):
   620             checkcollision(p2)
   620             _checkcollision(p2)
   621         action += forgetremoved(wc, p2, branchmerge)
   621         action += _forgetremoved(wc, p2, branchmerge)
   622         action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
   622         action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
   623 
   623 
   624         ### apply phase
   624         ### apply phase
   625         if not branchmerge: # just jump to the new rev
   625         if not branchmerge: # just jump to the new rev
   626             fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
   626             fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''