mirror of
https://github.com/facebook/sapling.git
synced 2024-10-07 07:17:55 +03:00
merge: check for path conflicts when merging (issue5628)
When merging, check for any path conflicts introduced by the manifest merge and rename the conflicting file to a safe name. Differential Revision: https://phab.mercurial-scm.org/D784
This commit is contained in:
parent
e2916432c0
commit
75628a759b
@ -851,6 +851,107 @@ def driverconclude(repo, ms, wctx, labels=None):
|
||||
This is currently not implemented -- it's an extension point."""
|
||||
return True
|
||||
|
||||
def _filesindirs(repo, manifest, dirs):
|
||||
"""
|
||||
Generator that yields pairs of all the files in the manifest that are found
|
||||
inside the directories listed in dirs, and which directory they are found
|
||||
in.
|
||||
"""
|
||||
for f in manifest:
|
||||
for p in util.finddirs(f):
|
||||
if p in dirs:
|
||||
yield f, p
|
||||
break
|
||||
|
||||
def checkpathconflicts(repo, wctx, mctx, actions):
|
||||
"""
|
||||
Check if any actions introduce path conflicts in the repository, updating
|
||||
actions to record or handle the path conflict accordingly.
|
||||
"""
|
||||
mf = wctx.manifest()
|
||||
|
||||
# The set of local files that conflict with a remote directory.
|
||||
localconflicts = set()
|
||||
|
||||
# The set of directories that conflict with a remote file, and so may cause
|
||||
# conflicts if they still contain any files after the merge.
|
||||
remoteconflicts = set()
|
||||
|
||||
# The set of directories that appear as both a file and a directory in the
|
||||
# remote manifest. These indicate an invalid remote manifest, which
|
||||
# can't be updated to cleanly.
|
||||
invalidconflicts = set()
|
||||
|
||||
# The set of files deleted by all the actions.
|
||||
deletedfiles = set()
|
||||
|
||||
for f, (m, args, msg) in actions.items():
|
||||
if m in ('c', 'dc', 'm', 'cm'):
|
||||
# This action may create a new local file.
|
||||
if mf.hasdir(f):
|
||||
# The file aliases a local directory. This might be ok if all
|
||||
# the files in the local directory are being deleted. This
|
||||
# will be checked once we know what all the deleted files are.
|
||||
remoteconflicts.add(f)
|
||||
for p in util.finddirs(f):
|
||||
if p in mf:
|
||||
if p in mctx:
|
||||
# The file is in a directory which aliases both a local
|
||||
# and a remote file. This is an internal inconsistency
|
||||
# within the remote manifest.
|
||||
invalidconflicts.add(p)
|
||||
else:
|
||||
# The file is in a directory which aliases a local file.
|
||||
# We will need to rename the local file.
|
||||
localconflicts.add(p)
|
||||
if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
|
||||
# The file is in a directory which aliases a remote file.
|
||||
# This is an internal inconsistency within the remote
|
||||
# manifest.
|
||||
invalidconflicts.add(p)
|
||||
|
||||
# Track the names of all deleted files.
|
||||
if m == 'r':
|
||||
deletedfiles.add(f)
|
||||
if m == 'm':
|
||||
f1, f2, fa, move, anc = args
|
||||
if move:
|
||||
deletedfiles.add(f1)
|
||||
if m == 'dm':
|
||||
f2, flags = args
|
||||
deletedfiles.add(f2)
|
||||
|
||||
# Rename all local conflicting files that have not been deleted.
|
||||
for p in localconflicts:
|
||||
if p not in deletedfiles:
|
||||
ctxname = str(wctx).rstrip('+')
|
||||
pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
|
||||
actions[pnew] = ('pr', (p,), "local path conflict")
|
||||
actions[p] = ('p', (pnew, 'l'), "path conflict")
|
||||
|
||||
if remoteconflicts:
|
||||
# Check if all files in the conflicting directories have been removed.
|
||||
ctxname = str(mctx).rstrip('+')
|
||||
for f, p in _filesindirs(repo, mf, remoteconflicts):
|
||||
if f not in deletedfiles:
|
||||
m, args, msg = actions[p]
|
||||
pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
|
||||
if m in ('dc', 'm'):
|
||||
# Action was merge, just update target.
|
||||
actions[pnew] = (m, args, msg)
|
||||
else:
|
||||
# Action was create, change to renamed get action.
|
||||
fl = args[0]
|
||||
actions[pnew] = ('dg', (p, fl), "remote path conflict")
|
||||
actions[p] = ('p', (pnew, 'r'), "path conflict")
|
||||
remoteconflicts.remove(p)
|
||||
break
|
||||
|
||||
if invalidconflicts:
|
||||
for p in invalidconflicts:
|
||||
repo.ui.warn(_("%s: is both a file and a directory\n") % p)
|
||||
raise error.Abort(_("destination manifest contains path conflicts"))
|
||||
|
||||
def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
|
||||
acceptremote, followcopies, forcefulldiff=False):
|
||||
"""
|
||||
@ -1026,6 +1127,9 @@ def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
|
||||
actions[f] = ('dc', (None, f, f, False, pa.node()),
|
||||
"prompt deleted/changed")
|
||||
|
||||
# If we are merging, look for path conflicts.
|
||||
checkpathconflicts(repo, wctx, p2, actions)
|
||||
|
||||
return actions, diverge, renamedelete
|
||||
|
||||
def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
|
||||
|
@ -103,7 +103,8 @@ attack back/test where back symlinks to ..
|
||||
back/test
|
||||
#if symlink
|
||||
$ hg update -Cr2
|
||||
abort: path 'back/test' traverses symbolic link 'back'
|
||||
back: is both a file and a directory
|
||||
abort: destination manifest contains path conflicts
|
||||
[255]
|
||||
#else
|
||||
('back' will be a file and cause some other system specific error)
|
||||
@ -160,8 +161,12 @@ try trivial merge
|
||||
|
||||
$ hg up -qC 1
|
||||
$ hg merge 2
|
||||
abort: path 'a/poisoned' traverses symbolic link 'a'
|
||||
[255]
|
||||
a: path conflict - a file or link has the same name as a directory
|
||||
the local file has been renamed to a~aa04623eb0c3
|
||||
resolve manually then use 'hg resolve --mark a'
|
||||
1 files updated, 0 files merged, 0 files removed, 1 files unresolved
|
||||
use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
|
||||
[1]
|
||||
|
||||
try rebase onto other revision: cache of audited paths should be discarded,
|
||||
and the rebase should fail (issue5628)
|
||||
@ -169,8 +174,11 @@ and the rebase should fail (issue5628)
|
||||
$ hg up -qC 2
|
||||
$ hg rebase -s 2 -d 1 --config extensions.rebase=
|
||||
rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
|
||||
abort: path 'a/poisoned' traverses symbolic link 'a'
|
||||
[255]
|
||||
a: path conflict - a file or link has the same name as a directory
|
||||
the local file has been renamed to a~aa04623eb0c3
|
||||
resolve manually then use 'hg resolve --mark a'
|
||||
unresolved conflicts (see hg resolve, then hg rebase --continue)
|
||||
[1]
|
||||
$ ls ../merge-symlink-out
|
||||
|
||||
$ cd ..
|
||||
@ -202,7 +210,8 @@ try linear update where symlink already exists:
|
||||
|
||||
$ hg up -qC 0
|
||||
$ hg up 1
|
||||
abort: path 'a/b' traverses symbolic link 'a'
|
||||
a: is both a file and a directory
|
||||
abort: destination manifest contains path conflicts
|
||||
[255]
|
||||
|
||||
try linear update including symlinked directory and its content: paths are
|
||||
@ -211,7 +220,8 @@ audited first by calculateupdates(), where no symlink is created so both
|
||||
|
||||
$ hg up -qC null
|
||||
$ hg up 1
|
||||
abort: path 'a/b' traverses symbolic link 'a'
|
||||
a: is both a file and a directory
|
||||
abort: destination manifest contains path conflicts
|
||||
[255]
|
||||
$ ls ../update-symlink-out
|
||||
|
||||
@ -222,7 +232,8 @@ a symlink.
|
||||
$ rm -f a
|
||||
$ hg up -qC 2
|
||||
$ hg up 1
|
||||
abort: path 'a/b' traverses symbolic link 'a'
|
||||
a: is both a file and a directory
|
||||
abort: destination manifest contains path conflicts
|
||||
[255]
|
||||
$ ls ../update-symlink-out
|
||||
|
||||
|
@ -966,8 +966,12 @@ and the merge should fail (issue5628)
|
||||
*** runcommand up -qC 2
|
||||
*** runcommand up -qC 1
|
||||
*** runcommand merge 2
|
||||
abort: path 'a/poisoned' traverses symbolic link 'a'
|
||||
[255]
|
||||
a: path conflict - a file or link has the same name as a directory
|
||||
the local file has been renamed to a~aa04623eb0c3
|
||||
resolve manually then use 'hg resolve --mark a'
|
||||
1 files updated, 0 files merged, 0 files removed, 1 files unresolved
|
||||
use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
|
||||
[1]
|
||||
$ ls ../merge-symlink-out
|
||||
|
||||
cache of repo.auditor should be discarded, so matcher would never traverse
|
||||
|
@ -25,11 +25,16 @@ Basic merge - local file conflicts with remote directory
|
||||
$ hg bookmark -i
|
||||
$ hg merge --verbose dir
|
||||
resolving manifests
|
||||
a: path conflict - a file or link has the same name as a directory
|
||||
the local file has been renamed to a~853701544ac3
|
||||
resolve manually then use 'hg resolve --mark a'
|
||||
moving a to a~853701544ac3
|
||||
getting a/b
|
||||
abort: *: '$TESTTMP/repo/a/b' (glob)
|
||||
[255]
|
||||
1 files updated, 0 files merged, 0 files removed, 1 files unresolved
|
||||
use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
|
||||
[1]
|
||||
$ hg update --clean .
|
||||
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
|
||||
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
|
||||
|
||||
Basic update - local directory conflicts with remote file
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user