summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJelmer Vernooij <jelmer@samba.org>2011-10-17 15:41:16 +0000
committerJelmer Vernooij <jelmer@samba.org>2011-10-17 15:41:16 +0000
commit312db166607189c3375a0eac42cf5d721804cdbc (patch)
tree90f9be1bf106cdc8e7b384505e65704da2640fd1
parent254ac63f937e378d5e516cfc1b9f82b35cc277c8 (diff)
parentf43bc2eb6afdf5f4ca21cd9b5f914588693d518b (diff)
downloadbzr-fastimport-312db166607189c3375a0eac42cf5d721804cdbc.tar.gz
Merge support for --rewrite-tag-names.
-rw-r--r--NEWS5
-rw-r--r--cmds.py14
-rw-r--r--exporter.py57
-rw-r--r--tests/test_commands.py19
-rw-r--r--tests/test_exporter.py35
5 files changed, 120 insertions, 10 deletions
diff --git a/NEWS b/NEWS
index fc6b85b..3926888 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,11 @@ Bug fixes
* Cope with non-utf8 characters in paths when importing.
(Jelmer Vernooij, #838980)
+Features
+--------
+
+* New option --rewrite-tag-names for 'bzr fast-export'. (Alex Usov, #872601)
+
0.11 2011-08-22
Bug fixes
diff --git a/cmds.py b/cmds.py
index 537e0f0..d5aedd6 100644
--- a/cmds.py
+++ b/cmds.py
@@ -578,6 +578,11 @@ class cmd_fast_export(Command):
future once the feature names and definitions are formally agreed
to by the broader fast-import developer community.
+ Git has stricter naming rules for tags and fast-export --plain
+ will skip tags which can't be imported into git. To replace characters
+ unsupported in git with an underscore instead, specify
+ --rewrite-tag-names.
+
:Examples:
To produce data destined for import into Bazaar::
@@ -624,12 +629,16 @@ class cmd_fast_export(Command):
Option('plain',
help="Exclude metadata to maximise interoperability."
),
+ Option('rewrite-tag-names',
+ help="Replace characters invalid in git with '_'"
+ " (plain mode only).",
+ ),
]
encoding_type = 'exact'
def run(self, source, destination=None, verbose=False,
git_branch="master", checkpoint=10000, marks=None,
import_marks=None, export_marks=None, revision=None,
- plain=True):
+ plain=True, rewrite_tag_names=False):
load_fastimport()
from bzrlib.plugins.fastimport import exporter
@@ -639,7 +648,8 @@ class cmd_fast_export(Command):
destination=destination,
git_branch=git_branch, checkpoint=checkpoint,
import_marks_file=import_marks, export_marks_file=export_marks,
- revision=revision, verbose=verbose, plain_format=plain)
+ revision=revision, verbose=verbose, plain_format=plain,
+ rewrite_tags=rewrite_tag_names)
return exporter.run()
diff --git a/exporter.py b/exporter.py
index 78b5dba..7848c52 100644
--- a/exporter.py
+++ b/exporter.py
@@ -46,7 +46,7 @@
# set new_git_branch to the previously used name)
from email.Utils import parseaddr
-import sys, time
+import sys, time, re
import bzrlib.branch
import bzrlib.revision
@@ -112,18 +112,52 @@ def check_ref_format(refname):
return True
+def sanitize_ref_name_for_git(refname):
+ """Rewrite refname so that it will be accepted by git-fast-import.
+ For the detailed rules see check_ref_format.
+
+ By rewriting the refname we are breaking uniqueness guarantees provided by bzr
+ so we have to manually
+ verify that resulting ref names are unique.
+
+ :param refname: refname to rewrite
+ :return: new refname
+ """
+ new_refname = re.sub(
+ # '/.' in refname or startswith '.'
+ r"/\.|^\."
+ # '..' in refname
+ r"|\.\."
+ # ord(c) < 040
+ r"|[" + "".join([chr(x) for x in range(040)]) + r"]"
+ # c in '\177 ~^:?*['
+ r"|[\177 ~^:?*[]"
+ # last char in "/."
+ r"|[/.]$"
+ # endswith '.lock'
+ r"|.lock$"
+ # "@{" in refname
+ r"|@{"
+ # "\\" in refname
+ r"|\\",
+ "_", refname)
+ return new_refname
class BzrFastExporter(object):
def __init__(self, source, destination, git_branch=None, checkpoint=-1,
import_marks_file=None, export_marks_file=None, revision=None,
- verbose=False, plain_format=False):
+ verbose=False, plain_format=False, rewrite_tags=False):
"""Export branch data in fast import format.
:param plain_format: if True, 'classic' fast-import format is
- used without any extended features; if False, the generated
- data is richer and includes information like multiple
- authors, revision properties, etc.
+ used without any extended features; if False, the generated
+ data is richer and includes information like multiple
+ authors, revision properties, etc.
+ :param rewrite_tags: if True and if plain_format is set, tag names
+ will be rewritten to be git-compatible.
+ Otherwise tags which aren't valid for git will be skipped if
+ plain_format is set.
"""
self.source = source
self.outf = _get_output_stream(destination)
@@ -134,6 +168,7 @@ class BzrFastExporter(object):
self.revision = revision
self.excluded_revisions = set()
self.plain_format = plain_format
+ self.rewrite_tags = rewrite_tags
self._multi_author_api_available = hasattr(bzrlib.revision.Revision,
'get_apparent_authors')
self.properties_to_exclude = ['authors', 'author']
@@ -559,9 +594,15 @@ class BzrFastExporter(object):
else:
git_ref = 'refs/tags/%s' % tag.encode("utf-8")
if self.plain_format and not check_ref_format(git_ref):
- self.warning('not creating tag %r as its name would not be '
- 'valid in git.', git_ref)
- continue
+ if self.rewrite_tags:
+ new_ref = sanitize_ref_name_for_git(git_ref)
+ self.warning('tag %r is exported as %r to be valid in git.',
+ git_ref, new_ref)
+ git_ref = new_ref
+ else:
+ self.warning('not creating tag %r as its name would not be '
+ 'valid in git.', git_ref)
+ continue
self.print_cmd(commands.ResetCommand(git_ref, ":" + str(mark)))
def _next_tmp_branch_name(self):
diff --git a/tests/test_commands.py b/tests/test_commands.py
index 282cfc3..5729660 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -80,6 +80,25 @@ class TestFastExport(ExternalBase):
except AttributeError: # bzr < 2.4
self.failUnlessExists("br.fi")
+ def test_tag_rewriting(self):
+ tree = self.make_branch_and_tree("br")
+ tree.commit("pointless")
+ self.assertTrue(tree.branch.supports_tags())
+ rev_id = tree.branch.dotted_revno_to_revision_id((1,))
+ tree.branch.tags.set_tag("goodTag", rev_id)
+ tree.branch.tags.set_tag("bad Tag", rev_id)
+
+ # first check --no-rewrite-tag-names
+ data = self.run_bzr("fast-export --plain --no-rewrite-tag-names br")[0]
+ self.assertNotEqual(-1, data.find("reset refs/tags/goodTag"))
+ self.assertEqual(data.find("reset refs/tags/"), data.rfind("reset refs/tags/"))
+
+ # and now with --rewrite-tag-names
+ data = self.run_bzr("fast-export --plain --rewrite-tag-names br")[0]
+ self.assertNotEqual(-1, data.find("reset refs/tags/goodTag"))
+ # "bad Tag" should be exported as bad_Tag
+ self.assertNotEqual(-1, data.find("reset refs/tags/bad_Tag"))
+
simple_fast_import_stream = """commit refs/heads/master
mark :1
diff --git a/tests/test_exporter.py b/tests/test_exporter.py
index c2a8442..f1c9530 100644
--- a/tests/test_exporter.py
+++ b/tests/test_exporter.py
@@ -24,6 +24,7 @@ from bzrlib import tests
from bzrlib.plugins.fastimport.exporter import (
_get_output_stream,
check_ref_format,
+ sanitize_ref_name_for_git
)
from bzrlib.plugins.fastimport.tests import (
@@ -79,11 +80,45 @@ class CheckRefFormatTests(tests.TestCase):
def test_invalid(self):
self.assertFalse(check_ref_format('foo'))
+ self.assertFalse(check_ref_format('foo/.bar'))
self.assertFalse(check_ref_format('heads/foo/'))
+ self.assertFalse(check_ref_format('heads/foo.'))
self.assertFalse(check_ref_format('./foo'))
self.assertFalse(check_ref_format('.refs/foo'))
self.assertFalse(check_ref_format('heads/foo..bar'))
self.assertFalse(check_ref_format('heads/foo?bar'))
self.assertFalse(check_ref_format('heads/foo.lock'))
self.assertFalse(check_ref_format('heads/v@{ation'))
+ self.assertFalse(check_ref_format('heads/foo\\bar'))
self.assertFalse(check_ref_format('heads/foo\bar'))
+ self.assertFalse(check_ref_format('heads/foo bar'))
+ self.assertFalse(check_ref_format('heads/foo\020bar'))
+ self.assertFalse(check_ref_format('heads/foo\177bar'))
+
+
+class CheckRefnameRewriting(tests.TestCase):
+ """Tests for sanitize_ref_name_for_git function"""
+
+ def test_passthrough_valid(self):
+ self.assertEqual(sanitize_ref_name_for_git('heads/foo'), 'heads/foo')
+ self.assertEqual(sanitize_ref_name_for_git('foo/bar/baz'), 'foo/bar/baz')
+ self.assertEqual(sanitize_ref_name_for_git('refs///heads/foo'), 'refs///heads/foo')
+ self.assertEqual(sanitize_ref_name_for_git('foo./bar'), 'foo./bar')
+ self.assertEqual(sanitize_ref_name_for_git('heads/foo@bar'), 'heads/foo@bar')
+ self.assertEqual(sanitize_ref_name_for_git('heads/fix.lock.error'), 'heads/fix.lock.error')
+
+ def test_rewrite_invalid(self):
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('foo./bar')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo/')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo.')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('./foo')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('.refs/foo')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo..bar')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo?bar')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo.lock')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/v@{ation')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo\bar')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo\\bar')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo bar')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo\020bar')))
+ self.assertTrue(check_ref_format(sanitize_ref_name_for_git('heads/foo\177bar')))