p4fastimport: work around P4 symlink/directory bug

The P4 repository can erroneously consider a file to be "inside" a symlink to a
directory. 'p4 sync' (the client) reports an error if it encounters this
situation, but 'hg update' aborts.

Work around the problem by discarding such files. In the testcase, we can't
accept changelist #2, because we have no changelist that deletes the symlink,
so instead we have to ignore the new file.

Differential Revision: https://phab.mercurial-scm.org/D1314
This commit is contained in:
Hollis Blanchard 2017-12-22 14:32:44 -08:00
parent 87eaccd8c9
commit 6fe60c08e1
3 changed files with 125 additions and 0 deletions

View File

@ -20,6 +20,10 @@ Config example:
# and a p4fastimporter import
ignore-time-delta = None
# The P4 database can become corrupted when it tracks symlinks to
# directories. Keep this corruption out of the Mercurial repo.
checksymlinks = True
"""
from __future__ import absolute_import

View File

@ -125,6 +125,45 @@ class ChangeManifestImporter(object):
continue
info = fileinfo[depotname]
localname, baserev = info['localname'], info['baserev']
if self._ui.configbool('p4fastimport', 'checksymlinks', True):
# Under rare situations, when a symlink points to a directory,
# the P4 server can report a file "under" it (as if it really
# were a directory). 'p4 sync' reports this as an error and
# continues, but 'hg update' will abort if it encounters this.
# We need to keep such damage out of the hg repository.
depotparentname = os.path.dirname(depotname)
# The manifest's flags for the parent haven't been updated to
# reflect this changelist yet. If the parent's flags are
# changing right now, use them. Otherwise, use the manifest's
# flags.
parentflags = None
parentinfo = fileinfo.get(depotparentname, None)
if parentinfo:
parentflags = parentinfo['flags'].get(change.cl, None)
localparentname = localname
while parentflags == None:
# This P4 commit didn't change parent's flags at all.
# Therefore, we can consult the Hg metadata.
localparentname = os.path.dirname(localparentname)
if localparentname == '':
# There was no parent file above localname; only
# directories. That's good/expected.
parentflags = ''
break
parentflags = mf.flags(localparentname, None)
if 'l' in parentflags:
# It turns out that some parent is a symlink, so this file
# can't exist. However, we already wrote the filelog! Oh
# well. Just don't reference it in the manifest.
# TODO: hgfilelog.strip()?
self._ui.warn("warning: ignoring {} because it's under a "
"symlink ({})\n".format(localname, localparentname))
continue
hgfilelog = self._repo.file(localname)
try:
mf[localname] = hgfilelog.node(baserev)

View File

@ -0,0 +1,82 @@
#require p4
$ . $TESTDIR/p4setup.sh
populate the depot
$ mkdir Main
$ mkdir Main/b
$ echo a > Main/a
$ echo c > Main/b/c
$ ln -s b Main/d
$ p4 add Main/a Main/b/c Main/d
//depot/Main/a#1 - opened for add
//depot/Main/b/c#1 - opened for add
//depot/Main/d#1 - opened for add
$ p4 submit -d initial
Submitting change 1.
Locking 3 files ...
add //depot/Main/a#1
add //depot/Main/b/c#1
add //depot/Main/d#1
Change 1 submitted.
$ rm Main/d
$ mkdir -p Main/d
$ echo d > Main/d/d
$ p4 add Main/d/d
//depot/Main/d/d#1 - opened for add
$ p4 submit -d two
Submitting change 2.
Locking 1 files ...
add //depot/Main/d/d#1
Change 2 submitted.
$ p4 files ...
//depot/Main/a#1 - add change 1 (text)
//depot/Main/b/c#1 - add change 1 (text)
//depot/Main/d#1 - add change 1 (symlink)
//depot/Main/d/d#1 - add change 2 (text)
According to P4, Main/d/d is a file inside Main/d, which is a symlink(!)
$ cd $hgwd
$ hg init --config 'format.usefncache=False'
$ hg p4fastimport --bookmark master -P $P4ROOT hg-p4-import
warning: ignoring Main/d/d because it's under a symlink (Main/d)
$ hg update
3 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ ls -l Main
total 8
-rw-r--r-- 1 .* a (re)
drwxr-xr-x 2 .* b (re)
lrwxrwxrwx 1 .* d -> b (re)
Repeat with a file nested under several directories
$ cd $p4wd
$ mkdir -p Main/d/e
$ echo e > Main/d/e/e
$ p4 add Main/d/e/e
//depot/Main/d/e/e#1 - opened for add
$ p4 submit -d three
Submitting change 3.
Locking 1 files ...
add //depot/Main/d/e/e#1
Change 3 submitted.
$ p4 files ...
//depot/Main/a#1 - add change 1 (text)
//depot/Main/b/c#1 - add change 1 (text)
//depot/Main/d#1 - add change 1 (symlink)
//depot/Main/d/d#1 - add change 2 (text)
//depot/Main/d/e/e#1 - add change 3 (text)
Second import
$ cd $hgwd
$ hg p4fastimport --bookmark master -P $P4ROOT hg-p4-import
warning: ignoring Main/d/e/e because it's under a symlink (Main/d)
$ hg update
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
End Test
stopping the p4 server