mercurial/merge.py
changeset 4404 47371e1c1db4
parent 4355 10edaed7f909
parent 4400 84cd52b48f94
child 4410 bbc97d419b16
equal deleted inserted replaced
4403:15289406f89c 4404:47371e1c1db4
    99 
    99 
   100 def findcopies(repo, m1, m2, ma, limit):
   100 def findcopies(repo, m1, m2, ma, limit):
   101     """
   101     """
   102     Find moves and copies between m1 and m2 back to limit linkrev
   102     Find moves and copies between m1 and m2 back to limit linkrev
   103     """
   103     """
       
   104 
       
   105     def nonoverlap(d1, d2, d3):
       
   106         "Return list of elements in d1 not in d2 or d3"
       
   107         l = [d for d in d1 if d not in d3 and d not in d2]
       
   108         l.sort()
       
   109         return l
       
   110 
       
   111     def dirname(f):
       
   112         s = f.rfind("/")
       
   113         if s == -1:
       
   114             return ""
       
   115         return f[:s]
       
   116 
       
   117     def dirs(files):
       
   118         d = {}
       
   119         for f in files:
       
   120             f = dirname(f)
       
   121             while f not in d:
       
   122                 d[f] = True
       
   123                 f = dirname(f)
       
   124         return d
   104 
   125 
   105     def findold(fctx):
   126     def findold(fctx):
   106         "find files that path was copied from, back to linkrev limit"
   127         "find files that path was copied from, back to linkrev limit"
   107         old = {}
   128         old = {}
   108         seen = {}
   129         seen = {}
   122 
   143 
   123         old = old.keys()
   144         old = old.keys()
   124         old.sort()
   145         old.sort()
   125         return old
   146         return old
   126 
   147 
   127     def nonoverlap(d1, d2, d3):
   148     copy = {}
   128         "Return list of elements in d1 not in d2 or d3"
   149     fullcopy = {}
   129         l = [d for d in d1 if d not in d3 and d not in d2]
       
   130         l.sort()
       
   131         return l
       
   132 
   150 
   133     def checkcopies(c, man):
   151     def checkcopies(c, man):
   134         '''check possible copies for filectx c'''
   152         '''check possible copies for filectx c'''
   135         for of in findold(c):
   153         for of in findold(c):
   136             if of not in man:
   154             if of not in man: # original file not in other manifest?
   137                 continue
   155                 continue
   138             c2 = ctx(of, man[of])
   156             c2 = ctx(of, man[of])
   139             ca = c.ancestor(c2)
   157             ca = c.ancestor(c2)
   140             if not ca: # unrelated
   158             if not ca: # unrelated?
   141                 continue
   159                 continue
       
   160             # named changed on only one side?
   142             if ca.path() == c.path() or ca.path() == c2.path():
   161             if ca.path() == c.path() or ca.path() == c2.path():
   143                 fullcopy[c.path()] = of
   162                 fullcopy[c.path()] = of # remember for dir rename detection
   144                 if c == ca and c2 == ca: # no merge needed, ignore copy
   163                 if c == c2: # no merge needed, ignore copy
   145                     continue
   164                     continue
   146                 copy[c.path()] = of
   165                 copy[c.path()] = of
   147 
       
   148     def dirs(files):
       
   149         d = {}
       
   150         for f in files:
       
   151             d[os.path.dirname(f)] = True
       
   152         return d
       
   153 
   166 
   154     if not repo.ui.configbool("merge", "followcopies", True):
   167     if not repo.ui.configbool("merge", "followcopies", True):
   155         return {}
   168         return {}
   156 
   169 
   157     # avoid silly behavior for update from empty dir
   170     # avoid silly behavior for update from empty dir
   158     if not m1 or not m2 or not ma:
   171     if not m1 or not m2 or not ma:
   159         return {}
   172         return {}
   160 
   173 
   161     dcopies = repo.dirstate.copies()
   174     dcopies = repo.dirstate.copies()
   162     copy = {}
       
   163     fullcopy = {}
       
   164     u1 = nonoverlap(m1, m2, ma)
   175     u1 = nonoverlap(m1, m2, ma)
   165     u2 = nonoverlap(m2, m1, ma)
   176     u2 = nonoverlap(m2, m1, ma)
   166     ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
   177     ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
   167 
   178 
   168     for f in u1:
   179     for f in u1:
   177     # generate a directory move map
   188     # generate a directory move map
   178     d1, d2 = dirs(m1), dirs(m2)
   189     d1, d2 = dirs(m1), dirs(m2)
   179     invalid = {}
   190     invalid = {}
   180     dirmove = {}
   191     dirmove = {}
   181 
   192 
       
   193     # examine each file copy for a potential directory move, which is
       
   194     # when all the files in a directory are moved to a new directory
   182     for dst, src in fullcopy.items():
   195     for dst, src in fullcopy.items():
   183         dsrc, ddst = os.path.dirname(src), os.path.dirname(dst)
   196         dsrc, ddst = dirname(src), dirname(dst)
   184         if dsrc in invalid:
   197         if dsrc in invalid:
   185             continue
   198             # already seen to be uninteresting
   186         elif (dsrc in d1 and ddst in d1) or (dsrc in d2 and ddst in d2):
   199             continue
       
   200         elif dsrc in d1 and ddst in d1:
       
   201             # directory wasn't entirely moved locally
       
   202             invalid[dsrc] = True
       
   203         elif dsrc in d2 and ddst in d2:
       
   204             # directory wasn't entirely moved remotely
   187             invalid[dsrc] = True
   205             invalid[dsrc] = True
   188         elif dsrc in dirmove and dirmove[dsrc] != ddst:
   206         elif dsrc in dirmove and dirmove[dsrc] != ddst:
       
   207             # files from the same directory moved to two different places
   189             invalid[dsrc] = True
   208             invalid[dsrc] = True
   190             del dirmove[dsrc]
       
   191         else:
   209         else:
       
   210             # looks good so far
   192             dirmove[dsrc + "/"] = ddst + "/"
   211             dirmove[dsrc + "/"] = ddst + "/"
       
   212 
       
   213     for i in invalid:
       
   214         if i in dirmove:
       
   215             del dirmove[i]
   193 
   216 
   194     del d1, d2, invalid
   217     del d1, d2, invalid
   195 
   218 
   196     if not dirmove:
   219     if not dirmove:
   197         return copy
   220         return copy
   198 
   221 
   199     # check unaccounted nonoverlapping files
   222     # check unaccounted nonoverlapping files against directory moves
   200     for f in u1 + u2:
   223     for f in u1 + u2:
   201         if f not in fullcopy:
   224         if f not in fullcopy:
   202             for d in dirmove:
   225             for d in dirmove:
   203                 if f.startswith(d):
   226                 if f.startswith(d):
       
   227                     # new file added in a directory that was moved, move it
   204                     copy[f] = dirmove[d] + f[len(d):]
   228                     copy[f] = dirmove[d] + f[len(d):]
   205                     break
   229                     break
   206 
   230 
   207     return copy
   231     return copy
   208 
   232