summaryrefslogtreecommitdiff
path: root/mercurial/copies.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/copies.py')
-rw-r--r--mercurial/copies.py147
1 files changed, 21 insertions, 126 deletions
diff --git a/mercurial/copies.py b/mercurial/copies.py
index 90aa036..abd16fa 100644
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -18,6 +18,15 @@ def _dirname(f):
return ""
return f[:s]
+def _dirs(files):
+ d = set()
+ for f in files:
+ f = _dirname(f)
+ while f not in d:
+ d.add(f)
+ f = _dirname(f)
+ return d
+
def _findlimit(repo, a, b):
"""Find the earliest revision that's an ancestor of a or b but not both,
None if no such revision exists.
@@ -75,124 +84,22 @@ def _findlimit(repo, a, b):
return None
return limit
-def _chain(src, dst, a, b):
- '''chain two sets of copies a->b'''
- t = a.copy()
- for k, v in b.iteritems():
- if v in t:
- # found a chain
- if t[v] != k:
- # file wasn't renamed back to itself
- t[k] = t[v]
- if v not in dst:
- # chain was a rename, not a copy
- del t[v]
- if v in src:
- # file is a copy of an existing file
- t[k] = v
-
- # remove criss-crossed copies
- for k, v in t.items():
- if k in src and v in dst:
- del t[k]
-
- return t
-
-def _tracefile(fctx, actx):
- '''return file context that is the ancestor of fctx present in actx'''
- stop = actx.rev()
- am = actx.manifest()
-
- for f in fctx.ancestors():
- if am.get(f.path(), None) == f.filenode():
- return f
- if f.rev() < stop:
- return None
-
-def _dirstatecopies(d):
- ds = d._repo.dirstate
- c = ds.copies().copy()
- for k in c.keys():
- if ds[k] not in 'anm':
- del c[k]
- return c
-
-def _forwardcopies(a, b):
- '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
-
- # check for working copy
- w = None
- if b.rev() is None:
- w = b
- b = w.p1()
- if a == b:
- # short-circuit to avoid issues with merge states
- return _dirstatecopies(w)
-
- # find where new files came from
- # we currently don't try to find where old files went, too expensive
- # this means we can miss a case like 'hg rm b; hg cp a b'
- cm = {}
- for f in b:
- if f not in a:
- ofctx = _tracefile(b[f], a)
- if ofctx:
- cm[f] = ofctx.path()
-
- # combine copies from dirstate if necessary
- if w is not None:
- cm = _chain(a, w, cm, _dirstatecopies(w))
-
- return cm
-
-def _backwardcopies(a, b):
- # because the forward mapping is 1:n, we can lose renames here
- # in particular, we find renames better than copies
- f = _forwardcopies(b, a)
- r = {}
- for k, v in f.iteritems():
- r[v] = k
- return r
-
-def pathcopies(x, y):
- '''find {dst@y: src@x} copy mapping for directed compare'''
- if x == y or not x or not y:
- return {}
- a = y.ancestor(x)
- if a == x:
- return _forwardcopies(x, y)
- if a == y:
- return _backwardcopies(x, y)
- return _chain(x, y, _backwardcopies(x, a), _forwardcopies(a, y))
-
-def mergecopies(repo, c1, c2, ca):
+def copies(repo, c1, c2, ca, checkdirs=False):
"""
- Find moves and copies between context c1 and c2 that are relevant
- for merging.
-
- Returns two dicts, "copy" and "diverge".
-
- "copy" is a mapping from destination name -> source name,
- where source is in c1 and destination is in c2 or vice-versa.
-
- "diverge" is a mapping of source name -> list of destination names
- for divergent renames.
-
- "renamedelete" is a mapping of source name -> list of destination
- names for files deleted in c1 that were renamed in c2 or vice-versa.
+ Find moves and copies between context c1 and c2
"""
# avoid silly behavior for update from empty dir
if not c1 or not c2 or c1 == c2:
- return {}, {}, {}
+ return {}, {}
# avoid silly behavior for parent -> working dir
if c2.node() is None and c1.node() == repo.dirstate.p1():
- return repo.dirstate.copies(), {}, {}
+ return repo.dirstate.copies(), {}
limit = _findlimit(repo, c1.rev(), c2.rev())
if limit is None:
# no common ancestor, no copies
- return {}, {}, {}
+ return {}, {}
m1 = c1.manifest()
m2 = c2.manifest()
ma = ca.manifest()
@@ -286,43 +193,31 @@ def mergecopies(repo, c1, c2, ca):
for f in u2:
checkcopies(f, m2, m1)
- renamedelete = {}
- renamedelete2 = set()
diverge2 = set()
for of, fl in diverge.items():
- if len(fl) == 1 or of in c1 or of in c2:
+ if len(fl) == 1 or of in c2:
del diverge[of] # not actually divergent, or not a rename
- if of not in c1 and of not in c2:
- # renamed on one side, deleted on the other side, but filter
- # out files that have been renamed and then deleted
- renamedelete[of] = [f for f in fl if f in c1 or f in c2]
- renamedelete2.update(fl) # reverse map for below
else:
diverge2.update(fl) # reverse map for below
if fullcopy:
- repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
- "% = renamed and deleted):\n")
+ repo.ui.debug(" all copies found (* = to merge, ! = divergent):\n")
for f in fullcopy:
note = ""
if f in copy:
note += "*"
if f in diverge2:
note += "!"
- if f in renamedelete2:
- note += "%"
repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
del diverge2
- if not fullcopy:
- return copy, diverge, renamedelete
+ if not fullcopy or not checkdirs:
+ return copy, diverge
repo.ui.debug(" checking for directory renames\n")
# generate a directory move map
- d1, d2 = c1.dirs(), c2.dirs()
- d1.add('')
- d2.add('')
+ d1, d2 = _dirs(m1), _dirs(m2)
invalid = set()
dirmove = {}
@@ -352,7 +247,7 @@ def mergecopies(repo, c1, c2, ca):
del d1, d2, invalid
if not dirmove:
- return copy, diverge, renamedelete
+ return copy, diverge
for d in dirmove:
repo.ui.debug(" dir %s -> %s\n" % (d, dirmove[d]))
@@ -369,4 +264,4 @@ def mergecopies(repo, c1, c2, ca):
repo.ui.debug(" file %s -> %s\n" % (f, copy[f]))
break
- return copy, diverge, renamedelete
+ return copy, diverge