diff --git a/hgext3rd/p4fastimport/__init__.py b/hgext3rd/p4fastimport/__init__.py --- a/hgext3rd/p4fastimport/__init__.py +++ b/hgext3rd/p4fastimport/__init__.py @@ -20,6 +20,10 @@ # 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 diff --git a/hgext3rd/p4fastimport/importer.py b/hgext3rd/p4fastimport/importer.py --- a/hgext3rd/p4fastimport/importer.py +++ b/hgext3rd/p4fastimport/importer.py @@ -125,6 +125,45 @@ 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) diff --git a/tests/test-p4fastimport-import-badsymlink.t b/tests/test-p4fastimport-import-badsymlink.t new file mode 100644 --- /dev/null +++ b/tests/test-p4fastimport-import-badsymlink.t @@ -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