editor: start separating svn copies from open files

The separation is not complete as we still have to update the
RevisionData deleted set when registering svn copies. This will be
cleaned up once open files are themselves separated from RevisionData.

Copied symlinks are also being prefixed with 'link '.
This commit is contained in:
Patrick Mezard 2012-10-03 21:27:02 +02:00
parent 77811f46f4
commit 0d98dd5413
6 changed files with 227 additions and 21 deletions

View File

@ -117,6 +117,8 @@ class HgEditor(svnwrap.Editor):
self._filecounter = 0
self._filebatons = {}
self._files = {}
# A mapping of svn paths to (data, isexec, islink, copypath) tuples
self._svncopies = {}
def _addfilebaton(self, path):
# XXX: enforce unicity here. This cannot be done right now
@ -137,6 +139,16 @@ class HgEditor(svnwrap.Editor):
# Tag deletion is not handled as branched deletion
return
self.meta.closebranches.add(branch)
# Delete copied entries, no need to check they exist in hg
# parent revision.
if path in self._svncopies:
del self._svncopies[path]
prefix = path + '/'
for f in list(self._svncopies):
if f.startswith(prefix):
del self._svncopies[f]
if br_path is not None:
ha = self.meta.get_parent_revision(self.current.rev.revnum, branch)
if ha == revlog.nullid:
@ -148,21 +160,13 @@ class HgEditor(svnwrap.Editor):
br_path2 = br_path + '/'
# assuming it is a directory
self.current.externals[path] = None
prefix = path + '/'
map(self.current.delete,
[pat for pat in self.current.files.iterkeys()
if pat.startswith(prefix)])
for f in ctx.walk(util.PrefixMatch(br_path2)):
f_p = '%s/%s' % (path, f[len(br_path2):])
if f_p not in self.current.files:
self.current.delete(f_p)
# Remove copied but deleted files
for f in list(self._files):
if f.startswith(prefix):
del self._filebatons[self._files.pop(f)]
if f_p in self._svncopies:
del self._svncopies[f_p]
self.current.delete(path)
if path in self._files:
del self._filebatons[self._files.pop(path)]
@svnwrap.ieditor
def open_file(self, path, parent_baton, base_revision, p=None):
@ -171,13 +175,14 @@ class HgEditor(svnwrap.Editor):
self.ui.debug('WARNING: Opening non-existant file %s\n' % path)
return None
if path in self._files:
# Remove this when copied files are no longer registered as
# open files.
return self._files[path]
self.ui.note('M %s\n' % path)
if path in self._svncopies:
base, isexec, islink, copypath = self._svncopies.pop(path)
self.current.set(path, base, isexec, islink)
self.current.copies[path] = copypath
return self._addfilebaton(path)
if not self.meta.is_path_valid(path):
return None
@ -203,6 +208,8 @@ class HgEditor(svnwrap.Editor):
@svnwrap.ieditor
def add_file(self, path, parent_baton=None, copyfrom_path=None,
copyfrom_revision=None, file_pool=None):
if path in self._svncopies:
raise EditingError('trying to replace copied file %s' % path)
if path in self.current.deleted:
del self.current.deleted[path]
fpath, branch = self.meta.split_branch_path(path, existing=False)[:2]
@ -283,29 +290,38 @@ class HgEditor(svnwrap.Editor):
frompath = '%s/' % frompath
else:
frompath = ''
svncopies = {}
copies = {}
for f in fromctx:
if not f.startswith(frompath):
continue
fctx = fromctx.filectx(f)
dest = path + '/' + f[len(frompath):]
self.current.set(dest, fctx.data(), 'x' in fctx.flags(), 'l' in fctx.flags())
# Put copied files with open files for now, they should
# really be separated to reduce resource usage.
self._addfilebaton(dest)
flags = fctx.flags()
islink = 'l' in flags
data = fctx.data()
if islink:
data = 'link ' + data
svncopies[dest] = (data, 'x' in flags, islink, None)
if dest in self.current.deleted:
# Remove this once svn copies and edited files are
# clearly separated.
del self.current.deleted[dest]
if branch == source_branch:
copies[dest] = f
if copies:
# Preserve the directory copy records if no file was changed between
# the source and destination revisions, or discard it completely.
parentid = self.meta.get_parent_revision(self.current.rev.revnum, branch)
parentid = self.meta.get_parent_revision(
self.current.rev.revnum, branch)
if parentid != revlog.nullid:
parentctx = self.repo.changectx(parentid)
for k, v in copies.iteritems():
if util.issamefile(parentctx, fromctx, v):
self.current.copies[k] = v
data, isexec, islink = svncopies[k][:-1]
svncopies[k] = (data, isexec, islink, v)
self._svncopies.update(svncopies)
# Copy the externals definitions of copied directories
fromext = svnexternals.parse(self.ui, fromctx)
for p, v in fromext.iteritems():
@ -414,6 +430,13 @@ class HgEditor(svnwrap.Editor):
raise
return txdelt_window
def close(self):
for c in self._svncopies.iteritems():
dest, (data, isexec, islink, copied) = c
self.current.set(dest, data, isexec, islink)
self.current.copies[dest] = copied
self._svncopies.clear()
_TXDELT_WINDOW_HANDLER_FAILURE_MSG = (
"Your SVN repository may not be supplying correct replay deltas."
" It is strongly"

View File

@ -76,6 +76,7 @@ def convert_rev(ui, meta, svn, r, tbdelta, firstrun):
svn.get_revision(r.revnum, editor)
else:
svn.get_replay(r.revnum, editor, meta.revmap.oldest)
editor.close()
current = editor.current
current.findmissing(svn)

View File

@ -33,6 +33,7 @@ _skipstandard = set([
'subdir_is_file_prefix.svndump',
'correct.svndump',
'corrupt.svndump',
'emptyrepo2.svndump',
])
def _do_case(self, name, stupid, layout):

44
tests/fixtures/emptyrepo2.sh vendored Executable file
View File

@ -0,0 +1,44 @@
#!/bin/sh
#
# Create emptyrepo2.svndump
#
# The generated repository contains a sequence of empty revisions
# created with a combination of svnsync and filtering
mkdir temp
cd temp
mkdir project-orig
cd project-orig
mkdir -p sub/trunk other
echo a > other/a
cd ..
svnadmin create testrepo
svnurl=file://`pwd`/testrepo
svn import project-orig $svnurl -m init
svn co $svnurl project
cd project
echo a >> other/a
svn ci -m othera
echo a >> other/a
svn ci -m othera2
echo b > sub/trunk/a
svn add sub/trunk/a
svn ci -m adda
cd ..
svnadmin create testrepo2
cat > testrepo2/hooks/pre-revprop-change <<EOF
#!/bin/sh
exit 0
EOF
chmod +x testrepo2/hooks/pre-revprop-change
svnurl2=file://`pwd`/testrepo2
svnsync init --username svnsync $svnurl2 $svnurl/sub
svnsync sync $svnurl2
svnadmin dump testrepo2 > ../emptyrepo2.svndump

129
tests/fixtures/emptyrepo2.svndump vendored Normal file
View File

@ -0,0 +1,129 @@
SVN-fs-dump-format-version: 2
UUID: 293d1f29-635d-48b8-9cdf-468fd987067a
Revision-number: 0
Prop-content-length: 261
Content-length: 261
K 8
svn:date
V 27
2012-10-03T18:58:42.535317Z
K 17
svn:sync-from-url
V 74
file:///Users/pmezard/dev/hg/hgsubversion/tests/fixtures/temp/testrepo/sub
K 18
svn:sync-from-uuid
V 36
241badf9-093f-4e71-8a58-1028abf52758
K 24
svn:sync-last-merged-rev
V 1
4
PROPS-END
Revision-number: 1
Prop-content-length: 105
Content-length: 105
K 10
svn:author
V 7
pmezard
K 8
svn:date
V 27
2012-10-03T18:58:42.556405Z
K 7
svn:log
V 4
init
PROPS-END
Node-path: sub
Node-kind: dir
Node-action: add
Prop-content-length: 10
Content-length: 10
PROPS-END
Node-path: sub/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-10-03T18:58:43.040912Z
K 7
svn:log
V 6
othera
PROPS-END
Revision-number: 3
Prop-content-length: 108
Content-length: 108
K 10
svn:author
V 7
pmezard
K 8
svn:date
V 27
2012-10-03T18:58:44.042124Z
K 7
svn:log
V 7
othera2
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-10-03T18:58:45.053459Z
K 7
svn:log
V 4
adda
PROPS-END
Node-path: sub/trunk/a
Node-kind: file
Node-action: add
Prop-content-length: 10
Text-content-length: 2
Text-content-md5: 3b5d5c3712955042212316173ccf37be
Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
Content-length: 12
PROPS-END
b

View File

@ -226,6 +226,14 @@ class TestStupidPull(test_util.TestBase):
self.assertEqual(node.hex(repo['tip'].node()),
'1a6c3f30911d57abb67c257ec0df3e7bc44786f7')
def test_empty_repo(self, stupid=False):
# This used to crash HgEditor because it could be closed without
# having been initialized again.
self._load_fixture_and_fetch('emptyrepo2.svndump', stupid=stupid)
def test_empty_repo_stupid(self):
self.test_empty_repo(stupid=True)
def suite():
all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout),
unittest.TestLoader().loadTestsFromTestCase(TestStupidPull),