diff options
Diffstat (limited to 'mercurial/copies.py')
-rw-r--r-- | mercurial/copies.py | 147 |
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 |