diff --git a/mercurial/copies.py b/mercurial/copies.py --- a/mercurial/copies.py +++ b/mercurial/copies.py @@ -108,26 +108,46 @@ return min(limit, a, b) def _chain(src, dst, a, b): - """chain two sets of copies a->b""" + """chain two sets of copies 'a' and 'b'""" + + # When chaining copies in 'a' (from 'src' via some other commit 'mid') with + # copies in 'b' (from 'mid' to 'dst'), we can get the different cases in the + # following table (not including trivial cases). For example, case 2 is + # where a file existed in 'src' and remained under that name in 'mid' and + # then was renamed between 'mid' and 'dst'. + # + # case src mid dst result + # 1 x y - - + # 2 x y y x->y + # 3 x y x - + # 4 x y z x->z + # 5 - x y - + # 6 x x y x->y + + # Initialize result ('t') from 'a'. This catches cases 1 & 2. We'll remove + # case 1 later. We'll also catch cases 3 & 4 here. Case 4 will be + # overwritten later, and case 3 will be removed later. t = a.copy() for k, v in b.iteritems(): if v in t: - # found a chain + # found a chain, i.e. cases 3 & 4. if t[v] != k: - # file wasn't renamed back to itself + # file wasn't renamed back to itself (i.e. case 4, not 3) t[k] = t[v] if v not in dst: # chain was a rename, not a copy + # this deletes the copy for 'y' in case 4 del t[v] if v in src: - # file is a copy of an existing file + # file is a copy of an existing file, i.e. case 6. t[k] = v for k, v in list(t.items()): - # remove criss-crossed copies + # remove criss-crossed copies, i.e. case 3 if k in src and v in dst: del t[k] - # remove copies to files that were then removed + # remove copies to files that were then removed, i.e. case 1 + # and file 'y' in cases 3 & 4 (in case of rename) elif k not in dst: del t[k]