mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 16:31:02 +03:00
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:
parent
77811f46f4
commit
0d98dd5413
@ -117,6 +117,8 @@ class HgEditor(svnwrap.Editor):
|
|||||||
self._filecounter = 0
|
self._filecounter = 0
|
||||||
self._filebatons = {}
|
self._filebatons = {}
|
||||||
self._files = {}
|
self._files = {}
|
||||||
|
# A mapping of svn paths to (data, isexec, islink, copypath) tuples
|
||||||
|
self._svncopies = {}
|
||||||
|
|
||||||
def _addfilebaton(self, path):
|
def _addfilebaton(self, path):
|
||||||
# XXX: enforce unicity here. This cannot be done right now
|
# 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
|
# Tag deletion is not handled as branched deletion
|
||||||
return
|
return
|
||||||
self.meta.closebranches.add(branch)
|
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:
|
if br_path is not None:
|
||||||
ha = self.meta.get_parent_revision(self.current.rev.revnum, branch)
|
ha = self.meta.get_parent_revision(self.current.rev.revnum, branch)
|
||||||
if ha == revlog.nullid:
|
if ha == revlog.nullid:
|
||||||
@ -148,21 +160,13 @@ class HgEditor(svnwrap.Editor):
|
|||||||
br_path2 = br_path + '/'
|
br_path2 = br_path + '/'
|
||||||
# assuming it is a directory
|
# assuming it is a directory
|
||||||
self.current.externals[path] = None
|
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)):
|
for f in ctx.walk(util.PrefixMatch(br_path2)):
|
||||||
f_p = '%s/%s' % (path, f[len(br_path2):])
|
f_p = '%s/%s' % (path, f[len(br_path2):])
|
||||||
if f_p not in self.current.files:
|
if f_p not in self.current.files:
|
||||||
self.current.delete(f_p)
|
self.current.delete(f_p)
|
||||||
# Remove copied but deleted files
|
if f_p in self._svncopies:
|
||||||
for f in list(self._files):
|
del self._svncopies[f_p]
|
||||||
if f.startswith(prefix):
|
|
||||||
del self._filebatons[self._files.pop(f)]
|
|
||||||
self.current.delete(path)
|
self.current.delete(path)
|
||||||
if path in self._files:
|
|
||||||
del self._filebatons[self._files.pop(path)]
|
|
||||||
|
|
||||||
@svnwrap.ieditor
|
@svnwrap.ieditor
|
||||||
def open_file(self, path, parent_baton, base_revision, p=None):
|
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)
|
self.ui.debug('WARNING: Opening non-existant file %s\n' % path)
|
||||||
return None
|
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)
|
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):
|
if not self.meta.is_path_valid(path):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -203,6 +208,8 @@ class HgEditor(svnwrap.Editor):
|
|||||||
@svnwrap.ieditor
|
@svnwrap.ieditor
|
||||||
def add_file(self, path, parent_baton=None, copyfrom_path=None,
|
def add_file(self, path, parent_baton=None, copyfrom_path=None,
|
||||||
copyfrom_revision=None, file_pool=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:
|
if path in self.current.deleted:
|
||||||
del self.current.deleted[path]
|
del self.current.deleted[path]
|
||||||
fpath, branch = self.meta.split_branch_path(path, existing=False)[:2]
|
fpath, branch = self.meta.split_branch_path(path, existing=False)[:2]
|
||||||
@ -283,29 +290,38 @@ class HgEditor(svnwrap.Editor):
|
|||||||
frompath = '%s/' % frompath
|
frompath = '%s/' % frompath
|
||||||
else:
|
else:
|
||||||
frompath = ''
|
frompath = ''
|
||||||
|
svncopies = {}
|
||||||
copies = {}
|
copies = {}
|
||||||
for f in fromctx:
|
for f in fromctx:
|
||||||
if not f.startswith(frompath):
|
if not f.startswith(frompath):
|
||||||
continue
|
continue
|
||||||
fctx = fromctx.filectx(f)
|
fctx = fromctx.filectx(f)
|
||||||
dest = path + '/' + f[len(frompath):]
|
dest = path + '/' + f[len(frompath):]
|
||||||
self.current.set(dest, fctx.data(), 'x' in fctx.flags(), 'l' in fctx.flags())
|
flags = fctx.flags()
|
||||||
# Put copied files with open files for now, they should
|
islink = 'l' in flags
|
||||||
# really be separated to reduce resource usage.
|
data = fctx.data()
|
||||||
self._addfilebaton(dest)
|
if islink:
|
||||||
|
data = 'link ' + data
|
||||||
|
svncopies[dest] = (data, 'x' in flags, islink, None)
|
||||||
if dest in self.current.deleted:
|
if dest in self.current.deleted:
|
||||||
|
# Remove this once svn copies and edited files are
|
||||||
|
# clearly separated.
|
||||||
del self.current.deleted[dest]
|
del self.current.deleted[dest]
|
||||||
if branch == source_branch:
|
if branch == source_branch:
|
||||||
copies[dest] = f
|
copies[dest] = f
|
||||||
if copies:
|
if copies:
|
||||||
# Preserve the directory copy records if no file was changed between
|
# Preserve the directory copy records if no file was changed between
|
||||||
# the source and destination revisions, or discard it completely.
|
# 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:
|
if parentid != revlog.nullid:
|
||||||
parentctx = self.repo.changectx(parentid)
|
parentctx = self.repo.changectx(parentid)
|
||||||
for k, v in copies.iteritems():
|
for k, v in copies.iteritems():
|
||||||
if util.issamefile(parentctx, fromctx, v):
|
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
|
# Copy the externals definitions of copied directories
|
||||||
fromext = svnexternals.parse(self.ui, fromctx)
|
fromext = svnexternals.parse(self.ui, fromctx)
|
||||||
for p, v in fromext.iteritems():
|
for p, v in fromext.iteritems():
|
||||||
@ -414,6 +430,13 @@ class HgEditor(svnwrap.Editor):
|
|||||||
raise
|
raise
|
||||||
return txdelt_window
|
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 = (
|
_TXDELT_WINDOW_HANDLER_FAILURE_MSG = (
|
||||||
"Your SVN repository may not be supplying correct replay deltas."
|
"Your SVN repository may not be supplying correct replay deltas."
|
||||||
" It is strongly"
|
" It is strongly"
|
||||||
|
@ -76,6 +76,7 @@ def convert_rev(ui, meta, svn, r, tbdelta, firstrun):
|
|||||||
svn.get_revision(r.revnum, editor)
|
svn.get_revision(r.revnum, editor)
|
||||||
else:
|
else:
|
||||||
svn.get_replay(r.revnum, editor, meta.revmap.oldest)
|
svn.get_replay(r.revnum, editor, meta.revmap.oldest)
|
||||||
|
editor.close()
|
||||||
|
|
||||||
current = editor.current
|
current = editor.current
|
||||||
current.findmissing(svn)
|
current.findmissing(svn)
|
||||||
|
@ -33,6 +33,7 @@ _skipstandard = set([
|
|||||||
'subdir_is_file_prefix.svndump',
|
'subdir_is_file_prefix.svndump',
|
||||||
'correct.svndump',
|
'correct.svndump',
|
||||||
'corrupt.svndump',
|
'corrupt.svndump',
|
||||||
|
'emptyrepo2.svndump',
|
||||||
])
|
])
|
||||||
|
|
||||||
def _do_case(self, name, stupid, layout):
|
def _do_case(self, name, stupid, layout):
|
||||||
|
44
tests/fixtures/emptyrepo2.sh
vendored
Executable file
44
tests/fixtures/emptyrepo2.sh
vendored
Executable 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
129
tests/fixtures/emptyrepo2.svndump
vendored
Normal 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
|
||||||
|
|
||||||
|
|
@ -226,6 +226,14 @@ class TestStupidPull(test_util.TestBase):
|
|||||||
self.assertEqual(node.hex(repo['tip'].node()),
|
self.assertEqual(node.hex(repo['tip'].node()),
|
||||||
'1a6c3f30911d57abb67c257ec0df3e7bc44786f7')
|
'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():
|
def suite():
|
||||||
all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout),
|
all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout),
|
||||||
unittest.TestLoader().loadTestsFromTestCase(TestStupidPull),
|
unittest.TestLoader().loadTestsFromTestCase(TestStupidPull),
|
||||||
|
Loading…
Reference in New Issue
Block a user