convert/svn: do not try converting empty head revisions (issue3347) stable
authorPatrick Mezard <patrick@mezard.eu>
Wed, 18 Apr 2012 14:04:58 +0200
branchstable
changeset 16466 c53a49c345e1
parent 16465 ad38b96c88f9
child 16467 7f59900e3f8b
convert/svn: do not try converting empty head revisions (issue3347) Subversion conversion works by picking trunk and branches heads, computing a revision graph from them and converting the selected commits. By design we fail to convert empty revisions so we have to be careful when discovering the revision graph. In this particular issue, the source svn repository was a partial mirror made by svnsync. The funny part is svnsync preserves all revisions including empty ones. Also, we trusted ra.stat(path, stop).created_rev to give us the latest revision with changes in path history up to stop. This assumption broke at least when path is '', that is the repository root, which always returned 'stop' revision despited being empty. The workaround is to first trust ra.stat() but if the returned revision appear empty, search the whole path history from stop to r1 until some changes are found.
hgext/convert/subversion.py
tests/svn/empty.svndump
tests/svn/svndump-empty.sh
tests/test-convert-svn-source.t
--- a/hgext/convert/subversion.py	Wed Apr 18 14:04:58 2012 +0200
+++ b/hgext/convert/subversion.py	Wed Apr 18 14:04:58 2012 +0200
@@ -563,11 +563,15 @@
         reported. Return None if computed module does not belong to
         rootmodule subtree.
         """
-        def findchanges(path, start, stop):
-            stream = self._getlog([path], start, stop)
+        def findchanges(path, start, stop=None):
+            stream = self._getlog([path], start, stop or 1)
             try:
                 for entry in stream:
                     paths, revnum, author, date, message = entry
+                    if stop is None and paths:
+                        # We do not know the latest changed revision,
+                        # keep the first one with changed paths.
+                        break
                     if revnum <= stop:
                         break
 
@@ -580,6 +584,8 @@
                                       (path, newpath, revnum))
                         path = newpath
                         break
+                if not paths:
+                    revnum = None
                 return revnum, path
             finally:
                 stream.close()
@@ -605,6 +611,19 @@
         # development, but it might be in *another module*. Fetch the
         # log and detect renames down to the latest revision.
         revnum, realpath = findchanges(path, stop, dirent.created_rev)
+        if revnum is None:
+            # Tools like svnsync can create empty revision, when
+            # synchronizing only a subtree for instance. These empty
+            # revisions created_rev still have their original values
+            # despite all changes having disappeared and can be
+            # returned by ra.stat(), at least when stating the root
+            # module. In that case, do not trust created_rev and scan
+            # the whole history.
+            revnum, realpath = findchanges(path, stop)
+            if revnum is None:
+                self.ui.debug('ignoring empty branch %r\n' % realpath)
+                return None
+
         if not realpath.startswith(self.rootmodule):
             self.ui.debug('ignoring foreign branch %r\n' % realpath)
             return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/svn/empty.svndump	Wed Apr 18 14:04:58 2012 +0200
@@ -0,0 +1,129 @@
+SVN-fs-dump-format-version: 2
+
+UUID: b70c45d5-2b76-4722-a373-d9babae61626
+
+Revision-number: 0
+Prop-content-length: 260
+Content-length: 260
+
+K 8
+svn:date
+V 27
+2012-04-18T11:35:14.752409Z
+K 17
+svn:sync-from-url
+V 73
+file:///Users/pmezard/dev/hg/hg-pmezard/tests/svn/temp/svn-repo/trunk/dir
+K 18
+svn:sync-from-uuid
+V 36
+56625b9e-e7e9-45be-ab61-052d41f0e1dd
+K 24
+svn:sync-last-merged-rev
+V 1
+4
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 112
+Content-length: 112
+
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2012-04-18T11:35:14.769622Z
+K 7
+svn:log
+V 10
+init projA
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 107
+Content-length: 107
+
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2012-04-18T11:35:15.052989Z
+K 7
+svn:log
+V 6
+adddir
+PROPS-END
+
+Node-path: trunk/dir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/dir/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 3
+Prop-content-length: 105
+Content-length: 105
+
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2012-04-18T11:35:16.050353Z
+K 7
+svn:log
+V 4
+addb
+PROPS-END
+
+Revision-number: 4
+Prop-content-length: 105
+Content-length: 105
+
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2012-04-18T11:35:17.050768Z
+K 7
+svn:log
+V 4
+addc
+PROPS-END
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/svn/svndump-empty.sh	Wed Apr 18 14:04:58 2012 +0200
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Use this script to generate empty.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir branches
+mkdir tags
+cd ..
+
+svnadmin create svn-repo
+svnurl=file://`pwd`/svn-repo
+svn import project-orig $svnurl -m "init projA"
+
+svn co $svnurl project
+cd project
+mkdir trunk/dir
+echo a > trunk/dir/a
+svn add trunk/dir
+svn ci -m adddir
+
+echo b > trunk/b
+svn add trunk/b
+svn ci -m addb
+
+echo c > c
+svn add c
+svn ci -m addc
+cd ..
+
+# svnsync repo/trunk/dir only so the last two revisions are empty
+svnadmin create svn-empty
+cat > svn-empty/hooks/pre-revprop-change <<EOF
+#!/bin/sh
+exit 0
+EOF
+chmod +x svn-empty/hooks/pre-revprop-change
+svnsync init --username svnsync file://`pwd`/svn-empty file://`pwd`/svn-repo/trunk/dir
+svnsync sync file://`pwd`/svn-empty
+svn log -v file://`pwd`/svn-empty
+
+svnadmin dump svn-empty > ../empty.svndump
--- a/tests/test-convert-svn-source.t	Wed Apr 18 14:04:58 2012 +0200
+++ b/tests/test-convert-svn-source.t	Wed Apr 18 14:04:58 2012 +0200
@@ -187,3 +187,24 @@
   extra:       branch=default
   extra:       convert_revision=svn:........-....-....-....-............/proj B/mytrunk@1 (re)
   $ cd ..
+
+Test converting empty heads (issue3347)
+
+  $ svnadmin create svn-empty
+  $ svnadmin load -q svn-empty < "$TESTDIR/svn/empty.svndump"
+  $ hg --config convert.svn.trunk= convert svn-empty
+  assuming destination svn-empty-hg
+  initializing destination svn-empty-hg repository
+  scanning source...
+  sorting...
+  converting...
+  1 init projA
+  0 adddir
+  $ hg --config convert.svn.trunk= convert file://$svnpath/svn-empty/trunk
+  assuming destination trunk-hg
+  initializing destination trunk-hg repository
+  scanning source...
+  sorting...
+  converting...
+  1 init projA
+  0 adddir