From patchwork Mon May 6 20:35:02 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [2, of, 7, STABLE] merge: check collision between files and directories before updating (issue29) From: Katsunori FUJIWARA X-Patchwork-Id: 1566 Message-Id: <1bede396d3f4dfd7763e.1367872502@juju> To: mercurial-devel@selenic.com Date: Tue, 07 May 2013 05:35:02 +0900 # HG changeset patch # User FUJIWARA Katsunori # Date 1367870651 -32400 # Tue May 07 05:04:11 2013 +0900 # Branch stable # Node ID 1bede396d3f4dfd7763e214befd12fea9df63130 # Parent 127d7233b406699617f6dbcbc34e5ea00c2aa223 merge: check collision between files and directories before updating (issue29) For example, it is assumed that the revision X consists file 'A', and the revision Y consists of file 'A/A'. Merging between X and Y causes collision between file 'A' (on X) and directory 'A' of 'A/A' (on Y). Before this patch, such collision is detected via failure of "mkdir()" on existing file 'A' (merging Y into X) or "unlink()" on directory 'A' of 'A/A' (merging X into Y) while updating working directory. This causes incomplete working directory status. This patch checks collision between files and directories before updating working directory. Collision check logic is a little redundant to fix collision problem with largefiles extension easily in succeeding patch. This patch skips collision check only for clean updating on case sensitive filesystem, because the files and the directories in the merged manifest may collide each other, even on case sensitive filesystem. This patch also adds "no-icasefs" to "#if" directive in test-audit-path.t, because collision between symbolic-link 'back' and directory 'back' of 'back/test' is detected before check by pathauditor on case insensitive filesystem, and this changes detail of abort message. diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -8,7 +8,7 @@ from node import nullid, nullrev, hex, bin from i18n import _ from mercurial import obsolete -import error, util, filemerge, copies, subrepo, worker, dicthelpers +import error, util, filemerge, copies, subrepo, worker, dicthelpers, scmutil import errno, os, shutil class mergestate(object): @@ -138,7 +138,7 @@ return actions -def _checkcollision(repo, wmf, actions, prompts): +def _checkcollision(repo, wmf, actions, prompts, casesensitive): # build provisional merged manifest up pmmf = set(wmf) @@ -185,14 +185,36 @@ assert op, m op(f, None) - # check case-folding collision in provisional merged manifest - foldmap = {} - for f in sorted(pmmf): - fold = util.normcase(f) - if fold in foldmap: - raise util.Abort(_("case-folding collision between %s and %s") - % (f, foldmap[fold])) - foldmap[fold] = f + if casesensitive: + normcase = None + def check(f, nf): + if nf in dirs: + raise util.Abort(_("collision between file %s and " + "other directory") % (f)) + else: + normcase = util.normcase + foldmap = {} + def check(f, nf): + if nf in dirs: + raise util.Abort(_("collision between file %s and " + "other directory") % (f)) + if nf in foldmap: + raise util.Abort(_("case-folding collision between %s and %s") + % (f, foldmap[nf])) + foldmap[nf] = f + + # check collision in provisional merged manifest + if normcase: + def normiter(): + for f in pmmf: + yield normcase(f) + dirs = scmutil.dirs(normiter()) + for f in sorted(pmmf): + check(f, normcase(f)) + else: + dirs = scmutil.dirs(pmmf) + for f in sorted(pmmf): + check(f, f) def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, acceptremote=False): @@ -350,13 +372,15 @@ raise util.Abort(_("untracked files in working directory differ " "from files in requested revision")) - if not util.checkcase(repo.path): - # check collision between files only in p2 for clean update - if (not branchmerge and - (force or not wctx.dirty(missing=True, branch=False))): - _checkcollision(repo, m2, [], []) - else: - _checkcollision(repo, m1, actions, prompts) + checkcase = util.checkcase(repo.path) + if (not branchmerge and + (force or not wctx.dirty(missing=True, branch=False))): + if not checkcase: + # check is not needed on case sensitive filesystem, + # because clean updating should not cause collision + _checkcollision(repo, m2, [], [], checkcase) + else: + _checkcollision(repo, m1, actions, prompts, checkcase) for f, m in sorted(prompts): if m == "cd": diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t --- a/tests/test-audit-path.t +++ b/tests/test-audit-path.t @@ -62,7 +62,7 @@ $ hg manifest -r2 back back/test -#if symlink +#if symlink no-icasefs $ hg update -Cr2 abort: path 'back/test' traverses symbolic link 'back' [255] diff --git a/tests/test-casecollision-merge.t b/tests/test-casecollision-merge.t --- a/tests/test-casecollision-merge.t +++ b/tests/test-casecollision-merge.t @@ -191,6 +191,29 @@ M x C A +Test for issue29: collision between file and directory, of which names +are different from each other only in the letter case, should be +detected on case insensitive filesystem. + + $ hg update -q --clean 2 + $ mkdir X + $ echo X/X > X/X + $ hg add X/X + $ hg commit -m '#5' + $ hg manifest -r 5 + A + X/X + $ hg manifest -r 3 + a + x + $ hg merge 3 + abort: collision between file x and other directory + [255] + $ hg update -q --clean 3 + $ hg merge 5 + abort: collision between file x and other directory + [255] + $ cd .. diff --git a/tests/test-merge1.t b/tests/test-merge1.t --- a/tests/test-merge1.t +++ b/tests/test-merge1.t @@ -173,4 +173,27 @@ $ hg revert -r -2 b $ hg up -q -- -2 +Test for issue29 + + $ hg update -q -C 2 + $ mkdir c + $ echo c/c > c/c + $ hg add c/c + $ hg ci -m "add c/c" + $ hg manifest -r 5 + a + b + c/c + $ hg manifest -r 3 + a + b + c + $ hg merge 3 + abort: collision between file c and other directory + [255] + $ hg update -q -C 3 + $ hg merge 5 + abort: collision between file c and other directory + [255] + $ cd ..