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.
This commit is contained in:
Patrick Mezard 2012-04-18 14:04:58 +02:00
parent b8a30fde3a
commit 6067403d0d
4 changed files with 218 additions and 2 deletions

View File

@ -563,11 +563,15 @@ class svn_source(converter_source):
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 @@ class svn_source(converter_source):
(path, newpath, revnum))
path = newpath
break
if not paths:
revnum = None
return revnum, path
finally:
stream.close()
@ -605,6 +611,19 @@ class svn_source(converter_source):
# 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

129
tests/svn/empty.svndump Normal file
View File

@ -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

47
tests/svn/svndump-empty.sh Executable file
View File

@ -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

View File

@ -187,3 +187,24 @@ This is also the only place testing more than one extra field in a revision.
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